# Екі рет қайтарып жіберуден қалай сақтану керек

> refund_unknown және refund_pending_unknown келгенде қайталау — ақшаны екі рет қайтарудың ең қысқа жолы. Оның орнына счёттың күйін оқу керек. Идемпоттылық, ішінара қайтару есебі және журнал жүргізу.

## Қысқаша

Қайтару сұрауы `refund_unknown` (502) немесе `refund_pending_unknown` (409) қайтарса, бұл «қайтару өтпеді» дегенді білдірмейді — бұл **нәтижесі әлі белгісіз** дегенді білдіреді. Сол сәтте қайталап жіберсеңіз, бірінші қайтару шын мәнінде өтіп кеткен болса, клиентке ақша екі рет барады. Дұрыс әрекет біреу: **қайталамаңыз, `GET /api/v1/invoices/{id}` арқылы счёттың күйін оқып, нәтижені содан біліңіз.**

## Неге «белгісіз» деген жауап болады

Қайтару — бірнеше жүйеден өтетін әрекет. Сұрау Kaspi-ге жетіп, ол әрекетті орындап та қойып, бірақ жауабы бізге жетпей қалуы мүмкін. Сол кезде біз сізге адал жауап береміз: нәтижесі белгісіз.

| Код | HTTP | Мағынасы |
|---|---|---|
| `refund_unknown` | 502 | Kaspi жауап бермеді, қайтару өтті ме, жоқ па — белгісіз |
| `refund_pending_unknown` | 409 | Алдыңғы қайтарудың нәтижесі әлі белгісіз, жаңасын қабылдамаймыз |
| `refund_failed` | 502 | Kaspi қайтаруды орындамады. Бұл — нақты «өтпеді» |
| `refund_state_conflict` | 409 | Қайтару күйі күткеннен басқа, күйді қайта оқу керек |

`refund_failed` пен `refund_unknown` арасындағы айырмашылық осы мақаланың негізі. Біріншісі — нақты сәтсіздік, оны түзетіп қайталауға болады. Екіншісі — белгісіздік, оны қайталауға **болмайды**.

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

## Дұрыс әрекет реті

1. **Қайталамаңыз.** Автоматты қайталау логикаңыз болса, қайтару сұрауын одан алып тастаңыз.
2. **Біраз күтіңіз** — бірнеше секунд.
3. **`GET /api/v1/invoices/{id}` жіберіңіз.** Жауапта счёттың күйі және оның қайтарулары болады.
4. **Күйге қарап шешіңіз:**

| Счёттың күйі | Нені білдіреді | Не істеу керек |
|---|---|---|
| `refunded` | Толық қайтарылған | Бітті. Қайталамаңыз |
| `partially_refunded` | Бір бөлігі қайтарылған | Қайтарылған соманы есептеп, жеткіліксіз болса ғана айырмасын жіберіңіз |
| `paid` | Қайтару өтпеген | Енді ғана қайталауға болады |

5. **Нәтижені өз журналыңызға жазыңыз** — келесі жолы қайта сұрамау үшін.

Егер бірнеше рет сұрағанда да күй анық болмай тұрса, күте тұрыңыз және қолдауға жазыңыз. Бұл жағдайда «бір рет қана қайталап көрейін» деген қауіпті.

## Webhook арқылы да біле аласыз

Қайтару әрекетінің нәтижесі оқиға түрінде де келеді: `refund.done`, `refund.failed`, `refund.unknown`, ал счёттың күйі өзгергенде `invoice.refunded` немесе `invoice.partially_refunded`.

Яғни `refund_unknown` алған соң екі жол бар: күйді өзіңіз сұрау немесе оқиғаны күту. Ең сенімдісі — екеуін қатар жүргізу, бірақ **екеуінің де нәтижесін бір орында, идемпотентті түрде өңдеу.**

## Идемпоттылық

Екі рет қайтарудан қорғайтын басты нәрсе — қайталанбайтындықты өз жағыңызда қамтамасыз ету.

