> Источник: https://apipay.kz/recurring-payments-kaspi · apipay.kz
> ApiPay — независимый сервис приёма платежей поверх вашего Kaspi Pay.

# Рекуррентные платежи и подписки через Kaspi Pay

**TL;DR.** Автосписания с карты, как у классической банковской подписки, в Kaspi Pay нет: оплату по номеру телефона всегда подтверждает сам покупатель в приложении Kaspi. Подписка ApiPay — это **авто-выставление**: в расчётную дату система сама создаёт обычный счёт Kaspi на номер клиента, тот получает push и нажимает «Оплатить». Настраивается один раз — дальше счета уходят по расписанию, при неоплате перевыставляются, а пауза и отмена делаются одним запросом к API. Подходит там, где клиент платит по кругу за одинаковый период: абонементы, онлайн-школы, аренда, SaaS, членские взносы. Деньги приходят напрямую на ваш Kaspi-счёт — ApiPay только выставляет счета поверх вашего Kaspi Pay.

## Как работает подписка

1. Вы создаёте подписку: номер телефона клиента, сумма (или корзина позиций каталога) и период.
2. В расчётную дату система сама создаёт **обычный счёт Kaspi** — клиенту приходит push в приложение Kaspi.
3. Клиент нажимает «Оплатить». Деньги идут напрямую на ваш Kaspi-счёт, вам приходит вебхук `subscription.payment_succeeded`.
4. Дата следующего выставления сдвигается на период — и так по кругу.

Каждое списание — это счёт, который клиент подтверждает одним нажатием. Для продавца это работает как подписка: настроили один раз, дальше ручной работы каждый месяц нет.

**Подписки B2B через Kaspi Pay** устроены так же, как для розницы. Но учтите, кто платит: счёт подписки уходит на номер телефона и подтверждается в личном приложении Kaspi — со стороны компании-клиента платит конкретный человек, а не расчётный счёт юрлица. Если контрагенту нужна оплата банковским переводом, подписка Kaspi Pay эту задачу не закрывает.

## Расписание и параметры

Подписка создаётся одним запросом `POST /api/v1/subscriptions`.

| Параметр | Описание |
|---|---|
| `phone_number` | Номер телефона клиента в формате `8XXXXXXXXXX` |
| `amount` либо `cart_items` | Сумма счёта либо корзина позиций каталога |
| `billing_period` | `daily`, `weekly`, `biweekly`, `monthly`, `quarterly`, `yearly` |
| `billing_day` | День списания (опционально): у `monthly`, `quarterly`, `yearly` — число месяца 1–28; у `weekly` и `biweekly` — день недели, 1 — понедельник … 7 — воскресенье |
| `billing_day_from_end` | Опора от конца месяца вместо `billing_day`: `0` — последний день месяца, `1` — предпоследний. Только для `monthly`, `quarterly`, `yearly`; вместе с `billing_day` вернёт `422` |
| `billing_time` | Час выставления `ЧЧ:ММ` по времени Алматы, окно 06:00–22:00. Не передан — счёт уходит в 13:00 |
| `first_billing_at` | Дата первого счёта (`ГГГГ-ММ-ДД`, календарь Алматы). Несовместим с `bill_immediately` |
| `bill_immediately` | Выставить первый счёт сразу при создании; иначе первое списание идёт по расписанию |
| `total_cycles` | 1–600, ограничение по числу **оплат**: неоплаченная попытка цикл не расходует |
| `description` | До 60 символов — Kaspi показывает покупателю только их |

Сумма счёта подписки — от 100 до 1 000 000 ₸ и только целые тенге. Числом больше 28 день месяца не задаётся: 29-го, 30-го и 31-го есть не в каждом месяце — для конца месяца используется `billing_day_from_end`.

Ответ `201` означает «подписка создана», а не «клиенту уже ушёл счёт»: немедленный push даёт только `bill_immediately: true`.

## Повторные попытки, льготный период и завершение

Разовая неоплата подписку не обрывает:

