Как устроены платежи
В базе 21 живой платёжный провайдер за единой абстракцией: один вебхук-роут, один пайплайн обработки, одна модель идемпотентности. Касса включается в кабинете без правки кода и рестартов, а кнопки оплаты сами появляются в боте и мини-аппе. Ниже — как проходит платёж от клика до выданной подписки и почему деньги не теряются, даже если вебхук провайдера не дошёл.
Путь платежа
- Юзер выбирает тариф и срок — бот показывает экран «Способ оплаты»: баланс (если включён), Telegram Stars и все активные кассы.
- База создаёт транзакцию в статусе
PENDINGс замороженным снапшотом тарифа и цены (plan_snapshot+pricing). Юзер получит ровно то, что заказал, даже если вы поменяете каталог между выставлением счёта и оплатой. - У провайдера вызывается
create_payment. Hosted-кассы возвращают ссылку на страницу оплаты — юзер получает кнопку «💳 Оплатить». Telegram Stars — особый случай: вместо ссылки бот отправляет XTR-инвойс прямо в чат. - Юзер платит. Провайдер шлёт вебхук на
POST /api/v1/payments/{код кассы}. - Роут верифицирует колбэк (подпись / повторный запрос к API провайдера), ставит задачу
process_paymentв очередь и сразу отвечает 200. Никакой выдачи в самом вебхуке. - Воркер завершает транзакцию идемпотентно: атомарный переход
PENDING → COMPLETED, затем фулфилмент — выдача/продление подписки (сначала панель, потом локальный коммит) или зачисление на баланс при пополнении. После — реферальная комиссия и уведомление юзеру (шаблон правится в кабинете; веб-покупателю без Telegram ссылка уходит на e-mail).
У Telegram Stars HTTP-вебхука нет — подтверждением служит in-bot событие successful_payment, но дальше оно идёт через тот же идемпотентный пайплайн.
Бесплатные покупки
Покупка со 100 % скидкой (промокод, персональная скидка) минует кассу целиком — подписка выдаётся сразу, счёт не создаётся.
Почему выдача не в вебхуке
Провайдеры (и Telegram) ретраят вебхук при любом не-200 ответе — значит, отвечать нужно быстро. А фулфилмент — это вызовы панели Remnawave, которая может отвечать медленно или лежать. Поэтому вебхук делает только verify → enqueue → 200, а тяжёлую работу выполняет воркер: задача ретраится до 5 раз, и повторный запуск безопасен — дубль находит транзакцию уже в терминальном статусе и ничего не делает.
Идемпотентность держится на трёх механизмах сразу:
- атомарный CAS-переход статуса (
allowed_from) — дубли, поздние и переупорядоченные вебхуки становятся no-op; UNIQUE(external_id, gateway_type)— один платёж провайдера не может породить две транзакции;SELECT ... FOR UPDATEна строке транзакции — параллельные вебхуки сериализуются.
Плюс защита от недоплаты: если провайдер сообщил сумму меньше 90 % счёта (в quickpay-формах юзер может править сумму), транзакция уходит в FAILED, а не в выдачу.
Реконсилятор: вебхук потерялся — оплата не потеряется
Раз в 5 минут фоновая задача reconcile_pending_payments берёт зависшие PENDING-транзакции (старше 3 минут, не старше 24 часов) и сама опрашивает статус у провайдера. Это закрывает оба реальных сценария потери: вебхук не дошёл (съел прокси/CDN, неверный URL в ЛК кассы) и фулфилмент упал уже после ответа 200 (панель лежала). Восстановленный платёж проходит тот же идемпотентный пайплайн с теми же сайд-эффектами — уведомлением и пост-топап автоматикой, поэтому гонка с опоздавшим вебхуком безвредна.
Фоновый опрос поддерживают кассы, у которых есть API статуса платежа: YooKassa, CryptoBot, Cryptomus, Heleket, Platega, WATA, PayPalych, RollyPay, RioPay. Остальные завершаются только вебхуком — для них особенно важно правильно прописать вебхук-URL (см. Вебхуки и безопасность).
Комиссии в чистой прибыли
У каждой кассы в кабинете есть поле «Комиссия %». Раздел «Платежи» считает по нему дневные KPI: оборот − комиссии провайдеров − налог (настройка TAX_RATE_PERCENT) = чистая прибыль, с разбивкой оборота по кассам. В расчёт входят только внешние деньги — пополнения и оплаты через кассу; покупки с внутреннего баланса не задваиваются.
Все 21 провайдер
| Провайдер | Способы оплаты | Возврат по API | Автосписания | Фоновый опрос |
|---|---|---|---|---|
| Telegram Stars | XTR-инвойсы в боте и мини-аппе | — | — | — |
| Вручную / баланс | начисление админом | — | — | — |
| YooKassa | карта, СБП, SberPay | ✓ | ✓ | ✓ |
| ЮMoney | карта, кошелёк ЮMoney | — | — | — |
| Robokassa | карта, СБП, кошельки | — | — | — |
| Platega | СБП, карта, крипта | — | — | ✓ |
| WATA | карта, СБП, TPay, SberPay | — | — | ✓ |
| CryptoBot | USDT, TON, BTC | — | — | ✓ |
| Cryptomus | крипта, 15+ монет | ✓ | — | ✓ |
| Heleket | крипта | ✓ | — | ✓ |
| FreeKassa | карта, СБП, кошельки | — | — | — |
| KassaAI | карта, СБП, кошельки | — | — | — |
| PayPalych | карта, СБП | — | — | ✓ |
| MulenPay | карта, СБП | — | — | — |
| CloudPayments | карта (виджет/заказы) | ✓ | — | — |
| Lava | карта, СБП | — | — | — |
| RollyPay | карта, СБП, крипта | — | — | ✓ |
| Antilopay | СБП, карта, SberPay | — | — | — |
| RioPay | карта, СБП | — | — | ✓ |
| SeverPay | карта, СБП | — | — | — |
| AuraPay | карта, СБП | — | — | — |
«Возврат по API» — деньги уходят обратно вызовом провайдера прямо из кабинета; у остальных возврат фиксируется в базе, а переводите вы сами (подробнее — Возвраты). «Автосписания» — рекуррентные платежи по сохранённой карте: у YooKassa база умеет сохранять карту при оплате и продлевать подписку без участия юзера.
Что дальше
- Подключение провайдера — ключи, тест, пейформы, минимальный депозит.
- Вебхуки и безопасность — верификация, коды ответов, reverse-proxy.
- Возвраты — по API и вручную.
- Добавить свой провайдер — один файл + значение enum + строка в БД.
