Insights / Технические материалы

Безопасная обработка Webhook платежей и возвратов: сверка состояния в Workers

Пример реализации, в котором отдельно рассматриваются проверка подписи, повторные и задержанные события, состояние возврата и ответы внешних API. Также показана повторная проверка прав перед административной операцией.

  • Cloudflare Workers
  • Stripe
  • Security
Безопасная обработка Webhook платежей и возвратов: сверка состояния в Workers
Содержание
  1. Проверяйте подпись и блокируйте повторную обработку на входе
  2. При задержанном событии о возврате запрашивайте текущее состояние провайдера
  3. Разделяйте результаты возврата, баллов и уведомлений
  4. Проверяйте ответы и в Node, и в среде Workers
  5. Фиксируйте границы выпуска и приёмки

Проверяйте платёжные webhook в Workers повторной доставкой и изменением порядка событий в тестовой среде, исключая двойное обновление состояния. Сверяйте обработку перенаправлений внешних API с Cloudflare Workers: Request и средой выполнения; отдельно фиксируйте приём, внешнее состояние и дальнейшие действия.

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

Проверяйте подпись и блокируйте повторную обработку на входе

Проверяйте подпись по неизменённому исходному телу запроса и сверяйте режим события с ожидаемым production или test. Записывайте обработку и захватывайте блокировку, чтобы повторная доставка того же события не повторяла бизнес-операцию. Это не гарантирует упорядоченную доставку событий. См. официальное руководство Stripe по Webhook.

При задержанном событии о возврате запрашивайте текущее состояние провайдера

Реализация обрабатывает refund.created, refund.updated и refund.failed. Для управляемого возврата она повторно получает текущий объект Stripe, чтобы позднее событие не вернуло локальную запись к старому состоянию. Перед решением о полном возврате заказа суммы успешных и ожидающих возвратов учитываются отдельно.

Проверяются сумма, валюта, PaymentIntent, metadata для связи заказа и операции, а также ранее сохранённые идентификаторы. ID платежа может начинаться с py_, а ID возврата — с pyr_. Реализация изменена так, чтобы корректный ответ не отклонялся только из-за отличия от одного известного префикса. Поддержка дополнительного префикса не ослабляет проверки суммы и идентичности. Неизвестные значения не считаются нулём.

Разделяйте результаты возврата, баллов и уведомлений

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

Успешный возврат и успешная корректировка баллов — разные состояния. Последующая ошибка не должна повторно запускать возврат; необходимую сверку или исправление следует записать. Настройка уведомлений, фактическое получение сообщения и последующая работа сотрудника — отдельные критерии приёмки, не относящиеся к обработке платёжных событий. Эта статья не утверждает, что уведомления работают.

От webhook к раздельным результатам Перед фиксацией завершения сверяйте текущее состояние. Возврат клиенту и изменение баллов не выполнялись.
  1. Проверка на входе Проверьте исходный body, mode и event ID; выявите повторную доставку и обработку в процессе.
  2. Сверка текущего состояния Не возвращайте запись к старому состоянию из-за задержанного события; сравните сумму, валюту и заказ.
  3. Раздельная фиксация результатов У возврата, баллов и уведомлений разные состояния. При неизвестном результате провайдера сначала сверяйте, а не повторяйте операцию.

Проверяйте ответы и в Node, и в среде Workers

Тестирование внешнего fetch только в Node может не выявить различий среды Workers. В этом случае воспроизвели проблему совместимости с redirect: 'error' и перешли на redirect: 'manual' с явной проверкой HTTP-статуса. Не разбирайте ответ 3xx или страницу ошибки как обычный JSON и не переходите автоматически на другой хост, передавая данные авторизации. См. API Request для Workers.

Фиксируйте границы выпуска и приёмки

Изменения базы данных, зависимая обработка и интерфейс управления были развёрнуты по порядку. Проверены тесты, CI, production-страницы только для чтения и соответствие результатам запросов к API провайдера. Тестовые денежные операции клиентов не выполнялись. При экспорте CSV ячейки, которые могут интерпретироваться как формулы, обрабатываются как текст; неизвестные комиссии не заменяются нулём.

О границе входа в панель управления см. Проектирование сеансов для нескольких сервисов.