Как сделать возврат Kaspi через API и почему он не проходит?

Обновлено 27 июля 2026 · Справочник · Версия в Markdown
Содержание
  1. Как сделать возврат: кабинет и API
  2. Частичный возврат: по сумме или по штукам?
  3. «Возврат создан, но не проходит» — что происходит?
  4. Почему статус счёта не меняется на «refunded»?
  5. Сроки: окно возврата и зачисление покупателю
  6. Если возврат так и не проходит — возврат по QR
  7. Вопросы и ответы

Как сделать возврат: кабинет и API

Из кабинета (без кода): apipay.kz → раздел «Счета» → найдите оплаченный счёт → кнопка возврата справа → укажите сумму (по умолчанию — вся доступная) → подтвердите. Дальше всё автоматически.

Через API:

curl -X POST "https://api.apipay.kz/api/v1/invoices/{id}/refund" \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json"

curl -X POST "https://api.apipay.kz/api/v1/invoices/{id}/refund" \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"amount": 1500}'

Ответ — 201 со статусом возврата pending. Это не «деньги вернулись», это «заявка принята»: дальше система асинхронно проводит возврат в Kaspi и присылает вебхук invoice.refunded с итоговым статусом completed или failed. Жизненный цикл возврата: pending → processing → completed | failed.

Синхронные отказы при создании возврата:

Код Когда Что делать
400 Invoice is not refundable Счёт не оплачен или уже возвращён полностью Неоплаченный счёт отменяют через POST /api/v1/invoices/{id}/cancel, а не возвращают
400 Refund amount exceeds available amount Сумма больше available_for_refund Прочитать остаток из GET /invoices/{id} и повторить с ним
422 partial_refund_requires_return_items Частичный возврат корзинного счёта отправлен плоским amount Передать return_items[]
403 tariff_inactive Не оплачена подписка ApiPay Оплатить тариф — без него возвраты закрыты

Список возвратов: GET /api/v1/invoices/{id}/refunds и общий GET /api/v1/refunds.

Частичный возврат: по сумме или по штукам?

Три способа, выбирайте один:

  • Просто сумма{"amount": 1500}: вернуть часть денег без привязки к товарам. Подходит счетам без корзины.
  • По позициям штукамиreturn_items: [{"catalog_item_id": 11, "count": 2}]: вернуть 2 штуки конкретного товара из корзины.
  • По позиции суммойreturn_items: [{"catalog_item_id": 15, "amount": 900}]: частичный возврат неделимой позиции (например, вернуть 900 ₸ из услуги за 5 000 ₸).

Правило API: в каждой позиции return_items[] указывается ровно одно из count (целые штуки) или amount (сумма), иначе 422.

⚠️ Частичную сумму по корзинному счёту передавайте только через return_items[]. Тот же error_code приходит в вебхуке invoice.refunded со status: failed, если синхронная проверка не сработала.

В вебхуке частичного возврата по сумме refund.items[].count может быть 0 — возврат был по сумме, а не по штукам.

Сколько ещё можно вернуть по счёту, видно в GET /invoices/{id}: поля total_refunded, is_fully_refunded и available_for_refund (число).

«Возврат создан, но не проходит» — что происходит?

Симптом: возврат создан, итог failed, Kaspi отвечает «Недостаточно денег на счёте. Пополните счёт или сделайте возврат наличными». Возврат уходит на полную сумму покупки, а на счёт организации она пришла за вычетом комиссии Kaspi — с пустого счёта полной суммы не хватит.

Такой ответ Kaspi терминален: возврат сразу получает failed, уходит вебхук invoice.refunded с refund.error_code. Автоповторов на него нет, ждать бесполезно.

Что делать вам:

  1. Пополните Kaspi-счёт организации (или дождитесь следующих оплат — комиссия «размажется» по обороту).
  2. Создайте возврат заново — той же кнопкой или тем же запросом. После failed сумма освобождается.

⚠️ Пока предыдущий возврат висит в pending/processing, его сумма занята: available_for_refund считается как сумма счёта минус завершённые минус незавершённые возвраты, поэтому дубль на ту же сумму отобьётся 400 Refund amount exceeds available amount.

Отключаете кассира? После удаления кассира возвраты по ранее оплаченным счетам через ApiPay недоступны — их придётся делать вручную в приложении Kaspi Pay. Окно возврата у Kaspi около 14 дней: если по счетам последних двух недель возможны возвраты, отключайте кассира после закрытия этого окна (Смена и отключение кассира).

Почему статус счёта не меняется на «refunded»?

Такого статуса нет: счёт и возврат — разные объекты. Счёт фиксирует факт продажи и остаётся paid; при частичном возврате переходит в partially_refunded, при полном получает флаг is_fully_refunded: true. Возврат — отдельная операция со своими статусами (pending → processing → completed | failed).

Для интеграции это значит: не ждите вебхук invoice.status_changed со статусом «refunded» — его не будет никогда. Итог возврата приносит событие invoice.refunded (и на успех, и на отказ). Дедуплицируйте его на своей стороне по паре (refund.id, refund.status); refund.error_message в вебхуке отсутствует намеренно — детали смотрите в кабинете или через GET /invoices/{id}/refunds. Настройка вебхуков — «Настройка вебхуков ApiPay».

Сроки: окно возврата и зачисление покупателю

  • Окно возврата — около 14 дней с момента оплаты. Точный срок контролирует Kaspi; после окна возврат откажет с кодом refund_window_expired. Если окно истекло, деньги покупателю возвращают вне ApiPay — переводом или наличными по вашим правилам торговли.
  • Зачисление покупателю после успешного возврата происходит на стороне Kaspi.

Возвраты, сделанные кассиром вручную в приложении Kaspi Pay, ApiPay подтягивает автоматически: они появятся в кабинете и придут тем же вебхуком invoice.refunded. Двойного учёта не будет.

Если возврат так и не проходит — возврат по QR

Вторая механика возврата — с подтверждением покупателя: он сканирует возвратный QR тем Kaspi, которым платил, и только после этого вы видите список его покупок и возвращаете нужную. В API это POST /api/v1/qr-refunds, в кабинете — кнопка «Возврат с подтверждением» на странице возвратов. Применяйте её, когда возврат по счёту упирается в отказ Kaspi или когда оплата шла мимо ваших счетов.

Флоу, коды ошибок и песочница — в статье Возврат по QR: покупатель подтверждает возврат в Kaspi.

Вопросы и ответы

Можно ли запретить возвраты совсем?

Серверного «запрета возвратов» не существует: возврат — штатная функция Kaspi. Контролируйте это на своей стороне — просто не вызывайте возврат из своей интеграции; в кабинете операция доступна владельцу.

Интеграция с ИИ

Не настраивайте вручную — поручите ИИ

Скопируйте готовый промпт и вставьте своему ИИ-ассистенту — он настроит интеграцию за вас: получит доступы, создаст тестовый счёт в песочнице и подключит вебхуки. Техническую часть делает ИИ, а не вы.

Остались вопросы — напишите нам в WhatsApp: +7 700 307 65 12. Отвечаем быстро, без звонков.

Написать в WhatsApp

Для разработчика. Полная спецификация API — apipay.kz/docs. Эта статья в Markdown — /guides/vozvraty-kaspi-cherez-api.md. Справочник фактов для ИИ-агента — apipay.kz/llms.txt.