Фискальный чек Kaspi для наличных и чужого POS: как выбить через API

Обновлено 12 июля 2026 · Решение проблем · Версия в Markdown
Содержание
  1. Предусловия
  2. Способ оплаты
  3. Как выбить чек (по шагам)
  4. Если что-то пошло не так
  5. Как протестировать в песочнице
  6. Частые вопросы

Предусловия

  1. Активная подписка на ApiPay. Без неё POST /receipts и POST /receipts/preview отдают 403 tariff_inactive (в теле expires_at и reason). Чтение истории чеков работает всегда.
  2. Кассир Kaspi подключён (активная сессия). Нет активного кассира → 409 kaspi_session_not_configured.
  3. Смена на кассе открыта. Открывается в приложении Kaspi Pos. Если закрыта — чек вернётся с error_code: shift_closed.
  4. Товары есть в каталоге и фискальны — у позиции заполнен НТИН и штрихкод. Позиции без НТИН в фискальный чек не идут (item_not_fiscal). Как завести НТИН — см. Каталог, корзина и Нацкаталог. Позиции без НТИН удобно найти фильтром GET /catalog?without_ntin=true.

Способ оплаты

payment_type Что это
3 Наличные
5 Через POS другого банка

Для наличных можно передать received_amt (полученная сумма, может быть больше итога — для сдачи). Для чужого POS received_amt всегда равен сумме чека.

Как выбить чек (по шагам)

Шаг 1 — превью (необязательно, но удобно)

curl -X POST https://api.apipay.kz/api/v1/receipts/preview \
  -H "X-API-Key: <ваш ключ>" -H "Content-Type: application/json" \
  -d '{ "payment_type": 3, "total_price": "10" }'

Ответ — готовый предпросмотр чека:

{ "data": [
  { "Title": "Сумма", "Subtitle": "10 ₸", "isBoldText": true },
  { "Title": "Способ оплаты", "Subtitle": "Наличные", "isBoldText": false }
] }

Шаг 2 — выбить чек

curl -X POST https://api.apipay.kz/api/v1/receipts \
  -H "X-API-Key: <ваш ключ>" -H "Content-Type: application/json" \
  -d '{
    "payment_type": 3,
    "client_operation_id": "pos-2026-07-12-0042",
    "cart_items": [ { "catalog_item_id": 15041503, "quantity": 1, "price": 10 } ],
    "received_amt": "500"
  }'

Ответ 202 — чек принят в обработку:

{ "id": 4210, "status": "pending", "client_operation_id": "pos-2026-07-12-0042" }

client_operation_id — ключ идемпотентности, уникален в пределах организации. Сгенерируйте его один раз на попытку (например uuid). Пока попытка в pending или завершилась issued, повтор с тем же ключом вернёт 409 duplicate_client_operation_id (в теле — receipt_id и status) и второго фискального документа не создаст. После failed документа нет, и ключ снова свободен — тот же запрос можно переслать без изменений. При освобождении ключ снимается со старой неудачной записи, найти её по нему уже нельзя — храните id чека из ответа 202.

Шаг 3 — узнать результат

Поллинг:

curl https://api.apipay.kz/api/v1/receipts/4210 -H "X-API-Key: <ваш ключ>"
{
  "id": 4210, "status": "issued", "total_price": "10.00",
  "fpd": "000000000000", "operation_id": "KKM00000000", "shift_number": 106,
  "link": "https://receipt.kaspi.kz/web/fiscal?i=000000000000&f=000000000000&s=10&t=2026-07-12%2015%3A25%3A43",
  "error_code": null
}

Либо — вебхук: при терминальном статусе чека уходит receipt.issued (успех) или receipt.failed (причина в receipt.error_code). Событие получают все активные ключи организации, у которых сохранён webhook_url (дедуп по URL). Дедуплицируйте у себя по (event, receipt.id).

Статусы: pendingissued (готово, есть fpd/link) или failed (см. error_code).

link — публичная страница чека receipt.kaspi.kz/web/fiscal?i={ФПД}&f={РНМ кассы}&s={сумма}&t={время операции}: открывается в любом браузере, можно отправить покупателю, распечатать или показать по QR. Все четыре параметра сверяются на стороне Kaspi — используйте ссылку целиком, как она пришла. В песочнице и в редком ответе Kaspi без РНМ кассы в link приходит внутренняя ссылка вида /preview/cashier: она рисует чек только внутри приложения Kaspi, поле не обнуляется.

