Вручную / баланс (Manual)
Это не внешняя касса, а встроенный шлюз на два случая: оплата с внутреннего баланса (кошелька юзера) и ручное подтверждение платежа админом — переводы, наличные, любой способ, для которого нет интеграции. Всегда доступен в базе, внешнего провайдера нет.
В отличие от остальных шлюзов, привязанных к рублю, manual принимает все валюты системы: RUB, USD, EUR, USDT, XTR.
Что понадобится
Ничего внешнего. Для флоу с webhook-подтверждением — придумать общий админ-секрет и вписать его в настройки шлюза.
Настройка в кабинете
| Поле | Что это | Обязательное |
|---|---|---|
secret | Общий админ-секрет, защищающий публичный маршрут подтверждения. Без него шлюз отклоняет все подтверждения (fail-closed) | Да — для webhook-подтверждения |
Секрет шифруется Fernet при сохранении и обратно в кабинете не показывается — виден только факт, что ключ заполнен.
Включение — переключатель активности в карточке «Вручную / баланс». Кнопка «Тест» всегда отвечает успехом: внешнего API нет.
Оплата с баланса дополнительно управляется параметром BALANCE_ENABLED в настройках бота (категория «Платежи», включён по умолчанию) — он показывает/прячет кнопку «С баланса» в боте и мини-аппе.
Вебхук
URL: https://ваш-домен/api/v1/payments/manual. Указывать его нигде не нужно — это не колбэк провайдера, а маршрут для подтверждения платежа админом (или вашей внешней автоматикой).
Подлинность проверяется заголовком X-Admin-Secret: его значение сравнивается с secret из настроек шлюза (constant-time сравнение). Тело — JSON с внутренним payment_id транзакции:
curl -X POST https://ваш-домен/api/v1/payments/manual \
-H "X-Admin-Secret: ВАШ_СЕКРЕТ" \
-H "Content-Type: application/json" \
-d '{"payment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": "confirm"}'Допустимые значения status:
| Значение | Результат |
|---|---|
completed или confirm | Платёж зачислен, покупка фулфиллится |
canceled или reject | Платёж отменён |
Если status не передан, считается completed. Неизвестный статус или кривой payment_id — отказ с 403.
Fail-closed
Пустой secret в настройках не «открывает» маршрут, а наоборот — шлюз отклоняет любые подтверждения. Так сделано намеренно: маршрут публичный, а payment_id виден в redirect-ссылках, и без секрета кто угодно мог бы завершить чужой pending-платёж.
Особенности
create_paymentвозвращает статус PENDING — бот сообщает юзеру, что платёж подтвердит админ, и транзакция ждёт подтверждения через маршрут выше.- Оплата с баланса через шлюз вообще не проходит: списание кошелька и выдача подписки идут одной DB-транзакцией (общий код для бота и мини-аппы). Если панель упала — откатывается всё, включая списание; деньги юзера не зависают.
- Балансовые покупки не имеют
gateway_typeи не считаются «внешними деньгами» в статистике оборота — двойного счёта пополнение+покупка не будет.
Платёж не пришёл?
Как устроен пайплайн подтверждения и реконсилятор зависших платежей — см. /payments/webhooks.
