# Жазылым API — кестеге сай счёт шығару

> Жазылым счётты кестеге сай автоматты шығарады, ал әр төлемді клиент өзі растайды. Барлық өрістер, аралықтар, қайталау сатысы, өткізіп алу саясаты, pause/resume/run әдістері және күй өрістері.

## Қысқаша

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

Ең қысқа мысал:

```http
POST /api/v1/subscriptions
X-API-Key: qp_live_…
Content-Type: application/json

{
  "amount": 9900,
  "interval": "month",
  "every": 1,
  "kind": "phone",
  "customer": { "phone": "77011234567", "name": "Айдана" }
}
```

Кілтте **`subscriptions:manage`** құқығы болуы керек.

## Не автоматтандырылады, не автоматтандырылмайды

| Автоматты | Қолмен / клиент арқылы |
|---|---|
| Счётты кестеге сай жасау | Клиент әр счётты өзі төлейді |
| Сәтсіз әрекетті қайталау | Соманы өзгерту — жазылымды өңдеу арқылы |
| Өткізіп алған кезекті өңдеу | Тоқтату туралы шешім |
| Оқиға жіберу (webhook) | Ақшаны қайтару |

Сондықтан жазылым — есте сақтайтын және уақытында счёт шығаратын кесте, ақша шешіп алатын механизм емес.

## Жасау: барлық өрістер

| Өріс | Тип | Сипаттама |
|---|---|---|
| `amount` | number, міндетті | Бір кезеңнің сомасы, теңге |
| `interval` | `day` \| `week` \| `month` | Аралық бірлігі |
| `every` | integer | Кратность: `interval: month`, `every: 3` — тоқсан сайын |
| `kind` | `qr` \| `phone` | Счёт түрі. `phone` — клиенттің Kaspi қосымшасына |
| `customer.phone` / `.name` / `.email` | string | `kind: phone` болса телефон міндетті |
| `dayOfMonth` | integer | Айдың қай күні счёт шығады |
| `startAt` | date-time | Бірінші кезек қашан |
| `maxRuns` | integer | Ең көп орындалу саны. Жеткенде жазылым жабылады |
| `retryDelaysMin` | number[] | Қайталау сатысы, минут. Әдепкі `[15, 60, 360]`, ең көбі 5 мән |
| `misfirePolicy` | `run_once` \| `skip` | Өткізіп алған кезекпен не істеу. Әдепкі `run_once` |
| `misfireAfterMin` | integer | Қанша минуттан кейін кезек «өткізіп алынды» саналады. Әдепкі 1440 |
| `description` | string | Клиент счётта көреді |
| `metadata` | object | Кез келген JSON, счётқа көшеді |

Жасалған әр счёт `metadata.subscriptionId` және `metadata.run` өрістерін алып жүреді — есепте қай жазылымның қай кезегі екенін осыдан білесіз.

## Қайталау сатысы

Счёт шығару сәтсіз болуы мүмкін: Kaspi жауап бермеді, кассир байланысы үзілді, тариф лимиті бітті. Ондайда жазылым `retryDelaysMin` сатысы бойынша қайталайды.

Әдепкі саты `[15, 60, 360]` — 15 минуттан кейін, сосын 1 сағаттан кейін, сосын 6 сағаттан кейін.

| Мән | Не болады |
|---|---|
| `[15, 60, 360]` | Үш қайталау: 15 мин, 1 сағ, 6 сағ |
| `[5, 5, 5, 5, 5]` | Бес қайталау, әрқайсысы 5 минут сайын (ең көп мән саны — 5) |
| `[]` | Қайталау жоқ, бірінші сәтсіздікте кезек тасталады |

Саты біткенде сол кезек **тасталады**, бірақ жазылым жабылмайды — кесте келесі кезеңмен жалғаса береді. Сәтсіздік туралы Telegram немесе email хабарламасы келеді.

## Өткізіп алу саясаты

Кезек уақытында орындалмай қалуы мүмкін (жазылым тоқтатылып тұрды, ұзақ үзіліс болды). Сонда не істеу керегін `misfirePolicy` шешеді:

| Мән | Мінезі |
|---|---|
| `run_once` (әдепкі) | Кешіккен кезек бір рет орындалады, бірнеше өткізілген кезек біреуге жиналады |
| `skip` | `misfireAfterMin`-нен (әдепкі 1440 минут = 1 тәулік) кеш болса, кезек өткізіледі, кесте келесіге көшеді |

Клиенттен кешіккен төлемді сұрағыңыз келсе — `run_once`. Ескірген счёт клиентті шатастырады десеңіз — `skip`.

## Басқару әдістері

