Qut Pay Сайт Кабинет Білім базасы Нұсқаулықтар API құжаттамасы ҚАЗРУС
Басты бетБілім базасы → Анықтамалық

Идемпоттылық: қайталаудан қорғану

Жаңартылды: 2026-09-14 · Markdown нұсқасы

Қысқаша

Идемпоттылық — бір әрекетті екі рет орындасаңыз, нәтиже бір рет орындағандағыдай болуы. Төлемде бұл екі жерде керек:

  1. Счёт жасағандаIdempotency-Key тақырыбы. Сол кілтпен қайталасаңыз жаңа счёт жасалмайды, бұрынғысы қайтады: HTTP 200 және жауапта idempotentReplay: true.
  2. Webhook өңдегенде(invoice.id, status) жұбы бойынша бір рет орындау.

Үшінші жер — қайтару, онда логика бөлек: белгісіз нәтижені соқыр қайталауға болмайды.

Idempotency-Key қалай жұмыс істейді

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-pay' \
  -d '{ "amount": 2500, "externalOrderId": "1001" }'
СұрауНәтиже
Бірінші ретHTTP 201, жаңа счёт жасалды
Сол кілтпен қайталауHTTP 200, сол счёт қайтады, idempotentReplay: true
Басқа кілтпенHTTP 201, жаңа счёт

Кілт жібермесеңіз, әр сұрау жаңа счёт жасайды — сондықтан клиент «төлеу» батырмасын екі рет бассаңыз, екі счёт шығады.

Node SDK-да бұл idempotencyKey параметрі арқылы беріледі:

const inv = await qp.createInvoice({
  amount: 2500,
  description: 'Тапсырыс №1001',
  externalOrderId: '1001',
  idempotencyKey: `order-${order.id}`,
});

Кілтті қалай құру керек

Кілт — бір нақты әрекетті сипаттайтын тұрақты жол. Дұрыс құрылым: тапсырыс нөмірі + әрекет.

НеМысалНеге
✅ Тапсырыс + әрекетorder-1001-payБір тапсырысқа бір счёт
✅ Тапсырыс + әрекет + талпынысorder-1001-pay-2Клиент әдейі жаңа счёт сұрағанда
✅ Жазылым + кезеңsub-88-2026-09Ай сайын бір счёт
❌ Кездейсоқ UUIDa3f1…Әр сұрауда жаңа, қорғамайды
❌ Уақыт белгісі1757167000Әр сұрауда жаңа, қорғамайды
❌ Тек тапсырыс нөмірі1001Бір тапсырысқа екінші рет счёт керек болса, тығырық

Басты ереже: кілт сіздің жүйеңізде есептеліп шығуы керек, яғни қайталап сұрау жібергенде дәл сол кілт қайта шығатын болсын. Егер кілтті әр сұрауда кездейсоқ генерациялап отырсаңыз, ол ешнәрседен қорғамайды.

Кілтті базада тапсырыспен бірге сақтап қойған дұрыс: сонда таймаут болып, сервер қайта іске қосылса да, сол кілтпен қайталай аласыз.

Таймаут болғанда

Ең қауіпті сәт — сұрау кетті, ал жауап келмеді. Счёт жасалды ма, жоқ па — белгісіз.

Idempotency-Key болса, жауап қарапайым: дәл сол кілтпен қайталаңыз. Счёт жасалып қойған болса, бұрынғысы қайтады; жасалмаған болса, жаңасы жасалады. Екі жағдайда да бір ғана счёт болады.

async function createInvoiceSafely(order) {
  const key = `order-${order.id}-pay`;
  for (let i = 0; i < 3; i++) {
    try {
      return await qp.createInvoice({ amount: order.total, externalOrderId: String(order.id), idempotencyKey: key });
    } catch (e) {
      if (i === 2) throw e;
      await new Promise((r) => setTimeout(r, 2 ** i * 1000));
    }
  }
}

externalOrderId-дің рөлі

externalOrderId — сіздің тапсырыс нөміріңіз. Ол қорғаныс құралы емес: бір externalOrderId мәнімен қалағаныңызша счёт жасай аласыз, ешкім тоқтатпайды.

Idempotency-KeyexternalOrderId
Не істейдіҚайталанған сұрауды тоқтатадыСчётты тапсырысыңызбен байланыстырады
Қайда беріледіHTTP тақырыбындаСчёттың денесінде
Webhook-та келе меЖоқИә
Іздеуге жарай маЖоқИә
Қайталаудан қорғай маИәЖоқ

Екеуін бірге қолданыңыз: Idempotency-Key қосарлануды болдырмайды, externalOrderId webhook келгенде қай тапсырыс екенін бірден табуға көмектеседі. Толығырақ: Metadata және тапсырыс нөмірі.

Webhook өңдеуде идемпоттылық

Біз 2xx емес жауапта сұрауды 11 рет қайталаймыз. Желі үзілсе, сіз 200 қайтаруға үлгермесеңіз, өңдеу екінші рет келеді. Сондықтан өңдеуді (invoice.id, status) жұбы бойынша бір рет орындаңыз.

