# Қайтару API — толық және ішінара қайтару

> POST /invoices/{id}/refund әдісінің толық анықтамасы: сұрау өрістері, толық және ішінара қайтару, сома шектеуі, барлық қате коды, refund_unknown келгенде не істеу керек және қандай оқиғалар жіберіледі.

## Қысқаша

Ақшаны қайтару — бір ғана әдіс:

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

{ "amount": 1500, "reason": "Тауар қайтарылды" }
```

`amount` жазбасаңыз — счёт **толық** қайтарылады. Жазсаңыз — сол сома ғана қайтады, счёт `partially_refunded` күйіне өтеді.

Екі нәрсені бірден есте ұстаңыз: кілтте **`refunds:write`** құқығы болуы керек, ал `refund_unknown` немесе `refund_pending_unknown` қатесі келсе **сұрауды қайталамау керек** — оның орнына счёт күйін оқисыз.

## Сұрау өрістері

| Өріс | Тип | Міндетті | Сипаттама |
|---|---|---|---|
| `amount` | number | жоқ | Қайтарылатын сома, теңге. Жазылмаса — толық қайтару |
| `reason` | string | жоқ | Себебі. Есепте және кабинетте көрінеді |

Жол параметрі `{id}` — счёттың `id` мәні (`inv_…`). `externalOrderId` бойынша қайтару жоқ: алдымен `GET /api/v1/invoices?externalOrderId=…` арқылы счётты тауып, содан соң оның `id` мәнін қолданыңыз.

## Қайтару қандай счётқа жасалады

| Счёт күйі | Қайтаруға бола ма |
|---|---|
| `new`, `pending` | Жоқ. Ақша әлі түспеген — счётты **болдырмау** керек: `POST /invoices/{id}/cancel` |
| `paid` | Иә, толық та, ішінара да |
| `partially_refunded` | Иә, қалған сома шегінде |
| `refunded` | Жоқ, бәрі қайтарылған |
| `cancelled`, `expired` | Жоқ |

Ақша **біздің шотымыздан емес, сіздің Kaspi шотыңыздан** кетеді. Біз ақшаны ешқашан ұстамаймыз, сондықтан қайтаруды да Kaspi орындайды. Шотта қалдық жеткіліксіз болса, операция өтпейді.

## Толық және ішінара қайтару

**Толық қайтару** — денесі бос немесе тек `reason` бар сұрау:

```bash
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_refundable` | 409 | Бұл счётты қайтаруға болмайды | Күйін оқыңыз: төленген бе, мерзімі өтпеген бе |
| `invalid_refund_amount` | 422 | Сома дұрыс емес | Оң сан, төленген сомадан аспауы керек |
| `refund_failed` | 502 | Kaspi қайтаруды орындамады | Kaspi шотындағы қалдықты тексеріп, қайталауға болады |
| `refund_unknown` | 502 | Kaspi жауап бермеді, нәтижесі белгісіз | **Қайталамаңыз.** Счёт күйін оқыңыз |
| `refund_pending_unknown` | 409 | Алдыңғы қайтарудың нәтижесі әлі белгісіз | **Қайталамаңыз.** Күте тұрыңыз |
| `refund_state_conflict` | 409 | Қайтару күйі күткеннен басқа | Счёт күйін қайта оқып, содан шешіңіз |
| `insufficient_scope` | 403 | Кілтте `refunds:write` жоқ | Кабинетте кілтке құқық қосыңыз |
| `invoice_not_found` | 404 | Счёт жоқ немесе кілтке көрінбейді | Идентификаторды және кілт байланған кассирді тексеріңіз |
| `kaspi_session_expired` | 409 | Кассир байланысы үзілген | Қайта байланыстырыңыз |

Қателерді әрқашан `error` коды бойынша өңдеңіз — `message` мәтіні өзгеруі мүмкін, код өзгермейді.

## Нәтижесі белгісіз болғанда

`refund_unknown` — Kaspi сұрауға жауап бермеді. Ақша кетті ме, жоқ па — бұл сәтте біз де білмейміз. `refund_pending_unknown` — алдыңғы қайтару әлі анықталмағандықтан жаңа сұрау қабылданбайды.

Екі жағдайда да реті бірдей:

1. Сұрауды **қайталамаңыз**. Қайтару әдісінде идемпоттылық кепілдігі жоқ — екінші сұрау екінші қайтаруға айналуы мүмкін.
2. Бірнеше секунд, қажет болса бір минут күтіңіз.
3. `GET /api/v1/invoices/{id}` арқылы күйді оқыңыз.
4. Күй `refunded` немесе `partially_refunded` болса — қайтару өтті, қайталаудың қажеті жоқ.
5. Күй әлі `paid`, ал қайтарулар тізімі бос болса — сонда ғана қайталаңыз.

Клиентке «ақша қайтарылды» деп счёт күйінен көрмей тұрып айтпаңыз.

```js
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);
}
```

## Оқиғалар

Қайтару өткен соң webhook келеді:

| Оқиға | Қашан |
|---|---|
| `invoice.refunded` | Счёт толық қайтарылды |
| `invoice.partially_refunded` | Сома ішінара қайтарылды |
| `refund.done` | Жеке қайтару операциясы орындалды |
| `refund.failed` | Қайтару орындалмады |
| `refund.unknown` | Нәтижесі белгісіз күйде қалды |

Автоматты есеп жүргізсеңіз, күйді сұрап отырғаннан гөрі осы оқиғаларға сүйенген ыңғайлы. Webhook өңдеуіңіз идемпотентті болсын: бір қайтару туралы екі рет хабар келгенде бухгалтерияда екі жазба пайда болмауы керек.

## Құқық және кассир

- Кілтте **`refunds:write`** құқығы болуы керек, әйтпесе `insufficient_scope`. Толығы: [Құқықтар (scopes)](/kb/scopes).
- Қайтару да Kaspi-дегі «Кассир» рөлі арқылы жүреді, сондықтан кассир байланысы белсенді болуы шарт. Байланыс үзілсе қайтару да, счёт жасау да өтпейді.
- Кілт нақты кассирге байланған болса, ол басқа кассирдің счёттарын көрмейді — сондай счётқа қайтару жіберсеңіз `invoice_not_found` келеді.

## Мерзім

Қайтару мәңгі қолжетімді емес: белгілі бір уақыттан кейін Kaspi ескі операция бойынша қайтаруға рұқсат бермейді. Мерзімді **Kaspi** белгілейді, біз оны ұзарта алмаймыз. Мерзімі өткен счётқа `invoice_not_refundable` келеді.

Sandbox режимінде бүкіл циклді қауіпсіз сынап көруге болады: счёт жасап, төлемді симуляциялап, содан кейін қайтаруды жіберіңіз.

## Жиі қойылатын сұрақтар

**Қайтаруды кері қайтаруға бола ма?** Жоқ. Өткен қайтару кері оралмайды — клиенттен қайта төлеуді сұрауға тура келеді.

**Комиссия қайтарыла ма?** Kaspi өз комиссиясын өзі анықтайды, оны Kaspi қолдауынан сұраңыз. Біз транзакциядан пайыз алмаймыз, сондықтан бізден қайтаратын ештеңе жоқ.

**Клиентке ақша қашан жетеді?** Оны Kaspi шешеді, мерзімге біз әсер ете алмаймыз.

**Қайтаруды кабинеттен жасауға бола ма?** Иә, Счёттар бөлімінде счётты ашып, қайтару батырмасын басасыз. API мен кабинет бір операцияны орындайды.

**Қате кодтарының толық тізімі қайда?** [Қателер каталогы](/kb/error-catalog) бетінде. Қайтару өтпей жатқанда себебін іздеу реті: [Қайтару өтпей жатыр](/kb/refund-not-working).
