Счета Kaspi и оплата
Как лагерь берёт деньги за путёвку, как оплатить частями, заменить или отменить счёт и что происходит с юрлицами лагеря.
Как устроена оплата
- Платит только плательщик и только счётом Kaspi. Счёт уходит с Kaspi лагеря на телефон
плательщика: телефон родителя из брони, а если указан
payer_phone— на него. - В запросе на счёт телефона нет. Сменить, кому уходят счета, можно только правкой брони
(
PATCH /bookings/{id}сpayer_phone). Так ключ не может отправить требование заплатить на чужой номер от имени лагеря. - У брони одновременно только один неоплаченный счёт.
- Счёт — только на целые тенге (Kaspi не знает тиынов) и не больше остатка по брони. Переплатить счётом нельзя.
- Не больше 5 счетов в час на одну бронь.
- Ввести платёж через API нельзя. Если родитель заплатил лагерю другим способом (наличными в кассе,
переводом на счёт организации), такой платёж вносит сам лагерь — вы увидите его в платежах брони с
source: manual.
Нужное право — invoices (выставить, отменить) и read (смотреть).
Состояния счёта
state | Что значит | Событие |
|---|---|---|
queued | Счёт создан и ждёт отправки в Kaspi (обычно секунды) | invoice.queued |
issued | Счёт у плательщика в приложении Kaspi | invoice.issued |
paid | Оплачен. Рядом появляется платёж (payment_id) | invoice.paid |
expired | Не оплачен в срок, Kaspi закрыл его | invoice.expired |
cancelled | Отменён (вами, лагерем или системой при замене) | invoice.cancelled |
error | Не выставлен — причина в поле error | invoice.error |
mismatch | Kaspi сообщил о сумме, отличной от счёта. Лагерь проверяет вручную | invoice.mismatch |
«Живой» (незакрытый) счёт — в состоянии queued, issued или mismatch. Список состояний может
пополняться: незнакомое значение не должно ломать вашу CRM.
Выставить счёт
POST /bookings/{id}/invoices с Idempotency-Key. Что выставить — поле kind:
kind | Сумма счёта |
|---|---|
min | Сколько ещё не хватает до минимального платежа. Если минимум уже внесён — отказ 409 min_already_paid |
full | Весь остаток по брони |
amount | Своя сумма из поля amount: целые тенге строкой ("50000" или "50000.00"), не больше остатка |
Code
Ответ 201 — счёт в очереди:
Code
Ответ приходит сразу, а в Kaspi счёт уходит очередью — следите за событиями invoice.issued и
invoice.paid или перечитайте счёт: GET /invoices/735.
payment_type система ставит сама: prepayment — если счёт меньше остатка, payment — если на весь
остаток.
Частые отказы:
| Ответ | Что значит |
|---|---|
409 invoice_live | У брони уже есть неоплаченный счёт. Дождитесь его оплаты, отмените его или замените (replace: true) |
400 amount_over_debt | Сумма больше остатка по брони |
400 fractional_amount | Сумма с тиынами. Только целые тенге |
400 nothing_to_pay | Остаток по брони равен нулю — платить нечего |
409 min_already_paid | kind: min, а минимум уже внесён. Выставьте full или amount |
409 booking_cancelled | Бронь отменена — счёт по ней не выставляется |
409 invoice_mismatch | По брони разбирается расхождение суммы — новый счёт до решения лагеря не выставить |
400 bad_kind, bad_amount | Опечатка в kind или сумма не по шаблону; amount передан без kind: amount |
429 rate_limited | Больше 5 счетов в час по этой брони |
Оплата частями
Родитель может платить путёвку несколькими платежами. Пример: путёвка 255 000 ₸, минимальный платёж 50 000 ₸.
| Шаг | Что происходит | paid | debt | payment_state |
|---|---|---|---|---|
| 1 | Бронь оформлена, ушёл счёт на 50 000 (invoice: min) | 0.00 | 255000.00 | awaiting |
| 2 | Родитель оплатил 50 000 | 50000.00 | 205000.00 | secured |
| 3 | Родитель просит счёт на 100 000 — вы выставляете kind: amount, amount: "100000" | 50000.00 | 205000.00 | secured |
| 4 | Родитель оплатил 100 000 | 150000.00 | 105000.00 | secured |
| 5 | В день full_payment_due лагерь сам выставил счёт на остаток 105 000 и родитель оплатил | 255000.00 | 0.00 | secured |
Правила:
- Следующий счёт можно выставить, только когда предыдущий закрыт (оплачен, отменён, истёк) — или заменить его.
- Место закрепляется (
secured), когда сумма всех оплат брони достигла минимального платежа. Можно набрать минимум и несколькими счетами меньше минимума. - «Оплачено» (
paid) и «остаток» (debt) в брони считаются по всем платежам, включая ещё не сверенные бухгалтерией — менеджеру не нужно ждать сверки. - Автоматический счёт на остаток лагерь выставляет один раз на бронь — начиная с дня срока полной оплаты и до дня заезда. Если в этот момент у брони есть неоплаченный счёт, лагерь не дублирует его, а ждёт, пока тот закроется, и выставляет счёт на остаток после. Бронь, оформленная уже после срока полной оплаты, получает такой счёт, как только закроется счёт на минимальный платёж.
Заменить счёт
Родитель получил счёт на 205 000, а хочет заплатить сейчас 100 000. Два живых счёта у брони быть не
может, поэтому замените старый новым — replace: true:
Code
Ответ 202 — замена принята, но ещё не завершена:
Code
Что происходит дальше:
- Старый счёт гасится в Kaspi — это занимает от секунд до минут. Придёт
invoice.cancelled. - Сразу после этого лагерь выставляет новый счёт — придёт
invoice.queued, затемinvoice.issued. Номер нового счёта вы узнаете из этих событий.
Пока старый счёт гасится, плательщик ещё может его оплатить — такая оплата принимается как обычно, а новый счёт не превысит того, что останется к оплате. Предупредите родителя, чтобы дождался нового счёта.
Отменить счёт
POST /invoices/{id}/cancel с Idempotency-Key, без тела:
Code
Ответ 200 — счёт после отмены:
- счёт ещё не ушёл в Kaspi (
queued) — отменяется сразу,state: cancelled; - счёт уже у плательщика (
issued) — гасится в Kaspi очередью: сначалаcancelling: true, потом событиеinvoice.cancelled. Пока это так, счёт ещё можно оплатить — оплата будет принята.
Отказы: 409 invoice_paid — счёт уже оплачен (это уже вопрос возврата, см.
Возвраты); 409 invoice_not_live — счёт уже закрыт, отменять нечего;
invoice_mismatch — по счёту расхождение суммы, его разбирает только лагерь.
Изменение брони и счёт
Если вы меняете цену, скидку или трансфер брони (PATCH /bookings/{id}), остаток меняется, и живой
счёт на старую сумму становится неправильным. Лагерь сам гасит его и выставляет новый на верную
сумму (события invoice.cancelled, затем invoice.queued). Делать ничего не нужно.
Если счёт не выставился
У счёта в состоянии error (и иногда у живого счёта) заполнено поле error:
Code
error.code | Что делать |
|---|---|
waiting, waiting_status | Ничего: ждём ответа Kaspi, счёт выставится или обновится сам |
rejected | Телефон плательщика не зарегистрирован в Kaspi. Исправьте payer_phone (или телефон родителя) в брони и выставьте счёт снова |
rejected_in_kaspi | Плательщик сам отклонил счёт в Kaspi. Уточните у него и выставьте новый |
queue_expired | Счёт не удалось выставить за сутки. Выставьте новый или обратитесь в лагерь |
rejected_sent, attempts_exhausted, queue_expired_sent | Не выставляйте новый — счёт мог дойти до плательщика. Обратитесь в лагерь |
cancel_waiting, cancel_retry | Отмена в процессе. Пока не завершена — счёт можно оплатить |
cancel_failed, orphan_live | Отменить автоматически не удалось, лагерь отменит вручную. Предупредите плательщика не оплачивать этот счёт |
mismatch | Kaspi сообщил о другой сумме — лагерь проверяет оплату |
expired_in_kaspi | Счёт не оплачен в срок |
| другой код | Счёт требует внимания лагеря — обратитесь в лагерь |
Список кодов может пополняться.
Одно юрлицо на бронь
У лагеря несколько юрлиц — ТОО и ИП, — и у каждого своя касса Kaspi. Лагерь время от времени переключает, через какую кассу выставляются новые счета (это решение лагеря, вы на него не влияете).
Главное правило: деньги одной брони никогда не делятся между ИП и ТОО.
- Пока по брони нет подтверждённой оплаты, новые счета уходят через кассу, которая сейчас активна у лагеря.
- Как только по брони пришла и подтвердилась первая оплата, все следующие счета этой брони — доплата, остаток, автоматический счёт к сроку — уходят через кассу того же юрлица, даже если лагерь уже переключил активную кассу на другое юрлицо.
- Уже выставленный счёт навсегда помнит свою кассу. Счёт ушёл от ТОО, лагерь переключился на ИП, родитель оплатил — деньги пришли на ТОО, и платёж записан на ТОО.
- Возврат делается с того же юрлица, которое приняло деньги.
Пример:
- 14 мая активна касса ТОО. Родитель оплатил минимальный платёж 50 000 — деньги на ТОО.
- 1 июня лагерь переключил активную кассу на ИП. Новые брони теперь оплачиваются на ИП.
- 24 июня (срок полной оплаты) лагерь выставляет этой брони счёт на остаток — через кассу ТОО, потому что первая оплата брони пришла на ТОО.
На какое юрлицо пришли деньги, видно в каждом платеже — поле legal_entity (name, bin — БИН ТОО
или ИИН ИП). Вашей CRM ничего делать не нужно; правило нужно знать, чтобы правильно отвечать
родителям и сверять акты с лагерем.
Платежи — только чтение
| Нужно | Запрос |
|---|---|
| Платежи брони | GET /bookings/{id}/payments |
| Все платежи отдела | GET /payments (фильтры paid_from, updated_since) |
| Один платёж | GET /payments/{id} |
| Платежи семьи | GET /families/{id}/payments |
Поля платежа:
| Поле | Что значит |
|---|---|
amount | Сумма. Возврат — отрицательной суммой |
type | prepayment — предоплата, payment — оплата, refund — возврат |
source | kaspi_invoice — оплата счёта Kaspi, manual — платёж внёс лагерь |
method | Как заплатили: kaspi_invoice, kaspi_transfer, bank_transfer, cash; null — не указано |
paid_on | День оплаты по календарю лагеря (время Казахстана), без времени |
verified | true — бухгалтерия лагеря сверила платёж с банком |
legal_entity | Юрлицо лагеря, на которое пришли деньги |
invoice_id | Счёт Kaspi, оплатой которого создан платёж |