SportAPI Документация
RU
C Документация продуктаCoupon API
v1
Услуга и цены ↗ Получить доступ ↗
Coupon API / Повторы и идемпотентность

Повторы и идемпотентность callback

Один callback может прийти повторно. Это нормальная часть надежной доставки, а не ошибка Системы расчета купонов SportAPI.

Например, партнер мог успешно сохранить пакет, но ответ HTTP 200 потерялся в сети. Система не знает, был ли запрос обработан, поэтому отправляет тот же пакет еще раз.

Обработчик должен быть идемпотентным: повтор одного пакета не должен повторно изменять купоны, ставки или баланс.

Два вида повторного появления купона

Важно различать:

Ситуацияcoupon_codebatchIdЧто делать
Повторная доставка того же HTTP-пакетаТот жеТот жеНе выполнять бизнес-операции повторно, вернуть HTTP 200.
Новое расчетное состояние купонаТот жеНовыйОбновить купон и ставки как новую версию.

Один coupon_code может законно приходить несколько раз с разными batchId, особенно для экспресса.

Назначение batchId

batchId идентифицирует конкретную версию пакета callback.

Гарантируется следующее:

  • повторная HTTP-доставка одного пакета сохраняет тот же batchId;
  • новое расчетное состояние получает новый batchId;
  • один пакет может содержать до 100 купонов;
  • batchId не является ID купона или ставки.

Создайте уникальный индекс по batchId в таблице входящих callback.

Пример:

CREATE TABLE coupon_callback_batches (
    batch_id VARCHAR(64) PRIMARY KEY,
    received_at TIMESTAMP NOT NULL,
    coupon_count INTEGER NOT NULL
);

Это только пример структуры. Названия таблиц и типы данных партнер выбирает самостоятельно.

Почему недостаточно coupon_code

Нельзя использовать coupon_code как уникальный ID доставки:

coupon_code = 000000000272
batchId = version_A

coupon_code = 000000000272
batchId = version_B

Обе версии относятся к одному купону, но содержат разные состояния ставок. Если отклонить вторую версию как дубликат по coupon_code, локальная запись не будет обновлена.

Используйте:

  • batchId — для дедупликации HTTP-пакетов;
  • coupon_code — для поиска и обновления купона;
  • uuid — для обновления конкретной ставки внутри купона.

Транзакционная обработка

Регистрацию batchId, обновление купонов и финансовые операции нужно выполнять в одной транзакции.

Рекомендуемый алгоритм:

  1. проверить HMAC-подпись;
  2. разобрать JSON;
  3. проверить couponCount и массив coupons;
  4. начать транзакцию;
  5. попытаться вставить batchId с уникальным ограничением;
  6. если batchId уже существует, не повторять бизнес-операции;
  7. обработать все элементы coupons;
  8. выполнить upsert купонов по coupon_code;
  9. выполнить upsert ставок по сочетанию coupon_code + uuid;
  10. выполнить необходимые финансовые операции;
  11. зафиксировать транзакцию;
  12. вернуть HTTP 200.

Упрощенная схема:

BEGIN

INSERT batchId
    если уже существует:
        COMMIT
        вернуть 200

FOR EACH coupon:
    UPSERT coupon BY coupon_code

    FOR EACH event:
        UPSERT event BY coupon_code + uuid

    APPLY financial changes idempotently

COMMIT
вернуть 200

Не фиксируйте batchId отдельной транзакцией до сохранения купонов. Если приложение завершится после записи batchId, но до обновления купонов, повторная доставка будет ошибочно считаться полностью обработанной.

Атомарная обработка пакета

В пакете может быть несколько купонов. HTTP 200 означает, что партнер успешно зафиксировал весь пакет.

Если хотя бы один элемент не удалось сохранить:

  • откатите транзакцию;
  • не возвращайте HTTP 200;
  • верните временный серверный код, при котором предусмотрен повтор.

Если архитектура не позволяет обновить все системы одной транзакцией, сначала атомарно сохраните входящий пакет во внутреннюю inbox-очередь партнера, а бизнес-обработку выполняйте из нее идемпотентно.

Идемпотентность финансовых операций

Уникальность batchId защищает от повторной доставки пакета, но финансовым операциям нужна дополнительная защита на уровне купона.

Причина: один купон может получить несколько разных batchId, например промежуточный и финальный снимки.

Партнер должен отдельно фиксировать:

  • был ли уже обработан текущий финальный результат;
  • было ли уже выполнено повторное списание при переходе в статус 15;
  • было ли начислено новое realWin после перерасчета.