| Әдіс | Не істейді |
|---|---|
| `POST /api/v1/subscriptions` | Жаңа жазылым жасайды |
| `GET /api/v1/subscriptions` | Тізім |
| `GET /api/v1/subscriptions/{id}` | Біреуі, толық күйімен |
| `PATCH /api/v1/subscriptions/{id}` | Сома, аралық, саясат сияқты өрістерді өзгертеді |
| `POST /api/v1/subscriptions/{id}/pause` | Кестені тоқтатады |
| `POST /api/v1/subscriptions/{id}/resume` | Қайта қосады |
| `POST /api/v1/subscriptions/{id}/cancel` | Біржола жабады |
| `POST /api/v1/subscriptions/{id}/run` | Кезектен тыс бір счёт шығарады |

`resume` әдісінің бір нюансы бар:

```json
POST /api/v1/subscriptions/sub_9Qp1/resume
{ "catchUp": true }
```

`catchUp: true` десеңіз, тоқтап тұрған кезде өткізіп алған кезек **бірден** орындалады. Әдепкіде (`catchUp` жоқ немесе `false`) кесте жай ғана келесі жоспарлы күннен жалғасады.

## Күй өрістері

`GET /api/v1/subscriptions/{id}` жауабында жазылымның қазіргі жағдайы көрінеді:

| Өріс | Мағынасы |
|---|---|
| `nextRunAt` | Келесі счёт қашан шығады |
| `runs` | Қанша кезек орындалды |
| `failedRuns` | Қанша кезек сәтсіз аяқталды |
| `retryAttempt` | Ағымдағы кезек бойынша нешінші қайталау жүріп жатыр |
| `lastRunStatus` | `ok` \| `retrying` \| `failed` \| `skipped` |
| `lastError` | Соңғы қатенің коды |
| `lastInvoiceId` | Соңғы жасалған счёт |

Жазылым бойынша счёт шықпай жатса, диагностиканы осы өрістерден бастаңыз: `lastRunStatus` пен `lastError` себебін бірден көрсетеді. Толығырақ реті: [Жазылым бойынша счёт шықпай жатыр](/kb/subscription-not-running).

## Кассир

Жазылым **қай кассир арқылы жасалғанын есте сақтайды** және кейін де сол кассир арқылы счёт шығарады. Осыдан екі салдар шығады:

- Сол кассирдің байланысы үзілсе, жазылым счёттары сәтсіз болады (`kaspi_session_expired`) және қайталау сатысына түседі. Байланысты қалпына келтіргенде кесте өздігінен жалғасады.
- Кассирді ауыстырсаңыз, ескі жазылымдарды қайта құру керек — олар ескі байланысқа тіркелген күйінде қалады.

## Қате кодтары

| Код | HTTP | Не болды |
|---|---|---|
| `invalid_interval` | 422 | `interval` `day`, `week` немесе `month` емес |
| `invalid_every` | 422 | `every` оң бүтін сан емес |
| `invalid_retry` | 422 | `retryDelaysMin` дұрыс емес: 5 мәннен көп немесе теріс сан |
| `invalid_misfire` | 422 | `misfirePolicy` `run_once` не `skip` емес |
| `invalid_max_runs` | 422 | `maxRuns` дұрыс емес |
| `subscription_closed` | 409 | Жазылым аяқталған немесе тоқтатылған |
| `subscription_not_found` | 404 | Идентификатор табылмады |
| `insufficient_scope` | 403 | Кілтте `subscriptions:manage` жоқ |

## Оқиғалар

`subscription.created` — жазылым жасалды, `subscription.status` — күйі өзгерді. Одан бөлек, жазылым шығарған әр счёт бойынша әдеттегі `invoice.created`, `invoice.paid` және басқа оқиғалар келеді. Клиенттің төлегенін `invoice.paid` арқылы біліп, `metadata.subscriptionId` бойынша қай жазылымға тиесілі екенін анықтайсыз.

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

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

**Соманы өзгертуге бола ма?** Иә, `PATCH` арқылы. Жаңа сома келесі кезектен бастап қолданылады.

**QR ма, phone ба — қайсысын таңдаған жөн?** Жазылымға көбіне `phone` ыңғайлы: счёт клиенттің Kaspi қосымшасына келеді. Айырмашылығы: [QR счёт пен телефонға счёт](/kb/qr-vs-phone).

**Жазылым бойынша ақшаны қайтаруға бола ма?** Иә, әдеттегідей — қайтару жеке счёт бойынша жасалады: [Қайтару API](/kb/refunds-api).

**Sandbox-та сынауға бола ма?** Иә. Жазылым жасап, `run` арқылы кезектен тыс счёт шығарып, төлемді симуляциялап көріңіз.
