Когда счёт требует 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 минут
GET /catalogтем же ключом: есть ли у организации каталог и какие id у позиций.- Каталога нет, а вы шлёте
cart_items→ уберите корзину, передавайтеamount. Каталог есть → соберите счёт изcart_items[] = {catalog_item_id, count}. has no price set→ задайтеselling_priceпозиции в каталоге. Нужна другая цена, чем в каталоге, → полеpriceв строке корзины (но сама цена в каталоге всё равно должна быть задана).- Скидка → только
discount_percentage(1–99) на весь чек;amountпри корзине не передавайте.
Вопросы и ответы
Влияет ли корзина на возвраты?
Да, возвраты можно делать поэлементно (return_items[] с count или amount) — подробнее в «Возвраты Kaspi через API». Совет: не забивайте все товары одной позицией «Услуги» — поштучный возврат станет неудобным.