# Счёттар қосарланып жатыр

> Бір тапсырысқа бірнеше счёт шығып жатса, алдымен ағынды тоқтату керек: API кілтті жойсаңыз, интеграция сол сәтте тоқтайды. Сосын себебін тауып, идемпоттылық қосасыз.

## Қысқаша

Ең алдымен **ағынды тоқтатыңыз**: кабинеттен API кілтті жойыңыз. Кілт жойылған сәттен бастап сол кілтпен келген сұраулардың бәрі 401 алады, яғни жаңа счёт жасалмайды — кодыңызды түзетіп үлгергенше клиенттер артық счёт көрмейді. Ағын тоқтағаннан кейін ғана себебін іздеңіз: әдетте бұл идемпоттылықтың жоқтығы, қайталау циклі немесе webhook-ты қате өңдеу. Тұрақты шешімі — әр счёт сұрауына `Idempotency-Key` тақырыбын қосу.

## Шұғыл тоқтату

1. Кабинетке кіресіз, API кілттер бөлімін ашасыз
2. Счёт шығарып жатқан кілтті **жоясыз**
3. Кодыңызды түзетесіз
4. Жаңа кілт жасап, серверде ауыстырасыз

Кілтті жою — ең жылдам «ажыратқыш». Оны өшіру үшін кодыңызды қайта жаюдың, серверді өшірудің қажеті жоқ.

Не өзгермейді: бұрын жасалған счёттар орнында қалады, webhook баптаулары сақталады, Kaspi байланысы үзілмейді. Кілттің өзі ғана жарамсыз болады.

Ескертпе: бұл бүкіл интеграцияны тоқтатады, яғни дұрыс счёттар да шықпай қалады. Егер бір ғана бөлік бұзылған болса және сізде бірнеше кілт болса, тек соның кілтін жойыңыз.

## Артық счёттарды тазалау

Ағын тоқтаған соң:

- Төленбеген артық счёттарды `POST /api/v1/invoices/{id}/cancel` арқылы жауып тастаңыз — сонда клиент оларды кездейсоқ төлеп қоймайды
- Клиент шынымен екі рет төлеп қойса, артығын қайтарасыз: [Қайтару өтпей жатыр](/kb/refund-not-working)
- Счёттар тізімін `externalOrderId` бойынша сүзіп, бір тапсырысқа нешеу шыққанын көресіз

Асықпаңыз: `pending` күйіндегі счёт әлі төленуі мүмкін, сондықтан тазалауды ағынды тоқтатқаннан кейін ғана бастаңыз.

## Себебін табу

| Белгісі | Ықтимал себебі |
|---|---|
| Бір тапсырысқа дәл екі счёт | Клиент «Төлеу» батырмасын екі рет басқан немесе форма екі рет жіберілген |
| Бір тапсырысқа ондаған счёт | Кодта цикл: қате келгенде қайта жіберу, шығу шарты жоқ |
| Счёттар тұрақты интервалмен шығып тұр | Кесте бойынша жүретін тапсырма әр жүрісте жаңа счёт жасайды |
| Счёт webhook келген сайын қосылады | Webhook өңдеушісі счёт жасап отыр, ал webhook 11 рет қайталанады |
| `tariff_daily_burst` қатесі келді | Тәуліктік қорғаныс іске қосылды — бұл дәл осындай циклдерді ұстау үшін жасалған |

`tariff_daily_burst` — бизнес лимиті емес, циклге түскен интеграциядан қорғайтын сақтандырғыш. Ол келсе, тарифті көтеруге асықпаңыз: алдымен кодты тексеріңіз. Айлық лимит бөлек кодпен келеді — `tariff_limit_reached`.

## Idempotency-Key қалай қолданылады

Счёт жасау сұрауына `Idempotency-Key` тақырыбын қосасыз. Сол кілтпен екінші рет жіберсеңіз, **жаңа счёт жасалмайды**: бұрынғысы қайтады, HTTP 200 және жауапта `idempotentReplay: true` белгісі болады.

