Как понять, что сообщение о платеже дошло до бэкенда
Что проверяют
Понимаете ли вы, что в платежах доставка не гарантирована сама по себе и её подтверждают несколько механизмов сразу. Следом обычно спрашивают, что будет, если вебхук пришёл дважды или не пришёл вовсе.
Как отвечать
Источник правды о статусе платежа это платёжный провайдер, а бэкенд узнаёт о нём двумя путями, которые страхуют друг друга:
- вебхук: провайдер (ЮKassa, CloudPayments, Stripe) шлёт POST на ваш эндпоинт и повторяет отправку, пока не получит ответ
2xx. Успешный ответ и есть для провайдера подтверждение доставки; - проверка подлинности: подпись проверяется по HMAC и сравнивается через
hmac.compare_digest, а если провайдер не подписывает запросы, статус перезапрашивается через его API поpayment_id. Телу вебхука без проверки верить нельзя; - сверка: периодическая задача в Celery забирает платежи, застрявшие в
pendingдольше N минут, и опрашивает API провайдера. Это страховка на случай, если вебхук потерялся; - идемпотентность: повторная доставка штатна. Уникальный индекс по
event_idили по паре(payment_id, status)не даст провести платёж дважды.
Каркас обработчика на FastAPI или Django: проверить подпись, записать сырое событие в таблицу входящих (inbox), закоммитить и сразу вернуть 200. Воркер забирает необработанные записи и уже там проводит платёж, выдаёт заказ и шлёт письмо. Если платёжное событие ходит между сервисами через RabbitMQ или Kafka, логика та же: ack или коммит смещения только после того, как событие сохранено.
Где спотыкаются
Верят редиректу на страницу «Оплата прошла». Пользователь может закрыть вкладку до редиректа или открыть этот адрес сам, поэтому статус меняется только по вебхуку или ответу API.
Делают всю обработку внутри запроса вебхука. Провайдер не дожидается ответа, повторяет отправку по таймауту, и без идемпотентности заказ проводится дважды.
Возвращают 200 до того, как событие сохранено, или 500 на уже обработанный дубль. В первом случае событие теряется, во втором провайдер будет бесконечно слать повторы.