roboguard

Как принимать платежи и webhook без ложной оплаты

Покупатель вернулся на страницу успеха — это ещё не основание выдать товар или подписку. Решение принимает сервер по подтверждённому состоянию платежа. Уведомления могут повторяться и приходить с задержкой, поэтому одно подтверждение должно приводить к одному бизнес-результату.

Зафиксируйте заказ до перехода к оплате

Учебный пример — бот продаёт доступ к набору материалов. До создания платежа сервер сохраняет владельца заказа, состав покупки, сумму, валюту и применённые условия. Браузер или бот передаёт выбор пользователя, но итоговую цену определяет сервер. Это модельный сценарий, а не описание платёжного инцидента клиента.

Свяжите платёж поставщика с конкретным внутренним заказом. Не ищите заказ только по email, приблизительной сумме или последнему открытому экрану: у пользователя могут быть несколько попыток и вкладок. Если условия изменились, создавайте новую согласованную попытку по правилам своей системы, сохраняя историю предыдущей.

ДанныеКак используются
Владелец и номер заказаОпределяют, кому и что выдавать после подтверждения.
Сумма, валюта, условияСверяются с подтверждённым платежом, а не со значениями из браузера.
Идентификатор платежа поставщикаСвязывает внешнее событие с сохранённой попыткой.
Статус и результат выдачиПозволяют безопасно обработать повтор и восстановиться после сбоя.

Проверьте подлинность по протоколу вашего поставщика

У разных платёжных сервисов различаются способы проверки уведомлений. ЮKassa описывает проверку текущего состояния объекта и источника уведомления. Stripe предусматривает проверку подписи webhook, для которой важно сохранить исходное тело запроса. Нельзя механически переносить заголовки и алгоритм одного сервиса в интеграцию другого.

Используйте официальный серверный SDK или точную текущую документацию провайдера. После подтверждения источника проверьте, что событие относится к вашему магазину и известной попытке оплаты. Название поля success или paid в пришедшем JSON само по себе не является доказательством.

  • Проверьте предусмотренную поставщиком подлинность до выдачи товара или доступа.
  • При проверке подписи не подменяйте исходные байты тела повторно собранным JSON.
  • Не считайте произвольный X-Forwarded-For надёжным источником IP; доверие к прокси настраивается отдельно.
  • Разделите тестовые и рабочие ключи, магазины и события.

Подробнее в первоисточнике: ЮKassa: проверка подлинности входящих уведомлений; Stripe: подпись, повторы и порядок webhook-событий.

Сверьте смысл платежа перед выдачей

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

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

  1. Найдите сохранённую попытку по проверенному идентификатору платежа.
  2. Получите подтверждённое состояние по протоколу поставщика.
  3. Сверьте его с сохранёнными параметрами заказа.
  4. Примените допустимый переход статуса и выдайте ровно предусмотренный результат.
  5. Сохраните причину расхождения для разбора, если проверка не прошла.

Повторное событие не должно повторять покупку

Stripe прямо предупреждает о повторных событиях и отсутствии гарантированного порядка доставки. Для любого выбранного поставщика проверьте его конкретные правила. В приложении нужна идемпотентность: повтор уже выполненной операции узнаётся и не создаёт новое последствие.

У учебной подписки это означает, что повтор webhook не прибавляет второй месяц. Сохранение платежа и выдача доступа должны быть согласованы: сбой между ними не должен оставлять покупателя без покупки или выдавать её дважды. Если выполнение вынесено в очередь, задача также должна узнавать ранее завершённый результат.

Не полагайтесь только на память процесса: после перезапуска она исчезнет. Устойчивый идентификатор операции и ограничения в базе помогают сохранить результат. Возвраты и отмены обрабатываются отдельными допустимыми переходами; позднее старое событие не должно случайно отменить более новое подтверждённое состояние.

Подробнее в первоисточнике: Stripe: подпись, повторы и порядок webhook-событий.

Проверьте неудобные сценарии в тестовом режиме

Используйте тестовые средства своего платёжного сервиса. Заранее определите ожидаемый результат и число записей покупки, чтобы проверка не заканчивалась на коде ответа webhook. Не выполняйте реальные списания только ради повторения этих сценариев.

СценарийОжидаемый результат
Открытие страницы успеха без оплатыДоступ не выдан; показывается подтверждённое сервером состояние.
Уведомление с неверной проверкой подлинностиНет изменения заказа и выдачи покупки.
Подлинное событие с несовпадающей суммой или заказомРасхождение сохранено, неверная покупка не выдана.
Две одинаковые доставки, в том числе одновременноОдин платёж и один результат выдачи.
Перезапуск после сохранения платежаОбработка безопасно продолжается без потери или двойной выдачи.
Уведомление задержалось, запрос статуса временно недоступенПонятное ожидание и последующая сверка вместо ложного успеха.
События пришли в другом порядкеСостояние соответствует допустимым переходам и актуальной сверке.

Сохраните доставку после подключения защиты

Платёжный webhook можно фильтровать на подключённом HTTP-домене, но он остаётся машинной интеграцией. Браузерная проверка или общий жёсткий лимит могут задержать настоящие уведомления. Настройте точный путь, метод, допустимый формат и совместимые правила, затем повторите тестовую доставку через новый маршрут.

RoboGuard помогает управлять HTTP-запросами, которые проходят через него. Подпись, сверка платежа, идемпотентность и выдача покупки выполняются вашим backend. Не открывайте весь API ради одного webhook и не считайте оплату подтверждённой только потому, что запрос пропустила защита.

Вопросы и ответы

Можно ли выдавать покупку по return_url?

Нет. Адрес возврата помогает показать результат пользователю. Право на покупку определяется подтверждённым серверным состоянием связанного платежа.

WAF проверит, действительно ли заказ оплачен?

Нет. WAF фильтрует поддерживаемые признаки HTTP-запросов. Подлинность уведомления, сумма, заказ и результат оплаты проверяются вашей платёжной интеграцией.

Защитите публичный HTTP-вход приложения

У вас есть собственный домен API, приложения или webhook? Проверьте требования к DNS и серверу. RoboGuard фильтрует запросы, которые проходят через подключённый домен; права пользователей и бизнес-правила остаются в вашем приложении.

Как подключить приложение