```
POST /api/v1/invoices
X-API-Key: qp_live_…
Idempotency-Key: order-10482
Content-Type: application/json

{ "amount": 12500, "externalOrderId": "10482" }
```

Кілтті қалай таңдау керек:

- **Тапсырысқа байланысты болсын**: тапсырыс нөмірі, себет идентификаторы, төлем әрекетінің идентификаторы
- **Кездейсоқ болмасын.** Әр сұрауда жаңа UUID жасасаңыз, идемпоттылықтан пайда жоқ
- **Қайталанбасын.** Бір тапсырысқа әдейі екі бөлек төлем керек болса, кілтке нөмір қосыңыз: `order-10482-1`, `order-10482-2`
- Сонымен қатар `externalOrderId` өрісін толтырыңыз: ол webhook-та қайта келеді және счёттарды іздеуге ыңғайлы

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

## Қайталау циклін дұрыс жазу

Счёт жасау сұрауы қате бергенде қайталау қауіпсіз болуы үшін:

- **Тек қайталауға болатын қателерде қайталаңыз**: 502, 503 және `Retry-After` көрсеткен 429. 4xx қателерін қайталаудың мәні жоқ — сұрауды түзету керек
- **Кідірісті өсіріңіз**: 1, 2, 4, 8 секунд. Бірден циклмен қайталамаңыз
- **Санын шектеңіз**: мысалы 5 әрекеттен кейін тоқтап, қолмен қарауға белгі қойыңыз
- **Әр әрекетте сол `Idempotency-Key` кілтін жіберіңіз**, жаңасын емес
- **Timeout-ты сәтсіздік деп есептемеңіз.** Сұрау жетіп, счёт жасалып, тек жауап жоғалған болуы мүмкін. Идемпоттылық кілті осы жағдайды шешеді

## Webhook өңдеуде идемпоттылық

Біз 2xx емес жауапқа хабарламаны **11 рет қайталаймыз**. Сондықтан бір оқиға бірнеше рет келеді — бұл қалыпты. Егер өңдеушіңіз әр келген хабарламаға жауап ретінде жаңа счёт жасаса немесе тапсырысты қайта өңдесе, қосарлану сол жерден шығады.

`(invoice.id, status)` жұбын кілт ретінде алып, бұрын өңдеген оқиғаны екінші рет өңдемеңіз. Өңдеу ұзақ болса, алдымен 200 қайтарып, жұмысты фонда істеңіз — әйтпесе біз timeout деп есептеп, қайта жібереміз.

## Алдын алу

- Клиенттің «Төлеу» батырмасын бірінші басқаннан кейін өшіріп қойыңыз
- Бір тапсырыста ашық счёт бар ма, жаңасын жасамай тұрып тексеріңіз
- Sandbox-та қайталауды әдейі сынаңыз: сол `Idempotency-Key` кілтімен екі рет жіберіп, екінші жауапта `idempotentReplay: true` келгенін көріңіз
- Кабинетте счёттардың саны кенет өсіп кетсе байқайтындай етіп, Telegram ботын қосып қойыңыз

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

**Кілтті жойсам, бұрынғы счёттар жойыла ма?** Жоқ. Счёттар, олардың күйлері мен тарихы сақталады. Тек кілттің өзі жарамсыз болады.

**Idempotency-Key қанша уақыт жарамды?** Бір кілтті шексіз ұзақ қайта қолдануға есептемеңіз: ол жақын арадағы қайталаудан қорғауға арналған. Тапсырыс нөмірін кілт ретінде алсаңыз, іс жүзінде бұл жеткілікті.

**Sandbox счёттары лимитке кіре ме?** Жоқ, sandbox счёттары айлық лимитке есептелмейді.

**Клиент екі счётты да төлеп қойды, енді не істеймін?** Артық сомасын қайтарасыз. Ішінара қайтару да қолдау көрсетіледі.

**`externalOrderId` идемпоттылықты өзі қамтамасыз ете ме?** Жоқ, ол іздеу мен есеп үшін. Қайталаудан қорғайтыны — `Idempotency-Key` тақырыбы.
