IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime31/69-dars16 daqiqa
Mundarija (18)

Uzum va boshqa mahalliy to'lov tizimlari: hujjatdan merchant API'gacha

Qisqacha: Uzum Bank Merchant API ham server-to-server: Uzum sizga beshta so'rov yuboradi — /check, /create, /confirm, /reverse, /status. Summa tiyinda, vaqt millisekundda, xato — HTTP 400 va errorCode satri (10001 … 10018, 99999). Payme'dan muhim farq: o'sha transId bilan qayta /create — xato (10010), o'sha javob emas. Bu dars yana bir ko'nikmani o'rgatadi: provayder hujjatini (bu yerda OpenAPI spetsifikatsiyasini) o'qib, uni test bilan kodga aylantirish. Kanon birinchi urinishda 99999 ortiga yashiringan SQL xatosini topdi — kutilmagan xatoni doim logga yozing.

Bu darsda

  • Uzum Merchant API'ning beshta metodini va javob formatini bilasiz.
  • Xato kodlarini va HTTP statuslarini to'g'ri qaytarasiz.
  • Payme, Click va Uzum'ning idempotentlik qoidalarini solishtirasiz.
  • 30 daqiqalik tasdiq muddati va /status so'rovlarini tushunasiz.
  • Postgres parametr turi xatosini o'z ko'zingiz bilan ko'rasiz.
  • Paynet, Octo, Atmos va Payme Subscribe qayerga to'g'ri kelishini bilasiz.
  • uzum/merchant.ts modulini yozasiz.

Oldin bilishingiz kerak: Click integratsiyasi, Payme Merchant API, Parametrli so'rovlar va SQL injection, Logging asoslari.

Oldingi ikki darsda Payme va Click hujjatlarini kodga aylantirdik. Uchinchi provayderda takrorlanadigan naqshni ko'rasiz: har yangi hujjatga bir xil savollar ro'yxati bilan qarash.

Diqqat: Uzum Bank Merchant API'si bilan ishlash uchun Uzum Bank bilan hamkorlik shartnomasi kerak. Kanon spetsifikatsiyani developer.uzumbank.uz/merchant sahifasidan o'qidi: sahifa ichiga joylangan OpenAPI 3.1 hujjati ("Merchant API 1.0.0") matn sifatida o'qildi (2026-10-27), kod bajarilmadi. Bu darsning kodi lokal simulyator bilan sinalgan; haqiqiy Uzum — sinalmagan.

1. Nega bu kerak?

Har to'lov provayderi "o'z tilida" gapiradi. Bugun Uzum, ertaga yangi bank ilovasi yoki marketplace. Har safar noldan "qanday qilishni" o'ylash — sekin va xatoli. Tajribali dasturchi yangi hujjatni ochganda bir xil savollarga javob qidiradi:

Savol Payme Click Uzum
Kim kimni chaqiradi Payme → biz Click → biz Uzum → biz
Format JSON-RPC, text/json forma, javob JSON JSON, REST uslubida
Auth Basic MD5 imzo Basic
Summa tiyin so'm, satr tiyin
Xato HTTP 200 + error.code JSON error HTTP 400 + errorCode
Takroriy yaratish o'sha javob o'sha prepare_id xato 10010
Timeout 12 soat — /confirm 30 daqiqa

Jadvaldagi har katak — kodda bitta qaror. Ularni hujjatdan oldindan yig'sangiz, kod deyarli o'zi yoziladi. Bu darsni shu jadvalning Uzum ustuni bo'yicha boramiz.

2. OpenAPI hujjatini o'qish

OpenAPI — HTTP API'ni mashina o'qiy oladigan formatda (JSON yoki YAML) tasvirlash standarti: yo'llar, metodlar, so'rov va javob sxemalari, statuslar. Uzum hujjati sayt ichida shunday spetsifikatsiya sifatida turadi (OpenAPI 3.1 darsida batafsil). Undan kanon quyidagilarni oldi:

  • yo'llar: POST /check, /create, /confirm, /reverse, /status;
  • so'rov tanasi — JSON: serviceId, timestamp, transId (UUID), params.account (buyurtma), amount (int64, tiyin);
  • muvaffaqiyat javobi — HTTP 200, status: OK, CREATED, CONFIRMED, REVERSED;
  • xato — HTTP 400, status: "FAILED" va errorCode (satr);
  • auth — Authorization: Basic ….

