Приём платежей

Возвраты

Возврат отправляет деньги покупателю обратно через Kaspi — со счёта того кассира, через которого прошла оплата. Можно вернуть всю сумму или её часть, в том числе несколькими возвратами.

Создание возврата

Вернуть можно платёж в статусе succeeded или partially_refunded. Без параметра amount возвращается весь остаток.

curl "https://pay.sezgir.com/api/v1/payments/pay_8Rt2XvL5nQ1mZ7cK4wB9pJ3s/refunds" \
  -H "Authorization: Bearer $SEZGIR_API_KEY" \
  -H "Idempotency-Key: refund-pos-77-1029-1" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 1000, "reason": "Нет круассанов" }'
Ответ · 201 Created
{
  "id": "re_5KdW2pQ9sXbN7mT1vLc8Rf4Z",
  "object": "refund",
  "payment_id": "pay_8Rt2XvL5nQ1mZ7cK4wB9pJ3s",
  "amount": 1000,
  "status": "succeeded",
  "requires_action": false,
  "reason": "Нет круассанов",
  "failure_reason": null,
  "created_at": "2026-10-25T12:40:19.007Z"
}
Всегда передавайте Idempotency-Key
Возврат двигает деньги. Если ответ потерялся, повтор с тем же ключом вернёт тот же возврат, а не создаст второй. Ключ удобно строить из вашего идентификатора возврата, например refund-<id заказа>-<номер>.

Частичные возвраты

Можно сделать несколько возвратов по одному платежу, пока их сумма не достигнет суммы платежа. После каждого успешного возврата растёт amount_refunded платежа, а статус становится partially_refunded или, когда возвращено всё, refunded.

Действиеamount_refundedСтатус платежа
Оплата 15 000 ₸0succeeded
Возврат { "amount": 5000 }5 000partially_refunded
Возврат {} — остаток15 000refunded
Ещё один возврат—409 already_refunded

Возвраты, ожидающие подтверждения (pending), резервируют свою сумму: пока результат неизвестен, вернуть её второй раз нельзя.

Ответы и статусы

Запрос на возврат синхронно обращается в Kaspi, поэтому результат обычно известен сразу:

HTTPstatus возвратаЧто произошлоЧто делать
201succeededKaspi выполнил возвратГотово
202pendingKaspi не ответил или результат не удалось подтвердить; requires_action: trueЖдать вебхук, не повторять с новым ключом
502failedKaspi явно отказал; в ответе code вида kaspi_<число>Посмотреть сообщение, решить вручную
503failedЗапрос не дошёл до Kaspi (provider_unavailable)Повторить позже — деньги не двигались

При отказе Kaspi и при 503 возврат сохраняется со статусом failed и причиной в failure_reason, а зарезервированная сумма освобождается. Любой неоднозначный исход (таймаут, оборванный ответ, неожиданная ошибка) — это всегда 202 с requires_action: true, никогда не ошибка. В тестовом режиме возвраты выполняются сразу и всегда успешно.

Ответ 202 и requires_action

Если Kaspi не ответил на запрос возврата (обрыв связи, таймаут), Sezgir Pay не знает, выполнен ли он. Пометить такой возврат неуспешным — верный способ вернуть деньги дважды, поэтому API отвечает 202 Accepted:

Ответ · 202 Accepted
{
  "id": "re_5KdW2pQ9sXbN7mT1vLc8Rf4Z",
  "object": "refund",
  "payment_id": "pay_8Rt2XvL5nQ1mZ7cK4wB9pJ3s",
  "amount": 1000,
  "status": "pending",
  "requires_action": true,
  "reason": "Нет круассанов",
  "failure_reason": "Kaspi не ответил — проверьте возврат в Kaspi Pay и подтвердите результат",
  "created_at": "2026-10-25T12:40:19.007Z"
}
  1. Сумма остаётся зарезервированной, и повторный возврат той же суммы невозможен.
  2. Администратор проверяет операцию в Kaspi Pay и подтверждает результат в кабинете.
  3. Вы получаете refund.succeeded (и payment.refunded) либо refund.failed. До этого момента считайте возврат незавершённым.
Не создавайте новый возврат после 202
Повтор с тем же Idempotency-Key безопасен — он вернёт тот же ответ 202. Новый ключ создаст второй возврат, и если первый на самом деле прошёл, покупатель получит деньги дважды.

События

СобытиеКогдаdata.object
refund.succeededВозврат выполнен — сразу или после подтвержденияRefund
payment.refundedСразу после refund.succeeded — для полного и для частичного возвратаPayment с новым amount_refunded
refund.failedKaspi отказал, запрос не дошёл до Kaspi или администратор отметил возврат неуспешнымRefund

Чтобы отличить полный возврат от частичного, смотрите на status платежа в payment.refunded: refunded или partially_refunded.

Ошибки

HTTPcodeПричина
400validation_failedamount не целое число или меньше 1; reason длиннее 500 символов
400—Сумма больше доступного остатка (param: "amount"). В сообщении указан доступный остаток.
404—Платёж не найден (или принадлежит другому сервису либо режиму)
409payment_not_refundableПлатёж не оплачен: pending, failed, expired или canceled
409already_refundedСумма уже возвращена полностью или остаток зарезервирован возвратом в обработке
502kaspi_*Kaspi отказал в возврате; код Kaspi передаётся как kaspi_<число>. Возврат — failed
503provider_unavailableKaspi недоступен или кассир требует повторного входа

Общий формат ошибок — в разделе Ошибки.

Просмотр возвратов

Все возвраты платежа

GET /payments/:id/refunds
curl "https://pay.sezgir.com/api/v1/payments/pay_8Rt2XvL5nQ1mZ7cK4wB9pJ3s/refunds" \
  -H "Authorization: Bearer $SEZGIR_API_KEY"

Возвращает все возвраты платежа, от новых к старым, одним списком (has_more всегда false).

Один возврат

GET /refunds/:id
curl "https://pay.sezgir.com/api/v1/refunds/re_5KdW2pQ9sXbN7mT1vLc8Rf4Z" \
  -H "Authorization: Bearer $SEZGIR_API_KEY"

Поля объекта описаны в справочнике.