- **Бір счётқа бір ғана «қайтару тапсырмасы» болсын.** Оны өз базаңызда жазба етіп сақтаңыз: `счёт id`, `сома`, `күйі` (жіберілді / расталды / белгісіз / өтпеді).
- **Жазбаны құлыптаңыз.** Сол счёт бойынша екінші қайтару тапсырмасы қосылмасын. Қатар жүретін екі процесс бір счётқа бір уақытта қайтару жібермеуі керек.
- **Кезектегі тапсырманы автоматты қайталайтын болсаңыз, «белгісіз» күйді қайталауға жарамсыз деп белгілеңіз.** Ондай тапсырма қайталауға емес, тексеруге кетуі керек.
- **Webhook өңдеуі де идемпотентті болсын.** `(счёт id, күй)` жұбы бұрын өңделген болса, ештеңе істемеңіз. Webhook 2xx емес жауап алса **11 рет қайталанады**, сондықтан бір оқиға сізге бірнеше рет келуі әбден мүмкін.
- **Қолмен қайтару да журналға түссін.** Кабинеттен қолмен қайтарып, сосын жүйеңіз тағы бір қайтару жіберсе, нәтиже сол екі есе болады.

## Ішінара қайтару есебі

Ішінара қайтару кезінде санақ адасу оңай. Ереже қарапайым: **қайтарылған сомалардың қосындысы төленген сомадан аспауы керек.** Артық жіберсеңіз `invalid_refund_amount` (422) аласыз — бұл жақсы, бірақ оған сүйенбеңіз, өзіңіз есептеңіз.

Мысалы: 10 000 ₸ төленген счёт. 3 000 ₸ қайтардыңыз — счёт `partially_refunded` болады, қалғаны 7 000 ₸. Екінші рет 3 000 ₸ жіберсеңіз, ол **бірінші қайтаруды қайталау емес, жаңа қайтару**: барлығы 6 000 ₸ болады. Сондықтан «қайталап жіберейін» деген ой ішінара қайтаруда ең қауіпті.

Есепті әрқашан `GET /api/v1/invoices/{id}` жауабындағы қайтарулар тізімінен жүргізіңіз, өз болжамыңызбен емес.

## Журнал нені жазуы керек

Даулы жағдайда сізді осы құтқарады.

- Қайтару сұрауын жіберген уақыт, счёт id, сома
- Жауаптың HTTP күйі және `error` коды
- Одан кейінгі `GET /invoices/{id}` жауабы: күйі мен қайтарулары
- Келген webhook оқиғалары, `X-Webhook-Delivery` тақырыбымен
- Қайтаруды кім бастады: жүйе ме, әлде адам қолмен бе

**Кілттің өзін журналға жазбаңыз.** Тақырыптарды тіркегенде `X-API-Key` мәнін сүзіп тастаңыз.

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

**`refund_unknown` алдым, күйі `paid` болып тұр. Қайталауға бола ма?** Иә. Күй `paid` болса, қайтару өтпеген, қайталауға болады.

**`refund_pending_unknown` қанша тұрады?** Алдыңғы қайтарудың тағдыры анықталғанша. Бірнеше секунд күтіп, счёттың күйін қайта оқыңыз.

**Екі рет қайтарып жіберсем, кері қайтаруға бола ма?** Жоқ, қайтаруды «болдырмау» деген әрекет жоқ. Клиентпен тікелей сөйлесіп, артық сомадан жаңа счёт шығару керек болады.

**Қайтарудың идемпоттылық кілті бар ма?** Счёт жасауда `Idempotency-Key` тақырыбы бар. Қайтаруда қорғаныс басқаша жұмыс істейді: алдыңғы қайтарудың нәтижесі белгісіз болса, жаңасы қабылданбайды (`refund_pending_unknown`). Өз жағыңыздағы журнал мен құлып — бәрібір міндетті.

**Қайтару ақшасы қайдан алынады?** Kaspi шотыңыздан. Ақша бізде тұрмайды: [Ақша қашан және қайда түседі](/kb/money-arrival).