1. **Счёт не оплачен** (истёк или отменён) — приходит `subscription.payment_failed` с причиной и номером попытки.
2. **Система сама перевыставляет счёт** — до `max_retry_attempts` раз (1–10, по умолчанию 3) с интервалом `retry_interval_hours` (1–168, по умолчанию 24 часа). Вручную пересоздавать не нужно — получите два параллельных счёта. Исключение одно: истёкший счёт перевыставляется сразу — покупатель уже израсходовал весь срок его жизни, и интервал к нему не применяется.
3. **Попытки исчерпаны** — начинается льготный период `grace_period_days` (1–30, по умолчанию 3 дня): новые счета не выставляются, любая оплата немедленно возвращает подписку в норму.
4. **Льготный период истёк** — `subscription.expired`: биллинг по этой подписке остановлен навсегда. Реактивации не существует — если клиент вернулся, создайте новую подписку. Подписка и все её счета остаются в кабинете и API.

Отдельно от этой лестницы стоит **явный отказ клиента**. Отдельной кнопки «отписаться» у него нет — он отклоняет очередной счёт в приложении Kaspi. Явный отказ отменяет подписку сразу: приходит `subscription.cancelled` с `reason: payer_refused`, ретраев и льготного периода не будет. Нехватка денег отказом не считается — там идут обычные повторы.

## Управление подпиской

| Метод | Endpoint | Действие |
|---|---|---|
| `POST` | `/subscriptions` | Создать подписку |
| `POST` | `/subscriptions/{id}/pause` | Поставить на паузу — выставление замирает |
| `POST` | `/subscriptions/{id}/resume` | Возобновить |
| `POST` | `/subscriptions/{id}/cancel` | Отменить, безвозвратно |
| `GET` | `/subscriptions/{id}/invoices` | Счета подписки |

`resume` продолжает выставление **от текущего момента**: пропущенные за паузу периоды не доначисляются, клиенту не прилетит «счёт за три месяца тишины». Отменённую подписку возобновить нельзя — создайте новую.

## События подписки

Помимо обычных invoice-вебхуков по каждому выставленному счёту, ApiPay присылает события жизненного цикла на тот же URL:

| Событие | Когда |
|---|---|
| `subscription.created` | Подписка создана |
| `subscription.payment_succeeded` | Счёт подписки оплачен |
| `subscription.payment_failed` | Счёт истёк, отменён или ушёл в ошибку |
| `subscription.grace_period_started` | Ретраи исчерпаны, начался льготный период |
| `subscription.expired` | Льготный период истёк либо закончились оплаты `total_cycles` |
| `subscription.cancelled` | Отмена вашим запросом либо явным отказом клиента |
| `subscription.paused` / `subscription.resumed` | Соответствующие действия |

У событий подписок нет защиты от дублей — дедуплицируйте по `(event, subscription.id, invoice_id)` и отвечайте `200` быстро, обрабатывая асинхронно.

## Пример: создание подписки

```javascript
const response = await fetch('https://api.apipay.kz/api/v1/subscriptions', {
  method: 'POST',
  headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    phone_number: '87001234567',
    amount: 15000,
    billing_period: 'monthly',
    description: 'Абонемент, ежемесячно'
  })
})
const subscription = await response.json()
```

С расписанием:

```bash
curl -X POST "https://api.apipay.kz/api/v1/subscriptions" \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "87001234567",
    "amount": 15000,
    "billing_period": "monthly",
    "billing_day": 1,
    "billing_time": "13:00",
    "total_cycles": 12,
    "description": "Абонемент, тариф Стандарт"
  }'
```

## Где пригодятся подписки

- Фитнес-абонементы — ежемесячная оплата зала или студии
- Онлайн-школы и курсы — доступ к урокам с помесячной оплатой
- Аренда — регулярные платежи за помещение, технику или оборудование
- SaaS-подписки — доступ к сервису или программе по подписке
- Клубы и сообщества — членские взносы и закрытые сообщества

## Ограничения

