Платежи и вебхуки
Как создаётся счёт, как приходит подтверждение и как это отлаживать
Как выглядит платёж
Игрок жмёт «Пополнить»
Фронт дёргает ручку выбранного провайдера:
POST /payment/methods/unitpay/link
Authorization: Bearer <accessToken>
{ "amount": 500 }Сервер создаёт счёт со статусом waiting, подписывает параметры своим ключом и возвращает ссылку:
{ "link": "https://unitpay.ru/pay/…?sum=500&account=1234&sign=…" }Игрок платит на стороне агрегатора
Мы в этом не участвуем. Карта, СБП, кошелёк остаются на стороне провайдера.
Агрегатор шлёт уведомление
POST /payment/methods/unitpay/handlerРучка публичная, авторизации у агрегатора нет и быть не может. Вместо неё работают две проверки.
Игрок возвращается на сайт
/payment/redirect/success/unitpay
/payment/redirect/fail/unitpayОбе ручки просто редиректят на страницу оплаты сайта с параметрами статуса. Баланс к этому моменту уже пополнен уведомлением, а не редиректом. Если игрок закрыл вкладку, редиректа не будет вовсе, а деньги всё равно дойдут.
Что проверяется в уведомлении
IP отправителя. У каждого провайдера захардкожен список его адресов. Запрос с чужого адреса отклоняется до всех остальных проверок.
Подпись. Сервер пересчитывает параметры с секретным ключом из .env и сверяет результат с присланной подписью. Не сошлось — отказ.
Только после обеих проверок счёт помечается оплаченным.
Двойное уведомление ничего не сломает
Смена статуса делается условным UPDATE ... WHERE status = 'waiting'. Если агрегатор пришлёт подтверждение дважды, второй запрос не изменит ни одной строки, и баланс не пополнится второй раз.
Что отвечает наша сторона
Формат ответа зависит от провайдера, каждый ждёт своё. Общая логика такая:
| Ситуация | Что вернётся |
|---|---|
| Всё хорошо | OK |
| IP не из списка провайдера | bad ip! |
| Подпись не сошлась | wrong sign! |
| Счёта нет или он уже оплачен | wrong payment id! |
Эти строки видно в логах вебхуков у агрегатора, по ним и разбираются с проблемой.
Отладка
Симптом почти всегда один. У агрегатора платёж прошёл, а на сайте баланс не изменился. Дальше по списку.
Свой провайдер
Все реализации лежат в src/payment/methods/<провайдер> и устроены одинаково:
export class MyPayService implements PaymentCoreService {
async createLink(user, input, ip): Promise<PaymentLink> {
const payment = await this.paymentHandler.create(MyPayModule.id, input.amount, user, ip)
return { link: '…' }
}
async handler(ip, input): Promise<any> {
// проверить IP и подпись
await this.paymentHandler.handler(input.account, input.billId)
return { result: 'OK' }
}
}PaymentHandlerService берёт на себя создание счёта, зачисление, бонусы и запись в историю. От вашего кода нужны только ссылка и проверка подлинности уведомления.
Контроллер, модуль и ключи в envconfig добавляются по образцу соседей. Не забудьте про requireSecrets в конце envconfig.ts: тогда провайдер, включённый без ключей, честно уронит старт вместо тихой поломки оплаты.