Kanon simulyatori bilan haqiqiy xato javobi:

text
Uzum xato (HTTP 400): {"serviceId":101202,"transId":"5c398d7e-76b6-11ee-96da-f3a095c6289d","status":"FAILED","errorCode":"10007"}

10007 — "atribut topilmadi", ya'ni bunday buyurtma yo'q. E'tibor bering: Uzum errorCode ni satr sifatida beradi ("10007"), son emas. Sxemani o'qimasdan errorCode: 10007 qaytarsangiz, qat'iy mijoz uni tushunmaydi.

3. Oqim

sequenceDiagram
  participant U as Uzum Bank
  participant S as Bizning server
  participant DB as Baza
  U->>S: POST /check (account)
  S-->>U: 200 OK
  U->>S: POST /create (transId, amount)
  S->>DB: INSERT 'yaratilgan', buyurtma 'kutilmoqda'
  S-->>U: 200 CREATED
  U->>U: kartadan yechish
  U->>S: POST /confirm (transId)
  S->>DB: 'bajarilgan', 'tolangan', ledger
  S-->>U: 200 CONFIRMED
  Note over U,S: confirm javobi kelmasa — /status 10 martagacha

Diagrammadagi beshta so'rovni bitta jadvalga yig'amiz — har biri nima so'raydi va biz nima qaytaramiz:

So'rov Uzum nima so'raydi Muvaffaqiyatli javob
/check "bu buyurtma bormi, to'lasa bo'ladimi?" OK
/create "shu transId bilan to'lovni ochaman" CREATED
/confirm "pul yechildi, tasdiqlang" CONFIRMED
/reverse "ochilgan to'lovni bekor qiling" REVERSED
/status "bu transId hozir qaysi holatda?" uchtadan biri

Uzum hujjatidagi ikki muddat qoidasi:

  1. /confirm /create dan keyin 30 daqiqa ichida kelmasa — tranzaksiya muvaffaqiyatsiz hisoblanadi.
  2. /confirm ga javob xato yoki timeout bo'lsa, Uzum holatni aniqlash uchun /status ni 10 martagacha so'raydi.

Ikkinchi qoida — server-to-server oqimining tabiiy davomi: tarmoq uzilsa, provayder taxmin qilmaydi, so'raydi. /status aniq javob berishi shart: CREATED, CONFIRMED yoki REVERSED.

4. Xato kodlari

Kod Ma'nosi
10001 auth xatosi
10002 JSON tahlil xatosi
10003 POST emas
10005 majburiy maydon yo'q
10006 serviceId boshqa
10007 buyurtma (atribut) topilmadi
10008 buyurtma allaqachon to'langan
10009 buyurtma bekor qilingan
10010 shu transId bilan allaqachon yaratilgan
10011 summa noto'g'ri
10012 / 10013 summa minimaldan kam / maksimaldan ko'p
10014 tranzaksiya topilmadi
10015 tranzaksiya bekor qilingan (confirm)
10016 allaqachon tasdiqlangan
10017 bekor qilib bo'lmaydi
10018 allaqachon bekor qilingan
99999 boshqa (kutilmagan) xato

Kanon har ishlovchini bitta o'ram (wrapper) bilan yozdi: umumiy tekshiruvlar (POST, auth, JSON, serviceId), keyin metod, xato bo'lsa — HTTP 400 va errorCode:

