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

Сквозной пример: ординар

Ординар — это купон с одной ставкой.

В этом руководстве показан полный сценарий:

Получить JWT

Получить указатель исхода

Отправить купон

Получить code = 1

Сохранить купон и ставку

Получить результат через callback или API

Один раз выполнить финансовую операцию

Что понадобится

Получите у менеджера:

  • базовый URL API;
  • логин и пароль клиентского аккаунта;
  • подключение callback и секретную фразу, если партнер хочет использовать callback.

В примерах используется условный адрес:

BASE_URL="https://coupon-api.example.com"

Замените его адресом, полученным у менеджера.

Шаг 1. Получите JWT

curl --request POST \
  --url "$BASE_URL/api/partner/login" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{
    "username": "<username>",
    "password": "<password>"
  }'

Успешный ответ содержит токен:

{
  "code": 1,
  "body": {
    "token": "eyJhbGciOiJIUzI1NiJ9...",
    "username": "partner-demo"
  },
  "error_code": null,
  "error_message": null,
  "date": 1784970000000,
  "time_ms": 35,
  "path": "/api/partner/login"
}

Сохраните body.token и передавайте его в следующих запросах:

Authorization: Bearer <token>

Подробнее: «Авторизация».

Шаг 2. Подготовьте ставку

Пользователь выбирает исход в спортивной линии. Партнер получает готовый указатель этого исхода:

line#737779544#1#1#0#1.85

В этом указателе:

line       → прематч
737779544  → ID события
1          → ID группы ставок
1          → ID исхода
0          → параметр исхода
1.85       → коэффициент, показанный пользователю

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

Подробнее: «Указатель ставки».

Шаг 3. Подготовьте финансовую операцию

До отправки купона система партнера должна проверить:

  • пользователя;
  • доступный баланс;
  • допустимость суммы и валюты;
  • возможность выполнить одно списание amount.

Содержимое корзины на этом этапе является только предварительным выбором. Купон еще не принят Системой расчета купонов SportAPI.

Партнер может предварительно зарезервировать сумму во внутренней системе, но окончательно считать ставку принятой можно только после успешного ответа API.

Шаг 4. Создайте ординар

Передайте один указатель в list_bets:

TOKEN="<jwt-token>"

curl --request POST \
  --url "$BASE_URL/api/partner/coupons/place" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "list_bets": [
      "line#737779544#1#1#0#1.85"
    ],
    "amount": 10,
    "currency": "USD",
    "callback_url": "https://partner.example.com/api/coupon-result",
    "lang": "en",
    "mode": "reject",
    "mode_type": null,
    "multi": false
  }'

В этом примере:

ПолеЗначение
list_betsСодержит одну ставку, поэтому создается ординар.
amountСумма ординара — 10.
currencyВалюта партнера — USD. Допускается любое строковое обозначение.
callback_urlURL обработчика партнера.
langНазвания в купоне будут сохранены на английском языке.
modereject запрещает принимать изменившийся коэффициент автоматически.
multifalse; для одного элемента все равно будет создан один ординар.

Если callback не используется, callback_url можно не передавать или передать null.

Если партнер хочет автоматически принимать изменения коэффициента, используется mode = "accept" и соответствующий mode_type. Все варианты описаны на странице «Изменение коэффициентов и доступности исхода».

Шаг 5. Проверьте ответ создания

Пример успешного ответа:

{
  "code": 1,
  "body": {
    "coupons": [
      {
        "coupon_code": "000000000272",
        "amount": 10,
        "win": 18.5,
        "potential_win": 18.5,
        "real_win": null,
        "coef": 1.85,
        "original_coef": 1.85,
        "calculate_coef": null,
        "has_return": false,
        "date": 1784970000000,
        "status": 0,
        "asian": false,
        "calculate_date": null,
        "coupon_type": 1,
        "events_count": 1,
        "events_data": [
          {
            "id": 912,
            "game_id": 737779544,
            "raw_pointer": "line#737779544#1#1#0#1.85",
            "line_type": "line",
            "is_live": false,
            "bet_group_id": 1,
            "bet_group_name": "Match result",
            "bet_id": 1,
            "bet_name": "First team to win",
            "status": 0,
            "opp1": "Team A",
            "opp2": "Team B",
            "coef": 1.85,
            "calc_coef": null,
            "calculate_date": null,
            "calculate_score": null,
            "rate": "0"
          }
        ]
      }
    ]
  },
  "error_code": null,
  "error_message": null,
  "date": 1784970000015,
  "time_ms": 45,
  "path": "/api/partner/coupons/place"
}

