Документ для интеграции · S7 Wallet API v1Integration document · S7 Wallet API v1

Подключение казино к играм S7Connecting a casino to S7 games

Со стороны казино это ровно две вещи: эндпоинт кошелька, который зовём мы, и ссылка на игру, которую вы открываете в iframe. Больше ничего. From the casino's side this is exactly two things: a wallet endpoint we call, and a launch link you open in an iframe. Nothing else.

Кому:For: бэкенд и фронтенд казиноthe casino's backend and frontend Кастомер:Customer: exter-casino Версия:Version: Wallet API v1
Подставить свои значенияFill in your own values

Значения подставятся по всему тексту, включая примеры. Хранятся только в вашем браузере — в документ они не сохраняются, так что каждый читатель видит свои. The values are substituted throughout the text, examples included. They live in your browser only — nothing is saved into the document, so every reader sees their own.

1Как устроена сессияHow a session works

Границу ответственности проще всего увидеть по шагам.The division of labour is easiest to see step by step.

1.  Игрок нажимает «Играть» у вас на сайте.
2.  Вы выпускаете сессионный токен и открываете нашу ссылку в <iframe>.
3.  Мы зовём POST /auth на вашем кошельке с этим токеном.
       Вы отвечаете, кто это и сколько у него денег —
       именно отсюда мы узнаём валюту.
4.  Идёт игра. Каждая ставка — POST /bet, каждая выплата — POST /win.
       Ставку, которую нужно вернуть, отменяет POST /rollback.
5.  Игрок уходит. Токен перестаёт быть валидным — кроме rollback.
1.  The player presses Play on your site.
2.  You mint a session token and open our link in an <iframe>.
3.  We call POST /auth on your wallet with that token.
       You answer with the player and their balance —
       this is where we learn the currency.
4.  The game runs. Every stake is POST /bet, every payout POST /win.
       A stake that has to be given back is POST /rollback.
5.  The player leaves. The token stops being valid, except for rollback.

Шаги 3–5 держите вы. Всё остальное — мы.You host steps 3–5. We host everything else.

Валюту называете вы, а не URL.The currency comes from you, not from the URL.

Параметр currency в ссылке необязателен. Если его нет, мы спросим кошелёк — потому что единственный, кто точно знает, в чём номинирован счёт игрока, это тот, кто этот счёт держит. The currency parameter is optional. Leave it out and we ask your wallet — because the only party that knows what a player's account is denominated in is the one holding it.

2Ссылка на игруThe launch link

Её вы собираете у себя и открываете в iframe.You build it on your side and open it in an iframe.

https://{launch-host}/game/{customer}/{module}/{game}/?<params>
{launch-host} Адрес, который мы выдаём при интеграции. Спросите его; не берите тот, что видели на тестах — это наш стенд, и он переезжает.The address we give you at integration. Ask for it; do not reuse one you were shown during testing — that is our stage and it moves.
{customer} Идентификатор, который мы вам присваиваем. Ваш — exter-casino.The id we assign you. Yours is exter-casino.
{module} / {game} Называют игру; оба даём мы, по каждому тайтлу.Name the game; we give you both per title.

2.1ПараметрыParameters

ПараметрParameter Что кладёмWhat goes in it
tokenобяз.required Сессионный токен игрока. Вернём его вам в каждом вызове кошелька.The player's session token. We send it back on every wallet call.
partnerIdopt. Ваш идентификатор. Если на одном кошельке несколько брендов — кладите сюда бренд: мы вернём его в каждом вызове как operatorId, и один и тот же токен под двумя брендами останется двумя разными игроками.Your own id. If you run several brands off one wallet, put the brand here: we return it on every call as operatorId, so one token string under two brands stays two different players.
currencyopt. ISO 4217. Без него спросим кошелёк.ISO 4217. Omit it and we ask your wallet.
languageopt. ISO 639-1: en, ru. en-US тоже принимаем, регион сохраняем.ISO 639-1: en, ru. en-US is accepted; the region is kept.
openTypeopt. real или fun. Всё, чего мы не узнаём, и отсутствие параметра — это реальные деньги.real or fun. Anything we do not recognise, and anything absent, means real money.
devicetypeidopt. 1 web, 2 мобильный web, 3 iOS, 4 Android.1 web, 2 mobile web, 3 iOS, 4 Android.
gameIdopt. Ваш каталожный id игры, если он у вас есть.Your own catalogue id for the game, if you have one.
exitURLopt. Куда кнопка «выход» уводит игрока.Where the game's exit button sends the player.
depositURLopt. Куда его уводит кнопка кассы.Where its cashier button sends them.
https://{launch-host}/game/exter-casino/crash/default/
  ?token=abc123&partnerId=198&language=en&openType=real
  &devicetypeid=1&exitURL=https://casino.example/lobby

