Leyla оплатила заказ и сразу закрыла вкладку: страница «Спасибо» так и не открылась. Как ваш сервер узнает, что деньги получены? От Stripe, а не от браузера: Stripe сам отправляет запрос на ваш сервер — вебхук. В этом уроке мы построим приёмник вебхуков в Express: проверим подпись, чтобы никто не подделал «оплату», отметим заказ PAID в одной транзакции вместе с событием для RabbitMQ, переживём повторы и перепутанный порядок событий и научимся проверять всё на своём компьютере через Stripe CLI.
Зачем нужны вебхуки
Многое в платеже происходит вне вашего запроса: клиент вводит код 3-D Secure, банк подтверждает платёж позже, вы возвращаете деньги из Dashboard, клиент оспаривает платёж. Спрашивать Stripe «ну что, оплатили?» каждую секунду расточительно. Вебхук устроен наоборот: когда что-то происходит, Stripe сам отправляет на ваш адрес HTTP-запрос POST с описанием события (event). Аналогия: SMS от банка «вам поступил перевод» надёжнее, чем слова отправителя «я заплатил».
Событие выглядит так (в сокращении):
{
"id": "evt_1Rk4ZtAb",
"object": "event",
"type": "checkout.session.completed",
"created": 1789000000,
"livemode": false,
"data": {
"object": {
"id": "cs_test_a1B2c3",
"object": "checkout.session",
"payment_status": "paid",
"amount_total": 5000,
"currency": "azn",
"client_reference_id": "42"
}
}
}id уникален (по нему отличают повторы), type говорит, что случилось, а в data.object лежит сам объект — здесь Checkout Session.
Какие события нужны
| Событие | Когда приходит | Что делает магазин |
|---|---|---|
checkout.session.completed | клиент завершил Checkout | если payment_status равен paid, заказ становится PAID |
checkout.session.async_payment_succeeded | отложенный способ оплаты наконец оплачен | заказ становится PAID |
checkout.session.async_payment_failed | отложенный платёж не прошёл | сообщить клиенту |
checkout.session.expired | сессия истекла без оплаты | освободить заказ (по желанию) |
payment_intent.succeeded | оплачен PaymentIntent (урок 041) | заказ PAID при своей форме оплаты |
Для Checkout берём события сессии: в них приходят ваши client_reference_id и metadata. У payment_intent.succeeded метаданных сессии нет, пока вы не передали payment_intent_data.metadata. Подписывайтесь только на нужные события: Stripe не советует слушать всё подряд.