# Спортзал мен фитнеске

> Абонемент сату, мерзімді ұзарту, ай сайынғы счётты жазылыммен автоматтандыру, мұздату мен қайтару және турникетті webhook арқылы ашу — фитнес клубқа арналған толық сценарий.

## Қысқаша

Спортзалда үш түрлі төлем бар: **бір реттік кіру**, **абонемент сату** және **ай сайын қайталанатын төлем**. Алғашқы екеуі — қарапайым счёт: ресепшенде QR көрсетесіз немесе клиенттің телефонына счёт жібересіз. Үшіншісіне [жазылым](/kb/subscriptions-api) бар: ол счётты кестеге сай өзі шығарады. Бірақ бірден келісіп алайық — **жазылым ақшаны клиенттің картасынан өздігінен алмайды**, ол тек счёт шығарады, клиент әр төлемді Kaspi қосымшасында өзі растайды.

## Абонемент сату

Клиент ресепшенге келді, үш айлық абонемент алғысы келеді.

1. Әкімші CRM-де немесе есеп жүйесінде абонемент карточкасын жасайды.
2. Жүйе счёт шығарады:

```json
POST /api/v1/invoices
{
  "amount": 45000,
  "kind": "qr",
  "description": "Абонемент 3 ай, ересек",
  "externalOrderId": "ABN-2026-1187",
  "metadata": { "client_id": "1187", "plan": "3m", "starts": "2026-09-15" }
}
```

3. Ресепшендегі экранда QR шығады, клиент сканерлеп төлейді.
4. `invoice.paid` webhook келеді — жүйе абонементті белсенді етеді, картаны/браслетті береді.

**Абонементті `invoice.paid` келгенде ғана белсендіріңіз**, счёт жасалған сәтте емес. Әйтпесе төлемей кетіп қалған клиент те залға кіріп жүреді.

Клиент қашықтан сатып алса (Instagram, WhatsApp, сайт) — `kind: "phone"` жіберіңіз, оның Kaspi-іне push келеді. Телефон `7XXXXXXXXXX` пішімінде, `description` 60 таңбадан аспасын.

## Мерзімді ұзарту

Ұзарту — жаңа счёт, ерекше API әдісі жоқ. Маңыздысы — **қашан жіберу** және **не жазу**.

| Не | Қалай |
|---|---|
| Ескерту | Аяқталуына 3-5 күн қалғанда счёт жіберіңіз |
| Түрі | `kind: "phone"` — клиент залда емес |
| Сипаттама | «Абонемент ұзарту, қазан» — не үшін төлейтіні түсінікті болсын |
| Байланыс | `externalOrderId` ішіне ұзарту нөмірін, `metadata` ішіне `client_id` мен жаңа мерзімді жазыңыз |
| Төленбесе | Мерзім біткен соң абонементті жабасыз, кейін клиент келгенде жаңа счёт |

Ұзарту счётының мерзімі шектеулі: QR терезесі шамамен үш минут, телефонға счёт та мәңгі тұрмайды. Сондықтан «айдың басында бәріне жіберіп қою» жұмыс істемейді — [жазылым](/kb/subscriptions-api) дұрысырақ, ол әр клиентке өз күнінде жаңа счёт шығарады.

## Ай сайынғы төлемді жазылымға беру

Жазылым — бұл **счётты кестеге сай автоматты шығару**. Клиенттің картасынан ақша өздігінен алынбайды, әр төлемді ол өзі растайды. Бұл маңызды айырмашылық: клиент кез келген айда «төлемеймін» деп шеше алады, сіз оны зорлай алмайсыз.

Не баптайсыз:

- **Аралық**: `month`, кратность `every: 1` — ай сайын. Апталық форматтар үшін `week` бар.
- **Қайталау сатысы** `retryDelaysMin`, әдепкі `[15, 60, 360]` минут. Клиент жұмыста болып, счётты көрмей қалса, 15 минуттан кейін, сосын бір сағаттан кейін, сосын алты сағаттан кейін қайта шығады. Ең көбі 5 мән. Фитнеске әдепкі жарайды, тым жиі қайталау клиентті мазалайды.
- **Өткізіп алу саясаты** `misfirePolicy`: `run_once` (әдепкі) немесе `skip`, `misfireAfterMin` әдепкі 1440. Жүйе бірнеше сағат жұмыс істемей қалса, кезек не бір рет жіберіледі, не тасталады.
- Саты біткенде сол айдың кезегі тасталады, кесте келесі айға жалғасады. `failedRuns`, `lastError`, `lastRunStatus` өрістерінен көресіз.

Клиент мұздатуға кетсе — жазылымды `pause` етесіз. Қайтып келгенде `resume`. Өткізіп қалған кезекті бірден жіберу керек болса, `POST /subscriptions/{id}/resume` `{ catchUp: true }`.

Толығырақ: [Жазылым API](/kb/subscriptions-api) және [Жазылымдық бизнеске](/kb/for-subscription-business).

## Мұздату мен қайтару

**Мұздату.** Клиент екі аптаға кетті. Абонементтің мерзімін өз жүйеңізде жылжытасыз, жазылымы бар болса — `pause`. Qut Pay жағында ештеңе қайтарудың қажеті жоқ, ақша қозғалмайды.

**Қайтару.** Клиент абонементті мүлде қайтарғысы келсе:

