Перейти к содержанию

Подписки и платежи — модуль оплаты через CloudPayments

Провайдер

CloudPayments (api.cloudpayments.ru). Карты МИР, Visa, Mastercard. Рекуррентные платежи.

Архитектура

Фронтенд (hadl-front)

Точки входа

  • src/views/Subscription.vue — страница выбора и оплаты подписки
  • src/stores/subscription.js — стор планов подписки
  • src/components/Subscription/SubscriptionPlanCard.vue — карточка плана

Поток оплаты

  1. resolvePlanAction() → генерирует paymentId (UUID), проверяет нет ли активной подписки → вызывает pay()
  2. pay() → открывает виджет CloudPayments (cp.CloudPayments), настройки рекуррента (recurrent)
  3. onComplete(paymentResult) → при success вызывает POST /api/subscribe с paymentId.value
  4. При ошибке 409 (уже активна) — показывает info-уведомление

Sync подписки

При onMounted для web/android вызывается POST /api/sync-subscription — синхронизирует статус подписки с CloudPayments API.

Бэкенд (cryptodiary)

Контроллеры

  • UserController::subscribe (POST /api/subscribe) — клиентский запрос после успешной оплаты в виджете
    • Идемпотентность по uuid (TransactionId / paymentId)
    • Блокирует повторную покупку при активной подписке (409)
    • Создаёт/продлевает Subscription + записывает SubscriptionPayment
  • UserController::syncSubscription (POST /api/sync-subscription) — синхронизирует статус с CP API
  • CloudPaymentsController::paymentSuccess (POST /api/cloudpayments/payment-success) — webhook Pay от CloudPayments
    • Создаёт подписку серверно (без зависимости от клиента)
    • Защита HMAC через middleware cloudpayments_hmac
    • Идемпотентность по TransactionId через uuid в subscription_payments
    • Логирование в канал cloudpayments (файл storage/logs/cloudpayments.log)

Модели

  • Subscription — подписка пользователя (user_id, subscription_plan_id, status_id, starts_at, ends_at, external_id)
  • SubscriptionPayment — запись платежа (subscription_id, amount, platform_id, uuid)
  • SubscriptionPlan — план (PAID_PLANS_IDS = [1, 2], коды: basic-m, basic-y, free, free-forever)
  • SubscriptionStatus — статусы (active, cancelled)
  • User::subscription()hasOne(Subscription)->latest('updated_at')

Сервисы

  • CloudPaymentsService — HTTP-клиент к API CloudPayments
    • FindUserSubscriptions — поиск подписок пользователя по accountId
    • UpdateSubscription — обновление рекуррентной подписки

Middleware

  • VerifyCloudPaymentsHmac (cloudpayments_hmac) — проверка HMAC-SHA256 подписи webhook'ов CloudPayments
  • SubscriptionGate (subscription_gate) — ограничение функций по тарифу

Команды

  • clear:outdated-subscriptions — удаляет подписки с ends_at < now() - 2 дня

Конфигурация

  • config/services.phpcloud_payments.public_id, cloud_payments.api_secret
  • .envCLOUD_PAYMENTS_PUBLIC_ID, CLOUD_PAYMENTS_API_SECRET
  • Фронт: VITE_CLOUD_PAYMENTS_PUBLIC_ID, VITE_CLOUD_PAYMENTS_PASSWORD

Webhook в личном кабинете CloudPayments

  • URL: https://hadl.app/api/cloudpayments/payment-success
  • Метод: POST
  • Формат: CloudPayments
  • Тип: Pay (о принятом платеже)

Ключевые нюансы

  • Двойная защита: подписка создаётся и клиентом (subscribe), и вебхуком (paymentSuccess). Оба проверяют uuid/TransactionId на дублирование.
  • SubscriptionPayment.uuid хранит paymentId (от клиента) или TransactionId (от webhook) — обеспечивает идемпотентность.
  • Рекуррентные платежи: виджет создаёт подписку в CP, последующие списания приходят через Pay webhook.
  • resolveSubscriptionPlan() в webhook определяет план по сумме платежа (точное совпадение цены или эвристика).