2.2Три вещи, которые стоит знатьThree things worth knowing

Регистр и разделители не важны. auth_token, authToken и AUTH-TOKEN — для нас один и тот же параметр. Если ваша платформа уже отдаёт другое написание — присылайте как есть: скорее всего мы его уже принимаем, а если нет, добавить его — это правка конфигурации у нас, а не у вас. Case and separators do not matter. auth_token, authToken and AUTH-TOKEN are the same parameter to us. If your platform already emits a different spelling, send it: we probably accept it already, and if not, accepting it is a configuration change on our side, not a change on yours.

Незнакомые параметры мы не выбрасываем. Они возвращаются в диагностике, так что если вы что-то прислали, а мы будто не заметили — видно ровно то, что пришло. Anything we do not recognise is kept, not dropped. It comes back in the diagnostics, so if you send us something and we appear to ignore it, you can see exactly what arrived.

Откройте ссылку в браузере до того, как напишете код кошелька. Мы отдадим страницу, которая перечисляет каждый присланный параметр, во что мы его сопоставили, и что ваш кошелёк ответил про токен. Это самый быстрый способ найти ссылку, собранную не на того кастомера. Open the launch link in a browser before writing any wallet code. We serve a page listing every parameter you sent, what we matched it to, and what your wallet said about the token. It is the fastest way to find a link built against the wrong customer.

В ссылке нет ваших секретов — и не должно быть.The link carries none of your secrets, and must not.

Общий ключ подписи не участвует в запуске: он живёт только на вашем бэкенде и на нашем. URL уходит в браузер игрока, где его видно целиком, поэтому единственная тайна там — сессионный токен, и он одноразовый и ваш. The shared signing key takes no part in a launch: it lives on your backend and ours. The URL goes into a player's browser where all of it is visible, so the only secret in it is the session token — single-use, and yours.

3Встраивание iframeEmbedding the iframe

Игра — обычная страница на нашем домене. От вас нужны размер и разрешение на звук.The game is an ordinary page on our domain. What it needs from you is a size and permission to make sound.

<iframe
    src="https://{launch-host}/game/exter-casino/crash/default/?token=abc123&openType=real"
    allow="autoplay; fullscreen"
    title="Crash"
    style="border:0; width:100%; height:100%; display:block">
</iframe>
  • Размер задаёте вы. Игра тянется на весь фрейм; своей высоты у неё нет. Дайте контейнеру реальную высоту — height:100% внутри блока нулевой высоты даёт пустой прямоугольник.
  • allow="autoplay" — без него в игре не будет звука до первого касания. fullscreen нужен, если даёте кнопку на весь экран.
  • Не ставьте sandbox, если не уверены в наборе флагов: игре нужны скрипты, свой origin и хранилище. Урезанный sandbox ломает её молча.
  • Новая сессия — новый элемент. Не переиспользуйте один iframe, меняя src: создавайте элемент заново, чтобы перезапуск был настоящей перезагрузкой, а не страницей, сохранившей прежнее состояние.
  • The size is yours to set. The game fills the frame and has no height of its own. Give the container a real height — height:100% inside a zero-height block is an empty rectangle.
  • allow="autoplay" — without it there is no sound until the first tap. fullscreen is needed if you offer a full-screen button.
  • Do not set sandbox unless you are sure of the flags: the game needs scripts, its own origin and storage. A trimmed sandbox breaks it silently.
  • A new session is a new element. Do not reuse one iframe by changing src; create the element again, so reopening is a real reload rather than a page that kept its state.

Только https, с обеих сторон.https on both sides, always.

Ваша страница по https не может открыть фрейм по http — браузер режет это как mixed content, молча и без диалога. Наш адрес всегда https; следите, чтобы и ваш был. A page on https cannot open a frame on http — browsers block it as mixed content, silently and with no prompt. Our address is always https; keep yours that way too.

4Эндпоинт кошелькаThe wallet endpoint

Пять методов. Вы даёте один базовый URL, пути мы дописываем сами.Five endpoints. You give us one base URL; we append the paths.

