Принимаем оплату через ЮKassa: заказ, вебхук и честный статус

Принимаем оплату через ЮKassa: заказ, вебхук и честный статус

В демо-магазине demo-form.dynapi.ru покупатель выбирает кофе, заполняет форму заказа и нажимает «Оплатить». Дальше покупателя ведут три сценария: первый создаёт заказ и уводит на страницу ЮKassa, второй принимает вебхук о платеже, третий показывает статус заказа. Собирает всю цепочку визард: восемь вопросов в окне создания сценария, на выходе — три сценария и шаблон, которые остаётся просмотреть и опубликовать. Разбираем цепочку по шагам: что происходит между «Оплатить» и «Заказ оплачен», как приёмник вебхука проверяет уведомление и почему статус заказа читается из базы, а не из редиректа.

Все платежи в демо — тестовые: магазин подключён к тестовому кабинету ЮKassa, деньги не двигаются.

Что собирает визард

Пресет «Приём оплаты через ЮKassa» живёт в окне создания сценария: Сайт → Сценарии → «Создать сценарий». Визард спрашивает восемь вещей:

  • тип контента заказа и поля суммы и статуса в нём (в демо — тип order с полями amount и status);
  • id магазина и секретный ключ из личного кабинета ЮKassa — ключ визард прячет в Секреты и подставляет в Basic-заголовок запросов к API;
  • токен для адреса вебхука;
  • базовый адрес сайта и путь страницы статуса.

Галерея пресетов в редакторе сценария: карточки «Форма с капчей», «Уведомление в Telegram», «JSON-эндпоинт», «Выход», «Страница» и «Приём оплаты через ЮKassa»

Настройка пресета «Приём оплаты через ЮKassa»: тип контента для заказов, Shop ID, поле суммы, поле статуса, секретный ключ магазина, секрет в URL уведомлений, адрес сайта

На выходе — три сценария и шаблон статус-страницы. Маршруты визард публикует сам: POST-биндинг сценария оплаты вешает на маршрут сценария (его путь вы называете при создании — в демо это /yookassa/checkout), а страницу статуса и приёмник вебхука ставит на собственные маршруты — GET /payment/return и POST /yookassa-hook/<токен>. Сами сценарии и шаблон визард оставляет черновиками — их смотрят и публикуют вручную; тост в конце так и говорит: «Проверь результат и опубликуй». После публикации магазин принимает платежи.

Канвас сценария оплаты: цепочка узлов «Начало», GraphQL, HTTP POST с адресом api.yookassa.ru/v3/payments, GraphQL, «Редирект», соединённых стрелками сверху вниз

Оплата: заказ, платёж, редирект

Форму заказа магазин отправляет прямо на POST /yookassa/checkout — сценарий оплаты висит на этом маршруте. Дальше четыре действия:

  1. GraphQL создаёт запись заказа — в демо со статусом «в обработке» (в данных он записан как pending).
  2. HTTP POST уходит в api.yookassa.ru/v3/payments: сумма, описание и return_url — адрес статуса, собранный из базового адреса сайта и пути.
  3. GraphQL дописывает в заказ идентификатор платежа, вернувшийся от ЮKassa.
  4. «Редирект» отправляет покупателя на confirmation_url — страницу ЮKassa, где вводятся карточные данные.

Сумма — самое капризное поле цепочки. Из формы она приходит без фиксированного формата: «990» или «990.5». ЮKassa же требует строку с двумя знаками после запятой. Пресет прогоняет сумму через фильтр money, который приводит значение к виду «990.00» — и в заказ, и в платёж уходит одно и то же число в одном и том же формате. Без этой нормализации сверка вебхука разваливается на ровном месте: «990» и «990.00» для сравнения строк — разные значения.

Вебхук: сверка перед доверием

Оплату ЮKassa подтверждает вебхуком — POST на адрес приёмника. Адрес содержит длинный токен, но постучаться на него может кто угодно: токен в URL — не доказательство платежа. Поэтому приёмник не верит самому факту уведомления. Он ищет в базе заказ, у которого совпадают и идентификатор платежа из уведомления, и сумма. Нашёл — ставит статус paid («оплачен»). Не нашёл — отвечает 400 mismatch и ничего не трогает.

Проверить защиту можно прямо из командной строки — послать уведомление с выдуманным идентификатором платежа:

curl -X POST https://demo-form.dynapi.ru/yookassa-hook/<токен> \
  -d '{"event":"payment.succeeded","object":{"id":"fake-id","status":"succeeded","amount":{"value":"990.00","currency":"RUB"}}}'

Приёмник отработает три узла — поиск заказа, условие, ответ — и вернёт отказ. В журнале запусков прогон выглядит как штатный: сценарий не сломался, отказ — его решение.

Журнал запусков приёмника вебхука: прогон от 15.09.2026 с бейджем «ок», три узла — graphql со статусом success и длительностью 49 мс, condition с веткой «else», respond_json со статусом success

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

Статус: из базы, а не из редиректа

Расплатившись, покупатель возвращается на /payment/return?order=<id>. Страница статуса — тоже сценарий: один запрос GraphQL читает заказ из базы, шаблон рисует то, что в базе написано. «Заказ оплачен» появляется только после вебхука; пока тот не пришёл, покупатель видит «Заказ в обработке» — даже если страница открыта в ту же секунду после редиректа. Редирект — это чьё-то слово, запись в базе — факт.

Страница статуса заказа: карточка с зелёной галочкой, заголовком «Заказ оплачен», номером заказа и словами «оплачен. Спасибо!»

Повторить у себя

Понадобятся тестовый магазин ЮKassa (id и секретный ключ — в личном кабинете) и сайт на платформе. Дальше: Сайт → Сценарии → «Создать сценарий» → пресет «Приём оплаты через ЮKassa», восемь вопросов, публикация сценариев и шаблона. Форму на сайте визард не создаёт — добавьте форму, которая отправляет название и сумму POST-запросом на маршрут сценария оплаты (в демо это форма заказа кофе). В личном кабинете ЮKassa пропишите адрес вебхука — его показывает визард.

Полный разбор пресета — с Liquid-шаблонами, фильтром money и настройкой вебхука — в документации: Визард «Приём оплаты через ЮKassa».


All posts