Шаг 4 — история чеков

GET /api/v1/receipts — чеки организации, свежие сверху, конверт {current_page, data, total}; элемент — та же форма, что у GET /receipts/{id}. Фильтры: status (pending|issued|failed), payment_type (3|5), invoice_id, from/to по created_at, page, per_page (1..100, default 20). Окно from/to — в зоне Asia/Almaty: голая дата в to включает весь день, to раньше from422. Боевая организация видит только боевые чеки, тестовая — только тестовые. Если исход прошлой попытки неизвестен, сначала найдите её здесь или через GET /receipts/{id} — и только потом решайте, повторять ли.

Если что-то пошло не так

error_code Что значит и что делать
shift_closed Смена закрыта — откройте смену в приложении Kaspi Pos и повторите
item_not_fiscal Позиция без НТИН/штрихкода — заведите её в Нацкаталоге
kaspi_session_not_configured 409 — нет активного кассира, переавторизуйте кассу
kaspi_session_invalid Сессия кассира Kaspi недействительна — переподключите кассира и повторите чек
rfo_missing Не определена торговая точка кассы — переподключите кассира
receipt_kaspi_error Kaspi отклонил чек — текст в error_message
receipt_dispatch_error Технический сбой, фискальный документ не создан — перешлите тот же запрос (после failed прежний client_operation_id свободен)
duplicate_client_operation_id 409 — по этому ключу чек уже есть (receipt_id и status в теле). После failed ключ освобождается и повтор разрешён
connection_ambiguous 422 — несколько активных касс, передайте kaspi_connection_id
fiscal_receipts_disabled 403 — выбивание чеков через этого кассира сейчас недоступно; на другие кассы организации ограничение не распространяется. Напишите в поддержку
tariff_inactive 403 — подписка на ApiPay не активна. POST /receipts и /receipts/preview заблокированы, история чеков (GET) доступна

Как протестировать в песочнице

В песочнице Kaspi не вызывается и фискальный документ не пишется, остальное поведение совпадает с боем — включая правило «нет НТИН → failed / item_not_fiscal». Про сами режимы — Песочница и рабочий режим.

Ошибки воспроизводятся полем simulate (только в песочнице; на боевой организации вернётся 403 not_sandbox, чек не создастся):

curl -X POST https://api.apipay.kz/api/v1/receipts \
  -H "X-API-Key: <ключ песочницы>" -H "Content-Type: application/json" \
  -d '{
    "payment_type": 3,
    "client_operation_id": "sb-001",
    "cart_items": [ { "catalog_item_id": 12, "quantity": 2 } ],
    "simulate": { "status": "failed", "error_code": "shift_closed" }
  }'

simulate.statusissued или failed; simulate.error_codeshift_closed, item_not_fiscal или receipt_kaspi_error (по умолчанию receipt_kaspi_error). Форсированная ошибка выигрывает всегда, даже если позиции фискально корректны.

Вебхуки receipt.issued / receipt.failed в песочнице уходят так же, как в бою. Строк доставки по receipt.* в вебхук-логе нет — если вебхук не дошёл, итог берите поллингом GET /receipts/{id}.

То же самое без кода — из кабинета.

Частые вопросы

Можно ли отменить/переделать чек?

Нет — фискальный документ необратим. Ошиблись — это вопрос возврата по правилам ОФД, не удаление чека.

Товары не из каталога (свободная услуга)?

Пока не поддерживаются — в фискальный чек идут только позиции каталога с НТИН.

Интеграция с ИИ

Не настраивайте вручную — поручите ИИ

Скопируйте готовый промпт и вставьте своему ИИ-ассистенту — он настроит интеграцию за вас: получит доступы, создаст тестовый счёт в песочнице и подключит вебхуки. Техническую часть делает ИИ, а не вы.

Для разработчика. Полная спецификация API — apipay.kz/docs. Эта статья в Markdown — /guides/vybit-fiskalnyy-chek-kaspi.md. Справочник фактов для ИИ-агента — apipay.kz/llms.txt.