Купон принят, только если одновременно:

  • HTTP-запрос успешно завершен;
  • code = 1;
  • в body.coupons есть созданный купон.

Важно. Не сохраняйте корзину как принятый купон и не выполняйте окончательное списание с конечного пользователя до получения этого подтверждения. Исход мог исчезнуть, быть заблокирован или изменить коэффициент.

При успешном ответе Система расчета купонов SportAPI уже атомарно списала amount с клиентского баланса интеграции. Это не заменяет отдельную финансовую операцию в кошельке пользователя партнера.

Даже для одного ординара body.coupons является массивом.

Шаг 6. Сохраните купон и обработайте баланс пользователя

После code = 1 партнер:

  1. сохраняет купон из body.coupons[0];
  2. связывает его со своим пользователем;
  3. сохраняет coupon_code как строку;
  4. сохраняет ставку из events_data[0];
  5. фиксирует в своей системе списание amount = 10 с конечного пользователя;
  6. переводит локальную ставку в состояние «принята».

Рекомендуемые ключи:

купон: coupon_code
ставка: coupon_code + events_data[].id

В примере:

coupon_code = "000000000272"
bet id      = 912

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

  • coupon_code;
  • amount;
  • переданная currency, если она используется;
  • статус купона;
  • events_data[].id;
  • статус ставки.

currency не входит в полную модель ответа и callback, поэтому при необходимости сохраните ее из запроса создания.

Если купон не принят

Пример отказа из-за изменения коэффициента:

{
  "code": 0,
  "body": {
    "changes": [
      {
        "game_id": 737779544,
        "bet_coefficient": 1.85,
        "actual_coefficient": 1.75,
        "change_type": 2,
        "status": "rejected"
      }
    ]
  },
  "error_code": 501,
  "error_message": "Coefficient is change",
  "date": 1784970000000,
  "time_ms": 20,
  "path": "/api/partner/coupons/place"
}

Основные причины отказа:

error_codeПричинаДействие партнера
501Коэффициент изменился.Показать актуальный коэффициент и запросить подтверждение пользователя, если это требуется логикой партнера.
502Исход больше недоступен.Удалить или обновить исход в корзине.
503Исход заблокирован.Сообщить, что прием временно недоступен.
504Проверка исхода завершилась ошибкой.Не считать купон принятым; предложить повторить позже.
507Недостаточно средств на клиентском балансе SportAPI.Не сохранять купон; пополнить клиентский баланс или изменить сумму.

При code = 0:

  • принятый купон не создается;
  • coupon_code для новой ставки не появляется;
  • окончательное списание выполнять нельзя;
  • предварительный резерв суммы нужно освободить.

При 507 ни один купон не создан и клиентский баланс SportAPI не изменен.

Не повторяйте неопределенный запрос вслепую

Если соединение оборвалось и ответ создания не получен, нельзя автоматически повторять POST /coupons/place, не исключив успешную обработку первого запроса. Иначе один выбор пользователя может создать два купона.

Такую ситуацию нужно отправить в отдельный процесс сверки и проверить доступные данные перед повторной отправкой.

Шаг 7. Получите результат

У ординара одна ставка, поэтому после ее расчета купон получает результат:

Статус купонаСтатус ставкиРезультатФинансовое действие
21ВыигрышНачислить real_win.
42ПроигрышНичего не начислять; real_win = 0.
83 или 23Возврат или pushНачислить real_win, обычно равный amount.

Для половинного выигрыша и половинного проигрыша ставка может иметь статус 21 или 22. Используйте готовое значение real_win, а не рассчитывайте выплату по статусу самостоятельно.

Результат можно получить через callback, API или обоими способами.

Вариант A. Получение через callback

Callback должен быть заранее подключен менеджером, а при создании купона должен быть передан callback_url.

Пример callback выигравшего ординара:

