# Счёт жасау: барлық өрістер

> POST /api/v1/invoices эндпоинтінің толық анықтамасы — әр өрістің типі мен шектеуі, жауаптағы барлық өріс, curl мен Node мысалдары, qr мен phone айырмашылығы және жиі кездесетін қателер.

## Қысқаша

Счёт жасау — бір ғана сұрау:

```
POST https://api.qut.kz/api/v1/invoices
X-API-Key: qp_live_…
Content-Type: application/json
```

Жауапта 201 күйі және `payUrl` келеді — клиентті сол адреске жіберіңіз. Міндетті өріс біреу ғана: `amount`.

Ескі жол `POST /api/v1/orders` да қабылданады, ол дәл осы эндпоинттің alias-ы.

## Сұраудың өрістері

| Өріс | Тип | Міндетті | Сипаттама |
|---|---|---|---|
| `amount` | number | ✅ | Теңге. QR счётта ең көбі 2 ондық, телефонға счётта бүтін сан |
| `kind` | `qr` \| `phone` | — | `qr` — әдепкі: QR код және сілтеме. `phone` — клиенттің Kaspi қосымшасына push |
| `description` | string | — | Клиент көреді. QR — 100 таңба, phone — 60 таңба |
| `externalOrderId` | string | — | Сіздің тапсырыс нөміріңіз. Webhook-та қайта келеді |
| `customer.name` | string | — | Клиенттің аты |
| `customer.phone` | string | `kind: phone` үшін ✅ | `7XXXXXXXXXX` пішімінде, 11 сан |
| `customer.email` | string | — | Берілсе клиентке чек хаты кетуі мүмкін |
| `successUrl` | url | — | Төлем сәтті өткенде клиент қайтатын адрес. Тек http(s) |
| `failUrl` | url | — | Төлем өтпегенде қайтатын адрес. Тек http(s) |
| `metadata` | object | — | Кез келген JSON. Өзгертілмей сақталады және webhook-та қайта келеді |

Тақырыптар:

| Тақырып | Міндетті | Не үшін |
|---|---|---|
| `X-API-Key` | ✅ | `qp_live_…` немесе `qp_test_…` |
| `Content-Type: application/json` | ✅ | Дене JSON |
| `Idempotency-Key` | — | Қайталаудан қорғайды, төменде |

`Idempotency-Key` жіберсеңіз, сол кілтпен екінші рет сұрау жаңа счёт жасамайды: бұрынғысы қайтады, HTTP 200 және жауапта `idempotentReplay: true` болады. Толығырақ: [Идемпоттылық](/kb/idempotency).

## qr мен phone айырмашылығы

| | `qr` | `phone` |
|---|---|---|
| Клиент не көреді | QR код немесе төлем сілтемесі | Kaspi қосымшасындағы push-счёт |
| `customer.phone` | Міндетті емес | Міндетті |
| Сома | 2 ондыққа дейін | Тек бүтін теңге |
| `description` | 100 таңба | 60 таңба |
| Қашан ыңғайлы | Сайт, офлайн нүкте, экран | Телефон арқылы сату, қашықтан |

Толық салыстыру: [QR счёт пен телефонға счёт](/kb/qr-vs-phone).

## Жауап

HTTP 201 және мынандай дене:

| Өріс | Не |
|---|---|
| `id` | Счёттың идентификаторы, `inv_…` |
| `status` | Жасалғанда `pending` |
| `payUrl` | Төлем беті, клиентті осында жіберіңіз |
| `qrUrl` | QR-дың мазмұны |
| `deepLink` | Kaspi қосымшасын ашатын сілтеме |
| `qrImageUrl` | QR суреті, өз бетіңізге қоюға болады |
| `expiresAt` | Осы уақыттан кейін счёт жарамсыз |

QR-дың сканерлеу терезесі шамамен үш минут, оны Kaspi белгілейді. Оны кодыңызда тұрақты сан деп жазбаңыз — әрқашан `expiresAt` өрісінен алыңыз.

## curl мысалы

```bash
curl -X POST https://api.qut.kz/api/v1/invoices \
  -H 'X-API-Key: qp_live_…' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-1001' \
  -d '{
    "amount": 2500,
    "kind": "qr",
    "description": "Тапсырыс №1001",
    "externalOrderId": "1001",
    "customer": { "name": "Айгүл", "phone": "77010000000" },
    "successUrl": "https://site.kz/ok",
    "failUrl": "https://site.kz/fail",
    "metadata": { "branch": "almaty-1", "cart": 17 }
  }'
```

## Node мысалы

