Сценарий: счета и фискализация позиций из 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}.