Skip to content

Как устроены платежи

В базе 21 живой платёжный провайдер за единой абстракцией: один вебхук-роут, один пайплайн обработки, одна модель идемпотентности. Касса включается в кабинете без правки кода и рестартов, а кнопки оплаты сами появляются в боте и мини-аппе. Ниже — как проходит платёж от клика до выданной подписки и почему деньги не теряются, даже если вебхук провайдера не дошёл.

Путь платежа

  1. Юзер выбирает тариф и срок — бот показывает экран «Способ оплаты»: баланс (если включён), Telegram Stars и все активные кассы.
  2. База создаёт транзакцию в статусе PENDING с замороженным снапшотом тарифа и цены (plan_snapshot + pricing). Юзер получит ровно то, что заказал, даже если вы поменяете каталог между выставлением счёта и оплатой.
  3. У провайдера вызывается create_payment. Hosted-кассы возвращают ссылку на страницу оплаты — юзер получает кнопку «💳 Оплатить». Telegram Stars — особый случай: вместо ссылки бот отправляет XTR-инвойс прямо в чат.
  4. Юзер платит. Провайдер шлёт вебхук на POST /api/v1/payments/{код кассы}.
  5. Роут верифицирует колбэк (подпись / повторный запрос к API провайдера), ставит задачу process_payment в очередь и сразу отвечает 200. Никакой выдачи в самом вебхуке.
  6. Воркер завершает транзакцию идемпотентно: атомарный переход 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 StarsXTR-инвойсы в боте и мини-аппе
Вручную / балансначисление админом
YooKassaкарта, СБП, SberPay
ЮMoneyкарта, кошелёк ЮMoney
Robokassaкарта, СБП, кошельки
PlategaСБП, карта, крипта
WATAкарта, СБП, TPay, SberPay
CryptoBotUSDT, TON, BTC
Cryptomusкрипта, 15+ монет
Heleketкрипта
FreeKassaкарта, СБП, кошельки
KassaAIкарта, СБП, кошельки
PayPalychкарта, СБП
MulenPayкарта, СБП
CloudPaymentsкарта (виджет/заказы)
Lavaкарта, СБП
RollyPayкарта, СБП, крипта
AntilopayСБП, карта, SberPay
RioPayкарта, СБП
SeverPayкарта, СБП
AuraPayкарта, СБП

«Возврат по API» — деньги уходят обратно вызовом провайдера прямо из кабинета; у остальных возврат фиксируется в базе, а переводите вы сами (подробнее — Возвраты). «Автосписания» — рекуррентные платежи по сохранённой карте: у YooKassa база умеет сохранять карту при оплате и продлевать подписку без участия юзера.

Что дальше

MIT License · сделано для тех, кто продаёт VPN, а не настраивает ботов