Возврат отправляет деньги плательщику обратно на его Kaspi по ранее оплаченной операции. В актуальном API это POST /v2/refunds с API-ключом: передайте operation_id оплаченного платежа и сумму возврата в тенге. Из кабинета (по JWT) тот же возврат делает POST /me/payments/{op_id}/refund, где сумма опциональна — без неё возвращается вся сумма платежа. Возврат может быть полным или частичным, а об успешном возврате PayBot присылает вебхук payment.refunded.
Устаревший эндпоинт POST /v1/refund помечен как DEPRECATED (Sunset: 31 декабря 2026) — для новой интеграции используйте /v2/refunds или /me/payments/{op_id}/refund.
Как сделать возврат через API
POST /v2/refunds возвращает средства по QR- или счёт-операции. Тело: operation_id (обязателен), amount (обязателен, целые тенге, не больше суммы платежа) и reason (опционально, ≤255 символов, для бухгалтерии). Заголовок Idempotency-Key обязателен.
curl -X POST https://api.paybot.kz/v2/refunds \
-H "X-API-Key: kp_live_00112233445566778899aabbccddeeff" \
-H "Idempotency-Key: b4d6f8a0-1c2e-4f6a-8b90-3d5f7a9c1e44" \
-H "Content-Type: application/json" \
-d '{
"operation_id": "1450012345",
"amount": 5000,
"reason": "Покупатель отменил заказ"
}'import requests, uuid
resp = requests.post(
"https://api.paybot.kz/v2/refunds",
headers={
"X-API-Key": "kp_live_00112233445566778899aabbccddeeff",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"operation_id": "1450012345", "amount": 5000, "reason": "Возврат по браку"},
)
print(resp.json())Пример ответа 200 OK:
{
"refund_id": "RFND12345",
"status": "completed"
}Как сделать возврат из кабинета (JWT)
Если вы работаете под JWT-авторизацией кабинета, используйте POST /me/payments/{op_id}/refund. Здесь тело — RefundBody: amount (опционально; по умолчанию возвращается полная сумма платежа) и reason (опционально, ≤500 символов).
curl -X POST https://api.paybot.kz/me/payments/1450012345/refund \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"amount": 2000, "reason": "Частичный возврат за одну позицию"}'import requests
requests.post(
"https://api.paybot.kz/me/payments/1450012345/refund",
headers={"Authorization": "Bearer <jwt>"},
json={"reason": "Полный возврат"}, # amount опущен = вернуть всё
)Полный и частичный возврат
Возврат может быть на всю сумму платежа или на её часть.
- •Полный возврат: в
/v2/refundsукажитеamount, равный сумме платежа; в/me/payments/{op_id}/refundпросто не передавайтеamount. - •Частичный возврат: укажите
amountменьше суммы платежа — например, вернуть 2000 ₸ из оплаченных 5000 ₸. - •Сумма возврата не может превышать сумму платежа.
- •Частичные возвраты поддержаны на уровне API, но история и связь «платёж → его возвраты» через API сейчас не читаются — храните
refund_idи сумму возврата у себя.
Ограничения возврата
Возврат делается только по успешно оплаченному платежу (status == "paid"). Учитывайте несколько правил.
- •Возвращать можно только оплаченную операцию; по неоплаченному QR или счёту возврата нет (счёт до оплаты можно отменить).
- •
amountне должен превышать сумму исходного платежа. - •Возврат — мутирующая операция, поэтому
POST /v2/refundsтребуетIdempotency-Key: один ключ на одно намерение, чтобы повтор не создал второй возврат. См. Идемпотентность. - •После возврата платёж переходит в статус
refunded.
Статусы возврата
Ответ /v2/refunds содержит status возврата.
| Статус | Значение |
|---|---|
completed | Возврат выполнен, деньги отправлены плательщику. |
pending | Возврат принят и обрабатывается на стороне Kaspi. |
failed | Возврат не удался. |
При pending дождитесь финального состояния — подтверждением служит вебхук payment.refunded. При failed разберите тело ошибки: категория temporary означает временный сбой Kaspi и допускает повтор, permanent — нет (см. Ошибки).
Вебхук о возврате
Об успешном возврате PayBot присылает вебхук с событием payment.refunded — на него подписывают учётную систему, чтобы отметить возврат без поллинга. Событие приходит по общему контракту доставки вебхуков: заголовки X-Webhook-Event: payment.refunded, X-Webhook-Timestamp, X-Webhook-Signature: sha256= и подпись HMAC-SHA256 от строки "{timestamp}.{body}". Проверяйте подпись, как описано в разделе Вебхуки, прежде чем доверять телу события.
Чтобы получать payment.refunded, добавьте это событие в список events при создании вебхука через POST /me/webhooks.
FAQ
Можно ли вернуть больше, чем оплатил покупатель?
Нет. Сумма возврата (amount) не может превышать сумму исходного платежа. Сумма нескольких частичных возвратов по одной операции в совокупности также ограничена суммой платежа.
Чем `/v2/refunds` отличается от `/me/payments/{op_id}/refund`?
Это два входа к одной и той же логике возврата под разной авторизацией. /v2/refunds работает по API-ключу (X-API-Key) и требует явный amount — удобно для серверной интеграции. /me/payments/{op_id}/refund работает по JWT кабинета, op_id передаётся в пути, а amount можно опустить для полного возврата. Устаревший /v1/refund использовать не стоит.
Как узнать, что возврат дошёл до покупателя?
Ориентируйтесь на статус completed в ответе и на вебхук payment.refunded. Если ответ вернул pending, возврат ещё обрабатывается на стороне Kaspi — финальным подтверждением служит вебхук. Сам платёж после возврата переходит в статус refunded и виден таким в списке платежей.