Что такое песочница и зачем она нужна?
Песочница — режим, в котором вы создаёте счета, получаете вебхуки и смотрите статусы, но в Kaspi ничего не уходит и настоящих денег нет.
Оплату тестового счёта вы имитируете сами: в личном кабинете отметьте счёт оплаченным или отменённым, а из кода — методом simulate-status (подробный прогон — в разделе для разработчиков ниже). Так проверяются все ветки: успех, отмена, истечение, ошибка. Тот же цикл для автономного ИИ-агента — «Интеграция с ApiPay с помощью ИИ».
Клиент не получил счёт? Сначала проверьте режим
Счёт создаётся, а оплата в Kaspi не приходит — проверьте три признака тестового режима:
- Плашка в кабинете. Вверху горит «ТЕСТОВЫЙ РЕЖИМ … Счета не передаются в Kaspi» — это он.
- Ответ API. У счёта
is_sandbox: true. - Номер счёта.
kaspi_invoice_idначинается сSANDBOX-.
Любой из трёх признаков означает: счёт живёт в песочнице, покупатель его не увидит. Включите рабочий режим — правки кода для этого не нужны.
Как включить рабочий режим
- Убедитесь, что кассир подключён: Настройки → «Авторизация Kaspi» (какой номер подойдёт — в статье «Требования к номеру кассира»). Без кассира счетам физически не через что уходить в Kaspi.
- В Настройках переключите режим на «Рабочий».
- Выставьте пробный счёт на свой личный номер и оплатите на минимальную сумму — убедитесь, что push пришёл и вебхук отработал.
Переключение обратимо, аккаунт, API-ключ и настройки вебхука не меняются: отдельного «sandbox-ключа» в ApiPay нет. Но каждый выход в рабочий режим стирает sandbox-данные организации — что именно, в разделе ниже; выгружайте нужное до переключения.
Ещё три правила переключателя:
- менять режим можно не чаще одного раза в 5 минут — кабинет покажет, сколько осталось ждать; повтор того же значения ничего не меняет и sandbox-данные не удаляет
- включить тестовый режим может только верифицированная организация
- для выхода в рабочий режим нужна живая привязка кассира: если она истекла — переподключите кассира, если проверка не прошла — повторите позже; в обоих случаях режим не меняется и sandbox-данные остаются на месте
С момента активации рабочего доступа у вас есть 3 бесплатных дня триала, дальше — тариф. Подробности — «Тестовый период».
Что удаляется при переходе в рабочий режим
У обычного клиента (личный кабинет apipay.kz) режим — переключатель, API-ключ и настройки вебхука не меняются. Но данные пропадают: каждый переход в рабочий режим удаляет sandbox-данные организации — тестовые счета, возвраты по ним, тестовые подписки и тестовые позиции каталога. Всё, что нужно сохранить, выгружайте до переключения. Боевые счета, возвраты и подписки не затрагиваются.
У партнёров сверх этого есть свой сценарий. Тестовые организации — отдельные сущности со своими API-ключами. При переводе партнёрского аккаунта в production все тестовые организации удаляются полностью — вместе с их тестовыми счетами, возвратами, подписками — а их API-ключи деактивируются. Восстановлению они не подлежат: не храните в тестовых организациях ничего ценного, держите конфигурацию (URL вебхука, соответствия «ваш клиент → организация ApiPay») в своей системе и планируйте перевыпуск ключей для боевых организаций. Тестовая организация архитектурно не может стать боевой: её ключи с боевой никогда не заработают. Подробнее о партнёрском контуре — «Партнёрский API».
Если после перехода «ключ перестал работать» — проверьте два случая: ключ принадлежал удалённой тестовой организации либо был перевыпущен повторным вызовом (повторная выдача ключа организации перегенерирует его и перезаписывает Webhook URL с секретом — старый ключ умирает мгновенно).
Технические отличия песочницы (для разработчиков)
- Отмена QR-счёта: в песочнице проходит (
200, статусcancelled), в рабочем режиме —409 qr_cancel_unsupported. Логику отмены калибруйте не по песочнице. - Вебхуки-ретраи короче: 3 попытки вместо 11 — медленно «просыпающийся» endpoint в тесте может не дождаться повтора.
- Подписки в песочнице — не больше 10 на организацию, превышение →
400 sandbox_subscription_limit. Создание подписки в любом режиме требует верифицированной организации, иначе403. - Имена тестовых организаций генерируются автоматически (вида «ТОО Синие птицы») — так их не спутаешь с боевыми.
- Настройка вебхуков, HMAC-подпись и локальное тестирование — «Настройка вебхуков ApiPay» и страница /local-testing.
Полный цикл прогона в песочнице (для разработчиков)
Прогоните перед продом весь путь реальными запросами: создать счёт → дождаться pending → симулировать статус → проверить вебхук → сделать возврат. Каждый шаг — обычный запрос к публичному API (https://api.apipay.kz/api/v1, заголовок X-API-Key), без единого телефонного номера живого человека.
Главный нюанс порядка — processing → pending перед симуляцией. POST /invoices (счёт по номеру) обрабатывается асинхронно: 201 приходит со status: "processing". Метод simulate-status переводит счёт из pending, поэтому сразу после создания симулировать нельзя — сначала опросите GET /invoices/{id} (лимит 1000/мин, читает из кэша) до status: "pending". Симуляция не-pending счёта вернёт 400 invalid_status_transition (в ответе — current_status и allowed_from: ["pending"]).
BASE="https://api.apipay.kz/api/v1"
KEY="YOUR_API_KEY"
ID=$(curl -s -X POST "$BASE/invoices" -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"amount":10000,"phone_number":"87770000001","description":"Sandbox test"}' | jq -r '.id')
until [ "$(curl -s "$BASE/invoices/$ID" -H "X-API-Key: $KEY" | jq -r '.status')" = "pending" ]; do sleep 1; done
curl -s -X POST "$BASE/invoices/$ID/simulate-status" -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" -d '{"status":"paid"}'
curl -s "$BASE/webhook-logs?invoice_id=$ID&event=invoice.status_changed" \
-H "X-API-Key: $KEY" | jq '.data[] | {event, status, response_status}'
curl -s -X POST "$BASE/invoices/$ID/refund" -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" -d '{"amount":5000,"reason":"Sandbox refund"}'
Метод POST /invoices/{id}/simulate-status — только для тестовых счетов (is_sandbox: true; боевой счёт → 403 not_sandbox), свой лимит 60 запросов/мин на ключ (общий бюджет 200/мин не тратит). Тело — { "status": <enum> }; опционально kaspi_source_type (GOLD|RED|LOAN|BUSINESSACCOUNT|BANKINTEGRATIONACCOUNT) и kaspi_sale_type (Remote|QR|Static|Restaurant); при paid без них они выбираются случайно.
status |
Что происходит со счётом | Какой вебхук уходит | Условия / ошибки |
|---|---|---|---|
paid |
→ терминальный paid (с paid_at) |
invoice.status_changed (paid) |
из pending |
cancelled |
→ терминальный cancelled |
invoice.status_changed (cancelled) |
из pending |
expired |
→ терминальный expired |
invoice.status_changed (expired) |
из pending |
error |
→ терминальный error, error_code: sandbox_simulated_error, error_message (свой текст параметром error_message ≤255; дефолт «Симулированная ошибка (sandbox).») |
invoice.status_changed (error) — как при реальной ошибке |
из pending |
qr_scanned |
остаётся pending (транзиентное суб-событие) |
invoice.qr_scanned (qr_substate: "scanned") |
только QR-счёт: не-QR → 400 not_qr_invoice; повтор → 400 already_scanned |
Критерий успеха каждого шага — в GET /webhook-logs по нужному event появляется запись status: success.
Возврат в песочнице: по-настоящему, а не симуляцией
Возврат по счёту на номер делают в песочнице реальным запросом POST /invoices/{id}/refund — своей симуляции у него нет. Возврат возможен только у счёта в paid, поэтому порядок такой: simulate-status paid → затем refund (параметры запроса — «Возвраты Kaspi через API»).
У возврата по QR ветка другая (см. «Возврат по QR»), и симуляция там есть: сессию POST /qr-refunds двигают ручкой POST /qr-refunds/{id}/simulate — event: identified (покупатель отсканировал возвратный QR, client_name = «Иван И.») или event: expired (QR просрочен). «Ещё не отсканировал» — это начальное состояние awaiting_scan, его достаточно прочитать через GET /qr-refunds/{id}. На боевой сессии ручка отдаёт 403 not_sandbox.
В песочнице бэкенд сам доставляет вебхуки возврата: invoice.refunded (со status: completed либо failed) и — на первый частичный возврат — дополнительно invoice.status_changed со status: partially_refunded. Проверить возвраты по счёту — GET /invoices/{id}/refunds. Нюанс ретраев: refund-вебхуки в песочнице ретраятся по полной боевой сетке (11 попыток), в отличие от invoice-вебхука (3 попытки в песочнице).
QR-счёт: создание и отсканирование в песочнице
- Создать QR-счёт —
POST /invoices/qr: запрос синхронный,201приходит сразу соstatus: "pending"и QR-полями (отдельногоpending-вебхука у QR нет). - Sandbox-шорткат: в теле
POST /invoices/qrможно передатьsimulate: paid|cancelled|expired— QR-счёт создастся сразу в терминальном статусе с мгновенной отправкой вебхука (когда сам скан проверять не нужно). - Отсканирование: чтобы проверить событие
invoice.qr_scanned, вызовитеsimulate-statusсоstatus: qr_scanned(только для QR; статус остаётсяpending, один раз — повтор даст400 already_scanned). После скана транзиентно возможны иpaid, иcancelled— симулируйте нужную ветку следом.
Проверка номера телефона: sandbox-моки
POST /clients/check в песочнице не ходит в Kaspi и ничего не пишет в кэш/счётчики — отдаёт детерминированный ответ ровно по двум номерам:
87770000001→{ "has_kaspi": true, "client_name": "Иван И." }87770000002→{ "has_kaspi": false, "client_name": null }- любой другой валидный номер →
{ "has_kaspi": false, "client_name": null }
Это единственные телефонные значения, которые стоит использовать в тестах.
Чек-лист выхода в рабочий режим
Перед переключением убедитесь, что в песочнице прошло:
- [ ]
POST /invoices→ счёт дошёл доpending(поллингGET /invoices/{id}работает). - [ ] Симулированы все терминалы —
paid,cancelled,expired,error— и по каждому вGET /webhook-logsестьstatus: successс нужнымevent. - [ ] Для QR (если используете):
qr_scanned→ затемpaid/cancelled. - [ ] Возврат:
simulate-status paid→refund→ пришёлinvoice.refunded(completed), а первый частичный далpartially_refunded. - [ ] Приёмник вебхуков проверяет подпись и отвечает
2xxбыстрее 5 секунд (детали — «Настройка вебхуков ApiPay»). - [ ] Обработчик дедуплицирует по
(invoice.id, invoice.status)и идемпотентен. - [ ] Ветки ошибок разобраны по
error_code(включая виденныйsandbox_simulated_error). - [ ] Всё, что нужно из песочницы, выгружено — переход его удалит.
Вопросы и ответы
Оплатил тариф, а счета всё ещё SANDBOX-.
Оплата тарифа режим не переключает — переключите его сами в Настройках.
Идёт ли триал, пока я в песочнице?
Нет. 3 бесплатных дня относятся к рабочему доступу; песочница бесплатна независимо от них.
Нужен ли отдельный API-ключ для песочницы?
Нет. У клиента личного кабинета ключ один и тот же для обоих режимов — переключается только режим. Отдельные тестовые ключи есть только у тестовых организаций партнёров, и они удаляются при переходе в production.