Как выставлять Kaspi-счета из 1С через API ApiPay?

Обновлено 26 августа 2026 · Решение проблем · Версия в Markdown
Содержание
  1. Сценарий: счета и фискализация позиций из 1С
  2. Маппинг данных: номенклатура и документы
  3. Выставление счёта: одиночное и пакетное
  4. Отслеживание оплаты: поллинг (основной путь) и вебхуки (опция)
  5. Возвраты
  6. Синхронизация каталога
  7. Чек-лист запуска: sandbox → прод
  8. Частые вопросы

Сценарий: счета и фискализация позиций из 1С

Поток:

Документ 1С → POST /invoices (или bulk) → покупателю приходит push в Kaspi → покупатель оплатил → 1С узнаёт об оплате (поллинг/вебхук) → проводит платёж по external_order_id.

Если в чек Kaspi нужны позиции (фискализация, Нацкаталог ntin/gtin/barcode), номенклатура заранее заводится в каталог ApiPay, и счёт создаётся корзиной cart_items — механику каталога держит отдельная статья Каталог, корзина и Нацкаталог.

Одна поверхность — клиентский X-API-Key (/api/v1). Отдельный Partner API для интеграции своей 1С не нужен.

Маппинг данных: номенклатура и документы

Это стержень 1С-интеграции. Две независимые связки, которые нельзя путать.

(а) Номенклатура 1С ↔ товар каталога ApiPay — через external_ref. Код (или GUID) номенклатуры 1С кладёте в external_ref при создании товара (POST /catalog). Потом точечно читаете GET /catalog?external_refs[]=… и получаете двусторонний матчинг «номенклатура ↔ товар каталога». Подробно — в статье про каталог.

(б) Документ 1С ↔ счёт — через два поля-ссылки. Их постоянно путают:

Поле Назначение Ограничения Поведение
external_order_id Свободная ссылка на документ 1С для матчинга оплаты. Возвращается в счёте и в вебхуке — по ней 1С находит документ и проводит платёж. ≤255 Не уникально, дублей не ловит
external_order_id_idempotency Ключ идемпотентности проведения: номер/GUID документа 1С. Повторное проведение того же документа не создаёт второй счёт. ≤191, пусто = выкл. Дубль → 409 duplicate_idempotency_key с телом { invoice_id, status } уже созданного счёта. Уникально в пределах организации

Рецепт для 1С: кладите идентификатор документа в оба поля — external_order_id для поиска документа по вебхуку/GET, external_order_id_idempotency для защиты от дубля при повторном проведении или сетевом ретрае. 409 — это не ошибка, а «счёт уже есть»: возьмите invoice_id/status из тела и продолжайте.

Важно: HTTP-заголовок Idempotency-Key — отдельный механизм, это не он. Идемпотентность счёта — через body-поле external_order_id_idempotency.

Выставление счёта: одиночное и пакетное

Одиночный — POST /invoices. Обязателен phone_number (8XXXXXXXXXX). Опционально: amount (только целые тенге, от 1 до 99 999 999 — обязателен, если нет cart_items; дробная сумма и дробный итог корзины после скидок отбиваются 422 amount_must_be_whole_tenge), description (≤60 — с 05.09.2026 длиннее 60 отклоняется с 422 description_too_long; организациям, зарегистрированным с 26.08.2026, — уже сейчас), external_order_id, external_order_id_idempotency, kaspi_connection_id (выбор кассира), cart_items (для каталог-организации), discount_percentage (1–99).

curl -X POST https://api.apipay.kz/api/v1/invoices \
  -H "X-API-Key: ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
  -d '{
    "phone_number": "8XXXXXXXXXX",
    "amount": 15000,
    "description": "Оплата по счёту №123 от 01.07.2026",
    "external_order_id": "1c-doc-000000123",
    "external_order_id_idempotency": "1c-doc-000000123"
  }'

Отказы, которые обработчик 1С обязан различать: 422 amount_must_be_whole_tenge — сумма документа с копейками, счёт не создан; округлите до целых тенге и повторите, ключ идемпотентности при этом не расходуется; 409 duplicate_idempotency_key — счёт уже есть, возьмите invoice_id и status из тела и продолжайте; 403 tariff_inactive — подписка на ApiPay не активна, ретрай бесполезен до продления; 429 с Retry-After — упёрлись в лимит, повторять не раньше момента из meta.reset_atЛимиты и квоты»).

Псевдокод из 1С (HTTP-сервис / внешняя обработка):

// 1С (условный BSL)
Запрос = Новый HTTPЗапрос("/api/v1/invoices");
Запрос.Заголовки.Вставить("X-API-Key", КлючAPI);
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ПараметрыСчёта)); // с external_order_id = НомерДокумента
Ответ = HTTPСоединение.ОтправитьДляОбработки(Запрос);
// 201 → сохранить invoice_id у документа; 409 → счёт уже есть, взять invoice_id из тела

