> Источник: https://apipay.kz/guides/integraciya-apipay-s-1c · Обновлено: 2026-07-06 · apipay.kz
> ApiPay — независимый сервис приёма платежей поверх вашего Kaspi Pay.

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

**TL;DR.** 1С ведёт учёт (документы, номенклатура), а деньги вы принимаете через Kaspi без терминала: документ 1С → `POST /invoices` → покупатель платит в Kaspi → 1С узнаёт об оплате и проводит платёж. Основной способ узнать об оплате из 1С — **поллинг `GET /invoices/{id}`, лимит специально поднят до 1000/min** под 1С (вебхуки — опция, если у 1С есть публичный HTTP-сервис). Два поля-ссылки не путать: `external_order_id` = ссылка на документ для матчинга оплаты, `external_order_id_idempotency` = ключ идемпотентности (повторное проведение не создаёт дубль-счёт, а даёт `409`). Готового модуля для 1С нет — это доработка конфигурации силами вашего 1С-специалиста; почему так — на витрине [ApiPay для 1С](/kaspi-pay-1c).

## Коротко

| Параметр | Значение |
|---|---|
| Хост API | `https://api.apipay.kz/api/v1`, заголовок `X-API-Key` |
| Счёт из документа | `POST /invoices` → `201` со `status: "processing"` (обработка асинхронная) |
| Пачка документов | `POST /invoices/bulk` — до 100 счетов, лимит **20/min** |
| Отслеживание оплаты (основное) | Поллинг `GET /invoices/{id}` — лимит **1000/min** под 1С; читает из БД/кэша |
| Пакетная проверка | `POST /invoices/status/check` `{ invoice_ids[] }` |
| Матчинг документа | `external_order_id` = номер документа 1С (виден в вебхуке/GET) |
| Защита от дублей | `external_order_id_idempotency` = номер документа; повтор → `409 duplicate_idempotency_key` |
| Даты | UTC `+00:00` везде, **кроме** `GET /tariff` и `GET /account/health` (`+05:00`) |
| Суммы в ответах | Строки: `"amount": "15000.00"` — парсить как строку |

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

1С остаётся системой учёта, а приём денег уходит в Kaspi без POS-терминала. Поток:

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

Если в чек Kaspi нужны **позиции** (фискализация, Нацкаталог `ntin`/`gtin`/`barcode`), номенклатура заранее заводится в каталог ApiPay, и счёт создаётся корзиной `cart_items` — механику каталога держит отдельная статья [Каталог, корзина и Нацкаталог](/guides/katalog-korzina-nackatalog). Глубокое «почему у 1С нет готового модуля» — на витрине [ApiPay для 1С](/kaspi-pay-1c); здесь — только как собрать интеграцию.

> Одна поверхность — клиентский `X-API-Key` (`/api/v1`). Для платформ, которые онбордят чужих мерчантов, есть отдельный Partner API (`X-Partner-Key`) — см. [документацию](/docs). Для интеграции своей 1С он не нужен.

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

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

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

**(б) Документ 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` (min 0.01, max 99999999.99 — обязателен, если нет `cart_items`), `description` (≤500), `external_order_id`, `external_order_id_idempotency`, `kaspi_connection_id` (выбор кассира), `cart_items` (для каталог-организации), `discount_percentage` (1–99).

```bash
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"
  }'
# 201, status: "processing" — Kaspi ещё не вызван; статус доедет поллингом/вебхуком (pending/error).
```

Псевдокод из 1С (HTTP-сервис / внешняя обработка): собираете тело, шлёте POST, из ответа `201` сохраняете `id` в реквизит документа.

```bsl
// 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С:**

```text
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`-вебхук и проводить оплату мгновенно; иначе поллинг самодостаточен. Подпись — `X-Webhook-Signature: sha256=<hex>`, HMAC-SHA256 по **сырому телу**; отвечать `200` за ≤5 с, дедуп по `(invoice.id, invoice.status)`. Полная настройка приёмника, ретраи и circuit breaker — в статье [Вебхуки ApiPay: настройка и проверка подписи](/guides/nastroyka-webhookov-apipay); здесь не дублируем.

## Возвраты

`POST /invoices/{id}/refund`: по умолчанию — полный возврат; частичный — по сумме `amount` или позиционный `return_items[]` (1–100; на позицию `catalog_item_id` + **ровно одно** из `count` или `amount`, не превышая доступное). Ответ `201` — `{ message, refund, invoice: { id, total_refunded, available_for_refund, pending_refund_amount } }`; результат приходит асинхронно (вебхук/поллинг, `completed`/`failed`+`error_code`).

