Сквозной пример: экспресс
Экспресс — это один купон, объединяющий несколько ставок на разные матчи.
Главные отличия от ординара:
- в
list_betsпередается несколько указателей; multiдолжен быть равенfalse;- создается один
coupon_code; amountотносится ко всему экспрессу;- с клиентского баланса SportAPI автоматически списывается один
amount; - партнер отдельно списывает один
amountс конечного пользователя в своей системе; - ставки могут рассчитываться в разное время;
- один купон может получить несколько последовательных callback.
Общая схема
Пользователь выбирает несколько разных матчей
↓
Партнер формирует одну корзину экспресса
↓
POST /api/partner/coupons/place
multi = false
↓
code = 1
↓
Один купон
Автоматическое списание amount с клиентского баланса SportAPI
Отдельное списание amount с пользователя в системе партнера
↓
Последовательные изменения ставок
↓
Финальный результат + выплата пользователю по real_win
Ограничения экспресса
Перед отправкой проверьте корзину:
- в экспрессе должно быть не менее двух ставок;
- один экспресс может содержать не более 15 событий;
- ставки должны относиться к разным матчам;
- нельзя объединять основное событие и его саб-событие;
- нельзя объединять разные саб-события одного матча.
Запрещенные сочетания:
- результат матча и угловые этого же матча;
- результат матча и отдельный тайм;
- угловые и фолы одного матча;
- разные периоды, сеты или другие саб-события одного матча.
Для определения общего матча учитывайте не только game_id, но и связь с основным событием. Готовую проверку также выполняет Система расчета купонов SportAPI.
Если правило нарушено, API возвращает:
error_code = 506
Шаг 1. Получите JWT
Авторизуйтесь через:
POST /api/partner/login
В дальнейших примерах используется:
BASE_URL="https://coupon-api.example.com"
TOKEN="<jwt-token>"
Фактический BASE_URL и данные входа партнер получает у менеджера.
Подробности: «Авторизация».
Шаг 2. Подготовьте указатели
В примере пользователь выбрал три исхода из трех разных матчей:
line#737779544#1#1#0#1.80
line#737880112#8#6#2.5#1.50
line#738004921#17#9#3.5#2.00
Каждый указатель нужно получить из выбранного исхода спортивной линии и передать без изменений.
Исходные коэффициенты:
1.80 × 1.50 × 2.00 = 5.40
При сумме 10 исходная возможная выплата:
10 × 5.40 = 54
API вернет фактические original_coef и potential_win после успешного создания. Для сохранения и отображения используйте значения из ответа, а не только предварительный расчет корзины.
Шаг 3. Покажите пользователю корзину
До подтверждения интерфейс партнера должен ясно показывать:
- что создается один экспресс;
- три выбранные ставки;
- коэффициент каждой ставки;
- общий предварительный коэффициент;
- сумму одного экспресса;
- возможную выплату;
- правило приема изменившегося коэффициента.
В нашем примере:
тип: экспресс
ставок: 3
сумма: 10 USD
общая сумма списания: 10 USD
предварительный коэффициент: 5.40
предварительная выплата: 54 USD
Корзина является предварительным выбором, а не принятым купоном.
Шаг 4. Создайте экспресс
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.80",
"line#737880112#8#6#2.5#1.50",
"line#738004921#17#9#3.5#2.00"
],
"amount": 10,
"currency": "USD",
"callback_url": "https://partner.example.com/api/coupon-result",
"lang": "en",
"mode": "reject",
"mode_type": null,
"multi": false
}'
Ключевое поле:
{
"multi": false
}
Оно объединяет все три элемента list_bets в один экспресс.
Если callback не используется, callback_url можно не передавать или передать null.
Не перепутайте с multi = true
При тех же трех указателях:
| Режим | Результат | Общая сумма |
|---|---|---|
multi = false | Один экспресс из трех ставок | 10 |
multi = true | Три отдельных ординара | 10 × 3 = 30 |
multi = true не создает экспресс. Сумма amount применяется полностью к каждому созданному ординару.
Шаг 5. Обработайте успешное создание
Сокращенный пример ответа:
{
"code": 1,
"body": {
"coupons": [
{
"coupon_code": "000000000350",
"amount": 10,
"win": 54,
"potential_win": 54,
"real_win": null,
"coef": 5.4,
"original_coef": 5.4,
"calculate_coef": null,
"has_return": false,
"date": 1784970000000,
"status": 0,
"calculate_date": null,
"coupon_type": 2,
"events_count": 3,
"events_data": [
{
"id": 912,
"game_id": 737779544,
"raw_pointer": "line#737779544#1#1#0#1.80",
"bet_name": "First team to win",
"status": 0,
"coef": 1.8,
"calc_coef": null,
"calculate_date": null
},
{
"id": 913,
"game_id": 737880112,
"raw_pointer": "line#737880112#8#6#2.5#1.50",
"bet_name": "Total over 2.5",
"status": 0,
"coef": 1.5,
"calc_coef": null,
"calculate_date": null
},
{
"id": 914,
"game_id": 738004921,
"raw_pointer": "line#738004921#17#9#3.5#2.00",
"bet_name": "Total over 3.5",
"status": 0,
"coef": 2,
"calc_coef": null,
"calculate_date": null
}
]
}
]
},
"error_code": null,
"error_message": null,
"date": 1784970000015,
"time_ms": 48,
"path": "/api/partner/coupons/place"
}
Проверьте:
code = 1
body.coupons.length = 1
coupon_type = 2
events_count = 3
events_data.length = 3
Только после этого:
- сохраните купон;
- сохраните все три ставки;
- сохраните
coupon_codeкак строку; - зафиксируйте одно списание
amount = 10с конечного пользователя в системе партнера; - отметьте локальный купон как принятый.
Рекомендуемые ключи ставок:
000000000350 + 912
000000000350 + 913
000000000350 + 914
Не создавайте три финансовых списания конечного пользователя: это ставки внутри одного экспресса, а не отдельные купоны. SportAPI со своей стороны уже списал один amount с клиентского баланса при успешном создании.
currency не входит в модель ответа и callback. Если она нужна, сохраните USD из исходного запроса.
Если экспресс отклонен
При code = 0:
- не сохраняйте корзину как принятый купон;
- не выполняйте окончательное списание;
- освободите предварительный резерв, если он использовался;
- обработайте
error_codeиbody.changes.
Несколько ставок одного матча
Пример:
{
"code": 0,
"body": null,
"error_code": 506,
"error_message": "<нельзя объединить ставки одного матча>",
"date": 1784970000000,
"time_ms": 12,
"path": "/api/partner/coupons/place"
}
Исправьте корзину так, чтобы от одного матча остался только один исход.
Изменение или недоступность исхода
Любой элемент экспресса может не пройти проверку:
error_code | Причина |
|---|---|
501 | Коэффициент изменился. |
502 | Исход отсутствует в текущей линии. |
503 | Исход заблокирован. |
504 | Проверка исхода завершилась ошибкой. |
507 | Недостаточно средств на клиентском балансе для создания экспресса. |
Проблемные элементы возвращаются в body.changes. В режиме reject изменение даже одного коэффициента не позволяет принять исходный экспресс.
При 507 экспресс не создается, а клиентский баланс SportAPI не изменяется.
Шаг 6. Сохраните начальное состояние
Сразу после создания:
status купона = 0
status ставок = [0, 0, 0]
real_win = null
calculate_coef = null
calculate_date = null
Статус 0 означает, что экспресс еще не получил окончательный результат.
Шаг 7. Обрабатывайте последовательные состояния
Ставки экспресса могут завершаться в разное время. Каждый callback содержит полный текущий снимок всех ставок.
Для нашего экспресса возможна последовательность:
[0, 0, 0] → купон создан
[1, 0, 0] → первая ставка выиграла
[1, 3, 0] → вторая ставка возвращена
[1, 3, 1] → третья ставка выиграла, экспресс завершен
Здесь:
0— ставка не рассчитана;1— выигрыш;3— возврат.
Первое промежуточное состояние
После выигрыша первой ставки callback может выглядеть так:
{
"event": "coupons.settled",
"batchId": "batch-version-001",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000350",
"realWin": 0,
"calculate_coefficient": 0,
"status": 0,
"calculate_date": 1784973600000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 1.8,
"calculate_date": 1784973600000,
"calculate_score": "2:1",
"timer": 0
},
{
"uuid": "913",
"status": 0,
"calculate_coefficient": null,
"calculate_date": null,
"calculate_score": "",
"timer": 0
},
{
"uuid": "914",
"status": 0,
"calculate_coefficient": null,
"calculate_date": null,
"calculate_score": "",
"timer": 0
}
]
}
]
}
Партнер:
- проверяет HMAC;
- сохраняет новый
batchId; - обновляет ставку
912; - оставляет купон активным;
- не начисляет
realWin = 0, потому чтоstatus = 0; - возвращает HTTP
200.
realWin = 0 в промежуточном снимке не означает проигрыш.
Второе промежуточное состояние
Вторая ставка возвращена:
batchId = batch-version-002
status купона = 0
status ставок = [1, 3, 0]
Это новое состояние того же купона:
coupon_codeостается000000000350;batchIdменяется;- ставка
913получает расчетный множитель1; - финансовую выплату пока выполнять нельзя.
Возврат одной ставки не возвращает весь экспресс. Эта ставка просто не увеличивает общий расчетный коэффициент.
Финальное выигрышное состояние
Третья ставка выиграла:
status ставок = [1, 3, 1]
Расчетные множители:
первая ставка: 1.80
вторая ставка: 1
третья ставка: 2.00
Итог:
calculate_coef = 1.80 × 1 × 2.00 = 3.60
real_win = 10 × 3.60 = 36
status купона = 2
Финальный callback:
{
"event": "coupons.settled",
"batchId": "batch-version-003",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000350",
"realWin": 36,
"calculate_coefficient": 3.6,
"status": 2,
"calculate_date": 1784980800000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 1.8,
"calculate_date": 1784973600000,
"calculate_score": "2:1",
"timer": 0
},
{
"uuid": "913",
"status": 3,
"calculate_coefficient": 1,
"calculate_date": 1784977200000,
"calculate_score": "0:0",
"timer": 0
},
{
"uuid": "914",
"status": 1,
"calculate_coefficient": 2,
"calculate_date": 1784980800000,
"calculate_score": "4:0",
"timer": 0
}
]
}
]
}
После проверки и сохранения партнер один раз начисляет пользователю:
realWin = 36
Используйте готовое значение из callback или real_win из полного API. Формула выше объясняет результат, но не должна заменять значение, рассчитанное API.
Отдельная ветка: первый проигрыш
Рассмотрим другой экспресс:
[1, 0, 0] → первая ставка выиграла
[1, 2, 0] → вторая ставка проиграла
Как только появилась ставка со статусом 2:
status купона = 4
realWin = 0
Система сразу отправляет callback о проигрыше, даже если третья ставка еще не рассчитана.
Партнер:
- обновляет купон до статуса
4; - фиксирует финансовый результат проигрыша;
- не выполняет начисление;
- сохраняет, что результат уже финансово обработан;
- возвращает HTTP
200.
Расчет оставшихся ставок продолжается, но отдельные промежуточные callback после первого проигрыша не отправляются.
Когда рассчитаны все ставки, приходит полный снимок:
[1, 2, 1]
status купона = 4
realWin = 0
новый batchId
Это не второй проигрыш и не новая финансовая операция. Обновите статусы оставшихся ставок, но не повторяйте обработку баланса.
Несколько версий и дедупликация
Для одного экспресса:
| Ситуация | coupon_code | batchId | Действие |
|---|---|---|---|
| Новое промежуточное состояние | Тот же | Новый | Обновить купон и ставки. |
| Финальный результат | Тот же | Новый | Обновить данные и один раз обработать выплату. |
| Повтор той же HTTP-доставки | Тот же | Тот же | Не повторять бизнес-операции, вернуть 200. |
Нельзя считать каждый новый batchId новой ставкой или новой финансовой операцией.
Храните:
coupon_code → идентификатор купона
uuid → идентификатор ставки внутри callback
batchId → идентификатор версии доставки
Половинные результаты в экспрессе
Половинный выигрыш
Две ставки:
WIN: coef = 1.80, factor = 1.80
HALF_WIN: coef = 2.00, factor = (2.00 + 1) / 2 = 1.50
При amount = 10:
calculate_coef = 1.80 × 1.50 = 2.70
real_win = 27
Половинный проигрыш
Две ставки:
WIN: coef = 1.80, factor = 1.80
HALF_LOSE: factor = 0.5
При amount = 10:
calculate_coef = 1.80 × 0.5 = 0.90
real_win = 9
HALF_LOSE не обнуляет экспресс. Полный проигрыш со статусом ставки 2 имеет множитель 0 и обнуляет весь купон.
Для начисления всегда используйте готовое real_win, возвращенное API.
Получение через API
Текущее полное состояние доступно по коду:
COUPON_CODE="000000000350"
curl --request GET \
--url "$BASE_URL/api/partner/coupons/get?coupon_code=$COUPON_CODE" \
--header "Accept: application/json" \
--header "Authorization: Bearer $TOKEN"
При обработке ответа:
- обновите купон по
coupon_code; - переберите весь
events_data; - обновите ставки по
coupon_code + id; - не считайте статус
0финальным; - используйте
real_winтолько для еще не обработанного финального состояния.
Даже при работающем callback рекомендуется резервный polling:
GET /api/partner/coupons/calculated?time=10
Возврат на перерасчет
Если экспресс возвращен на перерасчет:
status купона = 15
одна или несколько ставок имеют status = 4
Если предыдущий результат уже был финансово обработан, при первом получении этого состояния:
- повторно спишите одну сумму экспресса
amount; - переведите купон в ожидание нового результата;
- не считайте статус
15финальным; - после нового финального состояния начислите новое
real_win.
Повторное списание выполняется один раз для каждого нового перехода в статус 15, а не для каждой ставки со статусом 4.
Итоговый алгоритм
1. Проверить, что выбрано от 2 до 15 разных матчей.
2. Показать пользователю один экспресс и одну общую сумму.
3. Передать все указатели с multi = false.
4. При code = 0 не создавать принятый купон и не списывать сумму.
5. При code = 1 сохранить один купон и все events_data.
6. Один раз списать amount.
7. При каждом новом состоянии обновлять купон и ставки.
8. Не начислять деньги при status = 0.
9. При первом финансово новом финальном результате обработать real_win.
10. Повторный batchId подтверждать без повторной обработки.
11. Выполнять резервную сверку через API.
Контрольный список
- Экспресс содержит от 2 до 15 ставок.
- Каждая ставка относится к отдельному матчу.
- Основное событие не объединяется с его саб-событиями.
multiравенfalse.amountотносится ко всему экспрессу.- Пользователь видит одну общую сумму списания.
- Купон сохраняется только после
code = 1. - Сохраняются все ставки из
events_data. - Выполняется одно начальное списание.
- Подпись каждого callback проверяется по исходным байтам тела.
- Промежуточный статус купона
0не вызывает выплату. - Новый
batchIdобновляет существующийcoupon_code. - Повторный
batchIdне повторяет обработку. - Первый проигрыш обрабатывается сразу.
- Финальный полный снимок проигравшего экспресса не создает вторую финансовую операцию.
HALF_WIN,HALF_LOSE, возврат и push учитываются через готовоеreal_win.- Статус
15запускает один новый цикл ожидания результата. - Callback дополняется резервным polling.
Подробнее:
- «Ординары, экспрессы и multi»;
- «Жизненный цикл расчета»;
- «Статусы и расчет выплаты»;
- «Callback результатов»;
- «Проверка подписи callback»;
- «Повторы и идемпотентность».
Следующий раздел: «Восстановление после пропущенного callback».