Коротко
В зале есть три разных платежа: разовый вход, продажа абонемента и ежемесячный повторяющийся платёж. Первые два — обычный счёт: показали QR на ресепшене или отправили счёт на телефон клиента. Для третьего есть подписки: они выставляют счёт по расписанию. Сразу договоримся о главном — подписка не снимает деньги с карты сама, она только выставляет счёт, а каждую оплату клиент подтверждает в приложении Kaspi.
Продажа абонемента
Клиент пришёл на ресепшен и хочет абонемент на три месяца.
- Администратор заводит карточку абонемента в CRM или учётной системе.
- Система выставляет счёт:
POST /api/v1/invoices
{
"amount": 45000,
"kind": "qr",
"description": "Абонемент 3 месяца, взрослый",
"externalOrderId": "ABN-2026-1187",
"metadata": { "client_id": "1187", "plan": "3m", "starts": "2026-09-15" }
}
- На экране ресепшена появляется QR, клиент сканирует и платит.
- Приходит webhook
invoice.paid— система активирует абонемент и выдаёт карту или браслет.
Активируйте абонемент только по invoice.paid, а не в момент создания счёта. Иначе в зал будут ходить те, кто так и не заплатил.
Если клиент покупает удалённо — из Instagram, WhatsApp или с сайта — отправляйте kind: "phone", ему придёт push в Kaspi. Телефон в формате 7XXXXXXXXXX, description не длиннее 60 символов.
Продление
Продление — это новый счёт, отдельного метода API для него нет. Важно другое: когда отправлять и что написать.
| Что | Как |
|---|---|
| Напоминание | Счёт за 3–5 дней до окончания |
| Тип | kind: "phone" — клиента нет в зале |
| Описание | «Продление абонемента, октябрь» — чтобы было понятно, за что платят |
| Связь | В externalOrderId номер продления, в metadata — client_id и новый срок |
| Не оплатили | По окончании срока абонемент закрывается, новый счёт — при следующем визите |
У счёта на продление ограниченный срок: окно QR — около трёх минут, счёт на телефон тоже живёт не вечно. Поэтому «разослать всем в начале месяца» не работает — лучше подписка, она выставляет каждому клиенту свежий счёт в его собственную дату.
Ежемесячный платёж через подписку
Подписка — это автоматическое выставление счёта по расписанию. Деньги с карты сами не уходят, каждую оплату клиент подтверждает лично. Разница принципиальная: клиент в любой месяц может просто не платить, и заставить его нельзя.
Что настраивается:
- Интервал:
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 подписок и Бизнес по подписке.
Заморозка и возврат
Заморозка. Клиент уехал на две недели. Срок абонемента сдвигаете в своей системе, подписку ставите на pause. На стороне Qut Pay ничего возвращать не нужно, деньги не двигаются.
Возврат. Клиент хочет вернуть абонемент целиком:
POST /api/v1/invoices/{id}/refund
{ "amount": 30000, "reason": "Возврат абонемента, использован 1 месяц" }
Без поля amount вернётся вся сумма. При частичном возврате счёт переходит в статус partially_refunded. Расчёт делаете вы: вычитаете использованные дни и возвращаете остаток — пропорцию Qut Pay не считает.
Берегитесь двойного возврата: Как не вернуть деньги дважды. Если возврат не проходит: Возврат не проходит.
Турникет
Разовый вход можно автоматизировать: клиент сканирует QR, платит, турникет открывается.
Как это устроено:
- Экран у турникета (планшет или небольшой монитор) запрашивает счёт у вашего сервера:
amount: 2500,metadata: { "gate": "north" }. - На экране показывается
qrImageUrl. - Клиент платит.
- На ваш сервер приходит webhook
invoice.paid. Поmetadata.gateон понимает, какой это турникет, и отправляет контроллеру команду на открытие.
Подпись webhook проверяйте обязательно: HMAC-SHA256(secret, timestamp + "." + rawBody), причём тело — в неизменном байтовом виде, до разбора JSON. Там, где открывается физическая дверь, это особенно важно: Безопасность вебхуков.
Обработчик должен быть идемпотентным: если вы не ответили 2xx, доставка повторится до 11 раз — турникет не должен открыться одиннадцать раз. Храните пару (invoice.id, status) и игнорируйте повторы.
Место чувствительно к задержке, поэтому вместе с webhook опрашивайте и статус: Вебхук или опрос статуса. Точно такая же схема используется на парковках: Парковки и шлагбаумы.
Вариант без кода
- Постоянные ссылки на оплату. По ссылке на каждый тариф: «1 месяц», «3 месяца», «персональный тренер». Их кладут в профиль Instagram, в ответы в WhatsApp и на табличку у ресепшена: Ссылки на оплату.
- Счёт вручную из кабинета. Администратор вводит сумму и описание, получает QR.
- Telegram-бот.
/invoice 45000 Абонемент 3 месяца,/today— итог дня,/last— последние счета. Бота можно добавить в группу и видеть всю смену. - Подписку тоже можно создать из кабинета, без единой строчки кода.
Что учесть заранее
- Не обещайте, что деньги будут сниматься сами. Подписка только выставляет счёт. Объясните это и клиенту, иначе появится вопрос «почему я снова должен что-то подтверждать».
- Не заходите в приложение Kaspi Pay с номера кассира — привязка оборвётся, и на ресепшене перестанут выставляться счета: Привязка кассира оборвалась.
- Не забывайте о поздних оплатах. Деньги по счёту в статусе
expiredмогут прийти позже, иinvoice.paidпридёт с пометкойlate: true. Либо выдайте абонемент, либо верните деньги: Поздняя оплата. - Тариф считайте по числу счетов, а не клиентов. 300 подписчиков плюс продления плюс разовые входы легко переваливают за 800 счетов в месяц: Какой тариф выбрать.
Вопросы и ответы
При подписке клиенту нужно подтверждать оплату каждый месяц? Да. Подписка выставляет счёт, а оплату клиент подтверждает сам в приложении Kaspi. Деньги сами не уходят.
Что будет, если клиент пропустит месяц? Когда шаги повтора (по умолчанию 15, 60, 360 минут) закончатся, запуск этого месяца отбрасывается, расписание идёт дальше. Закрывать абонемент или нет — решает ваша система.
Нужно ли возвращать деньги при заморозке? Нет. Заморозка — это сдвиг срока в вашей учётной системе, подписку просто ставите на паузу.
Как быстро открывается турникет? После оплаты webhook обычно приходит в пределах 5 секунд. Это зависит от Kaspi и от сети, гарантировать точное время мы не можем — поэтому параллельно опрашивайте статус.
У нас два зала, можно разделить отчётность? Да: каждому залу свой API-ключ, привязанный к своему кассиру: Раздельная отчётность по точкам.