Как на самом деле работает подписка (и почему это удобно покупателю)
Аналогия для трека «на пальцах»: подписка ApiPay — это не «рука в кармане покупателя», а пунктуальный бухгалтер, который каждый месяц сам выписывает счёт и вежливо напоминает, если его не оплатили.
Цикл выглядит так:
- Вы создаёте подписку: номер телефона покупателя + период + сумма.
- В расчётную дату система сама создаёт обычный счёт Kaspi — покупателю приходит push в приложение Kaspi.
- Покупатель нажимает «Оплатить» — деньги, как всегда, идут напрямую на ваш Kaspi-счёт. Вам приходит вебхук
subscription.payment_succeeded. - Дата следующего выставления сдвигается на период — и так по кругу.
Для покупателя это прозрачно и не страшно: никто ничего не списывает без его ведома, каждый платёж он подтверждает сам. Для вас — предсказуемо: не нужно помнить про «выставить клиенту счёт первого числа», и не нужно писать свой планировщик.
Создание подписки через API
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,
"description": "Абонемент, тариф Стандарт"
}'
Параметры и границы: billing_period — daily | weekly | biweekly | monthly | quarterly | yearly; amount — от 100 до 1 000 000 ₸; billing_day — 1–28 (чтобы дата существовала в любом месяце); max_retry_attempts — 1–10 (по умолчанию 3); retry_interval_hours — 1–168 (по умолчанию 24); grace_period_days — 1–30 (по умолчанию 3); bill_immediately — выставить первый счёт сразу при создании.
Важная деталь, о которую спотыкаются: ответ 201 на POST /subscriptions означает «подписка создана», а не «покупателю уже ушёл счёт». Если вы ждёте немедленный push покупателю — передайте bill_immediately: true. Управление: POST /subscriptions/{id}/pause | resume | cancel, счета подписки — GET /subscriptions/{id}/invoices.
Что происходит, когда покупатель не платит?
Здесь подписка отрабатывает за вас всю рутину «дожима»:
- Счёт не оплачен (истёк через 24 часа или отменён) → вам приходит
subscription.payment_failedс причиной (Invoice expired/Invoice cancelled) и номером попытки. - Система сама перевыставляет счёт — по умолчанию до 3 попыток с интервалом 24 часа. Вручную ничего пересоздавать не нужно (и не надо: получите два параллельных счёта).
- Попытки исчерпаны → начинается grace-период (
subscription.grace_period_started, по умолчанию 3 дня): доступ покупателю вы пока не отключаете, а любая его оплата немедленно возвращает подписку в норму. - Grace истёк →
subscription.expired: биллинг по этой подписке остановлен навсегда.
После expired реактивации не существует — ни кнопки, ни метода API. Покупатель вернулся через месяц? Создайте новую подписку. Это осознанное правило: «воскрешение» старой подписки означало бы споры о пропущенных периодах и неожиданные счета.
Пауза устроена мягче: pause останавливает выставление, resume продолжает от текущего момента — пропущенные периоды не доначисляются, покупателю не прилетит «счёт за три месяца тишины». cancel — безвозвратен, как и expired.
События subscription.* для интеграции
Вебхуки подписок приходят на тот же URL, что и счета (настройка — «Настройка вебхуков ApiPay»):
| Событие | Когда | Доп. поля (в корне payload) |
|---|---|---|
subscription.created |
Подписка создана | — |
subscription.payment_succeeded |
Счёт подписки оплачен | invoice_id, amount, paid_at |
subscription.payment_failed |
Счёт истёк/отменён | invoice_id, amount, reason, attempt_number |
subscription.grace_period_started |
Ретраи исчерпаны | grace_period_days, expires_at |
subscription.expired |
Grace истёк, биллинг остановлен | — |
subscription.paused / resumed / cancelled |
Соответствующие действия | — |
Три инженерных нюанса (трек D):
- Дедупликация обязательна: у событий подписок нет защиты от дублей — дедуплицируйте по
(event, subscription.id, invoice_id). - Ретраев в логах не ищите: события
subscription.*не попадают в webhook-логи кабинета и не имеют ручного перезапуска — отвечайте200быстро и обрабатывайте асинхронно. - Известное ограничение: если счёт подписки завершился статусом
error(например,client_not_found— у номера нет Kaspi), это не считается провалом оплаты:payment_failedне придёт, ретрая не будет, период будет пропущен. Отлавливайте такие случаи поinvoice.status_changedсо статусомerrorдля счетов подписки.
Частые ошибки
- Обещать клиентам «автосписание как у Netflix». Не обещайте: покупатель подтверждает каждый платёж сам. Честная формулировка — «счёт будет приходить автоматически, оплата в один клик».
- Пересоздавать счёт вручную после
payment_failed. Система сама ретраит по расписанию — ручной счёт даст дубль. - Ждать
payment_failedпри счёте в статусеerror. Не придёт (известное ограничение) — мониторьтеerror-счета подписок отдельно. - Пытаться реактивировать
expired-подписку. Невозможно by design — создайте новую. - Ставить
billing_day: 29–31. API не примет: допустимы 1–28, чтобы дата существовала в феврале. - Тестировать подписки в песочнице без организации. Подпискам нужна верифицированная организация — API ответит
400 Subscriptions require a verified organization.
Вопросы и ответы
Почему нельзя сделать настоящее автосписание?
Оплата по номеру телефона в Kaspi всегда подтверждается самим покупателем: списание без подтверждения доступно только в карточном эквайринге с токенизацией карты. Поэтому подписка ApiPay выставляет счёт, а покупатель подтверждает оплату сам.
Чем подписка отличается от разового счёта?
Разовый счёт вы создаёте сами каждый раз. Подписка создаёт такие же счета автоматически по расписанию, сама ретраит неоплату, ведёт grace-период и шлёт события subscription.*.
Покупатель оплатил в grace-периоде — что дальше?
Подписка возвращается в нормальный цикл: grace снимается, следующий счёт придёт по обычному расписанию.
Можно ли изменить сумму действующей подписки?
Да, через PUT /subscriptions/{id} — изменения применяются к будущим счетам.
Как поставить подписку на паузу на время отпуска клиента?
POST /subscriptions/{id}/pause, затем resume — выставление продолжится от текущей даты, пропущенные периоды не доначисляются.
Подписка умерла (expired) — я потеряю историю?
Нет: подписка и все её счета остаются в кабинете и API. Остановлен только будущий биллинг — для продолжения создайте новую подписку.