- **Автосписания нет.** Оплату по номеру телефона в Kaspi всегда подтверждает сам покупатель. Клиентам это формулируется честно: «счёт будет приходить автоматически, оплата в один клик».
- **Платит человек, а не юрлицо.** Счёт подписки уходит на номер телефона и подтверждается в личном приложении Kaspi.
- **Нужна верифицированная организация.** Без неё API отвечает `400 Subscriptions require a verified organization`.
- **До одобрения анкеты о бизнесе боевых счетов ноль.** Маршрут такой: регистрация → анкета «Расскажите о вашем бизнесе» → подключение кассира → боевые счета. Пока анкета не одобрена, боевой счёт не выставится ни один — `429 kyc_daily_limit_reached` с `meta.limit = 0`. Песочница доступна сразу после регистрации, анкета для неё не нужна.
- **Свой тариф ApiPay тоже гейт.** Если тариф не оплачен (`403 tariff_inactive`) или исчерпан дневной лимит счетов (`429 tariff_limit_reached`), цикл пропускается молча: счёт не выставляется, дата следующего списания не сдвигается, вебхука об этом нет. Пропущенные за простой периоды сгорают — когда причина уходит, подписка выставляет один счёт за текущий период. Если списания стояли дольше недели, запустить их снова должен человек в кабинете, в разделе «Автоплатежи».
- **`expired` необратим.** Ни кнопки, ни метода API для реактивации нет.
- **Сломанная позиция корзины не откладывает списание, а тратит попытки.** Если в корзине подписки стоит позиция с `sellable: false` (`status: failed` при `operation: create`), счёт не выставится, и прогон засчитается неудачным: растут `failed_attempts`, дальше ретраи, льготный период и `expired`. Починить позицию нужно до исчерпания `max_retry_attempts` — работу по строке возобновляет `PATCH /catalog/{id}`, в кабинете — кнопка «Повторить».

## Сколько это стоит

ApiPay не берёт процент с оборота — фиксированная подписка независимо от суммы и числа платежей:

| Тариф | Цена | Счета в день |
|---|---|---|
| Старт | 10 000 ₸/мес | до 30 |
| Бизнес | 25 000 ₸/мес | до 100 |
| Про | 60 000 ₸/мес | до 300 |
| Про Макс | 90 000 ₸/мес | до 600 |

Больше 600 счетов в день — договорная цена. Kaspi удерживает свою обычную комиссию за приём платежа по вашим условиям Kaspi Pay — она не связана с ApiPay.

## Частые вопросы

**Можно ли настроить автосписание с карты через Kaspi?**
Нет — Kaspi не списывает деньги с карты сам. Вместо этого ApiPay автоматически выставляет счёт каждый период, а клиент подтверждает оплату привычным приложением Kaspi.

**Что будет, если у клиента не хватило денег?**
Счёт перевыставляется автоматически — по умолчанию до 3 раз с интервалом 24 часа. После исчерпания попыток подписка остаётся активной ещё 3 дня (льготный период). Любая успешная оплата его снимает.

**Можно ли поставить подписку на паузу?**
Да: `POST /subscriptions/{id}/pause`, `/resume`, `/cancel`. Пока подписка на паузе, новые счета не выставляются.

**Во сколько клиенту приходит счёт?**
По умолчанию в 13:00 по времени Алматы; час задаётся полем `billing_time` в окне 06:00–22:00.

**Можно ли изменить сумму действующей подписки?**
Да, через `PUT /subscriptions/{id}` — изменения применяются к будущим счетам. Дата первого счёта и `bill_immediately` задаются только при создании.

## Куда дальше

- Документация REST API: https://apipay.kz/apipay-api-docs.md
- Коды ошибок: https://apipay.kz/errors.md
- Обзор Kaspi Pay REST API: https://apipay.kz/kaspi-api.md
- Как подключить Kaspi Pay к сайту: https://apipay.kz/kaspi-pay-integration.md
- Главная: https://apipay.kz/index.md

Гайды базы знаний:

- Подписки ApiPay — есть ли автосписание: https://apipay.kz/guides/podpiski-apipay.md
- Настройка вебхуков: https://apipay.kz/guides/nastroyka-webhookov-apipay.md
- Счёт по номеру: https://apipay.kz/guides/kak-sozdat-schet-kaspi-po-nomeru.md
- Каталог, корзина и нацкаталог: https://apipay.kz/guides/katalog-korzina-nackatalog.md
- Анкета о бизнесе и лимит: https://apipay.kz/guides/anketa-o-biznese-i-limit.md
- Лимит счетов по тарифу: https://apipay.kz/guides/limit-schetov-po-tarifu.md
- Тарифы и комиссия: https://apipay.kz/guides/tarify-i-komissiya-apipay.md
- Подключение кассира Kaspi: https://apipay.kz/guides/podklyuchenie-kassira-kaspi.md
- Песочница и рабочий режим: https://apipay.kz/guides/pesochnitsa-i-rabochiy-rezhim.md

---

ApiPay — независимый сервис и не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.