ts
  const ishlov =
    (
      fn: (
        b: Record<string, unknown>,
      ) => Promise<Record<string, unknown>>,
    ) =>
    async (req: Request, res: Response) => {
      let tana: Record<string, unknown> = {};
      try {
        if (req.method !== "POST") throw new UzumXato("10003");
        if (!auth(req.get("authorization")))
          throw new UzumXato("10001");
        try {
          tana = JSON.parse(
            typeof req.body === "string" ? req.body : "",
          ) as Record<string, unknown>;
        } catch {
          throw new UzumXato("10002");
        }
        if (typeof tana !== "object" || Array.isArray(tana))
          throw new UzumXato("10002");
        if (
          tana["serviceId"] === undefined ||
          tana["timestamp"] === undefined
        )
          throw new UzumXato("10005");
        if (tana["serviceId"] !== s.serviceId)
          throw new UzumXato("10006");
        res
          .status(200)
          .json({ serviceId: s.serviceId, ...(await fn(tana)) });
      } catch (e) {
        // 99999 ortida haqiqiy sabab logda qolsin (birinchi urinishda
        // SQL xatosi yashiringan edi)
        if (!(e instanceof UzumXato)) (s.log ?? console.error)(e);
        const kod = e instanceof UzumXato ? e.kod : "99999";
        res
          .status(400)
          .json({
            serviceId: tana["serviceId"] ?? s.serviceId,
            ...(typeof tana["transId"] === "string"
              ? { transId: tana["transId"] }
              : {}),
            status: "FAILED",
            errorCode: kod,
          });
      }
    };

Diqqat qiling: Payme'da auth JSON'dan keyin (javobda id qaytishi uchun), bu yerda esa oldin. Uzum xato javobida transId ixtiyoriy — shuning uchun tanani o'qishdan oldin rad etsa bo'ladi. Har provayderning javob formati tartibni belgilaydi.

5. Idempotentlik: Uzum boshqacha

Payme'da o'sha id bilan qayta CreateTransaction — o'sha muvaffaqiyatli javob. Uzum hujjatida esa xuddi shu holat uchun alohida kod bor: 10010 — "shu transId bilan tranzaksiya allaqachon yaratilgan". Kanon testi buni tasdiqladi: birinchi /create — CREATED, ikkinchisi (o'sha transId) — 10010.

