Интеграции и приложения · 5 мин

Платёжный шлюз на сайте: callback, подпись и подтверждение заказа

Как отделить возврат покупателя от подтверждения платежа, проверить уведомление по правилам провайдера и не выдать заказ дважды.

Тематическая иллюстрация к материалу: Платёжный шлюз на сайте: callback, подпись и подтверждение заказа

Редакция Romanov Lab · Опубликовано

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

Разделите заказ и попытки оплаты

У одного заказа могут быть несколько попыток оплаты: первая отменена, вторая ещё ожидает результата. Поэтому одного поля «идентификатор платежа» на карточке заказа часто недостаточно. Полезно хранить отдельные записи попыток со связью с заказом, идентификатором провайдера, суммой, валютой, режимом среды и подтверждённым состоянием. Это предлагаемая модель магазина, а не формат какого-либо API.

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

Возврат на сайт показывает состояние, но не устанавливает его

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

Не меняйте состояние по параметрам вроде success=true, присланным браузером. Не раскрывайте чужой заказ по перебору последовательных номеров. Покупателю нужен понятный номер обращения, а серверу нужна отдельная проверка того, какие сведения вправе видеть этот пользователь.

Проверяйте уведомление способом конкретного провайдера

У платёжных систем различаются механизмы подтверждения подлинности. Например, Stripe проверяет подпись webhook с использованием исходного тела запроса, заголовка Stripe-Signature и секрета endpoint. Разобрать JSON, собрать его заново и затем проверять подпись нельзя: представление байтов изменится. Используйте поддерживаемую библиотеку и документированный порядок проверки.

В документации ЮKassa описана проверка текущего состояния объекта и адреса отправителя. Переносить туда название заголовка или алгоритм подписи Stripe нельзя. При проверке сетевого адреса также учитывают доверенную конфигурацию прокси: произвольный заголовок от посетителя не доказывает происхождение запроса.

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

Подлинность и подходящий платёж проверяются отдельно

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

Отдельно различайте авторизацию и окончательное завершение. У ЮKassa waiting_for_capture означает ожидание списания после авторизации, а succeeded обозначает успешно завершённый платёж. Отметка «деньги зарезервированы» не должна автоматически превращаться в «можно выполнить любую отгрузку»: правило исполнения определяет бизнес-процесс.

Учебный сценарий с двумя подтверждениями

Предположим, заказ R-204 рассчитан на 4 800 рублей и связан с попыткой P-A. Сервер получает подтверждение P-A, сверяет его с сохранённым расчётом и фиксирует результат. Через некоторое время приходит повтор того же уведомления. Повтор не должен заново создавать документ, письмо или задание на выдачу.

  1. Входящее сообщение сохраняют с идентификатором, областью провайдера и результатом проверки.
  2. Переход попытки и локальная запись задания на исполнение фиксируются согласованно, например в одной транзакции.
  3. Задание имеет собственную защиту от повторного исполнения, потому что сбой возможен и после обработки платежа.
  4. Поступление P-B для того же заказа рассматривают отдельно: это может быть другая реальная оплата, а не технический дубль P-A.

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

Подтверждение доставки не равно завершению всей работы

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

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

Что проверить перед приёмкой

  • Поддельное уведомление и неверная подпись не меняют заказ.
  • Настоящее событие с другой суммой или из тестовой среды не подтверждает рабочую покупку.
  • Закрытая вкладка не мешает серверному обновлению.
  • Повтор и одновременные копии создают одно локальное задание на исполнение.
  • Позднее событие старой попытки не перезаписывает результат другой попытки.
  • Сбой после записи, но до ответа провайдеру восстанавливается без повторной выдачи.
  • Неизвестный статус заметен сотруднику и не трактуется как успех по умолчанию.

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

Нужен план для вашего проекта? Напишите нам в Telegram или по электронной почте. Обсудим разработку, продвижение или поддержку сайта.

Читайте также