Схема жизненного цикла
Счёт по номеру телефона:
POST /invoices → [processing] → [pending] → [paid] деньги на Kaspi-счёте
│ │ → [cancelled] отменён
│ │ → [expired] истёк (24 ч)
│ └─ вебхук pending / paid / cancelled / expired
└─ (нет вебхука; если не удалось создать → [error] + вебхук error)
QR-счёт создаётся сразу в pending (шага processing нет):
POST /invoices/qr → [pending] → [paid] / [cancelled] / [expired]
└─ pending-вебхука нет; есть событие qr_scanned при скане
Отмена в боевом режиме проходит через техническое cancelling:
POST /invoices/{id}/cancel → [cancelling] → [cancelled] отменён Kaspi
│ → [pending] мягкий отказ (часто «уже оплачен»)
└─ (нет вебхука) → [error] невосстановимая ошибка
Что происходит на каждом шаге
processing — счёт создаётся
Когда вы вызываете POST /invoices, ApiPay сразу отвечает 201 со статусом processing. Это значит: запрос принят, но Kaspi ещё не вызван — создание идёт в фоне. Система сама повторяет обращение к Kaspi (до 200 попыток, интервал пробуждения ≤30 с), обрабатывая сеть, сессию и троттлинг Kaspi. Обычно это секунды.
Важно понимать про анти-панику: при большой очереди у кассира счёт может держаться в processing дольше 60 минут — это не зависание, а легитимный бэклог под троттлингом Kaspi. Не пересоздавайте счёт, пока он в processing — иначе получите два живых счёта. Вебхука на processing нет.
pending — ждёт оплаты
Счёт создан в Kaspi и отправлен покупателю. Приходит вебхук invoice.status_changed со статусом pending. С этого момента счёт живёт 24 часа. Для QR-счёта статус pending наступает сразу при создании (шага processing нет), и отдельного pending-вебхука по QR не отправляется.
paid — оплачен, деньги пришли
Покупатель оплатил. Приходит вебхук со статусом paid, а деньги поступают напрямую на ваш Kaspi-счёт — ApiPay к ним доступа не имеет. Скорость, с которой ApiPay замечает оплату, зависит от активности организации: у «горячей» — 10–30 секунд, у «спящей» детект может занять до ~10 минут. Сами деньги при этом уже у вас в Kaspi — задержка только в том, когда ApiPay узнает об оплате и пришлёт вебхук.
cancelled / expired — не оплачен
cancelled — счёт отменён: вами через API/кабинет или покупателем (закрыл/свернул приложение). expired — истекли 24 часа, и Kaspi отдал терминальный статус. На оба перехода приходит вебхук.
error — не удалось создать
Если после всех попыток счёт не удалось создать в Kaspi, он финализируется в error с осмысленным кодом причины (например client_not_found — у номера нет Kaspi). Приходит вебхук со статусом error.
«Странные», но законные переходы
Из-за гонок между оплатой и отменой возможны последовательности, которые выглядят нелогично, — но их нужно обрабатывать, а не считать багом:
cancelled → paid— вы (или система) отменили счёт, но покупатель успел оплатить на долю секунды раньше. Оплата выигрывает: счёт становитсяpaid, деньги у вас. Придёт корректирующий вебхукpaid.expired → paid— то же самое на границе 24 часов: оплата прошла в последний момент.error → pending— реконсиляция: счёт, отмеченный ошибочным, при сверке с Kaspi оказался живым. Придёт корректирующий вебхук.paid → partially_refunded— по оплаченному счёту сделали частичный возврат. Полного статусаrefundedу счёта не существует — после полного возврата счёт остаётсяpaidс признаком «полностью возвращён».
Единственный переход, которого не бывает: error → paid. Ошибочный счёт оплаченным не становится.
Практический вывод для интеграторов: не считайте cancelled/expired окончательными деньгами-мимо — дождитесь, что статус устоялся, и всегда обрабатывайте поздний paid. Подробнее про идемпотентную обработку — в настройке вебхуков.
Какой вебхук на каком переходе
invoice.status_changed— на переходах вpending,paid,cancelled,expired,error,partially_refunded.invoice.qr_scanned— покупатель отсканировал QR (счёт ещёpending, маркерqr_substate: scanned); приходит один раз на QR.invoice.refunded— обработан возврат (completedилиfailed).- Технические
processingиcancellingвебхуков не порождают — на них не завязывайтесь.
Гарантия: на каждый реальный переход invoice.status_changed приходит ровно один раз (есть гейт). Отвечайте на вебхук 2xx до 5 секунд, а обработку делайте асинхронно.
Частые ошибки
- Пересоздавать счёт, «зависший» в
processing. Это почти всегда легитимный бэклог под троттлингом Kaspi — пересоздание даёт два живых счёта. Дождитесьpendingилиerror. - Считать
cancelled/expiredфиналом. Возможен позднийpaid(покупатель успел оплатить). Обрабатывайте его. - Ждать вебхук на
processingилиcancelling. Их нет. Ориентируйтесь наpending/paid/cancelled/expired. - Думать, что деньги идут через ApiPay. Оплата поступает напрямую на ваш Kaspi-счёт; ApiPay лишь сообщает вам о ней вебхуком.
- Искать статус
refundedу счёта. Его нет: после возврата счёт остаётсяpaidили становитсяpartially_refunded.
Частые вопросы
Почему счёт долго в статусе processing?
processing означает, что счёт ещё создаётся в Kaspi. Обычно это секунды, но при большой очереди у кассира счёт может держаться дольше 60 минут под троттлингом Kaspi — это не зависание. Не пересоздавайте его, дождитесь pending или error.
Когда приходят деньги за оплаченный счёт?
Сразу при оплате — напрямую на ваш Kaspi-счёт. ApiPay деньги не держит. Вебхук paid приходит, когда ApiPay заметил оплату: у активных организаций за 10–30 секунд, у «спящих» — до ~10 минут. Деньги при этом уже у вас.
Счёт был cancelled, а стал paid — это ошибка?
Нет, это законно: покупатель оплатил на долю секунды раньше отмены, и оплата выиграла гонку. Придёт корректирующий вебхук paid, деньги у вас. То же с expired → paid на границе 24 часов.
На какие статусы приходит вебхук?
На pending, paid, cancelled, expired, error, partially_refunded — событие invoice.status_changed. Технические processing и cancelling вебхуков не порождают.
Есть ли статус refunded?
Нет. После полного возврата счёт остаётся paid с признаком «полностью возвращён»; после частичного — становится partially_refunded.