POST /api/coupon-result HTTP/1.1
Host: partner.example.com
Content-Type: application/json
X-Coupon-Signature: sha256=<hex_hmac_sha256>
{
  "event": "coupons.settled",
  "batchId": "d407e986f3a64d9d36a77bf532322ef8",
  "clientId": 17,
  "couponCount": 1,
  "coupons": [
    {
      "coupon_code": "000000000272",
      "realWin": 18.5,
      "calculate_coefficient": 1.85,
      "status": 2,
      "calculate_date": 1784973600000,
      "events_data": [
        {
          "uuid": "912",
          "status": 1,
          "calculate_coefficient": 1.85,
          "calculate_date": 1784973600000,
          "calculate_score": "2:1",
          "timer": 0
        }
      ]
    }
  ]
}

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

  1. проверить HMAC по исходным байтам тела;
  2. зарегистрировать batchId с уникальным ограничением;
  3. найти купон по coupon_code;
  4. найти ставку по coupon_code + uuid;
  5. проверить статус купона;
  6. обновить купон и ставку;
  7. один раз начислить realWin = 18.5;
  8. зафиксировать транзакцию;
  9. вернуть HTTP 200.

Рекомендуемый ответ:

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

Не выполняйте повторное начисление, если тот же batchId уже обработан.

Подробнее:

Вариант B. Получение через API

Callback необязателен. Актуальное состояние можно запросить по сохраненному коду:

COUPON_CODE="000000000272"

curl --request GET \
  --url "$BASE_URL/api/partner/coupons/get?coupon_code=$COUPON_CODE" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer $TOKEN"

Сокращенный ответ выигравшего ординара:

{
  "code": 1,
  "body": {
    "coupon_code": "000000000272",
    "amount": 10,
    "real_win": 18.5,
    "calculate_coef": 1.85,
    "status": 2,
    "calculate_date": 1784973600000,
    "events_data": [
      {
        "id": 912,
        "status": 1,
        "calc_coef": 1.85,
        "calculate_date": 1784973600000,
        "calculate_score": "2:1"
      }
    ]
  },
  "error_code": null,
  "error_message": null,
  "date": 1784973600100,
  "time_ms": 6,
  "path": "/api/partner/coupons/get"
}

Проверьте:

code = 1
body.status = 2
body.real_win = 18.5

После этого обновите локальный купон и один раз начислите body.real_win.

Для регулярного резервного контроля используйте:

GET /api/partner/coupons/calculated?time=10

Практическая схема polling описана на странице «Резервный polling».

Идемпотентность финансового результата

Callback и polling могут вернуть один и тот же результат.

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

один расчетный результат
→ не более одного начисления

Пример уникального ключа финансовой операции:

000000000272:0:final_credit

Конкретный формат ключа партнер выбирает самостоятельно. Важно, чтобы повторный callback, повторный polling или повторная бизнес-обработка не создавали второе начисление.

Редкий сценарий: статус 15

Если уже рассчитанный ординар возвращен на перерасчет:

status купона = 15
status ставки = 4

Если предыдущий результат уже был финансово обработан, при первом получении этого нового состояния партнер:

  1. повторно списывает amount;
  2. переводит купон в ожидание нового результата;
  3. не считает статус 15 финальным;
  4. после нового финального статуса начисляет новое значение real_win.

Операции должны быть идемпотентными. Повтор одного callback с тем же batchId не должен повторять списание.

Итоговый алгоритм

1. Получить JWT.
2. Получить готовый указатель ставки.
3. Проверить или зарезервировать баланс пользователя.
4. Отправить POST /api/partner/coupons/place.
5. При code = 0 не создавать принятый купон и освободить резерв.
6. При code = 1 сохранить body.coupons[0] и списать amount.
7. Получить результат через callback или API.
8. Проверить финальный status.
9. Начислить готовое real_win / realWin не более одного раза.
10. Периодически сверять результаты через API.

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

  • Используется клиентский JWT.
  • Указатель исхода передается без изменений.
  • list_bets содержит один элемент.
  • amount относится к одному ординару.
  • Корзина не сохраняется как принятый купон до code = 1.
  • При отказе предварительный резерв освобождается.
  • coupon_code хранится строкой с ведущими нулями.
  • Сохраняются данные купона и ставки из ответа API.
  • currency сохраняется из запроса, если она нужна партнеру.
  • Callback проверяется по HMAC до разбора JSON.
  • Повторный batchId не обрабатывается финансово второй раз.
  • Финальный результат определяется по статусу купона.
  • Для начисления используется real_win или realWin.
  • Callback дополняется резервным polling.
  • Статус 15 обрабатывается как ожидание нового результата.

Следующий раздел: «Интеграция экспресса».