ДокументацияВведение
Коды ошибок

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

Возвраты

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

Возврат отправляет деньги плательщику обратно на его 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 обязателен.

bash
Скачать
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": "Покупатель отменил заказ"
  }'
python
Скачать
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:

json
Скачать
{
  "refund_id": "RFND12345",
  "status": "completed"
}

Как сделать возврат из кабинета (JWT)

Если вы работаете под JWT-авторизацией кабинета, используйте POST /me/payments/{op_id}/refund. Здесь тело — RefundBody: amount (опционально; по умолчанию возвращается полная сумма платежа) и reason (опционально, ≤500 символов).

bash
Скачать
curl -X POST https://api.paybot.kz/me/payments/1450012345/refund \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{"amount": 2000, "reason": "Частичный возврат за одну позицию"}'
python
Скачать
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 и виден таким в списке платежей.