Сквозной пример: ординар
Ординар — это купон с одной ставкой.
В этом руководстве показан полный сценарий:
Получить 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_url | URL обработчика партнера. |
lang | Названия в купоне будут сохранены на английском языке. |
mode | reject запрещает принимать изменившийся коэффициент автоматически. |
multi | false; для одного элемента все равно будет создан один ординар. |
Если 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 партнер:
- сохраняет купон из
body.coupons[0]; - связывает его со своим пользователем;
- сохраняет
coupon_codeкак строку; - сохраняет ставку из
events_data[0]; - фиксирует в своей системе списание
amount = 10с конечного пользователя; - переводит локальную ставку в состояние «принята».
Рекомендуемые ключи:
купон: 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. Получите результат
У ординара одна ставка, поэтому после ее расчета купон получает результат:
| Статус купона | Статус ставки | Результат | Финансовое действие |
|---|---|---|---|
2 | 1 | Выигрыш | Начислить real_win. |
4 | 2 | Проигрыш | Ничего не начислять; real_win = 0. |
8 | 3 или 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
}
]
}
]
}
Обработчик партнера должен:
- проверить HMAC по исходным байтам тела;
- зарегистрировать
batchIdс уникальным ограничением; - найти купон по
coupon_code; - найти ставку по
coupon_code + uuid; - проверить статус купона;
- обновить купон и ставку;
- один раз начислить
realWin = 18.5; - зафиксировать транзакцию;
- вернуть 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
Если предыдущий результат уже был финансово обработан, при первом получении этого нового состояния партнер:
- повторно списывает
amount; - переводит купон в ожидание нового результата;
- не считает статус
15финальным; - после нового финального статуса начисляет новое значение
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обрабатывается как ожидание нового результата.
Следующий раздел: «Интеграция экспресса».