Страница «Оплатить» не подтверждает, что заказ оплачен. В приложении с hosted checkout сервер создаёт заказ и платёжную сессию, а результат получает от платёжного провайдера отдельно. Поэтому основной маршрут здесь — связать заказ, checkout session и одно подтверждённое событие провайдера.
Один путь от кнопки до доступа
- Браузер отправляет серверу ID выбранного тарифа, но не задаёт доверенную сумму.
- Сервер ищет товар в своём каталоге, рассчитывает сумму и валюту, создаёт заказ со статусом pending и запрашивает checkout session.
- Браузер переходит по URL, возвращённому сервером. Страница возврата показывает состояние заказа из backend; параметр success в URL не открывает платный доступ.
- Провайдер отправляет webhook. Сервер проверяет подпись по исходному телу запроса, тип события, session/order IDs, сумму и валюту.
- В одной транзакции приложение фиксирует ID события и допустимый переход заказа в paid. Только после этого оно выдаёт доступ.
Именно такой раздел обязан оставаться серверным: ключ checkout не попадает в bundle, а браузер не может подменить цену. Stripe Checkout quickstart показывает hosted checkout с созданием сессии сервером; webhook guide описывает доставку событий приложению.
Не обработай одно действие дважды
Сбой соединения может оставить неизвестным, создал ли провайдер объект. Для запроса создания session используй поддерживаемый провайдером idempotency key и сохраняй связь между ключом, заказом и ответом. Для webhook храни обработанные event IDs с уникальным ограничением. Это разные уровни защиты: первый предотвращает повтор создания объекта при сетевом retry, второй — повторную обработку доставленного события.
При повторном webhook обработчик должен подтвердить получение, но не выдавать товар снова. Если пришло событие с незнакомым заказом или несовпадающей суммой, оставь заказ без доступа и отправь запись на разбор. Не превращай ошибку проверки в автоматическое «успешно».
Stripe отдельно документирует idempotent requests, включая повтор запроса после ошибки соединения. Детали хранения результата и правила ошибок относятся к API провайдера; схема статусов заказа остаётся ответственностью приложения.
Проверка на тестовом окружении
Проведи четыре сценария: закрыть Checkout до оплаты; получить успешное тестовое событие; повторить тот же event ID; отправить событие с неверной подписью. Сверь запись заказа и доступ, а не только текст на странице. Используй sandbox/test mode и локальный webhook forwarder из Stripe testing; это не доказывает конфигурацию production. Для возвратов и подписок отдельно определи переходы по своим правилам продукта.
Не сохраняй платёжные реквизиты в собственной форме, если используешь hosted checkout. И не выдавай тестовый успешный event за реальную транзакцию.
