# Справочник Aitole API

Базовый адрес: `https://api.aitole.kz`

Приём Kaspi Pay по REST поверх аккаунта мерчанта. Все суммы — целые тенге.
Время — ISO 8601 в UTC. Тела запросов и ответов — JSON.

## Два контура доступа

| контур | заголовок | что делает |
|---|---|---|
| ключ интеграции | `X-API-Key: sk_live_…` / `sk_test_…` | принимает платежи: `/v2/*` |
| токен кабинета | `Authorization: Bearer …` | управляет аккаунтом: `/me/*` |

Ключ не управляет кабинетом, токен не двигает деньги. Это разделение обеспечено
структурой, а не проверками внутри обработчиков.

**Режим ключа жёстко связан с контуром подключения.** `sk_test_` работает только
с песочницей, `sk_live_` только с настоящим Kaspi. Попытка смешать даёт
`403 key_mode_mismatch`. Поэтому тестовым ключом невозможно списать настоящие деньги.

## Ошибка всегда выглядит одинаково

```json
{
  "error": {
    "code": "validation_error",
    "message": "Сумма должна быть целым числом тенге",
    "category": "permanent",
    "request_id": "req_0f3c…"
  }
}
```

`category` говорит, что делать: `transient` — повторить, `permanent` — не повторять,
`auth` — чинить доступ, `rate_limit` — подождать `Retry-After`.

## Выставить QR

```bash
curl -X POST https://api.aitole.kz/v2/qr \
  -H "X-API-Key: sk_test_…" \
  -H "Idempotency-Key: order-1024" \
  -H "Content-Type: application/json" \
  -d '{"amount": 5000, "comment": "Заказ №1024"}'
```

Ответ — операция со статусом `pending`, полем `qr_token` (ссылка для покупателя)
и `expires_at`. Покажите ссылку клиенту; оплату подтвердит событие, а не этот ответ.

**Провайдер может отказать с HTTP 200.** Тогда в ответе придёт `status: "failed"`
и `failure_reason` с `failure_message` от Kaspi. Это не ошибка транспорта, проверяйте статус.

## Счёт на номер телефона

```bash
curl -X POST https://api.aitole.kz/v2/invoices \
  -H "X-API-Key: sk_live_…" \
  -H "Idempotency-Key: order-1025" \
  -H "Content-Type: application/json" \
  -d '{"amount": 12000, "phone": "+77001234567", "comment": "Заказ №1025"}'
```

Покупателю приходит счёт в приложение Kaspi. Номер — в формате E.164;
к виду, который ждёт Kaspi, платформа приводит сама.

## Идемпотентность

`Idempotency-Key` обязателен для всех операций, создающих платёж. Повтор с тем же
ключом возвращает тот же результат, а не второй счёт. Ключ уникален **в пределах
аккаунта**, а не проекта: если два ваших проекта нумеруют заказы независимо,
добавьте префикс — `shop-a-1024`, а не `1024`.

## Узнать об оплате

Два способа, и лучше оба.

**Вебхук** — приходит сам за несколько секунд.

```
POST ваш-адрес
X-Webhook-Signature: sha256=<hex>
Content-Type: application/json

{"id":"evt_…","type":"payment.paid","created_at":"…","data":{…}}
```

Подпись — HMAC-SHA256 от строки `"{timestamp}.{сырое тело}"` секретом эндпоинта.
Проверяйте её по сырому телу до `JSON.parse`. Свежесть — 300 секунд.
Неудачные доставки повторяются через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч и 24 ч;
после двух суток непрерывных отказов эндпоинт отключается.

**Журнал событий** — `GET /v2/events`, курсорная пагинация. Резерв на случай,
когда вебхук не дошёл: история остаётся здесь всегда.

Типы событий: `payment.created`, `payment.paid`, `payment.expired`,
`payment.cancelled`, `payment.refunded`, `connection.session_expired`,
`connection.restored`, `billing.past_due`, `billing.downgraded`.

Каждое платёжное событие несёт `connection_id` — через какую кассу прошёл платёж.

## Возврат

```bash
curl -X POST https://api.aitole.kz/v2/refunds \
  -H "X-API-Key: sk_live_…" \
  -H "Idempotency-Key: refund-1024-1" \
  -H "Content-Type: application/json" \
  -d '{"operation_id": "…", "amount": 5000}'
```

Полный или частичный. Операция остаётся `paid`: возврат ложится поверх платежа,
а не отменяет его. Окно — 90 дней. Нужен ключ со scope `refund`.

## Песочница

Песочница — это подключение с драйвером `mock`. Настоящий Kaspi не участвует,
деньги не двигаются, `qr_token` заведомо ненастоящий.

```bash
# довести песочный счёт до оплаты
curl -X POST https://api.aitole.kz/v2/sandbox/operations/{id}/pay -H "X-API-Key: sk_test_…"
# или просрочить
curl -X POST https://api.aitole.kz/v2/sandbox/operations/{id}/expire -H "X-API-Key: sk_test_…"
```

Эти ручки меняют состояние **у провайдера**, а статус вашей операции двигает тот
же поллер, что и в бою, с той же задержкой. Вы проверяете ровно тот код, который
отработает на живых деньгах.

## Несколько Kaspi-касс

У аккаунта может быть несколько боевых подключений. Касса выбирается по порядку:
`connection_id` в запросе → касса, к которой привязан ключ → основная касса →
единственная активная.

Привязка ключа — **граница**, а не умолчание: ключ кассы A не выставит счёт
на кассу B, даже если передать её `connection_id`, ответ будет `403 forbidden`.
Если активных касс несколько и выбор не сделан — `409 connection_ambiguous`.

## Сессия Kaspi

У Kaspi одна активная сессия на номер. Вход в приложение Kaspi Pay под номером
кассира вытесняет сессию платформы: подключение уходит в `session_expired`,
новые платежи отвергаются с `409 no_active_connection`, в журнал пишется
`connection.session_expired`. Восстановление — повторный вход по SMS в кабинете.

Поэтому номер кассира, отданный платформе, должен быть выделенным.

## Лимиты

Частота запросов ограничена по ключу. Тарифные лимиты считаются по **оплаченным**
платежам, а не по выставленным счетам: брошенные покупателем счета квоту не расходуют.

## Полный список эндпоинтов

```
POST   /v2/qr                                   создать QR
POST   /v2/invoices                             счёт на телефон
GET    /v2/operations                           список операций
GET    /v2/operations/{id}                      операция по id
POST   /v2/operations/{id}/cancel               отменить
POST   /v2/refunds                              возврат
GET    /v2/events                               журнал событий
GET    /v2/status                               health, без ключа
POST   /v2/sandbox/operations/{id}/pay          песочница: оплатить
POST   /v2/sandbox/operations/{id}/expire       песочница: просрочить
POST   /v2/sandbox/connections/{id}/kill-session песочница: убить сессию
```

Управление аккаунтом (`/me/*`, по токену кабинета): ключи, подключения Kaspi,
вебхуки, уведомления, подписка, операции и события на чтение.
