Коротко
У каждого счёта есть свой срок, он приходит в поле expiresAt. Интеграция должна читать именно это поле — не зашивайте константу: срок задаёт Kaspi, и он может измениться.
Общее правило: окно сканирования QR-счёта короткое, а счёт, отправленный в приложение покупателя, живёт заметно дольше. По истечении срока счёт переходит в статус expired, «оживить» его нельзя — создаётся новый.
И главное: на уже закрытый счёт деньги могут прийти с опозданием. Тогда событие invoice.paid приходит с пометкой late: true, и ваш обработчик должен быть к этому готов.
Окно QR-счёта
Когда создаётся QR-счёт, Kaspi отводит на его сканирование короткое время — это сделано намеренно, чтобы картинка QR не жила долго. Конкретное значение задаёт Kaspi, а мы возвращаем его в поле expiresAt.
Что из этого следует на практике:
- Не создавайте QR заранее. Создавайте в момент, когда покупатель уже у кассы или открыл страницу оплаты
- QR на экране обновляйте до истечения окна: срок вышел — создайте новый счёт и покажите новый QR
- Не отправляйте такой счёт в мессенджер или на почту — пока человек его откроет, срок истечёт. Для этого есть постоянные ссылки на оплату
- Если покупатель видит «попробуйте позже», причина обычно именно в этом: QR показывает «попробуйте позже»
Срок счёта в приложении
Счёт kind: "phone" приходит покупателю уведомлением в приложение Kaspi и живёт значительно дольше — человек может открыть и оплатить его не сразу.
Точное значение берите там же, в поле expiresAt.
| QR-счёт | Счёт по телефону | |
|---|---|---|
| Окно | Короткое, под сканирование | Длинное, покупатель откроет позже |
| Где виден | На экране, на распечатке, на странице оплаты | В приложении Kaspi покупателя |
| Можно создать заранее | Нет | Да |
| Нужен телефон покупателя | Нет | Да |
Полное сравнение: QR-счёт или счёт по телефону.
Как правильно использовать expiresAt
В ответе на создание счёта (201) expiresAt приходит временем в формате ISO. Правильный порядок работы:
- Возьмите
expiresAtиз ответа и сохраните у себя. - Обратный отсчёт на странице стройте по этому значению.
- Когда время вышло, покажите кнопку «Срок QR истёк, создать новый».
- По нажатию создайте новый счёт — старый не переиспользуется.
Не зашивайте в код никакую константу срока: срок на стороне провайдера может измениться, и ваш таймер начнёт врать.
Статус expired
Просроченный счёт переходит в expired, и приходит событие invoice.expired. Это нормальное завершение, а не ошибка: покупатель просто не оплатил.
expired — финальный статус. Такой счёт:
- ❌ нельзя открыть заново;
- ❌ нельзя продлить;
- ❌ показывать его QR повторно бессмысленно;
- ✅ вместо него создаётся новый счёт, тот же
externalOrderIdможно передать снова.
При этом счёт никуда не исчезает: он остаётся в списке, попадает в отчёты, история сохраняется. Жизненный цикл счёта.
Если счёт непривычно долго висит в pending — это отдельный случай: Счёт завис в статусе pending.
Поздняя оплата: деньги на закрытый счёт
Самая важная часть. На просроченный (expired) или отменённый (cancelled) счёт деньги всё ещё могут прийти — покупатель подтвердил оплату на последних секундах, а информация дошла до нас позже.
В этом случае:
- счёт переходит в
paid; - вам приходит событие
invoice.paidс пометкойlate: true; - это настоящие деньги, они зачислены на ваш счёт в Kaspi.
Что делать:
- Обработчик вебхука должен принимать
invoice.paidпослеexpiredилиcancelled. Не отбрасывайте его по логике «счёт закрыт, игнорирую». - Либо окажите услугу, либо верните деньги — одно из двух.
- Не предлагайте покупателю оплатить повторно: деньги уже пришли один раз.
- Сделайте обработку идемпотентной по паре «идентификатор счёта + статус»: Идемпотентность.
Полный разбор сценария: Поздняя оплата.
Поэтому не считайте, что «окно закрылось — больше ничего не произойдёт»: какое-то время после закрытия статус счёта ещё может измениться. В системах, где товар выдаётся сразу, этот случай стоит продумать отдельно.
Вопросы и ответы
Можно ли продлить срок? Нет. Окно задаёт Kaspi, мы на него не влияем. Если покупателю нужно время — отправьте счёт в приложение.
Если я отменю счёт сам, деньги могут прийти? Да, на cancelled поздняя оплата тоже возможна — раздел выше действует полностью.
Просроченные счета считаются в лимите? Месячный лимит считается по созданным счетам, а не по оплаченным. Поэтому не создавайте лишних: Тарифы и лимиты.
Как это работает в песочнице? В песочнице статусы вы задаёте сами, включая expired: Симуляция оплаты в песочнице.
Можете назвать число минут для QR? Называть его константой неправильно — Kaspi может его изменить. Всегда читайте фактическое значение из expiresAt.