POST {base}/auth          проверить токен → кто игрок + баланс
POST {base}/getBalance    прочитать баланс, ничего не двигает
POST {base}/bet           списать ставку
POST {base}/win           начислить выплату
POST {base}/rollback      вернуть ставку
POST {base}/auth          verify a token → who the player is + balance
POST {base}/getBalance    read the balance, moves nothing
POST {base}/bet           debit a stake
POST {base}/win           credit a payout
POST {base}/rollback      give a stake back
  • Content-Type: application/json в обе стороны.
  • Всегда отвечайте HTTP 200, в том числе на отказ. Вердикт — это поле code в теле. Кошелёк, который говорит «недостаточно средств» четырёхсотым, неотличим от прокси, который говорит «плохой запрос», а нам эти два случая нужно различать: от них зависит, повторять операцию или нет.
  • Content-Type: application/json, both ways.
  • Always answer HTTP 200, including for a refusal. The verdict is the code in the body. A wallet that says "insufficient funds" with a 400 is indistinguishable from a proxy saying "bad request", and we have to tell those apart to decide whether to retry.

5ПодписьSignature

Каждый запрос несёт дайджест собственного тела.Every request carries a digest of its own body.

Auth: sha256(<сырые байты тела> + <общий ключ>)     hex, нижний регистр
Auth: sha256(<raw request body bytes> + <shared key>)     hex, lower case

Общий ключ обменивается при интеграции и по проводу не ходит.The shared key is exchanged at integration and never travels on the wire.

Проверяйте по тем байтам, которые пришли.Verify against the bytes you received.

Не по повторной сериализации разобранного тела. Пересборка меняет порядок ключей и пробелы, и расхождение выглядит как ошибка аутентификации — ровно до тех пор, пока кто-нибудь не заподозрит кодировщик. Прочитайте тело один раз, проверьте подпись, потом разбирайте. Not against a re-serialisation of the parsed body. Re-encoding reorders keys and changes spacing, and the resulting mismatch presents as an authentication failure for as long as nobody suspects the encoder. Read the body once, verify, then parse it.

6Запросы и ответыRequests and responses

В каждом запросе есть partnerId и token. Остальное зависит от операции. Every request carries partnerId and token. The rest depends on the operation.

6.1Что присылаем мыWhat we send

// POST /auth  и  POST /getBalance
{
  "partnerId":  "198",
  "operatorId": "brand-7",  // только если бренд был в ссылке
  "token":      "abc123",
  "currency":   "USD"       // только getBalance; в auth его нет
}

// POST /bet
{
  "partnerId":     "198",
  "token":         "abc123",
  "transactionId": "bet:alice:1187:0",   // наш; уникален навсегда
  "amount":        2.50,
  "currency":      "USD",
  "gameId":        "555",                // ВАШ каталожный id
  "roundId":       "crash:USD:1187",
  "roundFinished": false
}

// POST /win — то же плюс ставка, которую выплачивает
{
  "partnerId":        "198",
  "token":            "abc123",
  "transactionId":    "take:alice:1187:0",
  "betTransactionId": "bet:alice:1187:0",
  "amount":           7.00,
  "currency":         "USD",
  "gameId":           "555",
  "roundId":          "crash:USD:1187",
  "roundFinished":    true
}

// POST /rollback — вернуть ставку
{
  "partnerId":        "198",
  "token":            "abc123",   // МОЖЕТ БЫТЬ ПРОСРОЧЕН — см. §8
  "transactionId":    "cancel:alice:1187:0",
  "betTransactionId": "bet:alice:1187:0",
  "amount":           2.50,
  "currency":         "USD",
  "roundId":          "crash:USD:1187",
  "reason":           "rollback"
}
// POST /auth  and  POST /getBalance
{
  "partnerId":  "198",
  "operatorId": "brand-7",  // only when the link named a brand
  "token":      "abc123",
  "currency":   "USD"       // getBalance only; absent on auth
}

// POST /bet
{
  "partnerId":     "198",
  "token":         "abc123",
  "transactionId": "bet:alice:1187:0",   // ours; unique forever
  "amount":        2.50,
  "currency":      "USD",
  "gameId":        "555",                // YOUR catalogue id
  "roundId":       "crash:USD:1187",
  "roundFinished": false
}

// POST /win — the same, plus the bet it pays out
{
  "partnerId":        "198",
  "token":            "abc123",
  "transactionId":    "take:alice:1187:0",
  "betTransactionId": "bet:alice:1187:0",
  "amount":           7.00,
  "currency":         "USD",
  "gameId":           "555",
  "roundId":          "crash:USD:1187",
  "roundFinished":    true
}

// POST /rollback — give a stake back
{
  "partnerId":        "198",
  "token":            "abc123",   // MAY BE EXPIRED — see §8
  "transactionId":    "cancel:alice:1187:0",
  "betTransactionId": "bet:alice:1187:0",
  "amount":           2.50,
  "currency":         "USD",
  "roundId":          "crash:USD:1187",
  "reason":           "rollback"
}

