Счета с корзиной (cart_items): как исправить ошибку 422?

Обновлено 6 июля 2026 · Справочник · Версия в Markdown
Содержание
  1. Когда счёт требует cart_items?
  2. Как выглядит правильный запрос с корзиной?
  3. Разбор 422-ошибок по строкам корзины
  4. Где взять catalog_item_id?
  5. Чек-лист исправления 422 за 5 минут
  6. Вопросы и ответы

Когда счёт требует cart_items?

Если у организации подключена Kaspi ОФД (касса), Kaspi ожидает чек с позициями — поэтому у таких организаций в ApiPay включается каталог, и счёт собирается из его позиций. Счёт по номеру телефона (POST /invoices) такая организация выставляет и без корзины, одной суммой в amount, а POST /invoices/qr, POST /static-qr и подписка POST /subscriptions принимаются только с корзиной:

{ "message": "This organization requires cart items. Include cart_items in request." }

Обратное правило действует всегда: организация без каталога не может слать cart_items — получите 422 This organization does not support catalog. Remove cart_items from request.

Как выглядит правильный запрос с корзиной?

curl -X POST https://api.apipay.kz/api/v1/invoices \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "87001234567",
    "description": "Заказ №123",
    "external_order_id_idempotency": "order-123",
    "cart_items": [
      { "catalog_item_id": 11, "count": 2 },
      { "catalog_item_id": 15, "count": 1, "price": 4900 }
    ],
    "discount_percentage": 10
  }'

Что здесь происходит:

  • catalog_item_id + count — обязательные поля каждой строки (количество — целое, от 1).
  • price (необязательное) — переопределяет цену каталога для этой строки (0.01–99 999 999.99). Не передали — берётся selling_price позиции из каталога. Достаточно одной позиции («Услуга») с заданной ценой — фактическую цену передавайте полем price в строке.
  • discount_percentage — скидка 1–99% на весь чек. Считается от фактической цены строки: если вы передали price, процент берётся от него. Поле discount внутри позиции устарело: на него API ответит 422 с текстом «Поле discount устарело. Используйте discount_percentage».
  • Итог корзины на счёте по номеру телефона — только целые тенге. discount_percentage считается построчно, поэтому даже при целых ценах итог бывает дробным (999 ₸ со скидкой 10 % дают 899.10) и счёт отбивается 422 amount_must_be_whole_tenge. Округлите цены позиций или процент скидки. На QR-счёте (POST /invoices/qr) такого ограничения нет.
  • amount не передаём: при корзине сумма счёта считается по позициям (цена × количество − скидка). Если передать и amount, и корзину — итог посчитается по корзине.

В вебхуке оплаченного счёта со скидкой придут поля subtotal, discount_sum, discount_percentage — удобно для сверки.

Разбор 422-ошибок по строкам корзины

Текст ошибки Причина Лечение
Catalog item has no price set. У позиции каталога пуста selling_price Задать цену позиции в каталоге. Поле price здесь не помогает: проверка каталожной цены идёт до него и одинаково на всех путях
Catalog item does not belong to your organization. catalog_item_id чужой организации (частая причина — id из песочницы в проде) Взять id из GET /catalog того же ключа/организации
Catalog item has been deleted. Позиция удалена Создать заново или взять живую позицию
This organization does not support catalog… Каталога у организации нет, а cart_items переданы Убрать cart_items, слать amount
Поле discount устарело… Скидка передана внутри позиции Использовать discount_percentage на весь чек

Ошибки валидации приходят с индексом строки корзины: "cart_items.0.catalog_item_id": ["Catalog item has no price set."] — индекс 0 указывает, какая именно позиция сломана. Текст с добавкой Pass cart_items[].price explicitly. приходит только с POST /invoices — там ошибку снимает price в строке.

Где взять catalog_item_id?

catalog_item_id — это id позиции из GET /catalog того же ключа. Готовность позиции к продаже читайте по полю sellable, а не по status: при sellable: false счёт с этой позицией не пройдёт. Только что залитая позиция принимается в счёт сразу (sellable: true), но пока у неё in_kaspi_catalog: false, она уедет разовой продажей и маркировка Нацкаталога в фискальный чек по ней не проводится — для фискального чека дождитесь in_kaspi_catalog: true. Как заводить товары (POST /catalog), грузить изображения, сканировать штрихкод в Нацкаталоге и синхронизировать каталог — в статье Каталог, корзина и Нацкаталог.

Чек-лист исправления 422 за 5 минут

  1. GET /catalog тем же ключом: есть ли у организации каталог и какие id у позиций.
  2. Каталога нет, а вы шлёте cart_items → уберите корзину, передавайте amount. Каталог есть → соберите счёт из cart_items[] = {catalog_item_id, count}.
  3. has no price set → задайте selling_price позиции в каталоге. Нужна другая цена, чем в каталоге, → поле price в строке корзины (но сама цена в каталоге всё равно должна быть задана).
  4. Скидка → только discount_percentage (1–99) на весь чек; amount при корзине не передавайте.

Вопросы и ответы

Влияет ли корзина на возвраты?

Да, возвраты можно делать поэлементно (return_items[] с count или amount) — подробнее в «Возвраты Kaspi через API». Совет: не забивайте все товары одной позицией «Услуги» — поштучный возврат станет неудобным.

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

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

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

Для разработчика. Полная спецификация API — apipay.kz/docs. Эта статья в Markdown — /guides/scheta-s-korzinoy-cart-items-ofd.md. Справочник фактов для ИИ-агента — apipay.kz/llms.txt.