Блог
Sandbox тестирование платежей Kaspi Pay API CI/CD

Sandbox в Kaspi Pay API: как тестировать платежи без реальных денег

Полное руководство по Sandbox-режиму Pay Bot: настройка, тестовые сценарии, эмуляция QR-оплаты и webhook-уведомлений, CI/CD интеграция.

Pay Bot 5 апреля 2026 г. 4 мин чтения

Любой разработчик, который писал интеграцию с реальными деньгами, знает это чувство: код вроде работает, тесты зелёные, но кладёшь палец на «Deploy» — и сердце ёкает. Sandbox убирает этот стресс. В Pay Bot он встроен с первого дня и работает одинаково с live-окружением.

Чем Sandbox отличается от Live

Sandbox — это полноценная копия Pay Bot API, в которой:

  • Деньги не двигаются. Никаких списаний, никакого баланса.
  • QR-коды не открываются в Kaspi. Они «оплачиваются» через специальный эндпоинт-симулятор.
  • Webhook'и приходят как обычно. Те же поля, та же HMAC-подпись, та же retry-политика.
  • Rate-limits помягче. Можно прогонять стресс-тесты без блокировок.
  • Логи хранятся 7 дней. В live — 30+.

Главное: код вашей интеграции работает в Sandbox и в live идентично. Меняется только ключ.

Получение Sandbox-ключа

После регистрации в кабинете вам сразу выдают kp_test_.... Дополнительные шаги не нужны. Этот ключ работает даже без подключения Kaspi Business — что удобно, если хотите потрогать API до коммерческой настройки.

# Тест: ключ работает
curl https://api.paybot.kz/v1/status \
  -H "X-API-Key: kp_test_yourkey"
# {"status":"ok","worker_count":3,"queue_depth":0}

Если ответ 200 — всё, можно работать.

Базовый сценарий: создать платёж и «оплатить»

Создаём QR

import requests, uuid

resp = requests.post(
    "https://api.paybot.kz/v1/qr/create",
    headers={
        "X-API-Key": "kp_test_yourkey",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"amount": 1000},
).json()
print(resp["operation_id"])  # op_test_8f3a...

Ответ от Sandbox идентичен live — qr_url, deep_link, expires_at. Просто QR-код не работает в реальном Kaspi.

Эмулируем оплату

В Sandbox есть специальный эндпоинт-симулятор:

curl -X POST https://api.paybot.kz/sandbox/payments/op_test_8f3a.../complete \
  -H "X-API-Key: kp_test_yourkey"

Сразу после вызова:

  1. Статус операции меняется на paid.
  2. Срабатывает webhook payment.completed (если настроен).
  3. Sender name проставляется как "Sandbox User".

Можно также эмулировать другие сценарии:

# Клиент отменил
POST /sandbox/payments/{op_id}/cancel

# QR протух
POST /sandbox/payments/{op_id}/expire

# Возврат
POST /sandbox/payments/{op_id}/refund -d '{"amount": 1000}'

Тестовые webhook'и

Если вы делаете локальную разработку без публичного URL, есть несколько подходов.

Туннели в локальную сеть

Самое простое — ngrok или Cloudflare Tunnel:

ngrok http 3000
# Forwarding https://ab12cd34.ngrok-free.app -> http://localhost:3000

Вписываете URL https://ab12cd34.ngrok-free.app/webhook в кабинете → Webhooks → готово. Все Sandbox-уведомления летят прямо в ваш локальный сервер.

Webhook receiver в кабинете

В UI Pay Bot есть встроенный приёмник webhook'ов с историей — отдельно регистрировать ngrok не обязательно. Удобно, чтобы посмотреть, как именно формируется payload, и скопировать пример в свои фикстуры.

Кнопка «Test webhook» в UI

В разделе Webhooks по каждой подписке есть кнопка «Test» — она шлёт фиктивный payload вашему обработчику и показывает HTTP-статус, время ответа и тело. Незаменимо для отладки HMAC-верификации.

