Қысқаша
Бір сұрауда бірнеше счёт жасау керек болса — POST /api/v1/invoices/bulk. Бір сұрауға 1-ден 100-ге дейін элемент сыяды.
Ең маңызды қасиеті: әр элемент бөлек тексеріледі және бөлек орындалады. Тізімдегі біреуінің телефоны қате болса, қалғандарының бәрі бәрібір жасалады. Сондықтан жауапты «өтті / өтпеді» деп емес, әр элемент бойынша оқисыз.
Сұрау
POST /api/v1/invoices/bulk
X-API-Key: qp_live_…
Idempotency-Key: payroll-2026-09-14
Content-Type: application/json
{
"invoices": [
{ "amount": 12000, "kind": "phone", "customer": { "phone": "77011234567" }, "externalOrderId": "drv-101" },
{ "amount": 12000, "kind": "phone", "customer": { "phone": "77017654321" }, "externalOrderId": "drv-102" },
{ "amount": 8500, "kind": "qr", "description": "Қыркүйек айы", "externalOrderId": "drv-103" }
]
}
Әр элементтің ішінде — жалғыз счёт жасағандағы сол өрістер: amount, kind, description, externalOrderId, customer, successUrl, failUrl, metadata. Ешқандай жаңа өріс жоқ, ешқайсысы жоғалмайды.
| Шектеу | Мәні |
|---|---|
| Ең аз элемент | 1 |
| Ең көп элемент | 100 |
| Аралас түрлер | Бір тізімде qr да, phone да болады |
| Кілт | Бір кілт, бір ұйым, бір кассир |
Жауап
Жауап коды — 207: «әрқайсысында өз нәтижесі бар». Құрылымы:
{
"total": 3,
"created": 2,
"failed": 1,
"results": [
{ "index": 0, "ok": true, "id": "inv_7Kd2", "status": "pending", "payUrl": "https://qut.kz/p/…", "externalOrderId": "drv-101" },
{ "index": 1, "ok": true, "id": "inv_7Kd3", "status": "pending", "payUrl": "https://qut.kz/p/…", "externalOrderId": "drv-102" },
{ "index": 2, "ok": false, "error": "invalid_amount", "message": "Сома дұрыс емес", "externalOrderId": "drv-103" }
]
}
| Өріс | Мағынасы |
|---|---|
total | Жіберілген элемент саны |
created | Сәтті жасалғаны |
failed | Құлағаны |
results[].index | Сіздің тізіміңіздегі реттік нөмір, 0-ден басталады |
results[].ok | Осы элемент өтті ме |
results[].error | Құласа — қате коды |
results массивінің реті сіз жіберген реттен өзгермейді, сондықтан index бойынша өз деректеріңізбен салыстыра аласыз. Бірақ сенімді болу үшін әр элементке externalOrderId жазып қойған дұрыс — ол жауапта да, кейін webhook-та да қайта келеді.
Қателерді өңдеу
Негізгі қағида: бүкіл топты емес, тек құлаған элементтерді қайта жіберіңіз.
const res = await fetch(`${API}/invoices/bulk`, {
method: 'POST',
headers: {
'X-API-Key': KEY,
'Content-Type': 'application/json',
'Idempotency-Key': batchKey,
},
body: JSON.stringify({ invoices: batch }),
}).then((r) => r.json());
const retry = [];
for (const r of res.results) {
if (r.ok) {
saveInvoice(batch[r.index], r.id, r.payUrl);
continue;
}
if (['invalid_amount', 'invalid_phone', 'amount_must_be_whole_tenge'].includes(r.error)) {
// деректің өзі қате — қайталаудың мәні жоқ, операторға көрсетіңіз
reportToOperator(batch[r.index], r.error);
} else if (['invoice_create_failed', 'kaspi_error'].includes(r.error)) {
// уақытша ақау — кейін қайталауға болады
retry.push(batch[r.index]);
}
}
Қате түрлерін былай бөліңіз:
| Қате тобы | Мысал кодтар | Не істеу керек |
|---|---|---|
| Деректің өзі қате | invalid_amount, invalid_phone, invalid_kind, amount_must_be_whole_tenge | Түзетпей қайталамаңыз |
| Уақытша ақау | invoice_create_failed, kaspi_error | Кідіріспен қайталаңыз |
| Лимит | tariff_limit_reached, tariff_daily_burst | Тоқтатыңыз, тарифті қараңыз |
| Байланыс | kaspi_session_expired, no_provider | Кассирді қалпына келтіріңіз, содан кейін қайталаңыз |
Бүкіл сұрау бірден құлайтын жағдайлар да бар: кілт жарамсыз (unauthorized), құқық жоқ (insufficient_scope), тізім бос немесе 100-ден асып кеткен. Ондайда results мүлдем келмейді, әдеттегі { error, message } қайтады.
Идемпоттылық
Топтама сұрауында да Idempotency-Key тақырыбы жұмыс істейді. Бір кілтпен екінші рет жіберсеңіз, жаңа счёттар жасалмай, бұрынғы нәтиже қайтады.
Бұл желі үзілген жағдайда өте маңызды: жауапты ала алмасаңыз, жаңа счёттар жасалып қалды ма деп қорықпай, сол кілтпен қайта жіберіп, нәтижені оқисыз.
Кілтті мазмұнға байланысты етіп жасаңыз, мысалы payroll-2026-09-14 немесе orders-batch-4471. Ал әр элементтің ішіндегі externalOrderId жеке счёт деңгейінде қорғайды — екеуін қатар қолданған дұрыс. Толығырақ: Счёттар қосарланып жатыр.
Тариф лимитіне әсері
Топтама — лимит үшін жеңілдік емес. Сәтті жасалған әр счёт айлық лимитке де, тәуліктік қорғанысқа да жеке-жеке есептеледі.
| Тариф | Айына счёт | Тәуліктік қорғаныс |
|---|---|---|
| Сынақ | — | 50 |
| Бастау | 800 | 200 |
| Бизнес | 4 000 | 1 500 |
| Про | 15 000 | 5 000 |
Яғни, Бастау тарифінде бір тәулікте 200-ден көп счёт жасай алмайсыз — 100-дік екі топтама лимитті толтырады. Лимитке жеткенде элементтер tariff_limit_reached немесе tariff_daily_burst қатесімен құлай бастайды. Екеуі екі басқа нәрсе: біріншісі — тарифіңіздің айлық лимиті, екіншісі — циклге түскен интеграциядан қорғаныс.
Sandbox счёттары лимитке кірмейді, сондықтан топтаманы алдымен qp_test_… кілтімен сынап көріңіз.
Kaspi жиілік шектеуі болса
Kaspi өз жағынан сұрау жиілігін шектеуі мүмкін. Топтама жіберіп, элементтердің біраз бөлігі invoice_create_failed немесе kaspi_error қатесімен құлап жатса, бұл — жиілік белгісі.
Не істеу керек:
- Топтама көлемін кішірейтіңіз. 100 орнына 20-25 элементтен жіберіңіз.
- Топтамалар арасына пауза қойыңыз. 2-5 секунд жеткілікті.
- Өсіп отыратын кідіріспен қайталаңыз. 1, 2, 4, 8 секунд.
429жәнеRetry-Afterтақырыбы келсе, сол көрсетілген уақытты күтіңіз.- Барлық элемент бірдей құлап жатса, қайта-қайта жібермей тоқтаңыз — себебі көбіне кассир байланысында немесе тарифте.
Жаппай құлаудың себебін іздеу реті бөлек жазылған: Счёттар жаппай құлап жатыр.
Қашан топтама керек, қашан керек емес
| Сценарий | Дұрысы |
|---|---|
| Таксопарк жүргізушілерінен айлық жарна | Топтама |
| Курс тыңдаушыларына кезекті төлем | Топтама |
| Көтерме клиенттерге ай сайынғы есеп-шот | Топтама |
| Сайттағы бір тапсырыс | Жалғыз POST /invoices |
| Әрқайсысы әртүрлі уақытта шығатын кезеңдік төлем | Жазылым |
Ондаған мың счёт керек болса, топтамаларды кезекке қойып, біртіндеп жіберіңіз — барлығын бір мезетте «атып» жіберу тәуліктік қорғанысқа тіреледі.
Жиі қойылатын сұрақтар
Бір элемент құласа, қалғандары күшін жоя ма? Жоқ. Жасалғаны жасалған күйінде қалады. Транзакция жоқ — топтама «бәрі немесе ештеңе» принципімен жұмыс істемейді.
Webhook топтамаға бір рет келе ме? Жоқ, әр счёт бойынша әдеттегі оқиғалар жеке келеді. Одан бөлек invoice.bulk оқиғасы топтаманың өзі туралы хабарлайды.
100-ден көп жіберсем не болады? Сұрау түгел қабылданбайды. Тізімді өзіңіз 100-ден кіші бөліктерге бөліңіз.
Бір топтамада әртүрлі кассир бола ма? Жоқ. Счёттар кілтке байланған кассир арқылы жасалады. Әр кассир үшін бөлек кілт қолданыңыз: API кілттер.
Кабинеттен топтап счёт жасауға бола ма? API арқылы жасалған счёттар кабинетте әдеттегідей көрінеді, экспортқа да түседі.