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

Click integratsiyasi: Prepare, Complete va MD5 imzo

Qisqacha: Click SHOP API ikki bosqichli: avval Click sizning prepare manzilingizga "shu buyurtmaga to'lov tayyorla" deydi, pul yechilgach complete manziliga "tasdiqla" deydi. Har so'rov forma ko'rinishida (x-www-form-urlencoded) keladi va MD5 imzo (sign_string) bilan himoyalangan — maydonlar va maxfiy kalitdan hisoblangan hash. Summa so'mda, kasr bilan satr bo'lib keladi ("12500.50") — uni float'siz tiyinga o'giramiz va buyurtma summasi bilan aniq solishtiramiz. Click hujjat sahifasi (SPA) matnini kanon ololmadi; formula va xato kodlari Click'ning rasmiy PHP kutubxonasi kodidan olingan. Sinov — lokal simulyator bilan.

Bu darsda

  • Prepare va Complete bosqichlarini va ular orasidagi bog'lanishni bilasiz.
  • sign_string MD5 imzosini hisoblaysiz va tekshirasiz.
  • So'mdagi summani tiyinga xavfsiz o'girasiz.
  • Click xato kodlarini (0, -1 … -9) to'g'ri qaytarasiz.
  • Takroriy va parallel so'rovlar, muvaffaqiyatsiz to'lovdan keyin qayta to'lashni ko'rasiz.
  • click/shop.ts modulini yozasiz.

Oldin bilishingiz kerak: Payme Merchant API, Pul bilan ishlash JS'da, crypto 1: tasodif, hash va HMAC, Request body o'qish va hajm cheklovi.

Oldingi darsda Payme bitta URL va JSON-RPC bilan gaplashdi. Click boshqacha: ikki URL, forma maydonlari va imzo. Lekin savollar o'sha: takror, parallel, summa, holat.

Payme Click
kim chaqiradi provayder → sizning server provayder → sizning server
URL bitta ikkita: prepare, complete
tana JSON-RPC (text/json) forma (x-www-form-urlencoded)
kimligini isbotlash Basic auth (kassa kaliti) MD5 imzo (sign_string)
summa tiyinda, butun son so'mda, kasrli satr
xato error.code (-32xxx, -31xxx) error (0, -1 … -9)

Jadvaldagi har farqni bu darsda ko'ramiz.

Diqqat: Haqiqiy Click bilan ishlash uchun Click bilan shartnoma va kabinetdagi service_id, merchant_id, maxfiy kalit kerak. Kanon docs.click.uz sahifasi matnini ololmadi (u brauzerda JavaScript bilan chiziladigan sahifa — SPA). Shuning uchun imzo formulasi va xato kodlari rasmiy click-llc/click-integration-php kutubxonasi kodi asosida (GitHub, 2026-10-27). Bu darsning kodi lokal simulyator bilan sinalgan; haqiqiy Click — sinalmagan.

1. Nega bu kerak?

Click — O'zbekistondagi yana bir keng tarqalgan to'lov tizimi. Ko'p do'konlar Payme va Click'ni birga ulaydi. Agar Click uchun alohida, "tezda" yozilgan kod bo'lsa, eng ko'p uchraydigan xatolar:

  • imzo tekshirilmaydi — har kim complete manziliga so'rov yuborib, buyurtmani to'langan qiladi;
  • summa parseFloat bilan o'qiladi va 1000.001 "taxminan to'g'ri" deb qabul qilinadi;
  • mijoz to'lov o'tmagach qayta bossa, eski chala urinish buyurtmani qulflab qo'yadi.

Bu darsda hammasini kanon kodi va testlari bilan yopamiz.

2. Ikki bosqich

sequenceDiagram
  participant C as Click
  participant S as Bizning server
  participant DB as Baza
  C->>S: POST /prepare (action=0, imzo)
  S->>DB: imzo, buyurtma, summa, INSERT 'yaratilgan'
  S-->>C: error 0, merchant_prepare_id
  C->>C: kartadan yechish
  C->>S: POST /complete (action=1, merchant_prepare_id, imzo)
  S->>DB: 'bajarilgan', buyurtma 'tolangan', ledger
  S-->>C: error 0, merchant_confirm_id

Diagrammaga qarang: prepare javobidagi merchant_prepare_id — bizning tranzaksiya id'imiz. Click uni complete da qaytarib yuboradi va u imzoga ham kiradi. Shunday qilib ikki so'rov bir-biriga bog'lanadi.