Пакетный — POST /invoices/bulk (отдельный лимит 20/min). Тело: invoices (массив 1–100), опц. общий kaspi_connection_id (один кассир на батч). Ответ 201: { created, duplicates, failed, invoices: [ { index, status: "created"|"duplicate"|"failed", … } ] } — результат по каждому элементу по индексу. Структурная ошибка тела (например элемент без phone_number) отклоняет весь батч атомарно (422, частичного применения нет).

Рецепт: ночная выгрузка пачки неоплаченных документов → один bulk (≤100) → разобрать invoices[] по index, сохранить id у каждого документа. duplicate = документ уже выставлялся (сработал external_order_id_idempotency).

Отслеживание оплаты: поллинг (основной путь) и вебхуки (опция)

У 1С часто нет публичного веб-адреса (крутится в локальной сети / на терминальном сервере), поэтому основной путь — поллинг.

Поллинг GET /invoices/{id} — лимит 1000/min (заменяет общий 200/min именно под 1С). Читает из БД/кэша, не бьёт в Kaspi; терминальные счета кэшируются 24 часа. Ответ 200 — полный объект счёта + items[]. Пока счёт в processing, в объекте видны last_kaspi_error_code/last_kaspi_error_message — диагностика без ожидания вебхука.

Пакетная проверка POST /invoices/status/check { invoice_ids: [...] } — опросить сразу все неоплаченные счета одним запросом; ответ { invoices: [ { id, status, kaspi_invoice_id, amount, error_message, updated_at } ] }.

Паттерн регламентного задания 1С:

1. Выбрать документы с неоплаченными счетами (свои invoice_id).
2. POST /invoices/status/check со всеми invoice_ids (или поштучно GET /invoices/{id}).
3. Для каждого терминального статуса:
     paid                      → провести оплату по external_order_id;
     cancelled/expired/error   → снять резерв / пометить неоплаченным.
4. Реагировать на ПОСЛЕДНИЙ статус.

Легитимны переходы cancelled → paid и expired → paid (клиент оплатил в последний момент — «деньги получены»). error → paid невозможен; error → pending — реконсиляция (счёт на самом деле прошёл, следуйте последнему статусу).

Вебхуки — опция. Если 1С опубликована в интернет (веб-сервер публикации, обратный прокси), можно принимать paid-вебхук и проводить оплату мгновенно; иначе поллинг самодостаточен. Настройка приёмника, подпись, ретраи — в статье Вебхуки ApiPay: настройка и проверка подписи; здесь не дублируем.

Возвраты

POST /invoices/{id}/refund: по умолчанию — полный возврат; частичный — по сумме amount или позиционный return_items[]. Результат приходит асинхронно (вебхук/поллинг), поэтому 1С отражает возврат по последнему статусу, а не по ответу 201. Параметры, статусы счёта после возврата и причины отказов — Возвраты Kaspi через API.

Синхронизация каталога

Нужна, только если счёт должен нести позиции в чек Kaspi. Номенклатура заводится батчем POST /catalog (external_ref = код номенклатуры 1С), в рабочем режиме создание асинхронное — статус подтверждаете точечным чтением GET /catalog?external_refs[]=…. Инкрементальная выгрузка изменённого — GET /catalog?updated_after=<iso>. Ограничения батча, режимы чтения и правила обновления полей Нацкаталога — в статьях Каталог, корзина и Нацкаталог и Массовая загрузка каталога из 1С.

Чек-лист запуска: sandbox → прод

Сначала прогон в песочнице (детерминированные симуляции без реального Kaspi), потом рабочий режим. Что песочница даёт и что меняется при переходе — в статье Песочница и рабочий режим.

  • Симуляции жизненного цикла: POST /invoices/{id}/simulate-status { status: paid|cancelled|expired|error } на sandbox-счёте — прогнать все ветки проведения.
  • Идемпотентность проведения: один и тот же документ, проведённый дважды, не создаёт второй счёт (409).
  • Обработка 429/Retry-After: уважайте заголовок, снижайте темп.
  • Тайм-зоны: счета/возвраты/каталог — UTC +00:00; только GET /tariff и GET /account/health+05:00. Частый баг 1С — сдвиг времени проведения.
  • Матчинг: проведение строго по external_order_id; дедуп проводки по invoice.id + status.

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

Есть готовый модуль/обработка для 1С?

Нет. Это доработка конфигурации силами вашего 1С-специалиста: HTTP-запросы из 1С к REST ApiPay. Почему готового модуля нет — на витрине ApiPay для 1С.

Почему счёт долго висит в processing?

Штатно processing длится секунды. Дольше — либо у организации включён «Режим накопления счетов» (счета уходят в Kaspi пачкой раз в заданное окно), либо выставление не доехало до Kaspi, и тогда сервис сам финализирует счёт в error с вебхуком. Не пересоздавайте счёт, пока он в processing: получатся два живых счёта, и покупатель может оплатить оба. Смотрите last_kaspi_error_code/last_kaspi_error_message в GET /invoices/{id}.

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

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

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

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