API құжаттамасы
Бір HTTP сұраныспен Kaspi QR жасап, төлем өткенде webhook аласыз. Контракт gateway-дің kaspi-contract v1-імен бірдей: соған жазылған клиент AinHouse Pay-мен өзгеріссіз жұмыс істейді.
Кіріспе
- Негізгі адрес:
https://pay.ainhouse.kz/api/v1 - Аутентификация:
Authorization: Bearer qk_live_…неqk_test_…. Кілттерді кабинеттің «Сайттар» бөлімі бір рет көрсетеді — сақтап алыңыз. - Дене мен жауап — JSON. Сома — бүтін теңге (тиын емес), 0-ден үлкен.
orderId— сіздің тапсырысыңыздың id-і, үлгісі^[A-Za-z0-9._:-]{1,64}$.- Уақыт — ISO-8601 белдеуімен: Kaspi мәндері +05:00, біздікі UTC (Z).
- Шектеу: бір кілтке минутына 120 сұраныс.
PHP 5 минутта
- Тіркеліп, «Сайттар» бөлімінде сайт қосыңыз да, qk_test_ кілтін көшіріңіз — тест режимі бірден жұмыс істейді.
- Төмендегі кодты іске қосыңыз — сатып алушы дайын төлем бетіне өтеді.
- Webhook-ты қабылдаңыз: тест оқиғалары сайттың тест webhook URL-іне келеді.
- Нақты төлем үшін: бизнес анкетасын толтырыңыз, мақұлданған соң кабинетте Kaspi Pay кассаңызды қосып, qk_live_ кілтіне ауысыңыз.
<?php
// Кітапханасыз: PHP 7.4+ және curl кеңейтімі.
$apiKey = getenv('QRKASSA_KEY'); // тексеру үшін qk_test_…, жұмыста qk_live_…
$ch = curl_init('https://pay.ainhouse.kz/api/v1/payments/qr');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 12500, // бүтін теңге
'orderId' => 'order-1042', // тапсырыс нөміріңіз
'description' => 'Тапсырыс №1042',
]),
]);
$payment = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 200 || $status === 201) {
// Ең оңайы — сатып алушыны дайын төлем бетіне жіберу.
header('Location: ' . $payment['checkoutUrl']);
exit;
}
// 402 — жазылым төленбеген, 503 — касса қосылмаған: «Kaspi уақытша
// қолжетімсіз» деп көрсетіп, басқа төлем тәсілін ұсыныңыз.
error_log('QRkassa: ' . $status . ' ' . ($payment['error'] ?? ''));
curl -X POST https://pay.ainhouse.kz/api/v1/payments/qr \
-H "Authorization: Bearer qk_test_..." \
-H "Content-Type: application/json" \
-d '{"amount": 12500, "orderId": "order-1042", "description": "Тапсырыс №1042"}'
Эндпойнттар
Барлығы https://pay.ainhouse.kz/api/v1 астында.
| Сұраныс | Не істейді |
|---|---|
| POST /payments/qr | QR жасау. Дене: amount, orderId, description?. 201 — жаңа, 200 — сол orderId-дің күтіп тұрған QR-ы. |
| POST /payments/invoice | Сатып алушының Kaspi қосымшасына шот. Дене: amount, orderId, phone (10 цифр), comment?. |
| GET /payments/{paymentId} | Төлем. ?refresh=1 — Kaspi-ден мәртебені қайта сұрайды. |
| GET /payments?orderId=… | orderId бойынша барлық әрекет, жаңасы бірінші: {"payments": [...]}. |
| POST /payments/{paymentId}/cancel | Тек шотты болдырмау. QR болдырылмайды — 5 минутта өзі бітеді (409 not_cancellable). |
| POST /payments/{paymentId}/refund | Төленген QR-ды толық не жартылай қайтару. Дене: amount?, reason?; Idempotency-Key тақырыбы. Толығы — «Қайтарулар» бөлімінде. |
| GET /payments/{paymentId}/refunds | Төлемнің қайтарулары, жаңасы бірінші: {"refunds": [...]}. |
| POST /links | Төлем сілтемесі. Дене: amount, description, orderPrefix?. Жауап: {"url", "slug"}. |
| GET /status | Касса, сессия және жазылым күйі. Kaspi-ге сұраныс жібермейді. |
GET /status
Gateway-дің /status пішімімен бірдей (merchant, session, pending), оған subscription қосылған. state: not_started | trial | active | free | grace | expired | blocked.
not_started — тегін кезең әлі басталмаған: ол Kaspi кассасы алғаш қосылғанда басталады, сондықтан trialEndsAt — null. Бұл күйде тест кілттері мен оқу сұраныстары жұмыс істейді, ал live төлемге касса керек (503 cashier_not_connected).
free — AinHouse Pay сізге тегін ашқан: API әдеттегідей жұмыс істейді, freeUntil — мерзімнің соңы (мерзімсіз болса, null).
{
"merchant": {
"id": "01JC8Z5W4X3Y2Z1A0B9C8D7E6F",
"name": "ИП «Арман»",
"orgName": "ИП АРМАНОВ А.",
"organizationIdn": "900101300123",
"phoneHint": "+7 777 *** ** 41"
},
"session": { "state": "active", "authorizedAt": "2026-11-08T09:12:00Z", "lostAt": null },
"pending": 3,
"gatewayTime": "2026-11-08T11:40:00Z",
"appVersion": "26.1029",
"subscription": { "state": "trial", "trialEndsAt": "2026-11-15T09:00:00Z", "paidUntil": null, "freeUntil": null }
}
POST /links
curl -X POST https://pay.ainhouse.kz/api/v1/links \
-H "Authorization: Bearer qk_live_..." \
-H "Content-Type: application/json" \
-d '{"amount": 5000, "description": "Кеңес", "orderPrefix": "consult"}'
# 201 → {"url": "https://pay.ainhouse.kz/l/a8K2mQ7x", "slug": "a8K2mQ7x"}
Payment объектісі
Барлық төлем эндпойнттары мен webhook осы объектіні қайтарады. Контрактқа тек checkoutUrl пен mode қосылған.
{
"paymentId": "18517303624",
"type": "qr",
"mode": "live",
"orderId": "order-1042",
"projectId": "01JC8Z6V8Q3K2M1N0P9R7S6T5U",
"merchantId": "01JC8Z5W4X3Y2Z1A0B9C8D7E6F",
"amount": 12500,
"status": "QrTokenCreated",
"statusDesc": "",
"event": null,
"qrUrl": "https://qr.kaspi.kz/5348323210109151130255947803787637336075",
"payUrl": "https://pay.kaspi.kz/pay/5348323210109151130255947803787637336075",
"checkoutUrl": "https://pay.ainhouse.kz/pay/01JC8Z7A1B2C3D4E5F6G7H8J9K",
"receiptUrl": "https://receipt.kaspi.kz/preview/cashier?extTranId=QR18517303624&present=modal",
"expiresAt": "2026-11-08T16:44:12.000+05:00",
"createdAt": "2026-11-08T11:39:12.207Z",
"updatedAt": "2026-11-08T11:39:12.207Z",
"finishedAt": null,
"refundedAmount": 0
}
paymentId |
Төлемнің id-і (Kaspi QrOperationId, жол). Тест төлемінде — біздің id. |
|---|---|
mode |
live не test — қай кілтпен жасалды. |
qrUrl |
QR суретіне ОСЫНЫ салыңыз: Kaspi қосымшасы да, басқа банктердің қосымшалары да оқиды. |
payUrl |
Телефондағы «Kaspi-де төлеу» батырмасына — Kaspi қосымшасын ашады. |
checkoutUrl |
Біздің дайын төлем беті: QR, сома, кері санақ, төленген соң сайтыңызға қайтару. |
status |
Kaspi-дің шикі мәртебесі: QrTokenCreated → Wait → Processed (сәтті) не CancelledByUser. |
event |
Күтіп тұрғанда null, соңында: payment.success | failed | expired | lost. |
expiresAt |
QR-дың мерзімі: жасалғаннан 5 минут, +05:00 уақыт белдеуімен. |
amount |
Сіз сұраған сома, бүтін теңге. |
refundedAmount |
Сәтті қайтарулардың сомасы (әдепкі 0). Қайтару event-ті өзгертпейді: толық қайтарылған төлем де payment.success. |
Webhook
Әр соңғы оқиғада сайттың webhook URL-іне бір POST. payment.* оқиғаларының денесі — Payment объектісі, event толы. 2xx жауап — жеткізілді; басқасы (не 10 с ішінде жауап жоқ) қайталанады: 5 с, 30 с, 2 мин, 10 мин, 30 мин, 1 сағ, 2 сағ, 2 сағ (≈ 6 сағат).
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-Webhook-Event: payment.success
X-Webhook-Id: 18517303624:payment.success
X-Webhook-Signature: sha256=<hex HMAC-SHA256(secret, сұраныстың шикі денесі)>
X-Webhook-Mode: live
User-Agent: QRkassa/1
X-Webhook-Mode: live не test. Live оқиғалар сайттың webhook URL-іне webhook құпиясымен қол қойылып келеді; тест оқиғалар — тек тест webhook URL-іне, бөлек тест құпиясымен. Live серверде тек live құпияны сақтаңыз: сонда тест кілтімен жасалған «төлем» оның қолтаңба тексерісінен өтпейді. Редиректке ермейміз, ішкі желі адрестеріне жібермейміз.
payment.success |
Ақша түсті. amount-ты тапсырыспен салыстырыңыз — сәйкес келмесе, адам қарауы керек. |
|---|---|
payment.failed |
Сатып алушы бас тартты не банк қабылдамады. Тапсырыс ашық, жаңа QR жасауға болады. |
payment.expired |
QR мерзімінде ешкім төлемеді. failed сияқты. |
payment.lost |
Нәтиже белгісіз (сессия ұзақ үзілді т.б.). Ақша өтуі мүмкін — тапсырысты жаппай, адамға белгілеңіз. |
Басқа оқиғалар: конверт
Қайтару, автотөлем және тексеру оқиғаларының денесі — конверт: жоғарғы деңгейдегі event (әрқашан X-Webhook-Event-ке тең), оқиғаның өз объектісі және, бар болса, payment. Сондықтан өңдеушіңіз алдымен body.event-ке қарасын: конвертті Payment деп оқымаңыз. Тақырыптар, қолтаңба және қайталау — жоғарыдағыдай. Бейтаныс event-ке 2xx жауап беріп, елемеуге болады.
payment.refunded · refund.failed |
Қайтару өтті не өтпеді. Дене: event, payment, refund, timestamp. Қайтарулар |
|---|---|
subscription.* |
Автотөлемдердің оқиғалары. Дене: event, subscription, charge, payment, timestamp. Автотөлемдер |
webhook.test |
Кабинеттен жіберілген тексеру хабары. Дене: event, site, mode, timestamp. Тест сценарийлері |
Қолтаңбаны тексеру
<?php
// Қолтаңба ШИКІ дене бойынша тексеріледі — json_encode(json_decode(...)) бойынша емес.
$secret = getenv('QRKASSA_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (! hash_equals($expected, $signature)) {
http_response_code(403);
exit;
}
$payment = json_decode($raw, true);
$deliveryId = $_SERVER['HTTP_X_WEBHOOK_ID'] ?? '';
// Бір X-Webhook-Id қайта келуі мүмкін — оны бір рет қана өңдеңіз.
if ($payment['event'] === 'payment.success') {
// $payment['amount'] сомасын $payment['orderId'] тапсырысымен салыстырып, тапсырысты төленді деп белгілеңіз.
}
http_response_code(200); // кез келген 2xx = жеткізілді
// routes/web.php. Бұл жолды bootstrap/app.php-та CSRF тексерісінен шығарыңыз:
// $middleware->validateCsrfTokens(except: ['qrkassa/webhook']);
Route::post('/qrkassa/webhook', function (Illuminate\Http\Request $request) {
$raw = $request->getContent(); // шикі дене
$expected = 'sha256=' . hash_hmac('sha256', $raw, config('services.qrkassa.webhook_secret'));
abort_unless(hash_equals($expected, (string) $request->header('X-Webhook-Signature')), 403);
$payment = json_decode($raw, true);
if ($payment['event'] === 'payment.success') {
$order = App\Models\Order::where('number', $payment['orderId'])->firstOrFail();
// Сол X-Webhook-Id қайта келуі мүмкін: қолжетімділікті бір рет ашамыз.
if (! $order->paid && $order->amount === $payment['amount']) {
$order->update(['paid' => true]);
$order->user->courses()->attach($order->course_id); // қолжетімділік ашылды
}
}
return response()->noContent();
});
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// express.raw — денені кез келген JSON талдауынан бұрын Buffer күйінде алу үшін.
app.post('/qrkassa/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.QRKASSA_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const got = req.get('X-Webhook-Signature') ?? '';
const valid = got.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!valid) return res.sendStatus(403);
const payment = JSON.parse(req.body.toString('utf8'));
// req.get('X-Webhook-Id') бойынша идемпотентті өңдеңіз
res.sendStatus(200);
});
Идемпотенттік
Бір сайт пен бір orderId үшін:
- Бір мезеттегі сұраныстар кезекке тұрады — Kaspi-де тек бір QR жасалады.
- Төлем сәтті өтсе — 409 order_already_paid.
- Сол түрі мен сомасымен күтіп тұрған төлем болса — 200 және сол төлем.
- Басқа сомамен күтіп тұрса — 409 payment_in_progress.
- Әйтпесе жаңа әрекет жасалады; бұрынғы failed/expired/lost әрекеттер тарихта қалады.
Тест режимі
qk_test_ кілтімен жасалған төлем Kaspi-ге ешқашан бармайды. Ол QrTokenCreated күйінде жасалады да, 10 секундтан кейін өзі Processed болып, payment.success webhook-ы тест webhook URL-іне (X-Webhook-Mode: test, тест құпиясымен) келеді; тест URL бос болса, оқиға жіберілмейді. qrUrl мен payUrl біздің тест бетіне апарады: онда «Төленді» не «Сәтсіз» деп белгілеуге болады.
Тест төлемдері кабинетте «test» белгісімен көрінеді және жазылымға әсер етпейді.
10 секунд пен «төленді» — әдепкі мән. Басқа нәтижені, кідірісті, жасау қатесін, webhook сәтсіздігін не тест қайтаруының нәтижесін кабинеттің «Тест режимі» бетінде не X-Sandbox-Scenario тақырыбымен қойыңыз: Тест сценарийлері.
Кассир логині
Кассаны кабинетте қосу ең оңай жол. Бірақ өз әкімші панеліңізден қосқыңыз келсе, сайттың кілтімен осы үш сұранысты жіберіңіз — жолдары, денелері және қате кодтары gateway-дегідей.
# 1. Кіруді бастау — Kaspi SMS жіберуге дайындалады
curl -X POST https://pay.ainhouse.kz/api/v1/merchant/login/start -H "Authorization: Bearer qk_live_..."
# 201 → {"loginId": "6f1c…", "phoneHint": "+7 777 *** ** 41"}
# 2. Кассир нөмірі (10 таңба) — Kaspi SMS жібереді
curl -X POST https://pay.ainhouse.kz/api/v1/merchant/login/phone -H "Authorization: Bearer qk_live_..." \
-H "Content-Type: application/json" -d '{"loginId": "6f1c…", "phone": "7771234541"}'
# 200 → {"smsSent": true}
# 3. SMS коды
curl -X POST https://pay.ainhouse.kz/api/v1/merchant/login/otp -H "Authorization: Bearer qk_live_..." \
-H "Content-Type: application/json" -d '{"loginId": "6f1c…", "otp": "1234"}'
# 200 → {"ok": true, "merchant": {...}, "authorizedAt": "…Z"}
404 login_not_found— loginId жоқ не 10 минуттан ескі.422 phone_mismatch— нөмір кассирдің бекітілген нөміріне сәйкес емес.200 {"smsSent": false, "errorCode": "OldVersionToUpdate"}— Kaspi жаңа нұсқаны талап етеді: біз жаңартып жатырмыз, 1–2 сағаттан кейін қайталаңыз.200 {"ok": false, "errorMessage": "…"}— SMS коды қате.409 organization_mismatch— нөмір басқа ұйымның кассасына кірді; ештеңе сақталмады.429 too_many_attempts— 10 минутта 5-тен көп әрекет.403 live_key_required— тек qk_live_ кілтімен: тест кілті кассаға тимейді.POST /merchant/logout— кассаны ажырату.
Қайтарулар
Төленген QR-ды толық не жартылай қайтаруға болады: ақша сатып алушының шотына Kaspi арқылы қайтады. Бір төлемге бірнеше жартылай қайтару, бірақ бір мезетте біреу ғана; жалпы сомасы төлемнен аспайды. Қайтаруды кабинетте төлемнің бетінен де жасауға болады.
POST /payments/{paymentId}/refund
Дене: amount? (бос болса — қалғанының бәрі), reason? (сатып алушы көрмейді, Kaspi-ге кетпейді). Idempotency-Key тақырыбы (не денедегі idempotencyKey) міндетті емес, бірақ қатты ұсынамыз: жоқ болса, кілтті өзіміз жасаймыз, сонда желі үзілгенде қайталау екінші қайтару жасауы мүмкін.
# Толық қайтару: amount жібермейміз. Жартылай — amount теңгемен.
# Idempotency-Key: әр әрекетке өз кілті (сол кілтпен қайталау сол қайтаруды қайтарады).
curl -X POST https://pay.ainhouse.kz/api/v1/payments/18517303624/refund \
-H "Authorization: Bearer qk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rf-order-1042-1" \
-d '{"amount": 5000, "reason": "Тауар қайтарылды"}'
# Төлемнің қайтарулары (жаңасы бірінші)
curl https://pay.ainhouse.kz/api/v1/payments/18517303624/refunds \
-H "Authorization: Bearer qk_live_..."
HTTP/1.1 201 Created
{
"refund": {
"refundId": "01jcz8v6qd3m2k1n0p9r7s6t5u",
"paymentId": "18517303624",
"orderId": "order-1042",
"mode": "live",
"amount": 5000,
"status": "succeeded",
"reason": "Тауар қайтарылды",
"error": null,
"message": null,
"source": "api",
"createdAt": "2026-11-10T09:12:03.120Z",
"finishedAt": "2026-11-10T09:12:05.871Z"
},
"payment": {
"paymentId": "18517303624",
"orderId": "order-1042",
"amount": 12500,
"event": "payment.success",
"refundedAmount": 5000
}
}
| HTTP | Жауап | Мағынасы |
|---|---|---|
| 201 | refund.status = succeeded |
Kaspi ақшаны қайтарды. Денеде refund және payment (refundedAmount жаңарған). |
| 202 | refund.status = pending | uncertain |
Нәтижесі әлі белгісіз. Қайталамаңыз — payment.refunded не refund.failed webhook-ын күтіңіз. |
| 200 | idempotentReplay: true |
Осы Idempotency-Key-мен бұрын жіберілген сол сұраныс: қайтарудың қазіргі күйі. |
| 400 | validation |
amount — оң бүтін сан (теңге), reason — 255 таңбаға дейін, кілт — ^[A-Za-z0-9._:-]{1,64}$. |
| 404 | not_found |
Төлем жоқ не басқа сайттың / режимнің төлемі. |
| 409 | not_refundable |
Қайтаруға болмайды, себебі cause-та: not_paid — төленбеген, fully_refunded — толық қайтарылған, not_qr — шот арқылы төленген, kaspi_not_refundable — Kaspi рұқсат бермейді (мерзімі өткен болуы мүмкін), unknown_at_gateway — төлем API арқылы қайтару үшін тым ескі (Kaspi Pay-де қайтарыңыз), billing — AinHouse Pay жазылымының ақысы (қолмен қайтарылады). |
| 409 | amount_exceeds_available |
Сома қайтаруға болатыннан көп; жауапта available. |
| 409 | refund_in_progress |
Бұл төлемде аяқталмаған қайтару бар (бір мезетте біреу ғана). Денедегі refund — сол басқа қайтару; сұранысыңыз орындалмады, кілт бос. |
| 409 | idempotency_key_conflict |
Бұл кілт осы төлемде басқа сомамен не себеппен қолданылған; денедегі refund — сол бұрынғы қайтару. |
| 403 | merchant_blocked |
Аккаунт бұғатталған. Жазылымның бітуі қайтаруды тоқтатпайды. |
| 403 | refunds_disabled |
Қайтарулар платформада уақытша өшірулі. Ештеңе қайтарылған жоқ, кілт бос. |
| 503 | cashier_session_lost | cashier_not_connected |
Касса қосулы емес — кабинетте қайта кіріп, ЖАҢА кілтпен қайталаңыз. |
| 502 | kaspi_error |
Kaspi бас тартты (мысалы, Kaspi шотында қаражат жетпейді); message — Kaspi-дің мәтіні, refund.status = failed. Kaspi бас тартпай, жауап бере алмаса — message орнына cause: kaspi_unavailable (Kaspi уақытша қолжетімсіз, кейінірек қайталаңыз) не operation_not_found (Kaspi операцияны таппады, Kaspi Pay-де қайтарыңыз). |
| 502 | gateway_error |
Kaspi-мен байланыс сервисі сұранысты орындамады (мысалы, сайттың Kaspi байланысы әлі дайын емес). Ештеңе қайтарылған жоқ; кейінірек қайталаңыз — денеде refund болса, ЖАҢА кілтпен. |
Клиенттің ережесі
Жауапта refund болса — оның status-ы шешуші. failed — ештеңе қайтарылмады, ЖАҢА кілтпен қайталаңыз. uncertain — ақша кетуі мүмкін: қайталамаңыз; нәтижені AinHouse Pay Kaspi-ден өзі біледі (бір тәулікке дейін) — webhook-ты күтіңіз не GET /payments/{id}/refunds тексеріңіз. Ерекшеліктер: 409 refund_in_progress-тегі refund (pending не uncertain) — осы төлемнің басқа, ашық қайтаруы: сұранысыңыз орындалмады, кілт бос — сол қайтару аяқталған соң сол кілтпен қайталаңыз. 409 idempotency_key_conflict-тегі refund — осы кілтпен бұрын жасалған қайтару.GET /payments/{paymentId}/refunds
Төлемнің қайтарулары, жаңасы бірінші: {"refunds": [...]}.
Payment объектісі және тапсырыс
- Payment объектісінде refundedAmount — сәтті қайтарулардың сомасы. Толық қайтарылғаны: refundedAmount = amount.
- event өзгермейді: толық қайтарылған төлем де payment.success. Сол orderId-ге жаңа QR — 409 order_already_paid, қайта сату үшін жаңа orderId беріңіз.
- v1-де тек QR төлемдері қайтарылады; шот арқылы төленгенін Kaspi Pay қосымшасында қайтарыңыз.
- Kaspi-дің комиссиясы қайтарылмайды, ал Kaspi шотыңызда қайтаруға жеткілікті қаражат болуы керек. Kaspi-дің қайтару мерзімі шектеулі.
Webhook: payment.refunded, refund.failed
Дене — конверт: {event, payment, refund, timestamp}; event тақырыптағы X-Webhook-Event-ке тең, X-Webhook-Id = {paymentId}:{event}:{refundId}. Қолтаңба мен қайталау — басқа оқиғалардағыдай. Жеткізулер бір-біріне тәуелсіз: payment.refunded қайталанған payment.success-тан бұрын келуі мүмкін — payment.refundedAmount-ты өз жазбаңызбен салыстырыңыз. Қайтарудың нәтижесі кейін анықталса (мысалы, «өтпеді» деп белгіленген қайтаруды Kaspi растаса), сол refundId бойынша refund.failed-тен кейін payment.refunded келуі мүмкін — соңғы оқиға шешуші.
X-Webhook-Event: payment.refunded
X-Webhook-Id: 18517303624:payment.refunded:01jcz8v6qd3m2k1n0p9r7s6t5u
X-Webhook-Signature: sha256=…
X-Webhook-Mode: live
{
"event": "payment.refunded",
"payment": { "paymentId": "18517303624", "orderId": "order-1042", "amount": 12500, "event": "payment.success", "refundedAmount": 5000 },
"refund": { "refundId": "01jcz8v6qd3m2k1n0p9r7s6t5u", "amount": 5000, "status": "succeeded", "source": "api" },
"timestamp": "2026-11-10T09:12:05.871Z"
}
Тест кілтімен: тест төлемі Kaspi-сіз қайтарылады, нәтижесі — кабинеттегі «Тест режимі» баптауы не X-Sandbox-Scenario тақырыбы (refund_failed, refund_uncertain, refund_pending).
Автотөлемдер
Тұрақты клиенттерге кесте бойынша Kaspi шоты: абонемент, жалдау, оқу ақысы. Ақша автоматты түрде алынбайды — әр кезеңде клиенттің нөміріне шот (invoice) барады, клиент оны Kaspi қосымшасында растайды. Клиенттер кабинетте қосылады («Автотөлемдер» бөлімі) не Excel/CSV-ден жүктеледі.
Кесте
- Кезең: күн, апта, ай не жыл сайын. Шот уақыты — Қазақстан уақыты бойынша (әдепкі 10:00). Ай сайынғы кесте басталу күнінен есептеледі: 31-і жоқ айда шот айдың соңғы күні кетеді.
- Төленбеген шот: 24 сағаттан кейін жаңа шот, ең көбі 3 әрекет, одан кейін subscription.needs_attention.
- Ескі қарыз жиналмайды: 48 сағаттан ескі кезең шотсыз өтеді, кідіртілген кезеңдерге шот жіберілмейді.
- Бір нөмірге шоттар кемінде 10 минут арамен кетеді — Kaspi жиі шотты шектемеуі үшін.
Webhook: subscription.*
Автотөлемнің логикасын subscription.* оқиғаларымен жүргізіңіз. Әр шот әдеттегі payment.* оқиғасын да береді (orderId = rec:…) — оны автотөлем үшін елемеуге болады. Тақырыптар мен қолтаңба — төлемдікімен бірдей; дене — конверт: event, subscription, charge, payment, timestamp. Клиенттің нөмірі денеде жоқ. Тест клиентінің оқиғалары тек тест webhook URL-іне келеді.
POST /qrkassa/webhook
X-Webhook-Event: subscription.payment_succeeded
X-Webhook-Id: rec:01jd5k3m8x7q2w9e4r6t1y0u5i:subscription.payment_succeeded:01jd5p0a2b3c4d5e6f7g8h9j0k
X-Webhook-Signature: sha256=…
X-Webhook-Mode: live
{
"event": "subscription.payment_succeeded",
"subscription": {
"subscriptionId": "01jd5k3m8x7q2w9e4r6t1y0u5i",
"externalId": "cust-77",
"projectId": "01jc8v2n4m6b8x0z2c4v6b8n0m",
"merchantId": "01jc8t1r3e5w7q9a1s3d5f7g9h",
"mode": "live",
"name": "Айгүл С.",
"amount": 15000,
"description": "Абонемент {month}",
"period": "monthly",
"startDate": "2026-11-15",
"billTime": "10:00",
"nextChargeAt": "2026-12-15T05:00:00.000Z",
"cyclesLimit": 12,
"periodsIssued": 1,
"periodsPaid": 1,
"status": "active",
"attentionReason": null,
"finishReason": null
},
"charge": {
"chargeId": "01jd5p0a2b3c4d5e6f7g8h9j0k",
"periodIndex": 1,
"periodStart": "2026-11-15",
"attempt": 1,
"status": "paid",
"error": null,
"message": null,
"paymentId": "18517303624",
"amount": 15000
},
"payment": { "paymentId": "18517303624", "type": "invoice", "orderId": "rec:01jd5k3m8x7q2w9e4r6t1y0u5i:20261115:1", "event": "payment.success", "…": "…" },
"timestamp": "2026-11-15T05:03:11.000Z"
}
subscription.created |
Клиент қосылды (кабинетте не импортпен). |
|---|---|
subscription.invoice_issued |
Кезеңнің шоты клиентке жіберілді. charge.attempt — нешінші әрекет. |
subscription.payment_succeeded |
Кезең төленді — қызметті ұзартыңыз. |
subscription.payment_failed |
Шот төленбеді не мерзімі өтті. Әрекет қалса, 24 сағаттан кейін жаңа шот кетеді. |
subscription.needs_attention |
Адам қарауы керек: subscription.attentionReason ішінде себебі. |
subscription.paused / resumed |
Кабинетте кідіртілді не жалғастырылды. |
subscription.period_skipped |
Кезең шотсыз өтті: charge.error = skipped_by_merchant | missed | issue_failed. |
subscription.finished |
Аяқталды: finishReason = cycles_done (шот саны бітті) не cancelled. |
X-Webhook-Id: rec:{subscriptionId}:{event}:{chargeId|ulid} — бір оқиға бірнеше рет келуі мүмкін, өңдеу идемпотентті болсын. Күйлер (subscription.status): active, waiting_payment, needs_attention, paused, finished.
Назар аудару себептері
payment_failed |
Барлық әрекетте шот төленбеді. |
|---|---|
payment_lost |
Шоттың нәтижесі белгісіз — Kaspi Pay-де тексеріңіз. Автоматты қайта шот жоқ. |
cashier_session_lost / cashier_not_connected |
Касса қосылмаған — шот 15 минут сайын қайта жіберіледі. |
subscription_required |
AinHouse Pay жазылымы аяқталды. |
kaspi_error |
Kaspi шотты қабылдамады (мысалы, нөмір Kaspi-де жоқ) — мәтіні charge.message ішінде. |
gateway_error / site_disabled |
Байланыс қатесі не сайт сөндірулі. |
Excel/CSV импорты
Бірінші жол — бағандардың атауы (қазақша, орысша не ағылшынша): name, phone, amount, description, period (daily | weekly | monthly | yearly, не «ай сайын», «ежемесячно»), start_date (КК.АА.ЖЖЖЖ не ЖЖЖЖ-АА-КК), bill_time, cycles_limit, external_id, note. CSV-де бөлгіш «;» не «,». Ең көбі 1000 жол. Алдымен әр жолдың қатесі көрсетіледі, сосын тек дұрыс жолдар қосылады.
Тест режимі
Тест клиентінің шоты Kaspi-ге бармайды және тест төлемі сияқты өзі аяқталады. Тест режимінде ең көбі 10 клиент. Клиенттің бетіндегі «Шотты қазір жіберу» кезеңді күтпей, бүкіл тізбекті бірден тексеруге мүмкіндік береді.
Тест сценарийлері
Кабинеттегі «Тест режимі» бетінде әр сайтқа тест сценарийін қойыңыз: тест төлемінің нәтижесі және неше секундтан кейін, жасау кезіндегі қате, webhook-тың алғашқы 1–8 әрекеті сәтсіз, тест қайтаруының нәтижесі. Бұл тек qk_test_ кілтіне әсер етеді: ақша алынбайды, Kaspi-ге сұраныс кетпейді.
X-Sandbox-Scenario
Бір сұранысқа баптаудың орнына тақырыппен: тек qk_test_ кілтімен, live кілтте еленбейді. Қолданылса, жауапта X-Sandbox-Scenario-Applied тақырыбы болады.
paid | failed | expired | manual |
Тест төлемінің нәтижесі (manual — тек хостталған беттегі батырмалармен). |
|---|---|
cashier_session_lost | cashier_not_connected |
Төлем жасау 503 қайтарады. |
gateway_error | kaspi_error |
Төлем жасау 502 қайтарады (kaspi_error — message-пен). |
subscription_required |
Төлем жасау 402 қайтарады (paidUntil, payUrl). |
refund_failed | refund_uncertain | refund_pending |
POST /payments/{id}/refund: 502 kaspi_error, 202 uncertain не 202 pending (кейін payment.refunded). |
# Сценарий тек осы сұранысқа (qk_test_ кілті):
# paid | failed | expired | manual
# cashier_session_lost | cashier_not_connected | gateway_error | kaspi_error | subscription_required
# refund_failed | refund_uncertain | refund_pending (POST /payments/{id}/refund үшін)
curl -i -X POST https://pay.ainhouse.kz/api/v1/payments/qr \
-H "Authorization: Bearer qk_test_..." \
-H "Content-Type: application/json" \
-H "X-Sandbox-Scenario: failed" \
-d '{"amount": 5000, "orderId": "test-1001"}'
webhook.test
«Тест режимі» бетінен тексеру хабарын тест не live адреске жіберуге болады: дене {event: "webhook.test", site, mode, timestamp}, қолтаңба — сол адрестің құпиясымен, X-Webhook-Id = test:{id}. Әдейі сәтсіз жеткізу кабинетте «Тест: сәтсіздік әдейі жасалды» деп көрінеді.
Сайтқа батырма
Бағдарламашысыз сайтқа «Kaspi арқылы төлеу» батырмасын қойыңыз: кабинетте сілтеме не QR парағын жасап, «Сайтқа батырма» кодын көшіріңіз. Төлем терезесі сайттан шықпай ашылады; ақша тікелей Kaspi шотыңызға түседі.
<!-- «Kaspi арқылы төлеу» батырмасы: скрипт бетке бір рет -->
<script src="https://pay.ainhouse.kz/widget.js" defer></script>
<div data-qrkassa-link="Ab12Cd34"
data-lang="kk"
data-theme="green"
data-mode="modal"></div>
<!-- Сомасы ашық сілтеме: сома қойылады, бірақ min/max-ты сервер бәрібір тексереді -->
<div data-qrkassa-link="Xy98Zw76" data-amount="5000" data-size="lg" data-full-width></div>
<script>
// Интерфейске арналған белгі. Тапсырысты тек қолтаңбалы webhook бойынша орындаңыз.
QRkassa.on('paid', function (event) {
console.log('Төленді', event.orderId, event.paymentId);
});
</script>
data-qrkassa-link |
Сілтеменің не QR парағының 8 таңбалы коды (кабинеттегі /l/… адресінен). Міндетті. |
|---|---|
data-amount |
Тек сомасы ашық сілтемеге: алдын ала толтырылады, шегін (min/max) сервер бәрібір тексереді. Тұрақты сілтеме оны елемейді. |
data-lang |
kk не ru. Әдепкі — <html lang>, болмаса kk. |
data-theme |
green (әдепкі), dark не light. |
data-mode |
modal (әдепкі) — сайттың үстінде терезе; tab — жаңа қойынды. Телефонда (480 px-тен тар) әрқашан жаңа қойынды. |
data-label |
Батырманың өз мәтіні. Әдепкі: «Kaspi арқылы төлеу» / «Оплатить через Kaspi». |
data-size |
sm | md | lg |
data-full-width |
Батырма контейнердің бүкіл енін алады. |
JavaScript
QRkassa.open({ link, amount, lang, mode })— өз батырмаңыздан ашу.QRkassa.refresh()— бет жаңа батырмалармен қайта салынса (SPA).QRkassa.mount(element)— бір элементке батырма қою.QRkassa.on('paid' | 'failed' | 'expired' | 'close', callback)— оқиға: { orderId, paymentId }. Батырманың өзі де qrkassa:paid т.б. оқиғасын шығарады (bubbles).
Тапсырысты тек webhook бойынша орындаңыз
paid оқиғасы — тек интерфейске белгі: оны браузерде кез келген адам жасай алады. Тауарды қолтаңбалы payment.success webhook-ы келгенде ғана беріңіз. data-amount-ты да DevTools-та өзгертуге болады — сондықтан соманы сервер тексереді, ал тұрақты сілтеме оны мүлде елемейді.Сайтыңыздың CSP-і iframe-ге тыйым салса, data-mode="tab" қойыңыз. Kaspi қосымшасы жаңа терезеде ашылады (iOS Safari iframe ішінен ашпайды). Виджет cookie қолданбайды. AinHouse Pay — Kaspi-дің ресми серіктесі емес; батырмада Kaspi логотипі жоқ.
Қате кодтары
Қате денесі әрқашан {"error": "…"}, кейде қосымша өрістермен.
| HTTP | error | Не істеу керек |
|---|---|---|
| 400 | validation |
amount не orderId дұрыс емес; details ішінде өріс бойынша. |
| 401 | unauthorized |
Кілт жоқ не жарамсыз (ауыстырылған кілт бірден жарамсыз). |
| 402 | subscription_required |
Жазылым бітті не аккаунт бұғатталған. Жауапта paidUntil және payUrl (кабинеттегі төлем беті). |
| 403 | kyc_required |
Live төлем мен кассаны қосу үшін бизнес анкетасы мақұлдануы керек (кабинет → басты бет). Тест кілті жұмыс істей береді. |
| 403 | kyc_rejected |
Бизнес анкетасы қабылданбады: live төлемдер жабық. Қолдау қызметіне жазыңыз. |
| 403 | merchant_blocked |
Аккаунт бұғатталған: қайтару жасалмайды. Жазылымның бітуі қайтаруды тоқтатпайды. |
| 404 | not_found |
Төлем жоқ не басқа сайтқа тиесілі. |
| 409 | order_already_paid |
Бұл orderId төленген; жауапта payment. |
| 409 | payment_in_progress |
Бұл orderId-де басқа сомамен күтіп тұрған төлем бар. |
| 409 | not_cancellable |
QR болдырылмайды. |
| 409 | not_refundable |
Қайтаруға болмайды: төленбеген, толық қайтарылған не шотпен төленген (cause өрісінде). |
| 409 | amount_exceeds_available |
Қайтару сомасы қалғанынан көп; жауапта available. |
| 409 | refund_in_progress |
Бұл төлемде аяқталмаған қайтару бар — нәтижесін күтіңіз. |
| 409 | idempotency_key_conflict |
Бұл Idempotency-Key басқа сомамен не себеппен қолданылған. |
| 429 | too_many_requests |
Бір кілтке минутына 120 сұраныстан көп. |
| 502 | kaspi_error |
Kaspi сұранысты қабылдамады; message ішінде Kaspi-дің себебі. |
| 502 | gateway_error |
Kaspi-мен байланыс қызметі уақытша жауап бермеді — бір минуттан кейін қайталаңыз. |
| 503 | cashier_not_connected |
Касса әлі қосылмаған — кабинетте қосыңыз. |
| 503 | cashier_session_lost |
Касса сессиясы үзілді — кабинетте қайта кіріңіз. Сатып алушыға «Kaspi уақытша қолжетімсіз» деп көрсетіңіз. |
WordPress / WooCommerce
Бета
QRkassa for WooCommerce плагині дүкеніңізге «Kaspi QR» төлем тәсілін қосады. Сатып алушы QRkassa төлем бетінде төлеп, сайтыңызға оралады, ал тапсырыс қолтаңбасы бар webhook арқылы төленді деп белгіленеді. Ақша тікелей Kaspi Pay шотыңызға түседі.
Плагинді қалай алуға болады
Бета кезінде zip архивін сұраныс бойынша береміз.
Орнату
- WordPress-те: Плагиндер → Жаңасын қосу → Плагинді жүктеу, qrkassa-woocommerce-0.1.0-beta.zip файлын таңдап, іске қосыңыз.
- WooCommerce → Баптаулар → Төлемдер → QRkassa: кабинеттің «Сайттар» бөліміндегі API кілті мен webhook құпиясын қойыңыз.
- Плагин баптауларындағы Webhook URL-ді көшіріп, кабинеттегі сайт баптауларына қойыңыз: live кілт үшін — «Webhook URL», тест кілті үшін — «Тест webhook URL». Адрес мынадай болады:
https://shop.kz/wp-json/qrkassa/v1/webhook - WooCommerce → Баптаулар → Жалпы: валюта — теңге (KZT). Ондық таңбалар санын 0 етіп қойған дұрыс: Kaspi тиын қабылдамайды.
- Тапсырысты рәсімдеу беті «Checkout» блогымен жасалса, оны классикалық шорткодқа ауыстырыңыз:
[woocommerce_checkout]
Баптаулар
| API кілті | Сайттың qk_live_ не qk_test_ кілті. Режим кілттің басынан анықталады. |
|---|---|
| Webhook құпиясы | qk_live_ кілтіне — webhook құпиясы, qk_test_ кілтіне — тест webhook құпиясы. |
| Webhook URL | Тек оқуға арналған өріс. Оны сайт баптауларына көшіріңіз (төменде). |
| Төлемнен кейінгі күй | «Өңделуде» (әдепкі) не «Аяқталды». |
| Жөндеу журналы | WooCommerce → Күйі → Журналдар, көзі «qrkassa». Кілт пен құпия журналға жазылмайды. |
| API адресі | QRkassa API адресі: https://pay.ainhouse.kz/api/v1. Әдепкі мәнді тек сынақ сервері үшін өзгертіңіз. |
Тапсырыс күйі қалай өзгереді
payment.success |
Тапсырыс төленді: «Өңделуде» не «Аяқталды». Сома тапсырыспен сәйкес келмесе не тапсырыс бұрын төленген болса — «Ұсталды» күйі және ескертпе. |
|---|---|
payment.failedpayment.expired |
Тек ескертпе. Тапсырыс «Төлемді күтуде» күйінде қалады, сатып алушы қайта төлей алады. |
payment.lost |
«Ұсталды» күйі: ақша өтуі мүмкін, жөнелтпес бұрын Kaspi Pay-де тексеріңіз. |
payment.refunded |
QRkassa кабинетінде жасалған қайтару WooCommerce-ке өзі жазылады. |
Плагиннің orderId-і — wc-{тапсырыс нөмірі}. Сол тапсырысты қайта төлеу қауіпсіз: QRkassa бір тапсырыс үшін ақшаны екі рет алмайды. Webhook кешіксе, алғыс беті төлем күйін QRkassa-дан бір рет сұрайды.
Тест режимі
- qk_test_ кілтін және тест webhook құпиясын қойып, Webhook URL-ді сайттың «Тест webhook URL» өрісіне көшіріңіз.
- Тест төлемі Kaspi-ге бармайды. Ол 10 секундтан кейін өзі төленеді не тест бетінде қолмен белгіленеді. Тапсырыс ескертпелерінде [TEST] белгісі тұрады.
- WooCommerce тест тапсырысын шынымен аяқтайды (қойма, хаттар) — тексеруді дүкеннің сынақ көшірмесінде жасаңыз.
- Біз webhook-ты ішкі желі адрестеріне (localhost, LAN) жібермейміз. Жергілікті дүкенде тапсырыс тек алғыс бетіндегі тексеріспен расталады.
WooCommerce-тен қайтару
- Тапсырыс бетінде «Қайтару» батырмасын басып, соманы енгізіңіз де, «QRkassa — Kaspi QR» арқылы қайтару батырмасын басыңыз. Толық не жартылай, тек бүтін теңгемен; бір мезетте бір қайтару.
- Kaspi қайтаруды бірден растамаса, плагин ескерту көрсетеді: қайталамаңыз. Қайтару өткенде WooCommerce оны webhook бойынша өзі жазады.
- QRkassa кабинетінде жасалған қайтару да WooCommerce тапсырысында пайда болады.
Бета шектеулері
- Тек классикалық рәсімдеу беті: «Checkout» блогымен үйлесімсіз деп белгіленген.
- Дүкен валютасы тек KZT; сомасында тиыны бар тапсырысқа Kaspi QR ұсынылмайды.
- Тек QR: Kaspi қосымшасына шот жіберу плагинде жоқ.
- HPOS (тапсырыстардың жаңа кестелері) қолдау табады. PHP 7.4+, WooCommerce 7.0+.