# Документация API PayProverkaBot — Kaspi Pay invoice

Полный технический референс Kaspi Pay invoice API: авторизация по SMS, создание счёта, статусы, отмена, возвраты, вебхуки с `X-Webhook-Signature: sha256=...`, коды ошибок.

Источник: <https://pay.proverkacheka.kz/docs/>

---

## Содержание

- [Правовые положения](#правовые-положения)
- [Начало работы](#начало-работы)
- [Создание счёта](#создание-счёта)
- [Статус и список](#статус-и-список)
- [Отмена счёта](#отмена-счёта)
- [Возврат](#возврат)
- [Вебхуки](#вебхуки)
- [Ошибки](#ошибки)
- [Пример интеграции (Python)](#пример-интеграции-python)
- [Лимиты и особенности](#лимиты-и-особенности)
- [Краткая справка по эндпоинтам](#краткая-справка-по-эндпоинтам)
- Fallback: invoice + PDF (комбо-паттерн) — <https://pay.proverkacheka.kz/docs/fallback/>

---

## Правовые положения

### Отказ от ответственности

Сервис **PayProverkaBot** (pay.proverkacheka.kz) **не связан и не аффилирован** с АО «Kaspi Bank» или любыми другими организациями группы Kaspi. Мы не являемся платёжным сервисом, эквайрингом или финансовой организацией. Мы не являемся партнёрами, представителями или агентами Kaspi.

PayProverkaBot предоставляет техническую услугу — программный интерфейс к функции «удалённый счёт» приложения Kaspi Pay для бизнеса от имени самого мерчанта, после его добровольной авторизации по SMS на его собственный номер.

### Ответственность пользователя

Используя сервис, вы подтверждаете, что:

1. Вы являетесь владельцем (или уполномоченным представителем) аккаунта Kaspi Pay для бизнеса, по которому проходит авторизация.
2. Вы соблюдаете законодательство Республики Казахстан и условия Kaspi.
3. Вы используете сервис в законных целях — автоматизация выставления счетов, интеграция с CRM, чат-ботами, кассовыми системами.
4. Вы не используете сервис для незаконной деятельности, отмывания денег, уклонения от налогов или иных противоправных действий.

Администрация PayProverkaBot не несёт ответственности за прямые или косвенные убытки, возникшие в результате использования сервиса, блокировки аккаунта Kaspi или технических сбоев на стороне Kaspi.

---

## Начало работы

### Как получить API-ключ

1. Откройте [@PayProverkaBot](https://t.me/PayProverkaBot) в Telegram.
2. Нажмите `/start` — получите персональный `API_KEY`.
3. Авторизуйтесь в Kaspi Pay: `/login` → отправьте номер телефона ИП/ТОО → введите SMS-код.
4. Готово — можете выставлять счета через бота (`/invoice 87019009393 1500 За кофе`) или через REST API.

> **Совет.** Команда `/apikey` в боте покажет ваш ключ повторно. Команда `/account` покажет, к какому аккаунту Kaspi привязана сессия.

### Базовый URL и заголовки

- **Базовый URL:** `https://pay.proverkacheka.kz/api`
- **Авторизация:** заголовок `X-API-Key: <ваш_ключ>`
- **Content-Type:** `application/json` для всех POST/PUT

### Что делает сервис

- Создаёт удалённый счёт Kaspi Pay — клиенту приходит push-уведомление в Kaspi.kz, где он подтверждает оплату одним нажатием.
- Возвращает `paymentId` для отслеживания статуса.
- Поллит статус в фоне и доставляет вебхук, как только клиент оплатил, отказался или счёт истёк.
- Поддерживает отмену неоплаченного счёта и возврат (полный / частичный) оплаченного.

### Проверка API-ключа

Сам онбординг (SMS-логин в Kaspi) проходит **через Telegram-бота** — это внутренний процесс сервиса. Через API доступна только проверка выданного ключа:

#### `GET /v1/auth/me`

Возвращает информацию о привязанной сессии Kaspi.

```bash
curl https://pay.proverkacheka.kz/api/v1/auth/me \
  -H "X-API-Key: $API_KEY"
```

---

## Создание счёта

### `POST /v1/invoice/create`

Создаёт удалённый счёт Kaspi Pay с корзиной товаров. Клиенту приходит push-уведомление в Kaspi.kz. Сумма счёта рассчитывается автоматически как `Σ(items[i].price × items[i].count)`.

#### Тело запроса

| Поле | Тип | Описание |
|------|-----|----------|
| `phoneNumber` *(required)* | string | Номер клиента. Сервис автоматически нормализует — принимаются форматы `87019009393`, `77019009393`, `7019009393`, `+7 701 900 93 93`. На вход в Kaspi уходит каноничный `8XXXXXXXXXX`. На невалидный формат — `400 invalid_phone`. |
| `items` *(required)* | array | 1–50 позиций корзины. См. ниже. |
| `comment` *(optional)* | string | Описание счёта. По умолчанию — названия позиций через запятую. |

#### Структура `items[i]`

| Поле | Тип | Описание |
|------|-----|----------|
| `name` *(required)* | string (1–200) | Название товара/услуги — показывается в чеке клиента. |
| `price` *(required)* | number > 0 | Цена за единицу в тенге. |
| `count` *(optional)* | integer > 0 | Количество. По умолчанию `1`. |

#### Пример

```bash
curl -X POST https://pay.proverkacheka.kz/api/v1/invoice/create \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "87019009393",
    "comment": "Заказ #42",
    "items": [
      { "name": "Капучино", "price": 1200, "count": 2 },
      { "name": "Чизкейк",   "price": 1800, "count": 1 }
    ]
  }'
```

```json
{
  "paymentId": "14918505280",
  "cartId": "c8a91b...",
  "amount": 4200.0,
  "status": "RemotePaymentCreated",
  "receiptUrl": null
}
```

---

## Статус и список

### `GET /v1/invoice/{paymentId}`

Возвращает актуальный статус и (после оплаты) ссылку на чек.

```bash
curl https://pay.proverkacheka.kz/api/v1/invoice/14918505280 \
  -H "X-API-Key: $API_KEY"
```

```json
{
  "paymentId": "14918505280",
  "status": "Processed",
  "amount": "4 200 ₸",
  "clientMobile": "87019009393",
  "receiptUrl": "https://receipt.kaspi.kz/..."
}
```

#### Возможные статусы

| Status | Значение |
|--------|----------|
| `RemotePaymentCreated` | Ожидает оплаты клиентом |
| `Processed` | Оплачен |
| `RemotePaymentRejected` | Клиент отказался |
| `RemotePaymentCanceled` | Отменён мерчантом |
| `RemotePaymentExpired` | Истёк срок (~24 часа) |
| `Refunded` | Полностью возвращён |
| `PartiallyRefunded` | Частично возвращён |

### `GET /v1/invoice`

Список активных (не финализированных) счетов, отслеживаемых сервисом.

```bash
curl https://pay.proverkacheka.kz/api/v1/invoice \
  -H "X-API-Key: $API_KEY"
```

---

## Отмена счёта

### `POST /v1/invoice/{paymentId}/cancel`

Отменяет **ожидающий** счёт (статус `RemotePaymentCreated`). Уже оплаченные — см. возврат.

```bash
curl -X POST https://pay.proverkacheka.kz/api/v1/invoice/14918505280/cancel \
  -H "X-API-Key: $API_KEY"
```

```json
{ "cancelled": true, "paymentId": "14918505280", "status": "RemotePaymentCanceled" }
```

---

## Возврат

### `POST /v1/invoice/{paymentId}/refund`

Возвращает деньги по **оплаченному** счёту (`Processed`). Поддерживается полный и частичный возврат. Если `amount` опущен — возвращается весь остаток `AvailableReturnAmount`.

#### Тело запроса

| Поле | Тип | Описание |
|------|-----|----------|
| `amount` *(optional)* | number > 0 | Сумма возврата в тенге. Опустите для полного возврата. |

```bash
# Частичный возврат
curl -X POST https://pay.proverkacheka.kz/api/v1/invoice/14918505280/refund \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 1200}'
```

```json
{
  "paymentId": "14918505280",
  "refundedAmount": 1200.0,
  "isFull": false,
  "remainingRefundable": 3000.0,
  "refundsTotal": 1
}
```

### `GET /v1/invoice/{paymentId}/refunds`

История возвратов по счёту.

---

## Вебхуки

Подпишитесь на события платежей — сервис будет POST'ить на ваш URL JSON-пейлоад каждый раз, когда меняется финальный статус. Это рекомендуемый способ интеграции вместо поллинга.

### События

| Event | Когда срабатывает |
|-------|-------------------|
| `payment.success` | Клиент оплатил счёт |
| `payment.failed` | Клиент отказался или мерчант отменил |
| `payment.expired` | Истёк срок ожидания оплаты |
| `payment.refunded` | Произведён возврат (полный или частичный) |
| `*` | Подписка на все события сразу |

### `POST /v1/webhooks`

```bash
curl -X POST https://pay.proverkacheka.kz/api/v1/webhooks \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/kaspi/webhook",
    "events": ["payment.success", "payment.refunded"],
    "secret": "my-shared-secret-min-8-chars"
  }'
```

```json
{ "id": 7, "url": "https://example.com/...", "events": ["payment.success","payment.refunded"], "has_secret": true, "is_active": true }
```

### `GET /v1/webhooks` · `DELETE /v1/webhooks/{id}`

Просмотр и удаление подписок.

### Формат пейлоада

```http
POST https://example.com/kaspi/webhook
Content-Type: application/json
X-Webhook-Event: payment.success
X-Webhook-Attempt: 1
X-Webhook-Signature: sha256=<hex-hmac>
User-Agent: kaspipayinvoice-webhooks/1.0

{
  "event": "payment.success",
  "paymentId": "14918505280",
  "type": "invoice",
  "status": "Processed",
  "amount": 4200.0,
  "clientMobile": "87019009393",
  "receiptUrl": "https://receipt.kaspi.kz/...",
  "extras": {},
  "timestamp": 1747049280
}
```

### Проверка подписи

Заголовок `X-Webhook-Signature` = `sha256=` + HMAC-SHA256 от **сырого тела запроса**, ключ — ваш `secret`. Считайте подпись на своей стороне и сравнивайте константно (`hmac.compare_digest`).

```python
import hmac, hashlib

def verify(body_bytes: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), body_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)
```

> **Retry-политика.** На любой ответ кроме `2xx` или таймаут сервис повторит доставку с экспоненциальным backoff (несколько попыток в течение суток). Возвращайте `2xx` только после того, как успешно приняли событие.

---

## Ошибки

| HTTP | Когда |
|------|-------|
| `401` | Отсутствует или невалиден `X-API-Key`. |
| `400` | Неподдерживаемое событие вебхука, попытка возврата суммы больше `AvailableReturnAmount`, отмена уже завершённого счёта, небезопасный URL вебхука. |
| `404` | Вебхук не найден. |
| `412` | `kaspi_session_required` — сессия Kaspi отсутствует или была сброшена (например, вы залогинились в приложении Kaspi Pay на телефоне). Заново пройдите SMS-флоу. |
| `422` | Pydantic-валидация тела запроса (например, пустой `items`, `price ≤ 0`). |
| `502` | `bad_gateway` — Kaspi вернул ошибку. `detail` содержит `{"kaspi": "...", "code": N}`. |
| `500` | Внутренняя ошибка сервиса — администраторы уже получили алерт. |

#### Частые причины

- **«kaspi_session_required»** — вы или ваш сотрудник вошли в Kaspi Pay на смартфоне, и Kaspi выгнал нашу сессию. Зайдите в бот → `/login` → пройдите SMS заново.
- **Номер телефона неверен** (`400 invalid_phone`) — сервис принимает 10 или 11 цифр (с 8 или 7 в начале, можно с пробелами/скобками/плюсом). Если получили эту ошибку — в строке скорее всего меньше 10 цифр или есть лишние.
- **Клиент не получил push** — у клиента не установлен Kaspi.kz или отключены уведомления. Сумма всё равно зачислится после оплаты вручную в приложении.

---

## Пример интеграции (Python)

```python
import os, requests

API = "https://pay.proverkacheka.kz/api"
KEY = os.environ["KASPI_API_KEY"]
H   = {"X-API-Key": KEY, "Content-Type": "application/json"}

# 1. Выставить счёт
r = requests.post(f"{API}/v1/invoice/create", headers=H, json={
    "phoneNumber": "87019009393",
    "comment": "Заказ #42",
    "items": [
        {"name": "Капучино", "price": 1200, "count": 2},
        {"name": "Чизкейк",   "price": 1800, "count": 1},
    ],
})
r.raise_for_status()
payment_id = r.json()["paymentId"]
print("Счёт создан:", payment_id)

# 2. (Опционально) проверить статус — но лучше подписаться на вебхук
status = requests.get(f"{API}/v1/invoice/{payment_id}", headers=H).json()
print(status["status"])

# 3. Подписаться на вебхук один раз при старте бота/CRM
requests.post(f"{API}/v1/webhooks", headers=H, json={
    "url": "https://my-bot.example.com/kaspi/webhook",
    "events": ["payment.success", "payment.failed", "payment.expired", "payment.refunded"],
    "secret": "long-random-shared-secret",
})
```

#### Пример приёма вебхука (Flask)

```python
from flask import Flask, request, abort
import hmac, hashlib, os

SECRET = os.environ["KASPI_WEBHOOK_SECRET"].encode()
app = Flask(__name__)

@app.post("/kaspi/webhook")
def webhook():
    body = request.get_data()
    sig  = request.headers.get("X-Webhook-Signature", "")
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(401)

    event = request.json
    if event["event"] == "payment.success":
        notify_customer(event["paymentId"], event["amount"])
    return "", 200
```

---

## Лимиты и особенности

| Параметр | Значение |
|----------|----------|
| Срок действия неоплаченного счёта | ~24 часа (контролируется Kaspi) |
| Позиций в корзине | 1–50 |
| Формат номера клиента | 10/11 цифр, любой казахстанский формат (нормализуется в `8XXXXXXXXXX`) |
| Минимальная сумма позиции | > 0 ₸ |
| Активных счетов одновременно | без жёсткого лимита (поллим все) |
| Webhook retry | экспоненциальный backoff, до суток |
| Алгоритм подписи webhook | HMAC-SHA256 над сырым телом |

---

## Краткая справка по эндпоинтам

| Метод | Путь | Описание |
|-------|------|----------|
| GET | `/v1/auth/me` | Информация о ключе и привязанной сессии Kaspi |
| POST | `/v1/invoice/create` | Создать счёт |
| GET | `/v1/invoice/{id}` | Статус счёта |
| GET | `/v1/invoice` | Активные счета |
| POST | `/v1/invoice/{id}/cancel` | Отменить ожидающий счёт |
| POST | `/v1/invoice/{id}/refund` | Возврат оплаченного счёта |
| GET | `/v1/invoice/{id}/refunds` | История возвратов |
| POST | `/v1/webhooks` | Подписаться на события |
| GET | `/v1/webhooks` | Список подписок |
| DELETE | `/v1/webhooks/{id}` | Удалить подписку |

---

Не аффилирован с Kaspi Bank. Сервис предоставляет программный доступ к вашему собственному аккаунту Kaspi Pay для бизнеса после вашей добровольной авторизации.

Поддержка в Telegram: <https://t.me/PayProverkaBot>
