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 минутта

  1. Тіркеліп, «Сайттар» бөлімінде сайт қосыңыз да, qk_test_ кілтін көшіріңіз — тест режимі бірден жұмыс істейді.
  2. Төмендегі кодты іске қосыңыз — сатып алушы дайын төлем бетіне өтеді.
  3. Webhook-ты қабылдаңыз: тест оқиғалары сайттың тест webhook URL-іне келеді.
  4. Нақты төлем үшін: бизнес анкетасын толтырыңыз, мақұлданған соң кабинетте Kaspi Pay кассаңызды қосып, qk_live_ кілтіне ауысыңыз.
PHP
<?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
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).

JSON
{
  "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
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 қосылған.

Payment
{
  "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 сағат).

HTTP
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
<?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 = жеткізілді
Laravel
// 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();
});
Node.js / Express
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 үшін:

  1. Бір мезеттегі сұраныстар кезекке тұрады — Kaspi-де тек бір QR жасалады.
  2. Төлем сәтті өтсе — 409 order_already_paid.
  3. Сол түрі мен сомасымен күтіп тұрған төлем болса — 200 және сол төлем.
  4. Басқа сомамен күтіп тұрса — 409 payment_in_progress.
  5. Әйтпесе жаңа әрекет жасалады; бұрынғы 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-дегідей.

curl
# 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) міндетті емес, бірақ қатты ұсынамыз: жоқ болса, кілтті өзіміз жасаймыз, сонда желі үзілгенде қайталау екінші қайтару жасауы мүмкін.

curl
# Толық қайтару: 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_..."
JSON
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 болса, ЖАҢА кілтпен.

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 келуі мүмкін — соңғы оқиға шешуші.

HTTP
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-іне келеді.

subscription.payment_succeeded
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).
curl
# Сценарий тек осы сұранысқа (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 шотыңызға түседі.

HTML
<!-- «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).

Сайтыңыздың 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 архивін сұраныс бойынша береміз.

Орнату

  1. WordPress-те: Плагиндер → Жаңасын қосу → Плагинді жүктеу, qrkassa-woocommerce-0.1.0-beta.zip файлын таңдап, іске қосыңыз.
  2. WooCommerce → Баптаулар → Төлемдер → QRkassa: кабинеттің «Сайттар» бөліміндегі API кілті мен webhook құпиясын қойыңыз.
  3. Плагин баптауларындағы Webhook URL-ді көшіріп, кабинеттегі сайт баптауларына қойыңыз: live кілт үшін — «Webhook URL», тест кілті үшін — «Тест webhook URL». Адрес мынадай болады: https://shop.kz/wp-json/qrkassa/v1/webhook
  4. WooCommerce → Баптаулар → Жалпы: валюта — теңге (KZT). Ондық таңбалар санын 0 етіп қойған дұрыс: Kaspi тиын қабылдамайды.
  5. Тапсырысты рәсімдеу беті «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.failed
payment.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+.