```json
POST /api/v1/invoices/{id}/refund
{ "amount": 30000, "reason": "Абонемент қайтарылды, 1 ай пайдаланылды" }
```

`amount` жазбасаңыз толық қайтарылады. Ішінара қайтарғанда счёт `partially_refunded` күйіне өтеді. Есептеуді өзіңіз жасайсыз: пайдаланған күндерін шегеріп, қалғанын қайтарасыз — Qut Pay пропорция есептемейді.

Бір счётты екі рет қайтарып жіберуден сақтаныңыз: [Екі рет қайтарып жіберуден қалай сақтану керек](/kb/double-refund). Қайтару өтпей жатса: [Қайтару өтпей жатыр](/kb/refund-not-working).

## Турникетпен байланыстыру

Бір реттік кіруді автоматтандыруға болады: клиент QR-ды сканерлеп төлейді, турникет ашылады.

Реті:

1. Турникет алдындағы экран (планшет, шағын монитор) сіздің серверіңізден счёт сұрайды: `amount: 2500`, `metadata: { "gate": "north" }`.
2. Экранда `qrImageUrl` көрінеді.
3. Клиент төлейді.
4. Сіздің серверіңізге `invoice.paid` webhook келеді. Ол `metadata.gate` бойынша қай турникет екенін біліп, контроллерге «аш» сигналын жібереді.

Webhook-ты міндетті түрде тексеріңіз: `HMAC-SHA256(secret, timestamp + "." + rawBody)`, денені **өзгертілмеген байт күйінде**, JSON-ға айналдырғанға дейін. Бұл әсіресе физикалық есік ашатын жерде маңызды: [Webhook қауіпсіздігі](/kb/webhook-security).

Өңдеуіңіз идемпотентті болсын — webhook 2xx алмаса 11 рет қайталанады, турникет он бір рет ашылмауы керек. `(invoice.id, status)` жұбын сақтап, қайталанғанын елемеңіз.

Кідіріске сезімтал жер болғандықтан, webhook-пен қатар күйді сұрап та тұрыңыз: [Webhook пен күйді сұрау](/kb/polling-vs-webhook). Дәл осындай схема тұрақтарда да қолданылады: [Тұрақ пен шлагбаумға](/kb/for-parking).

## Кодсыз нұсқасы

- **Тұрақты төлем сілтемелері.** Әр тарифке бір сілтеме: «1 ай», «3 ай», «жеке жаттықтырушы». Оларды Instagram профиліне, WhatsApp жауаптарына, ресепшендегі тақтайшаға қоясыз: [Төлем сілтемелері](/kb/payment-links).
- **Кабинеттен қолмен счёт.** Әкімші ресепшенде сома мен сипаттаманы жазып, QR шығарады.
- **Telegram бот.** `/invoice 45000 Абонемент 3 ай`, `/today` — күндік қорытынды, `/last` — соңғы счёттар. Топқа қосып, бүкіл ауысымды көріп отыруға болады.
- **Жазылымды кабинеттен** де жасауға болады, API жазбай.

## Ерекше ескертулер

- **«Ақша автоматты алынады» деп уәде бермеңіз.** Жазылым тек счёт шығарады. Клиентке де солай түсіндіріңіз, әйтпесе «неге тағы растау керек» деген сұрақ туады.
- **Кассир нөмірімен Kaspi Pay қосымшасына кірмеңіз** — байланыс үзіледі де, ресепшенде счёт шығару тоқтайды: [Кассир байланысы үзілді](/kb/connection-lost).
- **Кеш келген төлемді ұмытпаңыз.** `expired` счётқа ақша кейін келсе, `invoice.paid` `late: true` белгісімен келеді. Абонементті беріңіз немесе ақшаны қайтарыңыз: [Кеш келген төлем](/kb/late-payment).
- **Тарифті клиент саны бойынша емес, счёт саны бойынша есептеңіз.** 300 жазылымшы + ұзартулар + бір реттік кірулер — айына 800-ден асып кетуі мүмкін: [Қай тарифті таңдау керек](/kb/tariff-choose).

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

**Жазылым қосылса, клиент әр ай сайын растауы керек пе?** Иә. Жазылым счётты шығарады, төлемді клиент Kaspi қосымшасында өзі растайды. Ақша өздігінен алынбайды.

**Клиент бір айды өткізіп жіберсе не болады?** Қайталау сатысы (әдепкі 15, 60, 360 минут) біткенде сол айдың кезегі тасталады, кесте келесі айға жалғасады. Абонементті жабу-жаппауды өз жүйеңіз шешеді.

**Мұздатқанда ақша қайтару керек пе?** Жоқ. Мұздату — мерзімді жылжыту, ол сіздің есеп жүйеңізде болады. Жазылымды `pause` етіп қоясыз.

**Турникетті ашу қанша уақыт алады?** Клиент төлегеннен кейін webhook әдетте 5 секунд ішінде келеді. Бұл Kaspi мен желіге байланысты, кепілдік бере алмаймыз — сондықтан webhook-пен қатар күйді сұрап та тұрыңыз.

**Екі залым бар, есепті бөлуге бола ма?** Иә: әр залға бөлек API кілт беріп, әрқайсысын өз кассиріне байлайсыз: [Нүктелер бойынша бөлек есеп](/kb/multi-point-reporting).
