Платный checkout через Platform API и confirmation URL.

Платежи

Поток

1. Пользователь выбирает платную plan_price
2. createPayment(plan_price_id, return_url)
3. API создаёт платёж у провайдера + pending в Platform
4. Приложение открывает confirmation_url
5. Webhook → succeeded → права / период подписки
   (если webhook не дошёл: worker ~1 мин, или getMyEntitlements / syncPayment)
6. Приложение обновляет entitlements (+ кэш)

Если webhook не дошёл, а в ЮKassa уже succeeded, Platform сам закрывает pending:

  • worker каждую минуту (POST /v1/internal/payments/reconcile)
  • getMyEntitlements() после return
  • SDK syncPayment(payment_id) / POST /v1/payments/{id}/sync

Тот же путь, что webhook: проверка в ЮKassa → finalize_yookassa_payment. Ручная кнопка в админке Платежи → Сверить с ЮKassa остаётся для отладки.

Правила

  • Нужны JWT + public key
  • Секреты провайдера не в клиенте
  • Free не оплачивается
  • После return — getMyEntitlements() (сам сверит pending). Опционально syncPayment(payment_id)

Ошибка free: free_plan_offline.
discount_percent — только UI; в оплату идёт amount.

Webhook (прод)

URL в кабинете ЮKassa: YOUR_API_URL/v1/webhooks/yookassa (хост API, не маркетинг).

Finalize читает токен из platform_config.yookassa_webhook_token (≥24 символов на проде). Env YOOKASSA_WEBHOOK_TOKEN — запасной, если в БД токен ещё слабый. URL webhook в кабинете ЮKassa должен отвечать 2xx только после того, как платёж найден у нас; иначе ЮKassa повторит запрос.

Сверка (admin)

POST /v1/payments/{paymentId}/sync
POST /v1/payments/sync-pending

Отмена в ЮKassa → статус canceled. Если тариф уже выдан вручную (admin/license), сверка только закрывает pending без повторной выдачи.