UnicoreCMS

Платежи и вебхуки

Как создаётся счёт, как приходит подтверждение и как это отлаживать

Как выглядит платёж

Игрок жмёт «Пополнить»

Фронт дёргает ручку выбранного провайдера:

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: тогда провайдер, включённый без ключей, честно уронит старт вместо тихой поломки оплаты.

Содержание