Одна из возможных схем — хранить номер локального цикла расчета купона:

coupon_code = 000000000272
settlement_generation = 0

При первом переходе в статус 15 номер увеличивается:

settlement_generation = 1

Для финансовых операций можно сформировать уникальные ключи:

000000000272:1:recalculation_debit
000000000272:1:final_credit

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

Конкретную схему партнер выбирает самостоятельно. Главное правило: один расчетный переход должен создавать не более одной финансовой операции каждого типа.

Когда выполняются повторы

Повторная доставка выполняется при:

  • timeout;
  • ошибке соединения или другой транспортной ошибке;
  • HTTP 500;
  • HTTP 502;
  • HTTP 503;
  • HTTP 504.

Эти ситуации считаются временными.

Расписание попыток

Первая отправка выполняется сразу. После неудачи используются интервалы:

1-я попытка: сразу
2-я попытка: через 1 минуту
3-я попытка: через 5 минут
4-я попытка: через 15 минут
5-я попытка: через 1 час

Всего выполняется не более пяти HTTP-отправок с учетом первой.

После исчерпания попыток автоматическая доставка прекращается. Для восстановления и сверки используйте методы чтения купонов, описанные в разделе «Резервный polling».

Таймаут

Полный HTTP-запрос callback ожидается не более 10 секунд.

Обработчик партнера должен успеть:

  1. проверить подпись;
  2. атомарно сохранить пакет;
  3. вернуть HTTP 200.

Долгие дополнительные операции рекомендуется выполнять после надежного сохранения пакета во внутреннюю очередь партнера.

Если endpoint обработал пакет, но не успел вернуть ответ, запрос может прийти повторно с тем же batchId.

Поведение для HTTP-ответов

Ответ или ситуацияПоведение доставки
HTTP 200, пустое телоУспешно.
HTTP 200, success: true, processed = couponCountУспешно.
HTTP 200, success: falseФинальная ошибка без автоматического повтора.
HTTP 200, processed < couponCountФинальная ошибка без автоматического повтора.
HTTP 401Неверная или отсутствующая подпись; финальная ошибка.
HTTP 403Запрос запрещен IP-фильтром; финальная ошибка.
HTTP 500, 502, 503, 504Временная ошибка; выполняются повторы.
Timeout или транспортная ошибкаВыполняются повторы.
Любой другой HTTP-кодФинальная ошибка без автоматического повтора.

Текущий контракт требует именно HTTP 200. Ответы 201, 202 и 204 не считаются успешными и автоматически не повторяются.

Как отвечать при временной ошибке

Не возвращайте HTTP 200 с телом:

{
  "success": false
}

Такой ответ считается финальным и не запускает повтор.

Если пакет временно невозможно сохранить, верните предусмотренный временный HTTP-код, например:

HTTP/1.1 503 Service Unavailable

После восстановления endpoint система повторит доставку по расписанию.

Успешное подтверждение

Минимальный ответ:

HTTP/1.1 200 OK

Тело может быть пустым.

Рекомендуемый расширенный ответ:

{
  "success": true,
  "processed": 3
}

processed должен быть равен полученному couponCount.

При повторном batchId, который уже полностью обработан, также верните HTTP 200. Повторять сохранение и финансовые операции не нужно.

Типичные ошибки интеграции

  • Уникальность установлена только по coupon_code.
  • batchId сохраняется до бизнес-данных в отдельной транзакции.
  • HTTP 200 возвращается до фиксации транзакции.
  • При повторном batchId снова выполняется начисление.
  • Каждый новый batchId автоматически считается новым финансовым результатом.
  • Обрабатывается только coupons[0], а остальные элементы пакета пропускаются.
  • Для временной ошибки возвращается success: false с HTTP 200.
  • Endpoint возвращает 204, хотя контракт требует 200.
  • Долгая бизнес-обработка превышает таймаут и вызывает повтор.

Контрольный список

  • batchId имеет уникальное ограничение.
  • Повторный batchId возвращает HTTP 200 без повторной обработки.
  • Один coupon_code может иметь несколько версий.
  • Весь массив coupons обрабатывается атомарно.
  • Купоны обновляются по coupon_code.
  • Ставки обновляются по coupon_code + uuid.
  • Финансовые операции имеют собственные уникальные ключи.
  • HTTP 200 возвращается только после надежного сохранения.
  • Временная ошибка возвращает 500, 502, 503 или 504.
  • success: false не используется для запроса автоматического повтора.

Следующий раздел: «Резервный polling».