roundId собран из игры, валюты и номера раунда: каждая игра нумерует свои раунды сама, поэтому голый номер уникален только внутри одной игры — составной id не даёт пятому раунду двух разных игр слиться в ваших отчётах. roundId is composed of the game, the currency and the round number. Each game numbers its own rounds, so the bare number is unique only within one game — a composed id keeps round 5 of two different games apart in your reports.

partnerId и operatorId — не два имени одного.partnerId and operatorId are not two names for one thing.

partnerId — это учётные данные: наш идентификатор в вашей системе, одно значение на всю интеграцию. operatorId приезжает с каждым запуском и говорит, к какому вашему бренду относится игрок. Он отсутствует целиком, если в ссылке бренда не было — так что при одном бренде про него можно забыть. partnerId is a credential: our id in your system, one value for the whole integration. operatorId arrives with each launch and says which of your brands the player belongs to. It is absent entirely when the link named no brand, so with one brand you can ignore it.

6.2Что отвечаете выWhat you answer

{
  "code":          0,
  "balance":       997.50,     // ПОСЛЕ операции
  "currency":      "USD",
  "transactionId": "bet:alice:1187:0",   // эхо нашего
  "message":       "",         // свободный текст, для человека в логах

  // только /auth:
  "playerId":      "p-1001",
  "username":      "alice"
}
{
  "code":          0,
  "balance":       997.50,     // AFTER the operation
  "currency":      "USD",
  "transactionId": "bet:alice:1187:0",   // echo ours
  "message":       "",         // free text, for humans reading logs

  // /auth only:
  "playerId":      "p-1001",
  "username":      "alice"
}

balance — баланс после операции, в той валюте, о которой спрашивали. На отказе присылайте баланс как есть, если он у вас под рукой: вам это ничего не стоит, а нам экономит вызов. balance is the balance after the operation, in the currency you were asked about. On a refusal send the unchanged balance if you have it: it costs you nothing and saves us a call.

7КодыCodes

КодCode ЗначениеMeaning Что делаем мыWhat we do
0OK операция примененаthe operation took effect
110 Уже примененоAlready processed операция прошла раньше; засчитываем один разthe operation took effect earlier; we count it once
1 Общая ошибкаGeneral error бросаем эту операциюgive up on this operation
3 Недостаточно средствInsufficient funds говорим игре, игрок это видитtell the game; the player sees it
4 Токен не найден или просроченToken not found or expired сессия оконченаthe session is over
7 Транзакция не найденаTransaction not found осмысленно только на rollback — см. нижеonly meaningful on rollback — see below

110 стоит перечитать дважды.110 is worth reading twice.

Это не синоним нуля. Он говорит, что деньги двинулись ровно один раз, а этот вызов был повтором — и именно он велит нам записать одну транзакцию вместо двух. На нём сходятся цифры оборота у вас и у нас. It is not a synonym for 0. It says the money moved exactly once and this call was a repeat — which is what tells us to record one transaction instead of two, and keeps the turnover figures on both sides agreeing.

8Пять правилThe five rules

Идемпотентность: ключ — это transactionId.Idempotency: transactionId is the key.

Повтор уже виденного не должен двигать деньги снова и должен отвечать 110 с текущим балансом. Мы повторяем: потерянный ответ не говорит нам, применили вы операцию или нет, — поэтому мы шлём тот же самый transactionId, пока не узнаем. Это самый важный пункт всего документа. A repeat of one you have seen must not move money again, and must answer 110 with the balance as it stands. We retry: a lost response does not tell us whether you applied the operation, so we re-send the same transactionId until we know. This is the single most important clause in this document.

Rollback обязан работать на просроченном токене.A rollback must work on an expired token.

Конец сессии игрока — не причина оставить его ставку у себя. Не проверяйте токен на /rollback. A player's session ending is not a reason to keep their stake. Do not check the token on /rollback.

Rollback того, чего у вас нет, — это 7, а не 1.A rollback of something you do not have is 7, not 1.

Семёрку мы считаем окончательным ответом: деньги в любом случае там, где должны быть, и мы останавливаемся. Единица означает «неизвестная ошибка», и мы продолжим пытаться. We treat 7 as settled — the money is where it should be either way — and stop. 1 means "unknown error" and we will keep trying.

Суммы положительные.Amounts are positive.

Направление задаёт эндпоинт: /bet списывает, /win начисляет, /rollback возвращает. Минуса не бывает никогда. The direction is the endpoint: /bet debits, /win credits, /rollback returns. There is never a minus sign.