**Важно:** статуса `refunded` у счёта **нет**. Полный возврат оставляет счёт `paid` + `is_fully_refunded=true`; первый частичный переводит `paid → partially_refunded`. Детали и причины отказов — [Возвраты Kaspi через API](/guides/vozvraty-kaspi-cherez-api).

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

Нужна, только если счёт должен нести позиции в чек Kaspi. Создание батчем `POST /catalog` (≤100, `external_ref` = код номенклатуры 1С, при маркировке — `ntin`/`gtin`); в рабочем режиме создание асинхронное (`202`), поэтому статус подтверждаете точечным чтением `GET /catalog?external_refs[]=…`. Инкрементальная выгрузка изменённого — `GET /catalog?updated_after=<iso>` (включая удалённые), keyset-экспорт больших каталогов — `?cursor=`. **Важно:** при обычном `PATCH` **не передавайте `ntin`/`gtin`** — затрёте идентичность Нацкаталога. Полная механика каталога, скан штрихкода и 4 режима чтения — в статье [Каталог, корзина и Нацкаталог](/guides/katalog-korzina-nackatalog).

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

Сначала прогон в песочнице (детерминированные симуляции без реального Kaspi), потом рабочий режим. Что песочница даёт и что меняется при переходе — в статье [Песочница и рабочий режим](/guides/pesochnitsa-i-rabochiy-rezhim).

- **Симуляции жизненного цикла:** `POST /invoices/{id}/simulate-status` `{ status: paid|cancelled|expired|error }` на sandbox-счёте — прогнать все ветки проведения.
- **Магические номера lookup** (`POST /clients/check`): `87770000001` → есть Kaspi (`"Иван И."`), `87770000002` → нет Kaspi.
- **Проверка доставки вебхуков** (если используете): `GET /webhook-logs?invoice_id=…`.
- **Идемпотентность проведения:** один и тот же документ, проведённый дважды, не создаёт второй счёт (`409`).
- **Обработка `429`/`Retry-After`:** уважайте заголовок, снижайте темп.
- **Тайм-зоны:** счета/возвраты/каталог — UTC `+00:00`; только `GET /tariff` и `GET /account/health` — `+05:00`. Частый баг 1С — сдвиг времени проведения.
- **Матчинг:** проведение строго по `external_order_id`; дедуп проводки по `invoice.id` + `status`.

## Частые ошибки

- **Путать `external_order_id` и `external_order_id_idempotency`.** Первый — метка для поиска документа, второй — ключ от дублей (`409`). Обычно оба равны номеру документа.
- **Пересоздавать счёт в `processing`.** Система ретраит сама — получите два живых счёта. Ждите терминальный статус, смотрите `last_kaspi_error_*`.
- **Парсить сумму как число.** В ответах суммы — строки (`"15000.00"`).
- **Сдвиг времени проведения.** Даты счетов — UTC; переводите в `Asia/Almaty` на стороне 1С.
- **Ждать `paid` после `error`.** `error → paid` невозможен; реагируйте на последний статус.

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

**Есть готовый модуль/обработка для 1С?**
Нет. Это доработка конфигурации силами вашего 1С-специалиста: HTTP-запросы из 1С к REST ApiPay. Почему готового модуля нет — на витрине [ApiPay для 1С](/kaspi-pay-1c).

**Как 1С без публичного адреса узнаёт об оплате?**
Поллингом. `GET /invoices/{id}` имеет лимит 1000/min специально под 1С и читает из кэша, не нагружая Kaspi. Вебхуки — опция, если 1С опубликована в интернет.

**Что вернётся при повторном проведении того же документа?**
`409 duplicate_idempotency_key` с `invoice_id` и статусом уже созданного счёта — если вы передали `external_order_id_idempotency`. Это норма при ретрае: работайте с существующим счётом.

**Клиент оплатил после отмены — как это выглядит?**
Статус может пройти `cancelled → paid` (или `expired → paid`). Реагируйте на **последний** статус — деньги получены, проводите оплату.

**Почему счёт долго висит в `processing`?**
При троттлинге Kaspi это легитимно; система сама ретраит. Не пересоздавайте — смотрите `last_kaspi_error_code`/`last_kaspi_error_message` в `GET /invoices/{id}`.

**Как принять в чек позиции номенклатуры?**
Через каталог: завести номенклатуру (`POST /catalog`, `external_ref` = код 1С) и выставлять счёт корзиной `cart_items`. Механика — в статье [Каталог, корзина и Нацкаталог](/guides/katalog-korzina-nackatalog).

---

ApiPay — независимый сервис и не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.
База знаний: https://apipay.kz/guides