Asosiy maydonlar:

Maydon Ma'nosi
click_trans_id Click tranzaksiya id'si (biz uchun tashqi id)
service_id sizning xizmatingiz raqami (kabinetda)
merchant_trans_id bizning buyurtma raqami
merchant_prepare_id prepare da biz bergan id (faqat complete da)
amount summa, so'mda, kasr bilan: "12500.50"
action 0 — prepare, 1 — complete
error, error_note Click tomonidagi holat (complete'da to'lov o'tmagan bo'lsa manfiy)
sign_time imzo vaqti, "YYYY-MM-DD HH:mm:ss"
sign_string MD5 imzo

So'rov tanasi — forma: click_trans_id=4000000&service_id=77001&…. Express'da express.urlencoded() o'qiydi. Javob — JSON.

Kanon simulyatori bilan haqiqiy prepare javobi:

text
Click prepare javob: {"click_trans_id":"4000000","merchant_trans_id":"2","merchant_prepare_id":2,"error":0,"error_note":"Success"}

3. MD5 imzo

Click so'rov haqiqatan Click'dan kelganini qanday isbotlaydi? Ikkala tomon bitta maxfiy kalitni biladi. Click maydonlarni va kalitni bir qatorga yopishtiradi, MD5 hash'ini oladi va sign_string qilib yuboradi. Siz xuddi shunday hisoblaysiz va solishtirasiz. Kalitni bilmagan odam to'g'ri imzo yasay olmaydi.

Formula (rasmiy PHP kutubxonasidan):

text
md5(click_trans_id + service_id + SECRET_KEY + merchant_trans_id
    + (action == 1 ? merchant_prepare_id : "")
    + amount + action + sign_time)