Bu "idempotentlik yo'q" degani emas: takroriy so'rov holatni o'zgartirmaydi (ikkinchi tranzaksiya yaratilmaydi). Faqat javob boshqacha. Uzum tomoni 10010 ni "yaratilgan ekan" deb tushunishi kerak — buni kanon sinay olmadi (haqiqiy Uzum yo'q).

Bitta jadvalda qoidalar:

js
const takrorga = {
  payme: { yarat: "o'sha javob", tasdiq: "o'sha javob" },
  click: { yarat: "o'sha prepare_id", tasdiq: "-4 Already paid" },
  uzum: { yarat: "10010 xato", tasdiq: "10016 xato" },
};
for (const [p, q] of Object.entries(takrorga)) {
  console.log(p.padEnd(6), "|", q.yarat.padEnd(17), "|", q.tasdiq);
}

Konsolda:

text
payme  | o'sha javob       | o'sha javob
click  | o'sha prepare_id  | -4 Already paid
uzum   | 10010 xato        | 10016 xato

Tekshirib ko'ring: Uzum ikkinchi /create ga 10010 oldi. Buyurtma holati nima bo'ladi va bazada nechta tranzaksiya?

Javob

Hech narsa o'zgarmaydi: buyurtma kutilmoqda, tranzaksiya bitta (UNIQUE (provayder, tashqi_id) va kod ichidagi tekshiruv). Kanon kodi 10010 ni buyurtma holatini tekshirishdan oldin qaytaradi — takroriy so'rov boshqa xatoga aylanib ketmasin.

Saboq: "idempotent" so'zi protokolda boshqa-boshqa javob bilan ifodalanadi. Umumiy kod yozishga (bitta merchant klassi uchala provayderga) shoshilmang — kanon har provayderni alohida modulda yozdi, umumiy qismi faqat tranzaksiya() va ledger.

6. 30 daqiqalik tasdiq

Kanon /confirm da tranzaksiya yoshini tekshiradi: 30 daqiqadan eski bo'lsa — tranzaksiya bekor, buyurtma yana yangi, Uzum'ga 10015. Payme'dagi timeout kabi bekor qilish COMMIT bo'lishi kerak, shuning uchun bu yerda ham xato qaytarilib, keyin tashlanadi.

/create da esa boshqa holat: buyurtmada 30 daqiqadan eski chala tranzaksiya qolib ketgan bo'lsa, u avval yopiladi va yangisiga joy bo'shaydi. Yangi (30 daqiqadan yosh) faol tranzaksiya bo'lsa — 10017.

Testda soat 30 daqiqa + 1 soniya oldinga surildi: /confirm → 10015, /status → REVERSED, keyin o'sha buyurtma yangi transId bilan muvaffaqiyatli to'landi.

/reverse faqat CREATED (yaratilgan) tranzaksiyani bekor qiladi. Tasdiqlangan (CONFIRMED) tranzaksiyani reverse qilish — hujjat aniq aytmagan; kanon qarori: 10017 (bekor qilib bo'lmaydi).

7. Kanon topilmasi: 99999 ortidagi SQL xatosi

Kanonning birinchi urinishida /create har doim 99999 qaytardi. Kod "kutilmagan xato" ni ushlab, 99999 berar va jim qolar edi. Logda hech narsa yo'q — sabab topilmadi. Kutilmagan xatolar logga yozila boshlagach, haqiqiy xabar ko'rindi: Postgres bitta parametrning turini aniqlay olmagan.

INSERT da $4 ikki joyda ishlatilgan edi: tashqi_vaqt ustuniga (bigint) va to_timestamp($4 / 1000.0) ichida (numeric bilan bo'lish). Buni o'z Postgres 17.6 klasterimizda kanon sxemasi ustida takrorladik (biz ishga tushirdik). Avval bitta buyurtma qo'shamiz, keyin xato so'rovni tayyorlaymiz. Ikkinchi chiqishda xato xabaridagi $4 ga qarang:

sql
INSERT INTO buyurtmalar (foydalanuvchi_id, summa)
VALUES (1, 150000);
text
INSERT 0 1
sql
\set VERBOSITY terse
PREPARE yarat AS
  INSERT INTO tranzaksiyalar (provayder, tashqi_id, buyurtma_id,
    summa, holat, tashqi_vaqt, yaratilgan_vaqt)
  VALUES ('uzum', $1, $2, $3, 'yaratilgan', $4,
    to_timestamp($4 / 1000.0));
text
ERROR:  inconsistent types deduced for parameter $4 at character 175

Postgres bitta $4 uchun ikki xil tur chiqardi: ustun uchun bigint, bo'lish uchun numeric. Tuzatish — ikkita alohida parametr (kanon kodida $4 va $5, ikkalasiga ham hozir() beriladi):

sql
PREPARE yarat2 AS
  INSERT INTO tranzaksiyalar (provayder, tashqi_id, buyurtma_id,
    summa, holat, tashqi_vaqt, yaratilgan_vaqt)
  VALUES ('uzum', $1, $2, $3, 'yaratilgan', $4,
    to_timestamp($5 / 1000.0))
  RETURNING id, tashqi_vaqt, yaratilgan_vaqt;
EXECUTE yarat2('t-1', 1, 150000, 1793095200000, 1793095200000);
text
PREPARE
 id |  tashqi_vaqt  |    yaratilgan_vaqt     
----+---------------+------------------------
  1 | 1793095200000 | 2026-10-27 10:00:00+00
(1 row)

INSERT 0 1

pg drayveri har parametrli so'rovni shunday tayyorlaydi (Parametrli so'rovlar va SQL injection), shuning uchun xato ilovada ham aynan shu edi.

Asosiy saboq SQL'da emas, xato ishlovida. 99999 — provayder uchun to'g'ri javob. Lekin sizga haqiqiy sabab kerak. Kanon uchala provayderda ham qoidani bir xil qildi: kutilmagan xato avval logga yoziladi (Logging asoslari), keyin umumiy kod qaytariladi — Payme -32400, Click -7, Uzum 99999.

8. Boshqa mahalliy tizimlar

Kanon bularni sinamagan — quyidagi tavsif umumiy, faqat yo'nalish uchun. Har biri bilan ishlashdan oldin rasmiy hujjat va shartnoma kerak.

Tizim Qanday to'lov Qachon kerak
Paynet to'lov shoxobchalari va ilova orqali naqd pul bilan to'lovchi mijozlar
Octo, Atmos karta to'lovi va tokenlash kartani saqlab, keyin qayta yechish
Payme Subscribe (cards API) karta tokeni orqali yechish obuna, avtomatik to'lov
Uzum Checkout redirect sahifasi, karta Uzum Bank ilovasisiz karta to'lovi

Tokenlash (tokenization) — karta raqami o'rniga provayder bergan token'ni saqlash. Karta ma'lumoti sizning serveringizga tushmaydi, lekin keyingi to'lovni mijoz ishtirokisiz qilish mumkin. Bu obunalar uchun asos (Obuna va davriy to'lovlar).

Yangi tizimga duch kelsangiz — «Nega bu kerak?» bo'limidagi jadvalning yangi ustunini to'ldiring. Ustun to'lmaguncha kod yozmang.

9. Ko'p uchraydigan xatolar

Xato Oqibati To'g'risi
errorCode ni son qilib qaytarish sxemaga mos emas satr: "10007"
xatoda HTTP 200 Uzum muvaffaqiyat deb o'ylaydi HTTP 400 + FAILED
takroriy /create ga CREATED protokolga zid 10010
/status ni yozmaslik timeout'dan keyin holat noma'lum aniq CREATED/CONFIRMED/REVERSED
30 daqiqa tekshiruvsiz eski urinish buyurtmani qulflaydi /create da eskisini yopish, /confirm da 10015
99999 ni logsiz qaytarish bug yashiringan avval log, keyin umumiy kod
bitta parametr ikki turda inconsistent types alohida parametrlar

10. Mashqlar

Mashqlar kurs/mashqlar/29/31-uzum/ da.

1-mashq (oson): qaysi kod?

  1. serviceId: 1 keldi, sizniki 101202.
  2. O'sha transId bilan ikkinchi /create.
  3. Tasdiqlangan tranzaksiyaga yana /confirm.
  4. /create da amount: 1, minimal summa 1 000 so'm.
  5. Hech qachon yaratilmagan transId bilan /status.
Yechim
  1. 10006. 2. 10010. 3. 10016. 4. 10012. 5. 10014. Hammasi HTTP 400 va status: "FAILED" bilan.

2-mashq (o'rta): xato javobi

xatoJavob(tana, kod, serviceId) yozing: { http: 400, json } qaytarsin. json da serviceId (tanada bo'lsa — o'shasi, yo'q bo'lsa — bizniki), transId (faqat tanada satr bo'lsa), status: "FAILED", errorCode — satr. Uch holatni sinang: to'liq tana, bo'sh tana (JSON buzuq bo'lgan), transId son bo'lgan tana.

Yechim
js
function xatoJavob(tana, kod, serviceId) {
  const json = { serviceId: tana.serviceId ?? serviceId };
  if (typeof tana.transId === "string") json.transId = tana.transId;
  json.status = "FAILED";
  json.errorCode = String(kod);
  return { http: 400, json };
}

const tid = "5c398d7e-76b6-11ee-96da-f3a095c6289d";
console.log(xatoJavob({ serviceId: 1, transId: tid }, 10006, 101202));
console.log(xatoJavob({}, 10002, 101202));
const sonli = { serviceId: 101202, transId: 7 };
console.log(xatoJavob(sonli, 10005, 101202));

Konsolda:

text
{
  http: 400,
  json: {
    serviceId: 1,
    transId: '5c398d7e-76b6-11ee-96da-f3a095c6289d',
    status: 'FAILED',
    errorCode: '10006'
  }
}
{
  http: 400,
  json: { serviceId: 101202, status: 'FAILED', errorCode: '10002' }
}
{
  http: 400,
  json: { serviceId: 101202, status: 'FAILED', errorCode: '10005' }
}

Birinchi holatda serviceId — Uzum yuborgan (noto'g'ri) qiymat: kanon ham shunday qiladi, provayder o'z so'rovini javobda taniydi. Ikkinchisida tana bo'sh — bizniki.

3-mashq (qiyin): /status so'rash simulyatsiyasi

Uzum /confirm javobini olmasa /status ni 10 martagacha so'raydi. holatniAniqla(sorash, maks) yozing: sorash(n) — n-urinishda holatni qaytaradi yoki "timeout" tashlaydi. Funksiya birinchi aniq javobni (CONFIRMED, REVERSED, CREATED) urinishlar soni bilan qaytarsin; maks tugasa — { holat: "noma'lum", urinish: maks }. Ikki ssenariy: 3-urinishda CONFIRMED; doim timeout (maks = 10).

Yechim
js
function holatniAniqla(sorash, maks) {
  for (let n = 1; n <= maks; n++) {
    try {
      return { holat: sorash(n), urinish: n };
    } catch {
      // timeout — keyingi urinish
    }
  }
  return { holat: "noma'lum", urinish: maks };
}

const uchinchida = (n) => {
  if (n < 3) throw new Error("timeout");
  return "CONFIRMED";
};
const doim = () => {
  throw new Error("timeout");
};

console.log(holatniAniqla(uchinchida, 10));
console.log(holatniAniqla(doim, 10));

Konsolda:

text
{ holat: 'CONFIRMED', urinish: 3 }
{ holat: "noma'lum", urinish: 10 }

"Noma'lum" holat — eng xavflisi: pul yechilgan bo'lishi mumkin, lekin tasdiqlanmagan. Uni avtomatik hal qilib bo'lmaydi; bunday tranzaksiyalar kunlik solishtirishda (reconciliation) chiqadi va qo'lda ko'riladi. Haqiqiy qayta urinishlar orasida kutish bo'ladi (eksponensial kutish — timers va timers/promises chuqur) — mashqda soddalik uchun yo'q.

11. Loyiha qadami: uzum/merchant.ts

O'ram (ishlov) yuqorida ko'rsatildi. /create ishlovchisi — eng ko'p qaror qabul qilinadigan joy (formatlangan nusxa, ma'nosi kanondagidek):

ts
  r.all(
    "/create",
    ishlov((b) =>
      tranzaksiya(s.pool, async (c) => {
        const oid = buyurtmaId(b);
        const tid = transId(b);
        const amount = b["amount"];
        if (
          typeof amount !== "number" ||
          !Number.isSafeInteger(amount)
        )
          throw new UzumXato("10005");
        const q = (
          await c.query<{
            id: string;
            summa: string;
            holat: string;
          }>(
            "SELECT id, summa, holat FROM buyurtmalar WHERE id = " +
              "$1 FOR UPDATE",
            [oid],
          )
        ).rows[0];
        if (!q) throw new UzumXato("10007");
        if (
          (
            await c.query(
              "SELECT 1 FROM tranzaksiyalar WHERE provayder = " +
                "'uzum' AND tashqi_id = $1",
              [tid],
            )
          ).rowCount
        )
          throw new UzumXato("10010");
        if (q.holat === "tolangan" || q.holat === "qaytarilgan")
          throw new UzumXato("10008");
        if (q.holat === "bekor") throw new UzumXato("10009");
        if (s.min !== undefined && amount < s.min)
          throw new UzumXato("10012");
        if (s.maks !== undefined && amount > s.maks)
          throw new UzumXato("10013");
        if (amount !== Number(q.summa)) throw new UzumXato("10011");
        // 30 daqiqadan eski chala tranzaksiya — muvaffaqiyatsiz deb
        // yopiladi (hujjat), yangisiga joy bo'shaydi
        await c.query(
          "UPDATE tranzaksiyalar SET holat = 'bekor', bekor_vaqt = " +
            "now() WHERE buyurtma_id = $1 AND holat = 'yaratilgan' " +
            "AND yaratilgan_vaqt < to_timestamp($2 / 1000.0)",
          [q.id, hozir() - CONFIRM_MUDDAT_MS],
        );
        if (
          (
            await c.query(
              "SELECT 1 FROM tranzaksiyalar WHERE buyurtma_id = $1 " +
                "AND holat = 'yaratilgan'",
              [q.id],
            )
          ).rowCount
        )
          throw new UzumXato("10017");
        const t = (
          await c.query<Tx>(
            "INSERT INTO tranzaksiyalar (provayder, tashqi_id, " +
              "buyurtma_id, summa, holat, tashqi_vaqt, " +
              "yaratilgan_vaqt) VALUES ('uzum', $1, $2, $3, " +
              "'yaratilgan', $4, to_timestamp($5 / 1000.0)) " +
              "RETURNING *",
            [tid, q.id, amount, hozir(), hozir()],
          )
        ).rows[0];
        if (!t) throw new UzumXato("99999");
        await c.query(
          "UPDATE buyurtmalar SET holat = 'kutilmoqda', " +
            "yangilangan_vaqt = now() WHERE id = $1",
          [q.id],
        );
        return { ...tolov(t), status: "CREATED", data: data(oid) };
      }),
    ),
  );

Testlar — tolov/test/uzum.test.ts, uchta: to'liq oqim (OK → CREATED → CONFIRMED → /status), xatolar (10001, 10002, 10006, 10007, 10010, 10011, 10012, 10016, 10014, 10017), reverse va 30 daqiqa. Biz ishga tushirdik (kanon nusxasi, Node 24.21.0, Postgres 17.6). Chiqishda har ✔ qatoridagi kodlar ro'yxatiga va pastdagi fail 0 ga qarang:

text
▶ Uzum Merchant API (simulyator)
  ✔ to'liq oqim: check OK → create CREATED → confirm CONFIRMED → status (123.0293ms)
  ✔ xatolar: 10001 auth, 10002 JSON, 10006 serviceId, 10007, 10010 takror create, 10011, 10012, 10016, 10014 (63.521ms)
  ✔ reverse (CREATED) → REVERSED; takror → 10018; confirm → 10015; 30 daqiqadan keyin confirm → 10015 (64.1536ms)
✔ Uzum Merchant API (simulyator) (1376.5466ms)
ℹ tests 3
ℹ suites 1
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1830.2868

Nima bo'ldi? Uchala test o'tdi: to'liq oqim, o'nta xato kodi va 30 daqiqalik qoida simulyatorda kutilganidek ishladi. Qavs ichidagi millisekundlar har ishga tushirishda boshqacha chiqadi — ular natija emas, faqat vaqt.

Kanon o'lchovi: 100 ta to'liq to'lov (check + create + confirm) — mediana 40.3 ms, p95 54.9 ms, 100/100 (lokal simulyator).

Commit xabari:

bash
M='feat(tolov): Uzum Merchant API — 5 metod, 30 daqiqa,'
M+=' kutilmagan xato logda, simulyator'
git commit -m "$M"

12. Real ishda

  • Hujjat versiyasi: OpenAPI hujjatida versiya bor (1.0.0). Provayder uni yangilasa, maydonlar o'zgarishi mumkin — integratsiyani qachon tekshirganingizni yozib qo'ying (kanon — 2026-10-27).
  • Ko'p provayder — bitta tranzaksiyalar jadvali, provayder ustuni bilan. Hisobot va solishtirish bitta joyda.
  • Monitoring: har provayder uchun "kutilmagan xato" soni alohida. 99999 ko'payishi — sizda bug, provayderda emas.
  • Intervyuda: "Yangi to'lov tizimi hujjatini qanday o'qiysiz?", "Takroriy so'rovga qanday javob berasiz?"

Xulosa

  • Uzum: beshta POST metod, JSON, Basic auth, summa tiyinda, xato — HTTP 400 + errorCode satri.
  • Takroriy /create — 10010 xatosi (Payme'dan farqli), lekin holat o'zgarmaydi.
  • /confirm 30 daqiqada kelmasa — bekor; javob yo'qolsa Uzum /status ni 10 martagacha so'raydi.
  • Bitta parametr ikki turda — inconsistent types deduced; kutilmagan xato avval logga, keyin umumiy kodga.
  • Yangi provayder — yettita savol jadvali: kim chaqiradi, format, auth, summa, xato, takror, timeout.

Keyingi dars: Stripe — xalqaro to'lovlar: PaymentIntent, Checkout Session va imzoli webhook.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!