Qut Pay Сайт Кабинет База знаний Инструкции Документация API ҚАЗРУС
ГлавнаяБаза знаний → Справочник

API возвратов — полный и частичный возврат

Обновлено: 2026-09-14 · Версия в Markdown

Коротко

Возврат денег — один метод:

POST /api/v1/invoices/{id}/refund
X-API-Key: qp_live_…
Content-Type: application/json

{ "amount": 1500, "reason": "Товар возвращён" }

Без поля amount счёт возвращается полностью. С amount возвращается только указанная сумма, а счёт переходит в статус partially_refunded.

Два правила, которые стоит запомнить сразу: ключу нужно право refunds:write, а при ошибках refund_unknown и refund_pending_unknown запрос повторять нельзя — вместо повтора вы читаете статус счёта.

Поля запроса

ПолеТипОбязательноеОписание
amountnumberнетСумма возврата в тенге. Без неё — полный возврат
reasonstringнетПричина. Видна в отчёте и в кабинете

В пути {id} — идентификатор счёта (inv_…). Возврата по externalOrderId нет: сначала найдите счёт через GET /api/v1/invoices?externalOrderId=…, затем используйте его id.

По какому счёту возможен возврат

Статус счётаВозврат
new, pendingНет. Деньги ещё не пришли — счёт нужно отменить: POST /invoices/{id}/cancel
paidДа, полный и частичный
partially_refundedДа, в пределах остатка
refundedНет, возвращено всё
cancelled, expiredНет

Деньги уходят не с нашего счёта, а с вашего счёта Kaspi. Мы деньги никогда не держим, поэтому сам возврат выполняет Kaspi. Если остатка не хватает, операция не пройдёт.

Полный и частичный возврат

Полный возврат — запрос с пустым телом или только с reason:

curl -X POST https://api.qut.kz/api/v1/invoices/inv_7Kd2/refund \
  -H "X-API-Key: $QUTPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Клиент отказался"}'

Частичный возврат — запрос с amount. Частичных возвратов может быть несколько, но правило жёсткое:

Сумма всех возвратов не может превышать оплаченную сумму.

Например, по счёту на 10 000 ₸ вы вернули 3 000 и 4 000 — третий раз можно вернуть не больше 3 000. Если запросить больше, придёт invalid_refund_amount. Когда вернёте последний остаток, счёт перейдёт в статус refunded.

Перед очередным возвратом полезно прочитать текущий остаток через GET /api/v1/invoices/{id}: в ответе есть список уже сделанных возвратов по этому счёту.

Коды ошибок

КодHTTPЧто случилосьЧто делать
invoice_not_refundable409По этому счёту возврат невозможенПрочитайте статус: оплачен ли, не истёк ли срок
invalid_refund_amount422Неверная суммаПоложительное число, не больше оплаченной суммы
refund_failed502Kaspi не выполнил возвратПроверьте остаток на счёте Kaspi, повтор допустим
refund_unknown502Kaspi не ответил, результат неизвестенНе повторять. Прочитайте статус счёта
refund_pending_unknown409Результат предыдущего возврата ещё не ясенНе повторять. Подождите
refund_state_conflict409Состояние возврата не то, которого ждалиПеречитайте статус и решайте по нему
insufficient_scope403У ключа нет права refunds:writeДобавьте право в кабинете
invoice_not_found404Счёта нет или он не виден ключуПроверьте идентификатор и кассира, к которому привязан ключ
kaspi_session_expired409Привязка кассира оборваласьПереподключите кассира

Обрабатывайте ошибки по коду error, а не по тексту message: текст может измениться, код — нет.

Когда результат неизвестен

refund_unknown — Kaspi не ответил на запрос. Ушли деньги или нет, в этот момент не знаем и мы. refund_pending_unknown — предыдущий возврат ещё не определён, поэтому новый запрос не принимается.

