Почему счёт с одной суммой не проходит, а требует cart_items?
Если у организации подключена Kaspi ОФД (касса), Kaspi ожидает чек с позициями — поэтому у таких организаций в ApiPay включается каталог, и счёт собирается из его позиций. Классическая ошибка, которую видели интеграторы:
{ "message": "This organization requires cart items. Include cart_items in request." }
Важное обновление: с 9 июня 2026 жёсткое требование корзины снято — счёт без cart_items (только amount) снова проходит и у организаций с каталогом, потому что режим «Касса» в Kaspi сам по себе не означает обязательный каталог. Если ваша интеграция всё ещё получает этот 422 — обновите представление о флоу и проверьте фактический ответ API.
Обратное правило действует всегда: организация без каталога не может слать 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позиции из каталога. Это снимает страх «придётся вести весь прайс в каталоге»: позиция может быть одна («Услуга»), а фактическую цену вы передаёте в запросе.discount_percentage— скидка 1–99% на весь чек. Полеdiscountвнутри позиции устарело: на него API ответит 422 с текстом «Поле discount устарело. Используйте discount_percentage».amountне передаём: при корзине сумма счёта считается по позициям (цена × количество − скидка). Если передать иamount, и корзину — итог всё равно посчитается по корзине; не удивляйтесь, что «моя сумма игнорируется».
В вебхуке оплаченного счёта со скидкой придут поля subtotal, discount_sum, discount_percentage — удобно для сверки.
Разбор 422-ошибок по строкам корзины
| Текст ошибки | Причина | Лечение |
|---|---|---|
Catalog item has no price set. |
У позиции каталога не заполнена цена | Задать цену позиции в каталоге — или передавать 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 на весь чек |
Ошибки приходят в формате Laravel-валидации — с индексом строки: "cart_items.0.catalog_item_id": ["Catalog item has no price set."] — индекс 0 указывает, какая именно позиция сломана.
Как завести позиции каталога через API?
curl -X POST https://api.apipay.kz/api/v1/catalog \
-H "X-API-Key: ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "name": "Доставка", "selling_price": 1500, "unit_id": 1 }
]
}'
- За один запрос — до 100 товаров; обязательны
name,selling_price,unit_id(справочник единиц —GET /catalog/units). - Ответ
202: позиции сохраняются со статусомpendingи уходят в Kaspi через очередь — дождитесьactive, прежде чем использовать id в счетах. - Изображение:
POST /catalog/upload-image— jpg/png/gif/webp до 10 МБ. - Список и id позиций:
GET /catalog(до 200 на страницу). - Скан штрихкода в Нацкаталоге (
ntin/gtin), 4 режима чтенияGET /catalogи синхронизация каталога — в статье Каталог, корзина и Нацкаталог.
Чек-лист исправления 422 за 5 минут
- Проверьте, есть ли у организации каталог:
GET /catalogтем же ключом. - Каталога нет, а вы шлёте
cart_items→ уберите корзину, передавайтеamount. - Каталог есть → соберите счёт из
cart_items[] = {catalog_item_id, count}. has no price set→ задайтеselling_priceпозиции или передайтеpriceв строке запроса.- Нужна другая цена, чем в каталоге → поле
priceв строке корзины. - Скидка → только
discount_percentage(1–99) на весь чек. - Сумма «не совпадает» → перестаньте передавать
amount: при корзине он игнорируется.
Частые ошибки
- Передавать
amountвместе с корзиной и ждать, что итог возьмётся из него. Сумма считается по позициям. Цена управляется полемpriceв строке. - Использовать
catalog_item_idиз песочницы в рабочем режиме. Тестовые организации и их каталоги — отдельные; в проде id другие. - Слать счёт сразу после создания позиции. Позиция создаётся асинхронно (
202, статусpending) — дождитесьactive. - Скидка в каждой позиции. Устаревший формат; только
discount_percentageна чек. - Заводить весь прайс в каталог ради одной услуги. Достаточно 1–2 позиций + переопределение
priceв запросе.
Вопросы и ответы
Обязательно ли создавать товары в каталоге, чтобы выставлять счета?
Если у организации нет каталога — нет, счёт выставляется просто суммой. Если каталог подключён (Kaspi ОФД/«Касса») — счёт можно собирать из позиций; с 9 июня 2026 жёсткое требование корзины снято, «голая» сумма тоже проходит.
Можно ли переопределить цену позиции в счёте?
Да: поле price в строке cart_items (0.01–99 999 999.99). В каталоге при этом цена должна быть задана — она служит значением по умолчанию.
Как задать скидку?
discount_percentage от 1 до 99 — процент на весь чек, только вместе с cart_items. Пер-позиционной скидки нет.
Что за ошибка «This organization requires cart items»?
Историческое требование корзины для организаций с каталогом; с 2026-06-09 гейт снят. Если видите её — проверьте актуальный ответ API и напишите в поддержку с телом запроса.
Влияет ли корзина на возвраты?
Да, возвраты можно делать поэлементно (return_items[] с count или amount) — подробнее в «Возвраты Kaspi через API». Совет: не забивайте все товары одной позицией «Услуги» — поштучный возврат станет неудобным.