Сценарии, которые имеет смысл протестировать

Минимальный набор:

  1. Happy path. Создал → оплатил → webhook → статус «paid».
  2. Идемпотентность. Дважды POST с одним Idempotency-Key → один платёж.
  3. Webhook retry. Ваш обработчик первый раз отвечает 500 → второй раз 200 → данные обработаны ровно один раз (проверка дедупа по event.id).
  4. Expired payment. Истечение срока → webhook payment.expired.
  5. Частичный возврат. Платёж на 1000, возврат на 400 → статус остаётся paid, появляется refund_id.
  6. Невалидная подпись. Шлёте кривой webhook руками — ваш сервер должен ответить 401.

Каждый из этих сценариев можно прогнать без денег.

Интеграция в CI

Sandbox идеален для интеграционных тестов. Минимальный пример на pytest:

import os, uuid, requests, pytest

API = "https://api.paybot.kz"
KEY = os.environ["KASPI_TEST_KEY"]

def headers(idempotent=None):
    h = {"X-API-Key": KEY}
    if idempotent: h["Idempotency-Key"] = idempotent
    return h

def test_create_and_pay():
    op = requests.post(
        f"{API}/v1/qr/create",
        headers=headers(str(uuid.uuid4())),
        json={"amount": 500},
    ).json()
    op_id = op["operation_id"]
    assert op["qr_url"]

    # Симулируем оплату
    requests.post(f"{API}/sandbox/payments/{op_id}/complete", headers=headers()).raise_for_status()

    # Статус
    status = requests.get(f"{API}/v1/qr/status/{op_id}", headers=headers()).json()
    assert status["status"] == "paid"

def test_idempotency():
    key = str(uuid.uuid4())
    op1 = requests.post(f"{API}/v1/qr/create", headers=headers(key), json={"amount": 100}).json()
    op2 = requests.post(f"{API}/v1/qr/create", headers=headers(key), json={"amount": 100}).json()
    assert op1["operation_id"] == op2["operation_id"]

Запускается в GitHub Actions / GitLab CI как обычный pytest. Ключ кладёте в secrets, в pipeline:

- run: pytest tests/integration/
  env:
    KASPI_TEST_KEY: ${{ secrets.KASPI_TEST_KEY }}

Чего нельзя в Sandbox

Несколько ограничений, о которых стоит знать заранее:

  • Реальные QR не открываются в Kaspi. Это by design.
  • Имя плательщика всегда «Sandbox User» — реальный sender name эмулировать нельзя.
  • Возвраты в Sandbox симулируются мгновенно. В live это занимает 1–3 минуты.
  • Rate-limits — 30 запросов/секунду на ключ. В большинстве live-планов — 60+.

Когда переходить на Live

Достаточно надёжных признаков «готовности» три:

  1. Все сценарии из списка выше проходят. Особенно — идемпотентность и retry webhook'ов.
  2. HMAC-верификация работает строго. Кривая подпись → 401, никаких исключений.
  3. Логирование настроено. Каждое событие записывается с event_id, timestamp и результатом обработки.

После этого подключаете Kaspi через SMS-онбординг, получаете kp_live_... ключ и меняете одну переменную окружения. Код не меняется.

Если что-то пошло не так

Большая часть «странного» поведения в Sandbox связана с одной из трёх причин:

  • Старый Idempotency-Key — Sandbox-операции живут 24 часа, потом возвращают новый платёж.
  • Webhook URL стал недоступным (например, ngrok-сессия завершилась) — переподпишитесь.
  • HMAC-секрет был ротирован, а старый ещё захардкожен в коде.

Если совсем застряли — пишите в @paybotkz_bot, мы помогаем с интеграцией в Sandbox бесплатно. Или оставьте заявку, если нужен полный onboarding с менеджером.

Готовы попробовать Pay Bot?

Зарегистрируйтесь в кабинете — Sandbox доступен сразу. Или оставьте заявку, и менеджер настроит подключение Kaspi за вас.