В обоих случаях порядок одинаковый:

  1. Не повторяйте запрос. Гарантии идемпотентности у возврата нет — второй запрос может стать вторым возвратом.
  2. Подождите несколько секунд, при необходимости минуту.
  3. Прочитайте статус: GET /api/v1/invoices/{id}.
  4. Если статус стал refunded или partially_refunded — возврат прошёл, повторять не нужно.
  5. Если статус всё ещё paid, а список возвратов пуст — только тогда повторяйте.

И не говорите покупателю «деньги вернули», пока не увидели это в статусе счёта.

async function refundOnce(id, amount) {
  const r = await fetch(`${API}/invoices/${id}/refund`, {
    method: 'POST',
    headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ amount, reason: 'Возврат' }),
  });
  if (r.ok) return 'done';

  const { error } = await r.json();
  if (error === 'refund_unknown' || error === 'refund_pending_unknown') {
    // не повторяем — читаем статус и решаем по нему
    return 'check_status';
  }
  if (error === 'refund_failed') return 'retry_later';
  throw new Error(error);
}

События

После возврата приходит вебхук:

СобытиеКогда
invoice.refundedСчёт возвращён полностью
invoice.partially_refundedВозвращена часть суммы
refund.doneОтдельная операция возврата выполнена
refund.failedВозврат не выполнен
refund.unknownРезультат остался неизвестным

Если ведёте автоматический учёт, опираться на эти события удобнее, чем опрашивать статус. Обработчик вебхуков делайте идемпотентным: два уведомления об одном возврате не должны превратиться в две проводки в бухгалтерии.

Права и кассир

Срок

Возврат доступен не бессрочно: спустя некоторое время Kaspi перестаёт разрешать возврат по старой операции. Срок задаёт Kaspi, продлить его мы не можем. По просроченному счёту приходит invoice_not_refundable.

В песочнице весь цикл можно безопасно прогнать: создайте счёт, симулируйте оплату, затем отправьте возврат.

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

Можно ли отменить возврат? Нет. Прошедший возврат обратно не откатывается — придётся просить покупателя оплатить заново.

Возвращается ли комиссия? Свою комиссию Kaspi определяет сам, уточняйте в поддержке Kaspi. Мы процент с транзакции не берём, поэтому с нашей стороны возвращать нечего.

Когда деньги дойдут до покупателя? Это определяет Kaspi, на сроки мы повлиять не можем.

Можно ли сделать возврат из кабинета? Да, в разделе «Счета» откройте счёт и нажмите кнопку возврата. API и кабинет выполняют одну и ту же операцию.

Где полный список кодов ошибок? В статье Каталог ошибок. Разбор ситуации, когда возврат не проходит: Возврат не проходит.

Связанные статьи

Возврат не проходитРазбор ошибок возврата по коду: счёт не подлежит возврату, неверная сумма, Kaspi не выполнил операцию или результат неизвестен. Где можно повторять запрос, а где категорически нельзя.Каталог ошибок — что возвращает API и что делатьВсе основные коды ошибок Qut Pay API по группам: авторизация, привязка Kaspi, счета, возвраты, лимиты тарифа, вебхуки, подписки. Причина и решение для каждой.Права доступа (scopes) — что может ключПолная таблица шести scope: invoices:read, invoices:write, refunds:write, subscriptions:manage, webhooks:manage, partner:manage. Какие методы открывает каждый, принцип минимальных прав и разбор ошибки insufficient_scope.API-ключи — создание, хранение, ротацияЧем отличаются ключи qp_live_ и qp_test_, как создать ключ в кабинете, где его хранить и где хранить категорически нельзя, зачем отдельный ключ на каждую интеграцию, как заменить ключ без простоя и что происходит при удалении.Оплата не приходит покупателюСчёт создан, но на телефон покупателя ничего не пришло или QR не открывается. Чаще всего причина в том, что остался включённым тестовый режим. Шесть шагов проверки.

Остались вопросы? WhatsApp +77788813333 · kazprose@gmail.com
Написать можно и из кабинета: Поддержка.

Qut Pay — независимый сервис, не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.