Разделы

Песочница

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

Песочница нужна, чтобы подключить площадку до боевого ключа: создать акцию, отправить записи, получить квитанции и итог, открыть страницу проверки. Протокол и объекты там те же, что в бою. Другие у песочницы ключи, журнал и сроки.

API песочницы: https://api.sandbox.lchv.ru, страница проверки: https://verify.sandbox.lchv.ru. Каждый ответ API песочницы несёт заголовок X-Sandbox: 1, по нему ваш код отличит песочницу от боевого сервиса.

Чем песочница отличается от боевого сервиса

Лимиты

Кроме open_at, этих ограничений в бою нет: они берегут песочницу, которую делят все площадки.

ЧтоВ песочницеОтвет сверх лимита
Окно приёма, от open_at до close_atне короче 60 с422 window
open_at от момента запросане ближе 60 с, как в бою422 open_at
Новых розыгрышей на площадку за сутки, черновики тоже20429 rate_limited
Записей на площадку, все ключи вместе5 в секунду, разово до 20 подряд429 rate_limited

На 429 запрос стоит повторить чуть позже. Остальные коды ошибок те же, что в бою (docs/API.md, раздел "Ошибки").

Первое событие за 15 минут

Нужны bash, curl, openssl и тестовый ключ: идентификатор k_... и секрет из 64 знаков hex. Ключ выдаёт оператор сервиса, секрет показывается один раз.

1. Подпись запроса

Каждый запрос площадки подписан HMAC-SHA256 секретом ключа (docs/API.md, раздел "Ключ и подпись запроса"). Функция call ниже делает подпись и сам запрос, at пишет время в формате протокола.

API=https://api.sandbox.lchv.ru
KEY_ID=k_...
SECRET=<64 знака hex>

# call МЕТОД ПУТЬ [ТЕЛО [ещё аргументы curl]]; без тела, но с аргументами curl тело пишется как ''
call() {
  local method=$1 path=$2 body=${3:-}
  shift $(( $# < 3 ? $# : 3 ))
  local ts nonce sum sig
  ts=$(date +%s)
  nonce=$(openssl rand -hex 16)
  sum=$(printf %s "$body" | openssl dgst -sha256 -r | cut -d' ' -f1)
  sig=$(printf '%s\n%s\n%s\n%s\n%s' "$method" "$path" "$ts" "$nonce" "$sum" \
    | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$SECRET" -r | cut -d' ' -f1)
  local args=(-sS -X "$method" "$API$path" -H "X-Key-Id: $KEY_ID" -H "X-Timestamp: $ts" -H "X-Nonce: $nonce" -H "X-Signature: $sig")
  [ -z "$body" ] || args+=(-H 'Content-Type: application/json' --data-binary "$body")
  curl "${args[@]}" "$@"
  echo
}

# Время через N секунд от сейчас, UTC с миллисекундами; GNU date и date на маке.
at() {
  local t=$(( $(date +%s) + $1 ))
  date -u -d "@$t" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date -u -r "$t" +%Y-%m-%dT%H:%M:%S.000Z
}

Пустое тело подписывается как SHA-256 от нуля байт, printf %s "" это и даёт. Часы машины должны идти верно: запрос, который разошёлся с часами сервиса больше чем на 300 секунд, получит 401 replay.

Проверка: call GET /v1/draws/no-such-draw '' -i отвечает 404 unknown_draw с заголовком x-sandbox: 1. Если пришёл 401, подпись не сошлась.

2. Акция: окно три минуты, один победитель

DRAW=first-event-$(date +%s)
OPEN=$(at 90)
CLOSE=$(at 270)
RULES=$(printf 'Правила акции для проверки подключения.' | base64 | tr -d '\n')
BODY=$(cat <<EOF
{"kind":"bookmaker_promo","draw":"$DRAW","organizer":"Ваша компания","title":"Проверка подключения",
 "rules_ack":["promo-v1"],"open_at":"$OPEN","close_at":"$CLOSE",
 "formula":{"winners":1,"alternates":0,"one_prize_per_subject":true},"prize":{"text":"Футболка"},
 "participation_condition":"free","rules_url":"https://example.com/rules",
 "rules_document_base64":"$RULES","rules_media_type":"text/plain","prize_delivery":"В личном кабинете"}
EOF
)
call POST /v1/draws "$BODY"

Для бренда вид brand_promo, условие участия free или purchase. Документ правил бывает PDF, обычным текстом или Markdown.

Ответ 201 с черновиком и условиями целиком. Что в нём смотреть:

Черновик видит только ваша площадка. До фиксации его можно поправить запросом PUT /v1/draws/<draw> с тем же телом.

3. Фиксация

call POST /v1/draws/$DRAW/commit

Сервис пишет лист фиксации в журнал песочницы, затем отмечает его в Rekor песочницы. Ответ ждёт лист до 5 секунд: 200 с "state":"committed", а если не дождался, 202 с "state":"committing", тогда состояние спросите позже.

В ответе commit_index, номер листа в общем журнале, и files, ссылка на страницу проверки акции https://verify.sandbox.lchv.ru/d/<draw>/. Ссылку можно открыть сразу, полной страница станет после итога.

Приём откроется в open_at, через полторы минуты после создания:

call GET /v1/draws/$DRAW

Там "state":"open", "entries":0 и commit_anchored_at, время отметки фиксации в Rekor. Оно всегда раньше open_at: без отметки приём не открывается вовсе.

4. Три записи и повтор

for i in 1 2 3; do
  call POST /v1/draws/$DRAW/entries "{\"platform\":\"site\",\"user_ref\":\"player-$i\"}" -H "Idempotency-Key: event-$i"
done

platform это короткое имя площадки латиницей. user_ref это ваш внутренний идентификатор игрока, без имени, почты и телефона: сервис хранит только его производную. Idempotency-Key это ключ события на вашей стороне, он обязателен.

Каждый ответ 201 с квитанцией. В ней leaf.seq, номер записи в журнале акции с нуля, entry_id, код квитанции, и подпись сервиса в sigs. Теперь первое событие ещё раз:

call POST /v1/draws/$DRAW/entries '{"platform":"site","user_ref":"player-1"}' -H 'Idempotency-Key: event-1'

Ответ 200 и та же квитанция: тот же номер записи, тот же код, та же подпись. Тот же ключ с другим телом получит 409 idempotency_conflict.

5. Квитанция у нас

Игрок открывает квитанцию на странице проверки по ссылке https://verify.sandbox.lchv.ru/r/<draw>/<seq>#<entry_id>: код стоит после # и на сервер не уходит. Квитанцию по коду можно получить и запросом, подпись для него не нужна:

curl -sS -X POST "$API/v1/draws/$DRAW/receipts/lookup" -H 'Content-Type: application/json' \
  --data-binary '{"seq":0,"entry_id":"<entry_id первой квитанции>"}'

Ответ: та же квитанция, а после закрытия ещё доказательство включения записи (inclusion) и чекпоинт журнала акции, до которого оно ведёт.

6. Закрытие и итог

Досрочного закрытия у акций нет, приём закроется сам в close_at. Примерно через минуту:

curl -sS -i "$API/v1/draws/$DRAW/result"

202 значит "ещё рано", 200 отдаёт подписанный итог с номерами записей победителей. 410 значит, что акция не состоялась по правилам, например её отменили.

Теперь откройте ссылку files из шага 3. Страница сама скачает файлы акции, пересчитает итог в браузере и напишет "Совпало". Сверху будет плашка "Песочница".

7. Что дальше

Заявка на подключение · Протокол