API, webhooks и backend Технический кейс Опубликовано 14 июля 2026 г. 8 мин чтения

Кейс: платежный backend для Laravel и Django с webhooks и возвратами

Как спроектировать payment backend, который выдерживает повторные события, сетевые сбои и ручные операции без повторной выдачи заказа.

Кейс: платежный backend для Laravel и Django с webhooks и возвратами

В фокусе статьи

Кейс: платежный backend для Laravel и Django с webhooks и возвратамиAPI, webhooks и backendLaravel payment gateway ArmeniaDjango payment integration Armeniapayment webhook

Исходная задача: один контракт между заказом и провайдером

Backend не должен передавать провайдеру произвольную сумму из браузера или менять заказ без проверяемого payment state.

Order API -> payment attempt -> provider checkout
Laravel или Django хранит внутреннее состояние заказа отдельно от внешнего состояния платежа.

В кейсе сервер сначала валидирует заказ, сумму, валюту и доступность действия, затем создает payment attempt с внутренним id и idempotency key. Только после этого формируется безопасный redirect или checkout URL.

Внутренний order id, payment attempt id и provider payment id сохраняются вместе. Это дает возможность повторить запрос безопасно, а поддержку и учет не заставляет угадывать связь между операциями.

  • не принимать финальную сумму платежа как доверенный frontend-параметр;
  • разделять order, payment attempt и refund как разные сущности;
  • фиксировать валюту и правила округления на backend;
  • отвечать на повторный create-payment тем же результатом, если ключ идемпотентности совпадает.

Решение: конечный автомат и идемпотентные webhook-обработчики

Webhook может прийти несколько раз, позже return URL или в другом порядке, поэтому бизнес-статус меняется только по допустимым переходам.

Pending -> paid or failed -> refund review
Каждое событие проверяется, сохраняется и проходит через таблицу разрешенных переходов.

Backend проверяет подпись, сумму, валюту, correlation id и источник события. Затем он определяет, допускает ли текущий payment state полученный переход. Например, paid нельзя заменить на failed поздним callback, а повторный paid не должен запускать вторую выдачу заказа.

Laravel job или Django worker может выполнять вторичные действия асинхронно: уведомления, CRM sync, фискальный запрос или выдачу доступа. Критичный webhook-ответ остается быстрым, а сбой вторичного сервиса не теряет подтвержденную оплату.

  • сохранять raw event и нормализованный результат проверки;
  • отделять прием webhook от тяжелых фоновых действий;
  • вводить retry policy и ручной review для неразрешимых расхождений;
  • не выполнять необратимое действие до подтвержденного paid.

Контроль после запуска: возвраты, наблюдаемость и сверка

У команды должен быть журнал для платежных попыток, возвратов, retries и расхождений с отчетом провайдера.

Payment ledger -> webhook log -> reconciliation report
Каждая операция доступна по внутреннему id, внешнему id и correlation id без раскрытия чувствительных данных.

Возврат начинается с проверки исходного платежа, доступной суммы и роли оператора. После отправки запроса backend записывает отдельный refund record, а финальный результат подтверждает тем же безопасным механизмом статусов.

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

  • не логировать карточные данные или секреты;
  • выдавать оператору поиск по order id, payment id и correlation id;
  • разделять инициированный и подтвержденный возврат;
  • отдельно отслеживать webhook retries и задачи ручного review.

FAQ

Нужен ли отдельный payment attempt, если уже есть заказ?

Да. Заказ и попытка оплаты имеют разный жизненный цикл: один заказ может содержать несколько попыток, отмену или возврат.

Как обрабатывать повторный webhook?

Проверить подпись и идентификатор события, сохранить его для аудита, затем применить только допустимый идемпотентный переход состояния.

Можно ли выполнять CRM sync прямо внутри webhook?

Лучше принять и проверить событие быстро, а CRM sync запускать через устойчивую очередь с retry и журналом ошибок.