Қысқаша
Node.js SDK — бір файлдан тұратын, сыртқы тәуелділігі жоқ ESM модулі. Node.js 18-ден жоғары нұсқада жұмыс істейді (ішінде кіріктірілген fetch пен node:crypto қолданылады). Екі нәрсені береді: QutPay клиенті және verifyWebhook функциясы.
Архив: https://api.qut.kz/downloads/qutpay-sdk-node.zip
SDK міндетті емес — бәрін кәдімгі fetch арқылы да жасауға болады. Бірақ қолтаңба тексеруді өз қолыңызбен жазбай, дайынын алғаныңыз дұрыс.
Орнату
Архивті жүктеп алып, жобаңыздағы бір қалтаға (мысалы vendor/qutpay-sdk) ашыңыз да, package.json ішіне жергілікті тәуелділік ретінде қосыңыз:
npm install ./vendor/qutpay-sdk
Немесе index.js файлын жобаңызға тікелей көшіріп, жолы бойынша импорттаңыз. Пакет атауы — @qutpay/sdk, түрі — ESM ("type": "module"), сондықтан CommonJS жобасында await import('@qutpay/sdk') арқылы алыңыз.
Инициализация
import { QutPay, verifyWebhook } from '@qutpay/sdk';
const qp = new QutPay({
apiKey: process.env.QUTPAY_API_KEY, // qp_live_… немесе qp_test_…
baseUrl: 'https://api.qut.kz', // әдепкі мән, өзгертудің қажеті жоқ
timeoutMs: 20000, // әдепкі 20 секунд
});
apiKey берілмесе конструктор бірден қате лақтырады. Кілт тек серверде тұруы керек — браузерге, мобильді қосымшаға немесе жария репозиторийге салмаңыз.
Счёт жасау
const inv = await qp.createInvoice({
amount: 12500,
kind: 'qr', // 'qr' (әдепкі) немесе 'phone'
description: 'Тапсырыс №4471',
externalOrderId: '4471',
customer: { name: 'Айгүл', phone: '77011234567', email: 'a@b.kz' },
successUrl: 'https://site.kz/ok',
failUrl: 'https://site.kz/fail',
metadata: { branch: 'almaty-abay' },
idempotencyKey: 'order-4471', // Idempotency-Key тақырыбына кетеді
});
console.log(inv.id, inv.status, inv.payUrl, inv.expiresAt);
Жауапта id, status, payUrl, qrUrl, deepLink, qrImageUrl, expiresAt болады. Клиентті inv.payUrl адресіне бағыттасаңыз жеткілікті.
kind: 'phone' таңдасаңыз customer.phone міндетті, сома бүтін теңге болуы керек.
Қалған әдістер
| Әдіс | Не істейді |
|---|---|
qp.getInvoice(id) | Счётты оқиды. qp.getInvoice(id, { live: true }) — күйді Kaspi-ден сұрап, жаңартады |
qp.listInvoices({ status, externalOrderId, from, to, limit, offset }) | Счёттар тізімі |
qp.cancelInvoice(id) | Ашық счётты болдырмайды |
qp.refundInvoice(id, amount, reason) | Қайтару. amount берілмесе — толық қайтару |
qp.simulateInvoice(id, 'paid') | Тек sandbox: төлемді имитациялау |
qp.account() | Ағымдағы ұйым туралы қысқа ақпарат — кілттің жарамдылығын тексеруге ыңғайлы |
const fresh = await qp.getInvoice(inv.id, { live: true });
if (fresh.status === 'paid') await markOrderPaid('4471');
await qp.refundInvoice(inv.id, 5000, 'Тауардың бір бөлігі қайтарылды');
SDK-да жазылым, bulk және форма-хук әдістері жоқ. Оларды тікелей HTTP арқылы шақырыңыз:
const resp = await fetch('https://api.qut.kz/api/v1/invoices/bulk', {
method: 'POST',
headers: { 'X-API-Key': process.env.QUTPAY_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ invoices: [{ amount: 1000, externalOrderId: 'a-1' }] }),
});
const result = await resp.json(); // HTTP 207, әр элемент бойынша нәтиже
Webhook қолтаңбасын тексеру
verifyWebhook({ secret, rawBody, headers, toleranceSec = 300 }) // → true / false
rawBody — денесі өзгертілмеген байттар немесе жол. JSON-ға айналдырылған объект жарамайды: қолтаңба дәл сол байттар бойынша есептеледі. Функция X-Webhook-Signature пен X-Webhook-Timestamp тақырыптарын өзі оқиды, sha256= префиксін тексереді, 5 минуттан ескі жеткізілімді қабылдамайды және салыстыруды уақыт бойынша тұрақты әдіспен жасайды.
Толық Express мысалы
import express from 'express';
import { QutPay, verifyWebhook } from '@qutpay/sdk';
const app = express();
const qp = new QutPay({ apiKey: process.env.QUTPAY_API_KEY });
// 1) Webhook — express.json() ҚОСЫЛҒАНҒА ДЕЙІН тұруы керек
app.post('/qutpay-webhook', express.raw({ type: '*/*' }), async (req, res) => {
const ok = verifyWebhook({
secret: process.env.QUTPAY_WEBHOOK_SECRET,
rawBody: req.body, // Buffer
headers: req.headers,
});
if (!ok) return res.sendStatus(401);
const { event, invoice } = JSON.parse(req.body.toString('utf8'));
// Идемпотенттілік: бір оқиға екі рет келуі мүмкін
const seen = await alreadyHandled(invoice.id, invoice.status);
if (!seen) {
if (event === 'invoice.paid') await markOrderPaid(invoice.externalOrderId, invoice);
if (event === 'invoice.refunded') await markOrderRefunded(invoice.externalOrderId);
await rememberHandled(invoice.id, invoice.status);
}
res.sendStatus(200); // 2xx бермесеңіз 11 рет қайталанады
});
// 2) Қалған маршруттар үшін JSON
app.use(express.json());
app.post('/buy', async (req, res) => {
const order = await createOrder(req.body);
try {
const inv = await qp.createInvoice({
amount: order.total,
description: `Тапсырыс №${order.id}`,
externalOrderId: String(order.id),
successUrl: `https://site.kz/orders/${order.id}`,
idempotencyKey: `order-${order.id}`,
});
await saveInvoiceId(order.id, inv.id);
res.redirect(303, inv.payUrl);
} catch (err) {
console.error('qutpay', err.status, err.code, err.message);
res.status(502).render('pay-error');
}
});
app.listen(3000);
Ең жиі кездесетін қате — express.json() webhook маршрутынан бұрын тұруы. Сонда req.body объектіге айналып кетеді де, қолтаңба ешқашан сәйкес келмейді.
Қате өңдеу
SDK әр сәтсіз жауапты Error түрінде лақтырады, оған екі қосымша өріс қояды:
| Өріс | Мәні |
|---|---|
err.status | HTTP күйі. Желі қатесі мен таймаутта 0 |
err.code | Машиналық код: invalid_amount, invoice_not_found, request_rate_limited… Таймаутта timeout, желі үзілсе network_error |
err.message | Адамға арналған мәтін |
try {
await qp.createInvoice({ amount, externalOrderId });
} catch (err) {
switch (err.code) {
case 'kaspi_session_expired':
notifyAdmin('Кассир байланысы үзілді');
break;
case 'tariff_limit_reached':
case 'tariff_daily_burst':
notifyAdmin('Лимит');
break; // қайталамаңыз
case 'request_rate_limited':
case 'timeout':
case 'network_error':
await retryLater(); // 1, 2, 4, 8 секунд бэкоффпен
break;
default:
throw err;
}
}
Логикаңызды әрқашан err.code мәніне қарап жазыңыз: message мәтіні өзгеруі мүмкін, код өзгермейді. Барлық кодтар: Қателер каталогы.
Жиі қойылатын сұрақтар
npm-нен орнатуға бола ма? Жоқ, пакет жария тізілімде емес. Архивті жүктеп алып, жергілікті тәуелділік ретінде қосыңыз.
CommonJS жобасында жұмыс істей ме? Иә: const { QutPay } = await import('@qutpay/sdk');.
TypeScript-та қолдануға бола ма? Иә. Модульде JSDoc аннотациялары бар, бірақ бөлек .d.ts файлы жоқ — өз типтеріңізді жазуыңызға тура келеді.
Fastify немесе Koa-да raw body қалай аламын? Fastify-да addContentTypeParser('*', { parseAs: 'buffer' }, …), Koa-да koa-bodyparser орнына raw-body. Негізгі шарт — денені өзгертілмеген күйінде алу.
Сынауды қалай бастаған дұрыс? Sandbox кілтімен счёт жасап, qp.simulateInvoice(id, 'paid') шақырыңыз — webhook шынайы жіберіледі: Sandbox-та төлемді симуляциялау.