/win с нулевой суммой — настоящий, и его нужно записать.A zero-amount /win is real and must be recorded.

Проигранный раунд не платит ничего, и мы всё равно о нём сообщаем: он должен дойти до вашего аудита, отчётности и бонусных лестниц. Не отбрасывайте его. A round the player lost pays nothing, and we still report it so that it reaches your audit, your reporting and your bonus ladders. Do not discard it.

9ДеньгиMoney

Суммы — JSON-числа в мажорных единицах с десятичными долями валюты: 2.50, не 250 и не "2.50". Amounts are JSON numbers in major units with the currency's decimals: 2.50, never 250 and never "2.50".

У себя мы держим деньги целым числом минорных единиц и конвертируем один раз — здесь, на краю провода. Конвертация, которая потеряла бы долю минорной единицы, падает, а не округляет: мы не станем молча срезать часть выплаты и предпочтём, чтобы интеграция громко сломалась на тестах. We hold money as an integer count of minor units and convert once, here at the wire edge. A conversion that would lose a fraction of a minor unit fails rather than rounding — we will not silently shave value off a payout, and we would rather an integration break loudly during testing.

Сделайте так же.Do the same on your side.

amount * 100 через float — это то, как 2.50 превращается в 249. amount * 100 through a float is how 2.50 becomes 249.

10Чек-листChecklist

Интеграция готова, когда выполняется всё перечисленное.Your integration is done when all of these hold.

  1. /auth с валидным токеном возвращает игрока и баланс./auth with a valid token returns the player and a balance.
  2. /bet уменьшает баланс ровно на сумму./bet decreases the balance by exactly the amount.
  3. /win увеличивает его ровно на сумму./win increases it by exactly the amount.
  4. Один и тот же transactionId дважды не двигает деньги дважды, и второй ответ — 110.The same transactionId twice does not move money twice, and the second answer is 110.
  5. /rollback восстанавливает баланс./rollback restores the balance.
  6. /rollback на просроченном токене всё равно работает./rollback with an expired token still works.
  7. /rollback неизвестного betTransactionId отвечает 7./rollback of an unknown betTransactionId answers 7.
  8. Ставка больше баланса отвечает 3, и баланс не меняется.A bet larger than the balance answers 3, and the balance is unchanged.
  9. /win с нулевой суммой принят и записан.A zero-amount /win is accepted and recorded.
  10. 2.50 на проводе — это 250 минорных единиц в вашем реестре, в обе стороны.2.50 on the wire is 250 minor units in your ledger, both ways.

Четвёртый и шестой — те, что падают в проде. Проверить их дёшево, а обнаружить дорого. Numbers 4 and 6 are the ones that fail in production. They are cheap to test and expensive to discover.

Есть эталонная реализация — тестируйте об неё.There is a reference implementation — test against it.

Мы отдадим эмулятор кошелька, который реализует этот документ буквально и работает строго: неверный дайджест тела он отвергает, а не терпит, — так что ошибка в подписи всплывает сейчас, а не на боевом запуске. Он же умеет ронять ответ после того, как деньги двинулись: это тот случай, который решает, спишется у игрока один раз или два, и его не даст на заказ ни одна песочница. We will give you a wallet emulator that implements this document exactly and is strict: a wrong body digest is refused rather than tolerated, so a signature mistake shows up now instead of at go-live. It also drops the response after the money has moved — the case that decides whether a player is charged once or twice, and the one no sandbox gives you on demand.

11Что прислать намWhat to send us

Базовый URL кошелькаWallet base URL один; пути /auth, /bet, … допишем самиone; we append /auth, /bet, …
Общий ключShared key для дайджеста телаfor the body digest
Ваш идентификаторYour operator id ляжет в partnerIdgoes in partnerId
Несколько ли брендов на одном кошелькеWhether you run several brands off one wallet если да — кладите бренд в ссылку и ждите его обратно как operatorIdif so, put the brand on the link and expect it back as operatorId
ВалютыCurrencies какие обслуживает эта интеграцияwhich ones this integration serves
Каталожные id игрGame ids ваш id для каждой нашей игры, если пользуетесь своимиyour catalogue id for each of our games, if you use your own
Горизонт повторовRetry horizon сколько вы держите transactionId, прежде чем считаете операцию окончательной — мы подстроимся, чтобы не сдаться раньше васhow long you keep a transactionId before you consider it settled — we match it, so that we never give up before you do

Если что-то из этого потом поменяется — всё это конфигурация на нашей стороне. Ни один пункт не требует деплоя. If any of it changes later, all of it is configuration on our side. None of it is a deploy.