```js
const res = await fetch('https://api.qut.kz/api/v1/invoices', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.QUTPAY_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': `order-${order.id}`,
  },
  body: JSON.stringify({
    amount: order.total,
    kind: 'qr',
    description: `Тапсырыс №${order.id}`,
    externalOrderId: String(order.id),
    successUrl: 'https://site.kz/ok',
    metadata: { orderId: order.id },
  }),
});

if (!res.ok) {
  const err = await res.json();
  console.error('qutpay', res.status, err.error, err.message);
  throw new Error(err.error);
}

const invoice = await res.json();
redirect(invoice.payUrl);
```

Қате болғанда `error` кодына қараңыз, `message` мәтініне емес: мәтін өзгеруі мүмкін, код өзгермейді.

## /orders alias

Ескі интеграциялар үшін `POST /api/v1/orders` жолы сақталған. Онда `merchantRef` (яғни `externalOrderId`) және `method: "invoice"` өрістері де қабылданады. Жаңа код жазып жатсаңыз, `/api/v1/invoices` қолданыңыз.

## Жиі кездесетін қателер

| Код | HTTP | Не болды | Шешімі |
|---|---|---|---|
| `invalid_amount` | 422 | Сома жоқ немесе сан емес | Оң сан жіберіңіз |
| `amount_must_be_whole_tenge` | 422 | Тиын жіберілген | Бүтін теңге жіберіңіз |
| `amount_too_small` / `amount_too_large` | 422 | Сома шектен тыс | Соманы түзетіңіз |
| `invalid_phone` | 422 | Телефон пішімі бөлек | `7XXXXXXXXXX`, 11 сан, `+` және бос орынсыз |
| `phone_required` | 422 | `kind: phone`, бірақ телефон жоқ | `customer.phone` қосыңыз |
| `invalid_kind` | 422 | Белгісіз түр | `qr` немесе `phone` |
| `invalid_url` | 422 | `successUrl`/`failUrl` дұрыс емес | Толық http(s) адрес жазыңыз |
| `unauthorized` | 401 | Кілт жоқ немесе жарамсыз | `X-API-Key` тақырыбын тексеріңіз |
| `insufficient_scope` | 403 | Кілтте `invoices:write` жоқ | Кабинеттен scope қосыңыз |
| `kaspi_session_expired` | 409 | Кассир байланысы үзілген | Қайта байланыстырыңыз |
| `tariff_limit_reached` | 429 | Айлық лимит бітті | [Тарифтер және лимиттер](/kb/tariff-limits) |
| `invoice_create_failed` | 502 | Kaspi счётты қабылдамады | Өсіп отыратын кідіріспен қайталаңыз |

Толық тізім: [Қателер каталогы](/kb/error-catalog).

## Шекті жағдайлар

- **Тиын.** QR счёт екі ондықты қабылдайды, ал телефонға счёт бүтін теңгені ғана. Сомаңызда тиын болса, `kind: phone` алдында дөңгелектеңіз.
- **Ұзын сипаттама.** Шектен асқан мәтін клиентте қиылып көрінуі мүмкін, сондықтан QR үшін 100, phone үшін 60 таңбадан асырмаңыз.
- **Бірнеше счёт бір тапсырысқа.** Клиент «төлеу» батырмасын екі рет бассаңыз екі счёт жасалады. `Idempotency-Key` қойыңыз.
- **Көп счёт бірден керек.** 100-ге дейін счётты бір сұраумен жасауға болады: [Топтап счёт жасау](/kb/bulk-invoices).
- **Жауапты күтіп қалдыңыз.** Таймаут болса счёт жасалған да болуы мүмкін. Сол `Idempotency-Key` мен қайталаңыз — жаңасы жасалмайды.

## Жиі қойылатын сұрақтар

**`payUrl` мен `deepLink` айырмашылығы неде?** `payUrl` — браузерде ашылатын төлем беті, кез келген құрылғыда жұмыс істейді. `deepLink` — Kaspi қосымшасын тікелей ашады, телефонда ыңғайлы.

**Счёттың күйін қалай білемін?** Webhook арқылы немесе `GET /api/v1/invoices/{id}` сұрауымен. Қайсысын қашан: [Webhook пен күйді сұрау](/kb/polling-vs-webhook).

**`metadata` ішіне не жазуға болады?** Кез келген JSON: нүкте нөмірі, себет идентификаторы, ұяшық нөмірі. Ол webhook-та да, күй сұрауында да сол күйінде қайтады.

**Счёт жасалғаннан кейін сомасын өзгертуге бола ма?** Жоқ. Счётты болдырып, жаңасын жасаңыз.

**Sandbox-та да осы өрістер жүре ме?** Иә, бірдей. Айырмашылығы — Kaspi шақырылмайды, төлемді өзіңіз симуляциялайсыз.
