Что такое песочница и зачем она нужна?
Песочница — это режим, в котором ApiPay работает «понарошку»: вы создаёте счета, получаете вебхуки, смотрите статусы — но в Kaspi ничего не уходит, и настоящих денег нет. Это безопасная среда, где можно отладить интеграцию (сайт, Telegram-бот, CRM), не рискуя выставить реальный счёт живому человеку.
Оплату тестового счёта вы имитируете сами: в личном кабинете отметьте счёт оплаченным или отменённым, а из кода — методом simulate-status (подробный прогон — в разделе для разработчиков ниже). Так проверяются все ветки: успех, отмена, истечение, ошибка.
Песочница бесплатна всегда: таймер триала на неё не идёт, подписка не нужна. Многие живут в ней неделями, пока пишут интеграцию, — это нормально.
Гоняете цикл автономным ИИ-агентом? Для этого есть отдельное руководство «Интеграция с ApiPay с помощью ИИ» — эта статья про ручной прогон человеком.
Клиент не получил счёт? Сначала проверьте режим
Самый частый сценарий: «всё подключили, счёт создаётся, а оплата в Kaspi не приходит». В 9 из 10 случаев включён тестовый режим. Проверьте три признака:
- Плашка в кабинете. Вверху горит «ТЕСТОВЫЙ РЕЖИМ … Счета не передаются в Kaspi» — это он.
- Ответ API. У счёта
is_sandbox: true. - Номер счёта.
kaspi_invoice_idначинается сSANDBOX-.
Любой из трёх признаков означает: счёт живёт в песочнице, покупатель его не увидит. Включите рабочий режим — и всё «взлетит» без единой правки кода.
Как включить рабочий режим
- Убедитесь, что кассир подключён: Настройки → «Авторизация Kaspi» (какой номер подойдёт — в статье «Требования к номеру кассира»). Без кассира счетам физически не через что уходить в Kaspi.
- В Настройках переключите режим на «Рабочий».
- Выставьте пробный счёт на свой личный номер и оплатите на минимальную сумму — убедитесь, что push пришёл и вебхук отработал.
Переключение обратимо: можно вернуться в тестовый режим и снова выйти — например, чтобы проверить новую фичу интеграции. Аккаунт, API-ключ и настройки вебхука при этом остаются те же: отдельного «sandbox-ключа» в ApiPay нет.
С момента активации рабочего доступа у вас есть 3 бесплатных дня триала (при самостоятельном подключении кассира они включаются сразу), дальше — тариф. Подробности — в статье «Тестовый период» (/guides/testovyy-period).
Тестовые организации и их ключи удаляются — как не потерять настройки
Это самое важное место статьи для тех, кто работает через партнёрский API (платформы, CRM, white-label).
У обычного клиента (личный кабинет apipay.kz) всё просто: режим — переключатель, ключ один и тот же, ничего не пропадает.
У партнёров иначе. Тестовые организации — отдельные сущности со своими API-ключами. При переводе партнёрского аккаунта в production все тестовые организации удаляются полностью — вместе с их тестовыми счетами, возвратами, подписками — а их API-ключи деактивируются. Это сделано специально («чистый старт»), восстановлению они не подлежат. Кроме того, тестовая организация архитектурно не может стать боевой: ключи, выданные тестовой организации, с боевой никогда не заработают.
Чтобы переход прошёл без нервов:
- Не храните ничего ценного в тестовых организациях. Всё, что там лежит, — расходный материал.
- Вынесите конфигурацию наружу. URL вебхука, названия организаций, соответствия «ваш клиент → организация ApiPay» держите в своей системе, а не «в памяти» тестовой среды.
- Планируйте перевыпуск ключей. После перехода создайте боевые организации и выпустите для них новые ключи и вебхук-секреты. Полный API-ключ показывается один раз — при создании; дальше видна только маска (key_hint). Сразу кладите его в менеджер секретов.
- Помните про лимиты песочницы: до 20 тестовых организаций на партнёра и до 500 тестовых счетов на организацию.
Если после перехода «ключ внезапно перестал работать» — почти всегда это один из двух штатных случаев: ключ принадлежал удалённой тестовой организации, либо ключ перевыпустили повторным вызовом (повторная выдача ключа организации перегенерирует его — старый мгновенно умирает).
Технические отличия песочницы (для разработчиков)
- Вебхуки-ретраи короче: в песочнице 3 попытки доставки (через 5 и 15 секунд), в рабочем режиме — 11 попыток с нарастающими интервалами до 1 часа. Если ваш endpoint «просыпается» медленно, в тесте вы можете не дождаться повтора — это отличие среды, а не баг.
- Симуляция статусов — метод
simulate-status(для счёта по номеру) и параметрsimulateпри создании QR-счёта; полный прогон — в разделе «Полный цикл прогона» ниже. - Подписки в песочнице доступны только при верифицированной организации — иначе API вернёт 400.
- Имена тестовых организаций генерируются автоматически (вида «ТОО Синие птицы») — так их не спутаешь с боевыми.
- Настройка вебхуков, HMAC-подпись и локальное тестирование — в статье «Настройка вебхуков ApiPay» (/guides/nastroyka-webhookov-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 (полный или частичный amount, опц. reason, опц. позиционный return_items[]). Отдельного simulate для возврата нет. Возврат возможен только у счёта в paid — поэтому порядок такой: simulate-status paid → затем refund.
В песочнице бэкенд сам доставляет вебхуки возврата: invoice.refunded (со status: completed либо failed + refund.error_code при неудаче) и — на первый частичный возврат — дополнительно 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 нет),description≤100 символов. - Sandbox-шорткат: в теле
POST /invoices/qrможно передатьsimulate: paid|cancelled|expired— QR-счёт создастся сразу в терминальном статусе с мгновенной отправкой вебхука (когда сам скан проверять не нужно). - Отсканирование: чтобы проверить событие
invoice.qr_scanned, вызовитеsimulate-statusсоstatus: qr_scanned(только для QR; статус остаётсяpending, один раз — повтор даст400 already_scanned). После скана транзиентно возможны иpaid, иcancelled— симулируйте нужную ветку следом. - Возврат и отмена QR-счёта не поддерживаются — это ограничение самого QR-флоу.
Проверка номера телефона: sandbox-моки
POST /clients/check в песочнице не ходит в Kaspi и ничего не пишет в кэш/счётчики — отдаёт детерминированный ответ ровно по двум номерам:
87770000001→{ "has_kaspi": true, "client_name": "Иван И." }87770000002→{ "has_kaspi": false, "client_name": null }- любой другой валидный номер →
{ "has_kaspi": false, "client_name": null }
Это единственные телефонные значения, которые стоит использовать в тестах. Формат имени Kaspi — имя и первая буква фамилии с точкой; полная фамилия не отдаётся.
Чек-лист выхода в рабочий режим
Перед переключением убедитесь, что в песочнице прошло:
- [ ]
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. - [ ] Приёмник вебхуков проверяет HMAC-подпись по raw body и отвечает
2xxбыстрее 5 секунд (детали — «Настройка вебхуков ApiPay»). - [ ] Обработчик дедуплицирует по
(invoice.id, invoice.status)и идемпотентен. - [ ] Ветки ошибок разобраны по
error_code(включая виденныйsandbox_simulated_error). - [ ]
POST /clients/checkиспользуется точечно, а не перебором. - [ ] Переключение — в кабинете; после него
is_sandboxстанетfalse, аsimulate-statusперестанет быть доступен — это ожидаемо.
Частые ошибки
- Ждать реальный push Kaspi в песочнице. Не придёт by design — имитируйте оплату сами.
- Включать рабочий режим до подключения кассира. Сначала кассир (Настройки → «Авторизация Kaspi»), потом режим.
- «Оплатил тариф, а счета всё ещё SANDBOX-». Оплата тарифа не переключает режим сама — проверьте переключатель в Настройках. Если рабочий режим включён, а счета всё равно тестовые — напишите в поддержку, разберём.
- Партнёру: перейти в production, не сохранив конфигурацию. Тестовые организации и ключи удалятся штатно — подготовьтесь по чек-листу выше.
- Потерять API-ключ. Он показывается один раз при создании. Не записали — перевыпустите ключ (старый перестанет работать).
- Тестировать «на проде» на счетах клиентов. Для экспериментов есть песочница — она для этого и существует.
Вопросы и ответы
Песочница платная? Сколько можно в ней сидеть?
Бесплатна всегда, по времени не ограничена. Лимит — 500 тестовых счетов на организацию.
Нужен ли отдельный API-ключ для песочницы?
Нет. У клиента личного кабинета ключ один и тот же для обоих режимов — переключается только режим. Отдельные тестовые ключи есть только у тестовых организаций партнёров, и они удаляются при переходе в production.
Можно ли вернуться из рабочего режима в тестовый?
Да, переключение обратимо в обе стороны. Счета, созданные в песочнице, останутся тестовыми навсегда.
Идёт ли триал, пока я в песочнице?
Нет. 3 бесплатных дня относятся к рабочему доступу; песочница бесплатна независимо от них.
Что произойдёт с тестовыми счетами при переходе в рабочий режим?
У клиента ЛК они останутся в истории с пометкой тестовых. У партнёра при переходе в production тестовые организации удаляются целиком вместе со счетами и ключами.
Как понять из кода, что счёт тестовый?
По полю is_sandbox: true в ответе API и вебхуке, и по префиксу SANDBOX- в kaspi_invoice_id.
Почему сразу после создания счёт нельзя симулировать?
POST /invoices возвращает счёт в статусе processing, а simulate-status переводит счёт из pending. Опросите GET /invoices/{id} до pending, потом симулируйте — иначе получите 400 invalid_status_transition с allowed_from: ["pending"].
Как проверить номер телефона в песочнице, не имея реального клиента?
Используйте детерминированные моки POST /clients/check: 87770000001 → has_kaspi: true (имя «Иван И.»), 87770000002 → has_kaspi: false. Любой другой валидный номер вернёт has_kaspi: false. В Kaspi запрос при этом не уходит.