Как это выглядит для продавца
- Продавец начинает возврат и получает ссылку для покупателя.
- Отправляет её покупателю — в WhatsApp, Telegram или любым другим способом.
- Покупатель открывает ссылку и подтверждает возврат в Kaspi.
- Продавец видит список покупок этого покупателя, выбирает нужную и возвращает деньги — целиком или частично.
Схема:
Продавец ApiPay Покупатель
│ начать возврат │ │
├───────────────────►│ ссылка + QR │
│◄───────────────────┤ │
│ отправляет ссылку ─────────────────────────► │
│ │◄── подтверждает в Kaspi ─┤
│ статус: покупатель опознан │
│◄───────────────────┤ │
│ список покупок │ │
│◄──────────────────►│ │
│ вернуть выбранную │ │
├───────────────────►│ ─── деньги покупателю ──►│
Флоу в API
Все запросы — с вашим ключом в заголовке X-API-Key.
1. Начать возврат
curl -X POST https://api.apipay.kz/api/v1/qr-refunds \
-H "X-API-Key: ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{}'
В ответе — ссылка и картинка QR для покупателя, срок действия и рекомендованный интервал опроса:
{
"id": 42,
"status": "awaiting_scan",
"qr_token_url": "https://qr.kaspi.kz/return/…",
"qr_image_url": "https://…/storage/qr/uuid.png",
"expires_at": "2026-07-27T17:27:09+05:00",
"poll_interval_seconds": 3,
"scan_wait_timeout_seconds": 90
}
Если у организации несколько активных касс, укажите kaspi_connection_id — иначе придёт 422 connection_ambiguous.
2. Дождаться подтверждения
curl https://api.apipay.kz/api/v1/qr-refunds/42 -H "X-API-Key: ВАШ_КЛЮЧ"
Опрашивайте с интервалом из poll_interval_seconds, пока статус не станет customer_identified (покупатель подтвердил) или expired (срок вышел, нужна новая сессия). Вместо опроса можно слушать вебхуки qr_refund.identified, qr_refund.completed, qr_refund.expired.
⚠️ Не прекращайте опрос после customer_identified. У сессии два срока: сам QR живёт минуты, и отдельно ограничено время на выбор покупки после подтверждения. Если продавец задумался, сессия истечёт — и запрос на возврат вернёт 409 qr_refund_expired уже после того, как человек выбрал покупку и ввёл сумму.
3. Посмотреть покупки
curl https://api.apipay.kz/api/v1/qr-refunds/42/operations -H "X-API-Key: ВАШ_КЛЮЧ"
{
"client_name": "Иван И.",
"operations": [
{ "ref": "…", "amount": 500, "date": "2026-07-25T16:12:52+05:00", "returnable": "full" },
{ "ref": "…", "amount": 10, "date": "2026-07-25T16:16:54+05:00", "returnable": "partial" },
{ "ref": "…", "amount": 10, "date": "2026-06-30T20:19:31+05:00", "returnable": "none" }
],
"has_more": false,
"next_cursor": null,
"remaining_count": 0
}
returnable говорит, что Kaspi разрешает по этой покупке: full — только целиком, partial — можно частично, none — возврат недоступен. Если покупок больше, передавайте next_cursor в ?cursor=.
⚠️ ref непрозрачен и привязан к сессии. Не разбирайте его на части, не показывайте покупателю и не сохраняйте между сессиями — просто возвращайте обратно как есть.
4. Детали покупки
curl https://api.apipay.kz/api/v1/qr-refunds/42/operations/REF -H "X-API-Key: ВАШ_КЛЮЧ"
Приходят доступная к возврату сумма, уже возвращённое, ссылка на чек и позиции корзины — у каждой свой ref и свой остаток.
5. Вернуть деньги
curl -X POST https://api.apipay.kz/api/v1/qr-refunds/42/execute \
-H "X-API-Key: ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"operation_ref":"REF"}'
-d '{"operation_ref":"REF","amount":7}'
-d '{"operation_ref":"REF","items":[{"ref":"ITEM_REF","amount":5}]}'
amount и items взаимоисключимы — вместе их присылать нельзя. У позиции amount необязателен: без него вернётся вся её доступная сумма.
Запрос синхронный, итог приходит сразу:
{
"id": 42,
"status": "completed",
"refunded_amount": "500.00",
"receipt_url": "https://receipt.kaspi.kz/…",
"client_name": "Иван И.",
"completed_at": "2026-07-27T17:20:11+05:00"
}
Частые ошибки
| Код | Что означает | Что делать |
|---|---|---|
qr_refund_not_identified |
Покупатель ещё не подтвердил возврат | Дождитесь customer_identified |
qr_refund_expired |
Срок сессии истёк | Начните новый возврат и отправьте свежую ссылку |
qr_refund_completed |
Возврат по этой сессии уже выполнен | Не повторяйте: запросите статус и покажите результат оттуда |
operation_not_returnable |
Kaspi не разрешает возврат по этой покупке | Выберите другую покупку |
refund_amount_exceeds_available |
Сумма больше доступной к возврату | Проверьте available_for_refund в деталях покупки |
partial_refund_requires_return_items |
Эту покупку Kaspi возвращает только по товарам | Пришлите items вместо amount |
connection_ambiguous |
У организации несколько активных касс | Укажите kaspi_connection_id при старте |
При ошибках 422 сессия остаётся живой — можно повторить с другой покупкой или суммой, не начиная заново. |
Как проверить без реального покупателя
В песочнице реальный QR никому не уходит, а шаги покупателя двигаются вручную:
curl -X POST https://api.apipay.kz/api/v1/qr-refunds/42/simulate \
-H "X-API-Key: ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"event":"identified"}'
-d '{"event":"expired"}'
После identified отдаются три покупки: 500 ₸ (возврат целиком), 10 ₸ (только по товарам) и одна невозвратная. Неуспех возврата форсируется полем simulate в теле execute:
{ "operation_ref": "REF", "simulate": { "status": "failed", "error_code": "refund_amount_exceeds_available" } }
Вне песочницы поле simulate даёт 403 not_sandbox.
Частые вопросы
Обычный возврат перестанет работать?
Нет. POST /invoices/{id}/refund не менялся и работает как раньше. Возврат по QR — запасной путь для случаев, когда обычный не проходит.
Покупатель может подтвердить с чужого телефона?
Открыть ссылку — да, но сканировать нужно тем Kaspi, которым он оплачивал. С другого аккаунта его покупка просто не найдётся, и список придёт пустым.
Сколько живёт ссылка?
Минуты — точное значение в expires_at. Поэтому не создавайте сессию заранее: сначала убедитесь, что покупатель у телефона.
Что будет, если продавец нажмёт «вернуть» дважды?
Второй запрос вернёт qr_refund_completed. Это не ошибка возврата: деньги уже вернулись, просто запросите статус сессии и покажите результат оттуда.
Можно вернуть несколько покупок за одну сессию?
Нет. Одна сессия — один возврат. Для второй покупки начните новый возврат.
Приходят ли вебхуки?
Да: qr_refund.identified при подтверждении покупателем, qr_refund.completed при успешном возврате и qr_refund.expired при истечении срока.