EN Прислать проект
Все статьи

Оплата без двойных списаний: вебхуки, подписи и сверка

Как принимать вебхуки платёжной системы так, чтобы повторы, гонки и пропуски не превращались в двойные списания и потерянные заказы.

· 4 минКодИнфраструктура

В прототипе оплата работает так: человек нажал «Оплатить», вернулся на страницу «Спасибо», и страница выдала доступ. В продукте эта схема ломается тремя способами: человек закрыл вкладку до возврата, нажал кнопку дважды или открыл страницу «Спасибо» руками. Надёжная оплата строится на другом принципе — источник правды не браузер, а платёжная система, и узнаёшь ты от неё всё через вебхуки.

Страница «Спасибо» ничего не выдаёт

Первое правило: страница возврата только показывает статус. Доступ, кредиты, отгрузку выдаёт обработчик вебхука на сервере. Если страница открылась раньше, чем пришёл вебхук, она пишет «платёж обрабатывается» и опрашивает сервер, а не решает сама.

Так исчезает целый класс проблем: и закрытая вкладка, и подделанный адрес возврата, и человек, который понял, что ?status=success можно дописать руками.

Подпись проверяется по сырому телу

Вебхук — обычный POST-запрос на публичный адрес. Отправить его может кто угодно, поэтому каждый запрос проверяется по подписи, которую платёжная система считает общим секретом.

Частая ошибка: фреймворк сам разбирает JSON до того, как до тела доберётся проверка. Пересобранный JSON отличается от исходного пробелами и порядком полей, подпись не сходится, и разработчик в отчаянии проверку отключает. Подпись считается по сырым байтам запроса — для этого маршрута разбор тела нужно выключить.

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

Повторы и порядок

Платёжные системы доставляют вебхуки «хотя бы один раз». Значит, одно событие может прийти дважды, а события могут прийти не по порядку: «подписка продлена» раньше, чем «подписка создана».

Отсюда два правила:

  • Обработка идемпотентна. Запись о событии и его последствия делаются в одной транзакции. Если запись уже была, обработчик ничего не делает и отвечает успехом.
  • Состояние не собирается из последовательности событий. Получив событие, сходи в API платёжной системы за текущим состоянием объекта и приведи свою запись к нему. Тогда порядок доставки перестаёт иметь значение.
insert into payment_events (provider_event_id, received_at)
values ($1, now())
on conflict (provider_event_id) do nothing
returning id;
-- пусто в ответе: событие уже обработано, выходим

И отвечай быстро. Если обработчик делает тяжёлую работу — генерирует документы, шлёт письма — положи событие в очередь и ответь сразу. Иначе платёжная система не дождётся ответа, сочтёт доставку неудачной и пришлёт событие снова.

Двойное нажатие — это не вебхук

Двойное списание чаще всего рождается не в вебхуках, а в момент создания платежа. Человек жмёт кнопку дважды, сеть повторяет запрос, и сервер создаёт два платежа.

Отключённая кнопка помогает, но не защищает. Защищает ключ идемпотентности: сервер создаёт заказ один раз, а запрос к платёжной системе отправляет с ключом, привязанным к этому заказу. Повтор с тем же ключом вернёт уже созданный платёж, а не новый. Большинство платёжных API такой ключ поддерживают.

Сверка

Даже правильный обработчик однажды пропустит событие: деплой, упавшая база, истёкший сертификат. Поэтому нужна сверка — регулярная задача, которая берёт у платёжной системы список платежей за период и сравнивает со своей базой.

Что ищет сверка:

  • оплачено у провайдера, но не выдано у тебя;
  • выдано у тебя, но платежа нет или он возвращён;
  • суммы и валюты не совпадают.

Каждое расхождение — сообщение живому человеку, а не автоматическое исправление: причины бывают разными, и в половине случаев их стоит понять, а не замазать.

Отдельно не забудь про возвраты и споры. Если по ним не приходят события в твой обработчик, доступ остаётся у человека, который деньги уже вернул.

SUBXCAT помогает выстроить приём оплат так, чтобы бухгалтерия и база сходились без ручных правок.

Хочешь, сделаем это за тебя?

Расскажи о проекте ассистенту — через пять минут будет примерная смета, через 48 часов разбор.

Созвон с командой 20 минут: инженер, дизайнер и маркетолог посмотрят на проект вместе с тобой.
Записаться на звонок