Mundarija (18)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki bosqich
- 3. MD5 imzo
- 4. Summa: so'mda keladi
- 5. Xato kodlari
- 6. Prepare: takror, qayta urinish, parallel
- 7. Complete: Click'ning o'z xatosi
- 8. Simulyator
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi kod?
- 2-mashq (o'rta): forma tanasidan maydonlar
- 3-mashq (qiyin): prepare qarori
- 11. Loyiha qadami: click/shop.ts
- 12. Real ishda
- Xulosa
- Manbalar
Click integratsiyasi: Prepare, Complete va MD5 imzo
Qisqacha: Click SHOP API ikki bosqichli: avval Click sizning
preparemanzilingizga "shu buyurtmaga to'lov tayyorla" deydi, pul yechilgachcompletemanziliga "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_stringMD5 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.tsmodulini 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 rasmiyclick-llc/click-integration-phpkutubxonasi 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
completemanziliga so'rov yuborib, buyurtmani to'langan qiladi; - summa
parseFloatbilan o'qiladi va1000.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_idDiagrammaga 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:
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):
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:
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:
a2768ec5d850e6f696822bfdf10c6580
false
feadb74510d0a53dc19e59c4b413603f- Bitta maydon (summa) o'zgarsa — imzo butunlay boshqa. Hujumchi summani 1 so'mga tushirsa, imzo mos kelmaydi.
complete(action = 1) imzosigamerchant_prepare_idqo'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):
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:
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:
1000 100000 0
1000.00 100000 0
999.99 99999 -2
1000.001 xato -2Muhim 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:
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;
};
- Maydonlar to'liqmi (yo'q bo'lsa
-8) — imzo hisoblash uchun ham kerak. - Imzo (
-1) — bazaga tegmasdan, birinchi bo'lib. Imzosiz so'rov bazada hech narsani o'qitmasligi kerak. actionmanzilga mosmi (-3),service_idbizniki mi (-8),merchant_trans_idraqammi (-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:
- tranzaksiyani
bekorqilamiz (Click xatosiniqoshimchaustunida saqlab); - buyurtmani
kutilmoqdadanyangiga qaytaramiz — qayta to'lash mumkin; - 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
Performga o'sha muvaffaqiyatli javobni qaytaradi, Click takroriycompletega esa-4qaytaradi. 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?
sign_string32 ta nol./preparegaaction=1keldi.merchant_trans_id=987654— bunday buyurtma yo'q.completedamerchant_prepare_id=999999— bunday prepare yo'q.- To'langan buyurtmaga yangi
click_trans_idbilanprepare.
Yechim
-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
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:
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
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:
{ 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):
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:
// 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):
✔ 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.5902Beshala 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:
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
preparevacompletemanzillari 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) vacomplete(action 1),merchant_prepare_idularni bog'laydi. - Har so'rov MD5
sign_stringbilan; 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; Clickerror < 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
- Click hujjati: https://docs.click.uz — SPA, kanon matnini ololmadi (2026-10-27)
- Click rasmiy PHP kutubxonasi (
sign_stringformulasi, xato kodlari): https://github.com/click-llc/click-integration-php — kanon 2026-10-27 da o'qigan - Node
crypto.createHash: https://nodejs.org/api/crypto.html#cryptocreatehashalgorithm-options URLSearchParams: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams- Kanon (IlmHamroh, 2026-10-27):
tolovpaketi, lokal Click simulyatori; haqiqiy Click — sinalmagan. Testlar — biz ishga tushirdik, PostgreSQL 17.6 (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!