# Счёттар жаппай құлап жатыр — не істеу керек

> Бір мезетте көп счёт жіберсеңіз, Kaspi кассирдің сұрау жиілігін шектеуі мүмкін. Автоматты қайталау жоқ. Пауза беру, кезекпен жіберу және қате кодтарын ажырату тактикасы.

## Қысқаша

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

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

## Алдымен тоқтатыңыз

Жаппай құлау кезінде реті осындай:

1. **Жіберетін процесті тоқтатыңыз.** Cron, кезек, скрипт — не болса да.
2. **Соңғы 50 қатенің `error` кодын жинаңыз.** Бәрі бірдей ме, әлде әртүрлі ме — шешім осыған байланысты.
3. **Бір ғана счётты қолмен жіберіп көріңіз.** Ол өтсе, мәселе жиілікте. Ол да құласа, мәселе басқада.
4. **Кабинеттен қолмен счёт шығарыңыз.** Ол шықса, Kaspi байланысы тірі.

Шұғыл жағдайда, мысалы код циклге түсіп кеткені белгілі болса, ең сенімді тоқтатқыш — [кабинеттен](https://qut.kz/app) сол API кілтті жою. Кілт жойылған соң бірде-бір счёт жасалмайды. Жаңа кілт кейін жасайсыз.

## Қате кодына қарап ажырату

| `error` | Нені білдіреді | Не істеу керек |
|---|---|---|
| `rate_limited`, `request_rate_limited` | Сұрау жиілігі шектен асты | `Retry-After` тақырыбын оқып, сонша күтіңіз |
| `too_many_attempts` | Бір әрекетті тым жиі қайталадыңыз | Бір минут күтіңіз |
| `tariff_daily_burst` | Тәуліктік қорғаныс іске қосылды | Бұл бизнес лимиті емес. Кодта цикл бар ма, қараңыз |
| `tariff_limit_reached` | Тарифтің айлық счёт лимиті бітті | Тарифті көтеріңіз немесе жаңа айды күтіңіз |
| `invoice_create_failed` | Kaspi счётты қабылдамады | Кідіріспен қайталауға болады |
| `kaspi_session_expired` | Кассир байланысы үзілген | Кассирді қайта байланыстырыңыз |
| `insufficient_scope`, `tariff_inactive` | Құқық немесе тариф мәселесі | [API 403 қайтарады](/kb/api-403) |
| `unauthorized` | Кілт танылмады | [API 401 қайтарады](/kb/api-401) |

**Бәрі бірдей код болса** — себебі біреу, оны түзету жеңіл. **Кодтар аралас болса** — көбіне жиілік: жүйе әртүрлі жерден қайтарып жатыр.

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

## Автоматты қайталау жоқ

Мұны нақты білген жөн: **счёт жасау сұрауы құласа, біз оны өзіміз қайта жібермейміз**. Бұл әдейі істелген — сіздің рұқсатыңызсыз қайталасақ, клиентке екі рет счёт барып қалуы мүмкін.

Қайталау сіздің жағыңызда болуы керек, әрі мынадай ережемен:

- **Қайталау кідірісі өсіп отырсын.** 1, 2, 4, 8, 16 секунд. Бірден қайталау ешқашан көмектеспейді.
- **Қайталау саны шектеулі болсын.** Үш-бес талпыныс, одан кейін кезекке қалдырып, ескерту жіберіңіз.
- **4xx қателерді қайталамаңыз.** Сома дұрыс емес болса, оны жүз рет жіберсеңіз де қабылданбайды.
- **`Idempotency-Key` жіберіңіз.** Сонда қайталағанда жаңа счёт жасалмай, бұрынғысы қайтады.

```js
const res = await fetch('https://api.qut.kz/api/v1/invoices', {
  method: 'POST',
  headers: {
    'X-API-Key': key,
    'Content-Type': 'application/json',
    'Idempotency-Key': `order-${orderId}`,
  },
  body: JSON.stringify({ amount, description, externalOrderId: String(orderId) }),
});
```

`Idempotency-Key` ретінде өз тапсырыс нөміріңізді алыңыз — сонда қайталау қауіпсіз болады, счёт екеу болып кетпейді.

## Кезекпен жіберу

Жүз счётты бір секундта жіберудің қажеті жоқ. Кассир — Kaspi-дегі бір ғана рөл, оның да сұрау жиілігі шектеулі.

Практикада жұмыс істейтін тәсіл:

```js
async function sendQueue(orders) {
  const failed = [];
  for (const order of orders) {
    try {
      await createInvoice(order);
    } catch (e) {
      failed.push({ order, error: e.code });
      if (e.code === 'rate_limited' || e.code === 'tariff_daily_burst') {
        await sleep(60_000); // бір минут пауза, сосын жалғастырамыз
      }
    }
    await sleep(300); // счёттар арасында шағын кідіріс
  }
  return failed;
}
```

Негізгі үш ұстаным:

- **Біртіндеп жіберіңіз, қатар емес.** Он ағынмен қатар жіберу — жаппай құлаудың ең жиі себебі.
- **Счёттар арасында кідіріс қалдырыңыз.** Бірнеше жүз миллисекунд жеткілікті.
- **Бірінші қатеде тоқтап, себебін оқыңыз.** Соқыр жалғастыру — барлық тізімді жоғалту деген сөз.

Бір сұрауда бірнеше счёт керек болса, әрқайсысын бөлек жіберудің орнына `POST /api/v1/invoices/bulk` әдісін қолданыңыз: бір сұрауда 1-100 счёт, әрқайсысы бөлек тексеріледі, нәтижесінде қайсысы өтіп, қайсысы құлағанын бөлшектеп көресіз.

## Жинақтау режимі

Тұрақты көп ағыны бар жүйелерде ең сенімді сұлба — **жинақтап, біртіндеп жіберу**:

1. Счёт жасау керек әр оқиғаны бірден API-ға емес, өз кезегіңізге жазыңыз
2. Бір ғана жұмысшы процесс сол кезектен алып, біртіндеп жібереді
3. Құлағанын кезекте қалдырып, кідіріспен қайта жібереді
4. Үш талпыныстан кейін де өтпесе, адамға ескерту кетеді

Бұл сұлбада шыңдар тегістеледі: клиенттер жүз тапсырыс берсе де, API-ға біркелкі ағын барады. Ал ең бастысы — жүйе құласа, тапсырыстар жоғалмай кезекте тұрады.

## Қайталанбауы үшін

- **Циклден қорғаныс қойыңыз.** Бір тапсырыс бойынша бір ғана счёт жасалатынын кодта тексеріңіз.
- **`externalOrderId` жіберіңіз.** Сонда счёт қай тапсырыстікі екені webhook-та да көрінеді, қосарлануды бірден байқайсыз.
- **Журнал жазыңыз.** Әр сұраудың уақыты, жауап коды, `error` мәні жазулы тұрсын. Мұнсыз себебін табу мүмкін емес.
- **Тәуліктік санды бақылаңыз.** Тарифіңіздің тәуліктік қорғаныс шегіне жақындап қалсаңыз, кодта цикл бар-жоғын тексеріңіз.

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

**Құлаған счёттар кейін өзі жасала ма?** Жоқ. Қайта жіберу — сіздің міндетіңіз.

**Тәуліктік қорғанысқа тірелдім, қалай алып тастауға болады?** Ол уақыт өтісімен өзі босайды. Бірақ алдымен себебін табыңыз: көп жағдайда кодта цикл бар.

**Бір мезетте қанша счёт жіберуге болады?** Нақты сан жоқ: шектеу жағдайға байланысты. Практикада бір-бірден, арасында шағын кідіріспен жіберу ешқашан мәселе тудырмайды.

**Бірнеше кассир қоссам, жылдамдық артады ма?** Жүктеме бөлінеді, бірақ бұл негізгі шешім емес. Алдымен жіберу логикасын реттеңіз.

**Мәселе менде емес, Kaspi-де деп қалай білемін?** Екі минуттық диагностика бар: [Мәселе менде ме, Kaspi-де ме](/kb/is-it-us-or-kaspi).