Brauzerning Web Crypto'sida MD5 yo'q (u faqat xavfsiz hash'larni beradi), shuning uchun bu misol Node'da — node:crypto:

js
import { createHash } from "node:crypto";

const md5 = (s) => createHash("md5").update(s).digest("hex");

function clickImzo(q, sir) {
  const prepareId = q.action === "1"
    ? (q.merchant_prepare_id ?? "") : "";
  return md5(q.click_trans_id + q.service_id + sir
    + q.merchant_trans_id + prepareId + q.amount + q.action
    + q.sign_time);
}

const q = {
  click_trans_id: "4000000", service_id: "77001",
  merchant_trans_id: "2", amount: "12500.50", action: "0",
  sign_time: "2026-10-27 15:00:00",
};
const imzo = clickImzo(q, "SOXTA-sir");
console.log(imzo);
const boshqa = clickImzo({ ...q, amount: "1.00" }, "SOXTA-sir");
console.log(boshqa === imzo);
const c = { ...q, action: "1", merchant_prepare_id: "2" };
console.log(clickImzo(c, "SOXTA-sir"));

Konsolda:

text
a2768ec5d850e6f696822bfdf10c6580
false
feadb74510d0a53dc19e59c4b413603f
  • Bitta maydon (summa) o'zgarsa — imzo butunlay boshqa. Hujumchi summani 1 so'mga tushirsa, imzo mos kelmaydi.
  • complete (action = 1) imzosiga merchant_prepare_id qo'shiladi.
  • Imzo matn ustida: "12500.50" va "12500.5" — boshqa imzolar. Shuning uchun imzoni Click yuborgan xom satrlardan hisoblaysiz, o'zingiz qayta formatlagan summadan emas.

Kanon solishtirishni ham ehtiyotkor qildi: imzo kichik harfga keltiriladi va timingSafeEqual bilan solishtiriladi (Payme darsidagi sabab bilan):

ts
export function clickImzo(q: ClickSorov, sir: string): string {
  const prepareId =
    q["action"] === "1" ? (q["merchant_prepare_id"] ?? "") : "";
  const s =
    (q["click_trans_id"] ?? "") +
    (q["service_id"] ?? "") +
    sir +
    (q["merchant_trans_id"] ?? "") +
    prepareId +
    (q["amount"] ?? "") +
    (q["action"] ?? "") +
    (q["sign_time"] ?? "");
  return createHash("md5").update(s).digest("hex");
}

const imzoTogri = (q: ClickSorov, sir: string) => {
  const k = Buffer.from(clickImzo(q, sir));
  const b = Buffer.from((q["sign_string"] ?? "").toLowerCase());
  return k.length === b.length && timingSafeEqual(k, b);
};

Maslahat: MD5 bugun kriptografik jihatdan zaif hash (to'qnashuv topish mumkin). Yangi tizim yaratayotgan bo'lsangiz — HMAC-SHA256 (Kiruvchi webhook'larni qabul qilish darsida). Lekin Click protokolini siz tanlamaysiz: provayder qanday talab qilsa, shunday tekshirasiz.

4. Summa: so'mda keladi

Payme va Uzum summani tiyinda butun son bilan yuboradi. Click — so'mda, satr bilan: "1000", "1000.00", "12500.5". Kanon clickSummasi — oldingi darsdagi somdanTiyin, vergul nuqtaga almashtirilgan holda:

js
function somdanTiyin(s) {
  const m = /^(-?)(\d+)(?:[.,](\d{1,2}))?$/.exec(s.trim());
  if (!m) throw new Error(`noto'g'ri summa: "${s}"`);
  const n = Number(m[2]) * 100 + Number((m[3] ?? "").padEnd(2, "0"));
  return m[1] === "-" ? -n : n;
}
const clickSummasi = (s) => somdanTiyin(s.replace(",", "."));

const buyurtma = 100_000; // 1 000 so'm, tiyinda
for (const s of ["1000", "1000.00", "999.99", "1000.001"]) {
  try {
    const t = clickSummasi(s);
    console.log(s, t, t === buyurtma ? 0 : -2);
  } catch {
    console.log(s, "xato", -2);
  }
}

Konsolda:

text
1000 100000 0
1000.00 100000 0
999.99 99999 -2
1000.001 xato -2

Muhim kanon qarori: rasmiy PHP kutubxonasi summalar orasida 0.01 farqqa ruxsat beradi (float solishtirishdagi xatoni yashirish uchun). Kanon aniq tenglik talab qiladi: tiyinda butun sonlar — farq yo'q. 999.99 — bir tiyin kam, -2. 1000.001 — kasrda uch raqam, yaxlitlab "to'g'rilamaymiz", -2.

5. Xato kodlari

Kod error_note Qachon
0 Success hammasi joyida
-1 SIGN CHECK FAILED! imzo mos emas
-2 Incorrect parameter amount summa noto'g'ri yoki mos emas
-3 Action not found action manzilga mos emas
-4 Already paid buyurtma yoki tranzaksiya to'langan
-5 User does not exist buyurtma topilmadi
-6 Transaction does not exist complete da prepare topilmadi
-7 Failed to update user kutilmagan xato (kanon tanlovi)
-8 Error in request from click maydon yetishmaydi, service_id boshqa
-9 Transaction cancelled tranzaksiya bekor qilingan

"User does not exist" — Click'ning tarixiy nomi; amalda merchant_trans_id (buyurtma) topilmadi degani.

Tekshiruv tartibi muhim. Kanon avval umumiy tekshiruvlarni bajaradi, keyin bazaga boradi:

ts
  const umumiy = (
    q: ClickSorov,
    action: "0" | "1",
  ): number | undefined => {
    if (
      MAJBURIY.some((k) => q[k] === undefined) ||
      (action === "1" && q["merchant_prepare_id"] === undefined)
    )
      return -8;
    if (!imzoTogri(q, s.sir)) return -1;
    if (q["action"] !== action) return -3;
    if (q["service_id"] !== s.serviceId) return -8;
    if (!/^\d{1,18}$/.test(q["merchant_trans_id"] ?? "")) return -5;
    return undefined;
  };
  1. Maydonlar to'liqmi (yo'q bo'lsa -8) — imzo hisoblash uchun ham kerak.
  2. Imzo (-1) — bazaga tegmasdan, birinchi bo'lib. Imzosiz so'rov bazada hech narsani o'qitmasligi kerak.
  3. action manzilga mosmi (-3), service_id bizniki mi (-8), merchant_trans_id raqammi (-5).

-7 haqida ochiq aytamiz: rasmiy kutubxona bu kodni ishlatmaydi. Kanon uni kutilmagan xato uchun tanladi (baza uzildi va h.k.): Click JSON kutadi, Express'ning sukut HTML 500 sahifasi emas. Click bu javobga qanday munosabatda bo'lishi — sinalmagan.

6. Prepare: takror, qayta urinish, parallel

prepare ichidagi qarorlar (kanon, bitta ma'lumotlar bazasi tranzaksiyasida, buyurtma qatori FOR UPDATE bilan qulflangan):

Holat Javob
o'sha click_trans_id qayta keldi, tranzaksiya yaratilgan 0 va o'sha merchant_prepare_id
o'sha click_trans_id, tranzaksiya bajarilgan / bekor -4 / -9
buyurtma to'langan yoki qaytarilgan -4
summa mos emas -2
buyurtmada boshqa provayderning faol tranzaksiyasi -9
buyurtmada eski chala Click urinishi eskisi bekor, yangisi yaratiladi

Oxirgi ikki qator — kanon qarori, hujjatda yo'q. Sababi hayotiy: mijoz Click'da to'lashni boshladi, oynani yopdi, qayta bosdi. Click yangi click_trans_id bilan keladi. Eski urinish yaratilgan holatida qolgan — agar uni bekor qilmasak, qisman noyob indeks yangi urinishni to'xtatardi va mijoz hech qachon to'lay olmasdi. Lekin buyurtma Payme orqali to'lanayotgan bo'lsa (boshqa provayder), uni bekor qilmaymiz: -9.

Kanon testi bir buyurtmaga 10 ta parallel prepare yubordi (har biri yangi click_trans_id): hammasi 0 oldi (har biri oldingisini bekor qildi), bazada esa faqat bitta faol tranzaksiya qoldi. Navbatni buyurtma qatoridagi qulf ta'minladi.

7. Complete: Click'ning o'z xatosi

complete da Click o'z holatini error maydonida yuboradi. Masalan, kartada mablag' yetmasa — manfiy kod (kanon testida -5017, "Insufficient funds"). Bu holda biz:

  1. tranzaksiyani bekor qilamiz (Click xatosini qoshimcha ustunida saqlab);
  2. buyurtmani kutilmoqda dan yangi ga qaytaramiz — qayta to'lash mumkin;
  3. Click'ga -9 (Transaction cancelled) javob beramiz.

Test buni tasdiqladi: -5017 dan keyin -9, so'ng o'sha buyurtma yangi urinishda muvaffaqiyatli to'landi.

error = 0 bo'lsa: summa yana tekshiriladi (imzo bilan himoyalangan, lekin buyurtma bilan mosligi — bizning mas'uliyat), tranzaksiya bajarilgan, buyurtma tolangan, ledger yozuvi, javobda merchant_confirm_id. Takroriy complete — -4 (Already paid).

Tekshirib ko'ring: Payme takroriy Perform ga o'sha muvaffaqiyatli javobni qaytaradi, Click takroriy complete ga esa -4 qaytaradi. Qaysi biri to'g'ri?

Javob

Ikkalasi ham — har biri o'z provayderining protokoli bo'yicha. Payme hujjati takrorga o'sha natijani talab qiladi. Click kutubxonasi esa "allaqachon to'langan" xatosini qaytaradi va Click buni to'lov bajarilgan deb tushunadi. Idempotentlik — "har doim o'sha javob" emas, balki "takroriy so'rov holatni ikkinchi marta o'zgartirmaydi" degani. Javob formati provayderga bog'liq.

8. Simulyator

mock/click.ts — Click serverining o'rnini bosadi: maydonlarni to'ldiradi (sign_time — Toshkent vaqti), imzolaydi va fetch bilan forma yuboradi. imzoBuz sozlamasi bilan ataylab noto'g'ri imzo yuborish mumkin (test -1 ni shunday tekshiradi). Kanon 100 ta to'liq to'lov (Prepare + Complete) o'lchadi: mediana 42.8 ms, p95 224.5 ms, 100/100.

Taqlid qilinmaganlar: Click'ning test emulyatori (hujjatda tilga olingan dastur), Click Merchant API (invoys yaratish, karta tokeni) — hujjat bo'yicha, sinalmagan.

9. Ko'p uchraydigan xatolar

Xato Oqibati To'g'risi
imzoni tekshirmaslik soxta complete — tekin buyurtma har so'rovda MD5, bazadan oldin
imzoni qayta formatlangan summadan haqiqiy so'rov -1 oladi Click yuborgan xom satr
parseFloat(amount) 1000.001 "to'g'ri" satrdan tiyinga, aniq tenglik
0.01 farqqa ruxsat tiyin yo'qoladi tiyinda aniq solishtirish
eski chala urinishni bekor qilmaslik mijoz qayta to'lay olmaydi yangi Click urinishi eskisini bekor qiladi
Express HTML 500 Click JSON kutadi xato ishlovchi -7 JSON
error < 0 ni e'tiborsiz qoldirish to'lanmagan buyurtma "to'langan" tranzaksiya bekor, -9

10. Mashqlar

Mashqlar kurs/mashqlar/29/30-click/ da.

1-mashq (oson): qaysi kod?

  1. sign_string 32 ta nol.
  2. /prepare ga action=1 keldi.
  3. merchant_trans_id=987654 — bunday buyurtma yo'q.
  4. complete da merchant_prepare_id=999999 — bunday prepare yo'q.
  5. To'langan buyurtmaga yangi click_trans_id bilan prepare.
Yechim
  1. -1. 2. -3. 3. -5. 4. -6. 5. -4. Kanon testi beshalasini ham shu kodlar bilan tekshiradi.

2-mashq (o'rta): forma tanasidan maydonlar

Click tanasi — forma satri. URLSearchParams bilan tahlil(tana) yozing: majburiy maydonlar (click_trans_id, service_id, merchant_trans_id, amount, action, sign_time, sign_string) bormi — yo'q bo'lsa { error: -8, yoq: [...] }, bor bo'lsa maydonlar obyekti. Ikki holatni sinang: to'liq tana va faqat click_trans_id=1.

Yechim
js
const MAJBURIY = ["click_trans_id", "service_id", "merchant_trans_id",
  "amount", "action", "sign_time", "sign_string"];

function tahlil(tana) {
  const q = Object.fromEntries(new URLSearchParams(tana));
  const yoq = MAJBURIY.filter((k) => q[k] === undefined);
  return yoq.length ? { error: -8, yoq } : q;
}

const toliq = new URLSearchParams({
  click_trans_id: "4000000", service_id: "77001",
  merchant_trans_id: "2", amount: "12500.50", action: "0",
  sign_time: "2026-10-27 15:00:00", sign_string: "abc",
}).toString();

console.log(toliq.slice(0, 48));
const q = tahlil(toliq);
console.log(q.amount, q.sign_time);
console.log(tahlil("click_trans_id=1"));

Konsolda:

text
click_trans_id=4000000&service_id=77001&merchant
12500.50 2026-10-27 15:00:00
{
  error: -8,
  yoq: [
    'service_id',
    'merchant_trans_id',
    'amount',
    'action',
    'sign_time',
    'sign_string'
  ]
}

Formada bo'shliq + bo'lib keladi (2026-10-27+15:00:00), URLSearchParams uni qaytadan bo'shliqqa aylantiradi. Imzo aynan shu dekodlangan satrdan hisoblanadi. Kanon ro'yxatida yana click_paydoc_id, error va error_note ham majburiy.

3-mashq (qiyin): prepare qarori

Bazasiz prepareQarori(buyurtma, tranzaksiyalar, clickTransId, summa) yozing. buyurtma — { id, summa, holat }, tranzaksiyalar — { provayder, tashqiId, holat } ro'yxati. «Prepare: takror, qayta urinish, parallel» bo'limidagi jadval bo'yicha { e, amal } qaytarsin: amal — "yangi", "oldingi", "eskini-bekor-qil" yoki null. To'rt holatni sinang: toza buyurtma; o'sha click_trans_id qayta; eski Click urinishi bor; Payme urinishi bor.

Yechim
js
function prepareQarori(b, txlar, clickTransId, summa) {
  const bor = txlar.find((t) =>
    t.provayder === "click" && t.tashqiId === clickTransId);
  if (bor) {
    if (bor.holat === "bekor") return { e: -9, amal: null };
    if (bor.holat === "bajarilgan") return { e: -4, amal: null };
    return { e: 0, amal: "oldingi" };
  }
  if (["tolangan", "qaytarilgan"].includes(b.holat)) {
    return { e: -4, amal: null };
  }
  if (b.holat === "bekor") return { e: -9, amal: null };
  if (b.summa !== summa) return { e: -2, amal: null };
  const faol = txlar.find((t) => t.holat === "yaratilgan");
  if (faol && faol.provayder !== "click") {
    return { e: -9, amal: null };
  }
  return { e: 0, amal: faol ? "eskini-bekor-qil" : "yangi" };
}

const b = { id: 1, summa: 100_000, holat: "yangi" };
const k = (id, holat) =>
  ({ provayder: "click", tashqiId: id, holat });
console.log(prepareQarori(b, [], "40", 100_000));
console.log(prepareQarori(b, [k("40", "yaratilgan")], "40", 100_000));
console.log(prepareQarori(b, [k("40", "yaratilgan")], "41", 100_000));
const payme = {
  provayder: "payme", tashqiId: "p", holat: "yaratilgan",
};
console.log(prepareQarori(b, [payme], "42", 100_000));

Konsolda:

text
{ e: 0, amal: 'yangi' }
{ e: 0, amal: 'oldingi' }
{ e: 0, amal: 'eskini-bekor-qil' }
{ e: -9, amal: null }

Qarorni toza funksiyaga ajratish — uni bazasiz, millisekundlarda test qilish imkonini beradi. Kanon kodida bu mantiq SQL so'rovlari orasida; o'z loyihangizda shunday ajratishni o'ylab ko'ring.

11. Loyiha qadami: click/shop.ts

prepare ishlovchisi (formatlangan nusxa, ma'nosi kanondagidek):

ts
  r.post("/prepare", async (req, res) => {
    const q = req.body as ClickSorov;
    const x = umumiy(q, "0");
    if (x !== undefined) {
      res.json(javob(q, x));
      return;
    }
    let summa: number;
    try {
      summa = clickSummasi(q["amount"] ?? "");
    } catch {
      res.json(javob(q, -2));
      return;
    }
    const n = await tranzaksiya(s.pool, async (c) => {
      const b = (
        await c.query<{ id: string; summa: string; holat: string }>(
          "SELECT id, summa, holat FROM buyurtmalar WHERE id = $1 " +
            "FOR UPDATE",
          [q["merchant_trans_id"]],
        )
      ).rows[0];
      if (!b) return { e: -5 };
      const bor = (
        await c.query<{ id: string; holat: string }>(
          "SELECT id, holat FROM tranzaksiyalar WHERE provayder = " +
            "'click' AND tashqi_id = $1",
          [q["click_trans_id"]],
        )
      ).rows[0];
      if (bor)
        return bor.holat === "bekor"
          ? { e: -9 }
          : bor.holat === "bajarilgan"
            ? { e: -4 }
            : { e: 0, id: bor.id };
      if (b.holat === "tolangan" || b.holat === "qaytarilgan")
        return { e: -4 };
      if (b.holat === "bekor") return { e: -9 };
      if (Number(b.summa) !== summa) return { e: -2 };
      // Eski chala Click urinishi (foydalanuvchi qayta bosdi) —
      // bekor; boshqa provayderda jarayon bo'lsa — rad
      const faol = (
        await c.query<{ id: string; provayder: string }>(
          "SELECT id, provayder FROM tranzaksiyalar WHERE " +
            "buyurtma_id = $1 AND holat = 'yaratilgan'",
          [b.id],
        )
      ).rows[0];
      if (faol && faol.provayder !== "click") return { e: -9 };
      if (faol)
        await c.query(
          "UPDATE tranzaksiyalar SET holat = 'bekor', bekor_vaqt = " +
            "now() WHERE id = $1",
          [faol.id],
        );
      const t = await c.query<{ id: string }>(
        "INSERT INTO tranzaksiyalar (provayder, tashqi_id, " +
          "buyurtma_id, summa, holat, qoshimcha) VALUES ('click', " +
          "$1, $2, $3, 'yaratilgan', $4) RETURNING id",
        [
          q["click_trans_id"],
          b.id,
          summa,
          { click_paydoc_id: q["click_paydoc_id"] },
        ],
      );
      await c.query(
        "UPDATE buyurtmalar SET holat = 'kutilmoqda', " +
          "yangilangan_vaqt = now() WHERE id = $1",
        [b.id],
      );
      return { e: 0, id: t.rows[0]?.id };
    });
    res.json(
      javob(
        q,
        n.e,
        n.e === 0 ? { merchant_prepare_id: Number(n.id) } : {},
      ),
    );
  });

complete xuddi shu naqshda: umumiy tekshiruvlar, merchant_prepare_id va click_trans_id bo'yicha tranzaksiyani FOR UPDATE bilan topish, Click error < 0 bo'lsa bekor qilish, aks holda summa tekshiruvi, bajarilgan, tolangan va ledger. Oxirida kutilmagan xatolar uchun JSON ishlovchi:

ts
  // Kutilmagan xato (baza va h.k.): Click JSON kutadi — HTML 500
  // emas. -7 "Failed to update user" (kanon tanlovi; Click'ning bu
  // holdagi xulqi — sinalmagan)
  // Express xato ishlovchisi 4 parametr bilan taniladi
  r.use(
    (
      e: unknown,
      req: Request,
      res: Response,
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      _next: NextFunction,
    ) => {
      (s.log ?? console.error)(e);
      res.json(javob((req.body ?? {}) as ClickSorov, -7));
    },
  );
  return r;
}

Express xato ishlovchisini to'rtta parametri bo'yicha taniydi (Global error handler), shuning uchun ishlatilmaydigan _next ham qoladi va lint ogohlantirishi shu qatorda o'chiriladi.

Testlar — tolov/test/click.test.ts, beshta: imzo formulasi (md5 bilan to'g'ridan solishtirish), muvaffaqiyatli oqim va takroriy complete, xatolar -1 … -8, Click -5017 → -9 va qayta to'lov, parallel 10 prepare. Biz ishga tushirdik (kanon nusxasi, Node 24.21.0, Postgres 17.6):

text
✔ clickImzo: formula (prepare — merchant_prepare_id'siz, complete — bilan) (2.5451ms)
▶ Click SHOP API (simulyator)
  ✔ muvaffaqiyatli: prepare → complete, ledger; takror complete → -4 (122.248ms)
  ✔ xatolar: imzo -1, action -3, summa -2, buyurtma yo'q -5, maydon yo'q -8, prepare yo'q -6, to'langan -4 (65.0384ms)
  ✔ Click complete'da error < 0 (mablag' yetarli emas) → -9, buyurtma qayta to'lanadi (37.4503ms)
  ✔ qayta urinish (yangi click_trans_id) eski chala urinishni bekor qiladi; parallel 10 prepare — bitta faol (204.1217ms)
✔ Click SHOP API (simulyator) (1452.5143ms)
ℹ tests 5
ℹ suites 1
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2186.5902

Beshala test o'tdi (ℹ fail 0). Oxirgi testga e'tibor bering: 10 ta parallel prepare dan keyin bazada faqat bitta faol tranzaksiya qoldi — buyurtma qatoridagi qulf ishladi. Kanonda (Postgres 18.6) ham 5/5.

Commit xabari:

bash
M='feat(tolov): Click SHOP API — prepare/complete,'
M+=' MD5 imzo, aniq summa, simulyator'
git commit -m "$M"

12. Real ishda

  • Ikki URL kabinetda: Click kabinetida prepare va complete manzillari kiritiladi; ular ommaviy HTTPS bo'lishi kerak.
  • Click Merchant API — boshqa narsa: siz Click'ni chaqirasiz (invoys yaratish, karta tokeni bilan yechish). Obuna uchun kerak bo'lishi mumkin (Obuna va davriy to'lovlar); kanon sinamagan.
  • Rasmiy kutubxonalar (PHP, ba'zan boshqa tillar uchun jamoa kutubxonalari) — o'qish uchun foydali, lekin ko'r-ko'rona ko'chirmang: kanon 0.01 farq qoidasini ataylab qabul qilmadi.
  • Intervyuda: "Imzo nima uchun kerak?", "MD5 nega zaif va baribir nega ishlatiladi?"

Xulosa

  • Click — ikki bosqich: prepare (action 0) va complete (action 1), merchant_prepare_id ularni bog'laydi.
  • Har so'rov MD5 sign_string bilan; Click yuborgan xom satrlardan hisoblanadi va bazadan oldin tekshiriladi.
  • Summa so'mda satr — float'siz tiyinga, buyurtma bilan aniq tenglik (kutubxonadagi 0.01 farq yo'q).
  • Xato kodlari 0, -1 … -9; kutilmagan xato — JSON -7 (kanon tanlovi).
  • Yangi Click urinishi eski chala urinishni bekor qiladi; boshqa provayder jarayonda bo'lsa — -9; Click error < 0 — bekor va qayta to'lash mumkin.

Keyingi dars: Uzum va boshqa mahalliy to'lov tizimlari — OpenAPI hujjatidan merchant API yozish va Payme'dan farqli idempotentlik qoidasi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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