CREATE TABLE qutpay_events (
  invoice_id TEXT NOT NULL,
  status     TEXT NOT NULL,
  handled_at TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY (invoice_id, status)
);
const ins = await db.query(
  'INSERT INTO qutpay_events (invoice_id, status) VALUES ($1, $2) ON CONFLICT DO NOTHING',
  [invoice.id, invoice.status],
);
if (ins.rowCount === 0) return res.sendStatus(200);  // бұрын өңделген
await fulfil(invoice);

Неге invoice.id жалғыз жеткіліксіз: бір счёт бойынша бірнеше түрлі күй келеді (pending, paid, сосын refunded). Әрқайсысын бөлек өңдеу керек, бірақ әрқайсысын бір рет қана.

Webhook-пен қатар күйді сұрап отырсаңыз (вендинг, турникет сияқты кідіріске сезімтал сценарийлер), идемпоттылық бұдан да маңызды: екі арна бір нәтижені екі рет әкелуі мүмкін, ал құрылғы екі рет ашылмауы тиіс. Қолтаңбаны тексерумен бірге: Webhook қауіпсіздігі.

Қайтаруда идемпоттылық

Қайтару — ақша қозғалысы, сондықтан оны соқыр қайталауға болмайды. Екі қате коды бар, екеуі де «нәтижесі белгісіз» дегенді білдіреді:

КодHTTPМағынасы
refund_unknown502Kaspi жауап бермеді, қайтару өтті ме, өтпеді ме — белгісіз
refund_pending_unknown409Алдыңғы қайтарудың нәтижесі әлі белгісіз

Осы екеуін көрсеңіз реті мынандай:

  1. Қайтаруды қайталамаңыз.
  2. GET /api/v1/invoices/{id} арқылы счёттың күйін оқыңыз — жауапта қайтарулар тізімі де келеді.
  3. Күйі refunded немесе partially_refunded болса, қайтару өткен. Ештеңе істемеңіз.
  4. Күйі әлі paid болса, біраз күтіп қайта оқыңыз.
  5. Жағдай ұзақ анықталмаса, қолдауға жазыңыз: WhatsApp +7 778 881 3333, Telegram @qutpaybot.
try {
  await qp.refund(invoiceId, { amount });
} catch (e) {
  if (e.error === 'refund_unknown' || e.error === 'refund_pending_unknown') {
    await waitAndCheckState(invoiceId);   // қайталамау
  } else {
    throw e;
  }
}

Толығырақ: Қайтару API және Екі рет қайтарып жіберуден қалай сақтану.

Счёттар қосарланып кетсе

Егер қосарланған счёттар шығып жатса, бұл әдетте кодтағы цикл немесе идемпоттылықтың жоқтығы. Шұғыл қадамдар: Счёттар қосарланып жатыр.

Қосарлану тарифтің айлық лимитін де тез жеп қояды: лимит жасалған счёт бойынша есептеледі, төленгені бойынша емес.

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

Кілттің жарамдылық мерзімі бар ма? Кілт шексіз сақталмайды. Бір тапсырысты бірнеше ай өткен соң қайта жіберсеңіз, жаңа счёт жасалуы мүмкін — сондықтан тапсырыстың күйін өз базаңыздан да тексеріңіз.

Сол кілтпен басқа сомамен жіберсем ше? Кілт бұрынғы счётты қайтарады. Сома шынымен өзгерсе, жаңа кілт қолданыңыз: мысалы order-1001-pay-2.

Sandbox-та да жұмыс істей ме? Иә, бірдей.

Топтап счёт жасағанда ше? POST /api/v1/invoices/bulk ішіндегі әр элемент жеке тексеріледі. Тізімге әр тапсырыс бір рет кіретініне өз жағыңызда көз жеткізіңіз: Топтап счёт жасау.

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

Байланысты мақалалар

Счёт жасау: барлық өрістерPOST /api/v1/invoices эндпоинтінің толық анықтамасы — әр өрістің типі мен шектеуі, жауаптағы барлық өріс, curl мен Node мысалдары, qr мен phone айырмашылығы және жиі кездесетін қателер.Счёттар қосарланып жатырБір тапсырысқа бірнеше счёт шығып жатса, алдымен ағынды тоқтату керек: API кілтті жойсаңыз, интеграция сол сәтте тоқтайды. Сосын себебін тауып, идемпоттылық қосасыз.Metadata және тапсырыс нөміріexternalOrderId мен metadata өрістерінің айырмашылығы, олардың webhook-та қалай қайта келетіні, metadata ішіне нені жазуға болады және нені ешқашан жазбау керек — нақты мысалдармен.Webhook қауіпсіздігі және қолтаңбаны тексеруҚолтаңба қалай құралады, неге raw body міндетті, timestamp-ты қалай тексеру керек, Express, Laravel, Django және таза Node үшін код мысалдары, идемпотентті өңдеу және жиі кездесетін қателер.Қайтару API — толық және ішінара қайтаруPOST /invoices/{id}/refund әдісінің толық анықтамасы: сұрау өрістері, толық және ішінара қайтару, сома шектеуі, барлық қате коды, refund_unknown келгенде не істеу керек және қандай оқиғалар жіберіледі.

Сұрағыңыз қалды ма? WhatsApp +77788813333 · kazprose@gmail.com
Кабинеттен де жазуға болады: Қолдау.

Qut Pay — тәуелсіз сервис, «Kaspi Bank» АҚ-мен аффилирленбеген. Kaspi және Kaspi Pay — құқық иесінің тауар белгілері.