Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Mini App va bot
- 2.1 Qanday ochiladi
- 2.2 initData tuzilishi
- 2.3 Bot va Mini App aloqasi
- 3. initData imzosi
- 3.1 Algoritm
- 3.2 Brauzerda o'zingiz hisoblang
- 3.3 Imzodan keyingi tekshiruvlar
- 3.4 Uchinchi tomon: Ed25519 signature
- 4. Sessiya: keyingi so'rovlar
- 5. Telegram Stars
- 5.1 Nima va qachon
- 5.2 Oqim
- 5.3 Invoice
- 5.4 pre_checkout_query: tekshiruv
- 5.5 successful_payment: idempotentlik
- 5.6 Refund
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): tekshiruv satri
- 2-mashq (o'rta): auth_date tekshiruvi
- 3-mashq (qiyin): idempotent to'lov qabul qiluvchi
- 9. Loyiha qadami: miniapp.ts moduli
- 10. Real ishda
- Xulosa
- Manbalar
Telegram Mini App backendi va Stars: initData va to'lov oqimi
Qisqacha: Mini App — Telegram ichida ochiladigan web sahifa. U serverga foydalanuvchi haqida
initDatayuboradi; server unga ishonishdan oldin imzoni tekshiradi:secret = HMAC_SHA256("WebAppData", bot_token),hash = HMAC_SHA256(secret, data_check_string). Imzo to'g'ri vaauth_dateyangi bo'lsa — foydalanuvchi haqiqiy. Raqamli tovar uchun to'lov — Telegram Stars (XTR):provider_tokenbo'sh, narx bitta;pre_checkout_queryga 10 soniya ichida javob;successful_paymenttakror kelsa —telegram_payment_charge_idbo'yicha e'tiborsiz qoldiring.
Bu darsda
- Mini App va bot qanday bog'lanishini tushunasiz.
initDataimzosini Web Crypto bilan o'zingiz hisoblaysiz va soxtasini aniqlaysiz.- Imzo tekshiruvidan tashqari
auth_date,timingSafeEqualva Express middleware'ni yozasiz. answerWebAppQueryvaweb_apptugmasi haqida bilasiz.- Telegram Stars bilan hisob-faktura, tekshiruv, idempotent to'lov va refund oqimini kanon tajribasida ko'rasiz.
- Loyiha uchun
miniapp.tsmodulini va uning 4 ta testini yozasiz.
Oldin bilishingiz kerak: Bot prodakshnda, crypto 1: tasodif, hash va HMAC, Middleware va request lifecycle, Kiruvchi webhook'larni qabul qilish g'oyasi (imzo, idempotentlik) — keyinroq to'liq.
Telegram Login va Mini App autentifikatsiyasi 28-qismda Telegram Login va Mini App initData tekshiruvi darsida auth tomonidan ko'riladi. Bugun — backend amaliyoti va to'lov.
1. Nega bu kerak?
Bot interfeysi — tugma va matn. Ba'zi ishlar uchun bu tor: vazifalar kalendari, filtrli ro'yxat, rasm muharriri. Mini App (avval Web App deyilgan) — oddiy web sahifa, lekin Telegram ichida, foydalanuvchi tizimga kirmasdan ochiladi.
"Kirmasdan" — eng qiziq joyi. Sahifa Telegram'dan foydalanuvchi ma'lumotini oladi: id, ism, til. Lekin sahifa — brauzer, brauzerdagi hamma narsani foydalanuvchi o'zgartira oladi. Hujumchi sahifa o'rniga o'z so'rovini yuboradi: "men — foydalanuvchi 1 (admin)". Server buni qanday ajratadi?
Javob — imzo. Telegram ma'lumotni bot tokeni asosidagi kalit bilan imzolaydi. Tokenni faqat Telegram va sizning serveringiz biladi. Imzo mos kelsa — ma'lumotni Telegram bergan, hech kim o'zgartirmagan.
O'xshatish: muhrli ma'lumotnoma. Odam "men shu tashkilot xodimiman" desa, ishonmaysiz. Muhrli ma'lumotnoma ko'rsatsa va muhr haqiqiy bo'lsa — ishonasiz. Muhrni soxtalashtirib bo'lmaydi, chunki muhr faqat tashkilotda. Lekin ma'lumotnoma sanasiga ham qaraysiz: o'tgan yilgi ma'lumotnoma yaroqsiz.
2. Mini App va bot
2.1 Qanday ochiladi
Mini App bir necha joydan ochiladi: inline tugma (web_app), reply klaviatura tugmasi, bot menyusi tugmasi, havola (hujjat bo'yicha). grammY'da inline tugma:
import { InlineKeyboard } from "grammy";
await ctx.reply("Vazifalar kalendari:", {
reply_markup: new InlineKeyboard()
.webApp("Ochish", "https://example.com/miniapp"),
});Sahifa Telegram'ning telegram-web-app.js skriptini yuklaydi va window.Telegram.WebApp.initData dan satrni oladi. Shu satr serverga yuboriladi.
2.2 initData tuzilishi
initData — URL so'rov qatori (query string). Maydonlar (hujjat bo'yicha, asosiylari):
| Maydon | Nima |
|---|---|
user |
foydalanuvchi, JSON: id, first_name, language_code … |
auth_date |
Mini App ochilgan vaqt, Unix soniya |
query_id |
answerWebAppQuery uchun (inline tugmadan ochilganda) |
hash |
imzo, 64 ta hex belgi |
signature |
uchinchi tomon uchun Ed25519 imzo |
2.3 Bot va Mini App aloqasi
Mini App botga ma'lumotni uch yo'l bilan qaytaradi:
- Serveringizga
fetch— eng ko'p ishlatiladigan yo'l:initDatabilan so'rov, server tekshiradi va javob beradi. answerWebAppQuery— hujjat: Web App bilan o'zaro ta'sir natijasini o'rnatadi va so'rov kelgan chatga foydalanuvchi nomidan xabar yuboradi. ServerinitDatadagiquery_idbilan chaqiradi. Hujjatga ko'raweb_appinline tugmasi faqat bot bilan shaxsiy chatda mavjud.Telegram.WebApp.sendData— faqat reply klaviatura tugmasidan ochilganda; botweb_app_dataxizmat xabarini oladi.
Kanon mock'ida answerWebAppQuery bor, lekin u alohida sinalmagan. Bu darsda birinchi yo'l asosiy.
3. initData imzosi
3.1 Algoritm
Telegram hujjati (2026-10-28 da o'qildi):
data_check_string—hashdan boshqa barcha maydonlar, kalit bo'yicha alifbo tartibida,kalit=qiymatko'rinishida, orasida yangi qator (\n).secret_key= HMAC-SHA256, kalit —"WebAppData"satri, ma'lumot — bot tokeni.hex(HMAC-SHA256(kalit = secret_key, ma'lumot = data_check_string))hashga teng bo'lsa — ma'lumot Telegram'dan.
E'tibor bering: 2-qadamda kalit — doimiy "WebAppData", token esa ma'lumot. Ko'p xato aynan shu yerda — ikkalasini almashtirib qo'yish.
HMAC — crypto 1 darsida ko'rgansiz: kalitli hash. Kalitni bilmasdan to'g'ri HMAC yasab bo'lmaydi.
3.2 Brauzerda o'zingiz hisoblang
Web Crypto (crypto.subtle) bilan butun algoritm — Node'da ham, brauzerda ham ishlaydi. Token — soxta, faqat namuna uchun:
const enc = new TextEncoder();
const hex = (b) => [...new Uint8Array(b)]
.map((x) => x.toString(16).padStart(2, "0")).join("");
async function hmac(kalit, matn) {
const k = await crypto.subtle.importKey(
"raw", typeof kalit === "string" ? enc.encode(kalit) : kalit,
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
return crypto.subtle.sign("HMAC", k, enc.encode(matn));
}
const tekshiruvSatri = (p) => [...p.entries()]
.filter(([k]) => k !== "hash")
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([k, v]) => `${k}=${v}`)
.join("\n");
async function imzola(maydonlar, token) {
const p = new URLSearchParams(maydonlar);
const sir = await hmac("WebAppData", token);
p.set("hash", hex(await hmac(sir, tekshiruvSatri(p))));
return p.toString();
}
async function tekshir(initData, token) {
const p = new URLSearchParams(initData);
const sir = await hmac("WebAppData", token);
const kutilgan = hex(await hmac(sir, tekshiruvSatri(p)));
return kutilgan === p.get("hash");
}
const TOKEN = "123456:SOXTA-faqat-lokal-mock-uchun";
const user = JSON.stringify({ id: 501, first_name: "Aziz" });
const init = await imzola(
{ query_id: "AAH-soxta", user, auth_date: "1793095140" }, TOKEN,
);
console.log(new URLSearchParams(init).get("hash"));
console.log(tekshiruvSatri(new URLSearchParams(init)));
console.log(await tekshir(init, TOKEN));
const p = new URLSearchParams(init);
p.set("user", JSON.stringify({ id: 1, first_name: "Admin" }));
console.log(await tekshir(p.toString(), TOKEN));
console.log(await tekshir(init, "999:BOSHQA"));
const teskari = new URLSearchParams(
[...new URLSearchParams(init)].reverse(),
);
console.log(await tekshir(teskari.toString(), TOKEN));Konsolda:
646e75bb3783235b588c4dde49befd4f5ae022b7b1d5ed66c13c11abdd9f3f8a
auth_date=1793095140
query_id=AAH-soxta
user={"id":501,"first_name":"Aziz"}
true
false
false
trueQatorma-qator:
hash— 64 ta hex belgi (32 bayt). Kirish bir xil bo'lsa — har doim shu qiymat. Biz uni kanon modulining NodecreateHmacversiyasi bilan solishtirdik: bir xil chiqdi.data_check_string— uch qator, alifbo tartibida (auth_date,query_id,user).user— URL-kodlangan emas, ochilgan JSON.- To'g'ri imzo —
true. - Hujumchi
userni "Admin" ga almashtirdi —false.hashni qayta hisoblay olmaydi: token yo'q. - Boshqa bot tokeni —
false. Bitta botninginitDatasi boshqa bot serverida o'tmaydi. - Maydonlar tartibi teskari —
true. Tartib tekshiruv satrida alifbo bo'yicha tiklanadi.
Tekshirib ko'ring: Nega tekshiruv satri
URLSearchParamsqiymatlaridan (user={"id":501,…}) yasaladi, xom satr bo'lagidan (user=%7B%22id%22…) emas?
Javob
Hujjat kalit=<qiymat> deydi — ochilgan qiymat. Telegram imzoni ochilgan qiymatlar ustidan hisoblaydi. Mijoz satrni boshqacha kodlashi mumkin (%20 yoki +). Ochilgan qiymat ustidan tekshirsangiz, kodlash farqi imzoni buzmaydi. Kanon testi buni ham tekshiradi: "maydonlar tartibi va URL-kodlash imzoga ta'sir qilmaydi".
3.3 Imzodan keyingi tekshiruvlar
To'g'ri imzo — yetarli emas.
auth_date— eskirganmi? Hujjat qo'shimcha tekshiruvni tavsiya qiladi. Imzo vaqt o'tsa ham to'g'ri qoladi. Kimdir bir marta foydalanuvchininginitDatasini qo'lga kiritsa (log, ekran yozuvi), uni abadiy qayta ishlatadi — takroriy hujum (replay). Kanon modul 1 soatdan eskisini rad etadi.- Kelajak vaqt.
auth_datehozirdan 60 s dan ko'p oldinda bo'lsa — soat sozlanmagan yoki soxta. Kanonkelajaksababini beradi. hashformati. Faqat^[0-9a-f]{64}$— aks holdaBuffer.from(hash, "hex")qisqa bufer beradi.- Doimiy vaqtli solishtirish.
===birinchi farqli belgida to'xtaydi; vaqtni o'lchab, imzoni bayt-bayt topish nazariy jihatdan mumkin. Node'datimingSafeEqual(crypto 2). Bizning Web Crypto misolimiz soddalik uchun===ishlatdi — serverda kanon moduli kabi yozing. userJSON.JSON.parsexato bersa yokiidbutun son bo'lmasa —format.
3.4 Uchinchi tomon: Ed25519 signature
Hujjatda ikkinchi usul bor: ma'lumotni bot tokenisiz tekshirish (masalan, boshqa xizmat). Buning uchun signature maydoni — Ed25519 imzo, Telegram'ning ochiq kaliti bilan tekshiriladi; tekshiruv satri bot_id:WebAppData qatoridan boshlanadi. Bu kanonda sinalmagan — hujjat bo'yicha. O'z serveringizda tokeningiz bor, HMAC usuli yetarli.
4. Sessiya: keyingi so'rovlar
Har so'rovda initData ni yuborib tekshirish mumkin. Mini App hamjamiyati hujjatlarida Authorization: tma <initData> sarlavhasi uchraydi — kanon modul shu shaklni oladi. Bu Telegram'ning rasmiy hujjatida yo'q (biz 2026-10-28 da core.telegram.org/bots/webapps da qidirdik — topilmadi); bu shunchaki kelishuv.
Ikkinchi yo'l — initData ni bir marta tekshirib, o'z sessiyangizni berish: cookie yoki token. Bu 28-qismdagi auth mavzusi (Session-based auth, JWT anatomiyasi). Muhimi tartib:
initDataimzosi vaauth_datetekshiriladi.user.idbo'yicha bazadan foydalanuvchi topiladi (yoki yaratiladi).- O'z sessiyangiz beriladi.
Shundan keyin user.id — ishonchli. Mijoz yuborgan boshqa userId maydoniga hech qachon ishonmang.
sequenceDiagram
participant M as Mini App (brauzer)
participant S as Server
participant D as Baza
M->>S: GET /me, Authorization tma initData
S->>S: hash, auth_date tekshiruvi
alt imzo yoki vaqt noto'g'ri
S-->>M: 401 Problem Details
else to'g'ri
S->>D: user.id bo'yicha foydalanuvchi
D-->>S: foydalanuvchi
S-->>M: 200 ma'lumot (yoki sessiya)
endDiagrammaga qarang: server initData ni tekshirmaguncha bazaga bormaydi. 401 javobi — Problem Details formatida.
5. Telegram Stars
5.1 Nima va qachon
Telegram Stars — Telegram ichidagi virtual valyuta, kodi XTR. Telegram hujjati (Bot Payments API for Digital Goods, 2026-10-28): raqamli tovar va xizmatlar Stars bilan sotiladi. Jismoniy tovar — boshqa sahifa, to'lov provayderlari orqali.
vazifalar misolida "Premium — 30 kun" (cheksiz eslatmalar) — raqamli xizmat, demak Stars. Do'kondagi non — jismoniy, u O'zbekiston to'lov tizimlari orqali (Payme Merchant API, Click integratsiyasi).
Stars'ni pulga aylantirish, komissiya va soliq — hujjat bo'yicha, kanonda sinalmagan. Haqiqiy Stars to'lovi ham sinalmagan: kanon lokal mock'da oqimni taqlid qildi.
5.2 Oqim
Hujjatdagi qadamlar:
sendInvoice(yokicreateInvoiceLink) —currency: "XTR".pre_checkout_queryupdate'ini kutish.answerPreCheckoutQuerybilan tasdiqlash yoki rad etish — 10 soniya ichida.successful_paymentxabarini kutish.telegram_payment_charge_idni saqlash — refund uchun kerak.- Tovarni yetkazish.
sequenceDiagram
participant F as Foydalanuvchi
participant T as Telegram
participant B as Bot
B->>T: sendInvoice (XTR, 50, payload)
F->>T: "To'lash"
T->>B: pre_checkout_query
B->>B: buyurtma bor, egasi, summa, holat?
B->>T: answerPreCheckoutQuery (ok yoki sabab)
T->>T: Stars yechiladi
T->>B: successful_payment (charge_id)
B->>B: charge_id yangi? → Premium yoqiladiDiagrammaga qarang: pre_checkout_query paytida pul hali yechilmagan. Bu oxirgi tekshiruv nuqtasi. Pul successful_payment dan oldin yechiladi va tovar faqat shundan keyin beriladi.
5.3 Invoice
bot.command("premium", (ctx) => ctx.replyWithInvoice(
"Premium — 30 kun", "Cheksiz eslatmalar va fayllar",
"prem-501-1", "XTR",
[{ label: "Premium", amount: 50 }],
{ provider_token: "" },
));payload (prem-501-1) — sizning buyurtma id'ngiz: u keyingi update'larda qaytadi. Hujjat: Stars uchun provider_token — bo'sh satr, prices — aynan bitta element. Kanon mock'i shu qoidalarni tekshirdi:
sendInvoice: {"title":"Premium — 30 kun","currency":"XTR","total_amount":50,"payload":"prem-501-1"}
XTR + provider_token: Bad Request: provider_token must be empty for Telegram Stars
XTR + 2 narx: Bad Request: exactly one price must be specified for Telegram StarsXato matnlari — mock'niki (haqiqiy server matni solishtirilmagan); qoidalar — hujjatdagi.
5.4 pre_checkout_query: tekshiruv
Bu bosqichda bot buyurtmani bazadan tekshiradi. Kanon tekshiruvi — toza mantiq, uni alohida funksiya qilamiz:
function tekshirTolov(buyurtmalar, q) {
const b = buyurtmalar.get(q.invoice_payload);
if (!b) return "Buyurtma topilmadi";
if (b.foydalanuvchi !== q.from.id) return "Begona buyurtma";
if (b.holat !== "kutilmoqda") return "Buyurtma faol emas";
if (b.summa !== q.total_amount || q.currency !== "XTR") {
return "Summa mos emas";
}
return null;
}
const buyurtmalar = new Map([
["prem-501-1",
{ foydalanuvchi: 501, summa: 50, holat: "kutilmoqda" }],
["prem-501-2",
{ foydalanuvchi: 501, summa: 50, holat: "bekor" }],
]);
const holatlar = [
[501, "prem-501-1", 50], [501, "prem-501-1", 1],
[777, "prem-501-1", 50], [501, "prem-501-2", 50],
[501, "yoq", 50],
];
for (const [kim, payload, summa] of holatlar) {
const q = {
from: { id: kim }, invoice_payload: payload,
total_amount: summa, currency: "XTR",
};
const xato = tekshirTolov(buyurtmalar, q);
console.log(`${kim} ${payload} ${summa} → ${xato ?? "ok"}`);
}Konsolda:
501 prem-501-1 50 → ok
501 prem-501-1 1 → Summa mos emas
777 prem-501-1 50 → Begona buyurtma
501 prem-501-2 50 → Buyurtma faol emas
501 yoq 50 → Buyurtma topilmadiKurs tajribasi haqiqiy grammY va mock bilan xuddi shu besh holatni berdi:
chat 501, prem-501-1, 50 XTR → ok=true
chat 501, prem-501-1, 1 XTR → ok=false, "Summa mos emas"
chat 777, prem-501-1, 50 XTR → ok=false, "Begona buyurtma"
chat 501, prem-501-2, 50 XTR → ok=false, "Buyurtma faol emas"
chat 501, yoq, 50 XTR → ok=false, "Buyurtma topilmadi"Bot rad etganda answerPreCheckoutQuery(false, { error_message }) — sabab foydalanuvchiga ko'rsatiladi. 10 soniya chegarasi sabab bu yerda og'ir ish qilmang: bitta indeksli so'rov — yetarli.
Nega summa ham tekshiriladi? payload ni bot yaratadi, lekin invoice'ni qayta yuborish yoki eski invoice orqali to'lash holatlari bo'lishi mumkin: narx o'zgargan, eski xabar qolgan. Summa va valyuta — bazadagi qiymat bilan solishtiriladi, mijozga ishonilmaydi.
5.5 successful_payment: idempotentlik
To'lov o'tdi. Telegram successful_payment xabarini yuboradi. Lekin update ikki marta kelishi mumkin — Bot prodakshnda darsida ko'rganingizdek, webhook javobi kechiksa Telegram qayta yuboradi. Ikki marta "Premium yoqildi" — ikki oy bepul Premium.
Himoya — telegram_payment_charge_id bo'yicha idempotentlik: shu id bilan to'lov bazada bormi? Bor bo'lsa — e'tiborsiz. Bazada UNIQUE cheklov (CREATE TABLE va cheklovlar).
bot.on("message:successful_payment", async (ctx) => {
const p = ctx.message.successful_payment;
if (tolovlar.has(p.telegram_payment_charge_id)) return;
tolovlar.set(p.telegram_payment_charge_id, p);
buyurtmalar.get(p.invoice_payload).holat = "tolangan";
await ctx.reply("Rahmat! Premium yoqildi.");
});Kurs tajribasi to'lovni yakunladi, keyin o'sha charge_id bilan update'ni qo'lda qayta yubordi:
to'landi prem-501-1 50 XTR
takror successful_payment stxSOXTAfc… — e'tiborsiz
buyurtma holati: tolangan | to'lovlar: 1Bitta to'lov, bitta yozuv. stxSOXTA… — mock yaratgan id, har ishga tushishda boshqa.
Prodakshnda has va set orasida ikki parallel update o'tib ketishi mumkin (poyga holati). Shuning uchun tekshiruvni bazaning o'ziga bering: INSERT … ON CONFLICT DO NOTHING va qo'shilgan qatorlar soniga qarab qaror (UPSERT va MERGE).
5.6 Refund
Hujjat: refundStarPayment — Stars'dagi muvaffaqiyatli to'lovni qaytaradi; unga user_id va telegram_payment_charge_id kerak. Stars sotadigan bot /paysupport buyrug'iga javob berishi kerak (hujjat: to'lov muammolari bo'yicha murojaatlar).
refundStarPayment: true
bo'sh charge_id: Bad Request: CHARGE_ID_INVALIDKurs tajribasi refund qildi va bo'sh id bilan xatoni ko'rdi. Refunddan keyin buyurtma holatini o'zingiz o'zgartiring (Premium o'chadi) — Telegram buni siz uchun qilmaydi. Moliyaviy hisob haqida — Refund, reconciliation va moliyaviy hisob.
Tekshirib ko'ring: Nega Premium'ni
pre_checkout_queryda emas,successful_paymentda yoqamiz?
Javob
pre_checkout_query — "to'lashga tayyormisiz?" savoli. Undan keyin ham to'lov bajarilmasligi mumkin (Stars yetmaydi, foydalanuvchi bekor qildi). Pul yechilgani faqat successful_payment bilan ma'lum. Oldin yoqsangiz — tekin Premium.
6. Hujumchi nigohi
| Hujum | Himoya | Kanon tekshiruvi |
|---|---|---|
user ni o'zgartirib yuborish |
HMAC imzo | test: imzo |
boshqa botning initData si |
token bo'yicha kalit | test: 999:BOSHQA → imzo |
eski initData ni qayta ishlatish |
auth_date ≤ 1 soat |
test: eskirgan |
kelajak auth_date |
60 s chegara | test: kelajak |
hash siz so'rov |
401 | test: hash-yoq |
| begona buyurtmani to'lash | from.id = egasi |
kurs tajribasi: "Begona buyurtma" |
| summani 1 Star qilish | bazadagi summa | kurs tajribasi: "Summa mos emas" |
takroriy successful_payment |
charge_id UNIQUE |
kurs tajribasi: "e'tiborsiz" |
Bot tokeni — ham bot API kaliti, ham initData imzosi kaliti. U oqsa, hujumchi istalgan foydalanuvchi nomidan initData yasaydi. Token faqat serverda.
7. Ko'p uchraydigan xatolar
| Xato | Natija | To'g'risi |
|---|---|---|
HMAC(token, "WebAppData") — kalit va ma'lumot almashgan |
imzo hech qachon mos kelmaydi | kalit "WebAppData", ma'lumot token |
initDataUnsafe dagi user ga ishonish |
soxta foydalanuvchi | server initData ni tekshiradi |
| xom (kodlangan) qiymatlardan tekshiruv satri | imzo mos kelmaydi | ochilgan qiymatlar |
auth_date tekshirilmaydi |
takroriy hujum | maksimal yosh |
=== bilan solishtirish (serverda) |
vaqt orqali sizish | timingSafeEqual |
provider_token Stars'da |
400 | bo'sh satr |
Premium pre_checkout da |
tekin Premium | successful_payment da |
charge_id saqlanmaydi |
takror to'lov ikki marta; refund qilib bo'lmaydi | UNIQUE yozuv |
8. Mashqlar
Mashqlar kurs/mashqlar/29/26-miniapp/ da.
1-mashq (oson): tekshiruv satri
initData: user=%7B%22id%22%3A7%7D&query_id=Q1&auth_date=1700000000&hash=ab12. data_check_string ni qo'lda yozing (qatorlar tartibi va ko'rinishi).
Yechim
auth_date=1700000000
query_id=Q1
user={"id":7}hash chiqarib tashlanadi, qolganlari alifbo bo'yicha, user qiymati URL-kodlashdan ochilgan.
2-mashq (o'rta): auth_date tekshiruvi
vaqtniTekshir(authDate, hozirSoniya, maks = 3600) funksiyasini yozing: butun son bo'lmasa "format", hozirdan 60 s dan ko'p oldinda bo'lsa "kelajak", maks dan eski bo'lsa "eskirgan", aks holda "ok". Hozir — 1793095200. Sinang: "1793095140", "1793091000", "1793095300", "abc", "1793095259". Ishora: Number.isInteger (Matn ↔ son va sonni tekshirish).
Yechim
function vaqtniTekshir(authDate, hozir, maks = 3600) {
const a = Number(authDate);
if (!Number.isInteger(a)) return "format";
if (a > hozir + 60) return "kelajak";
if (hozir - a > maks) return "eskirgan";
return "ok";
}
const hozir = 1793095200;
for (const a of ["1793095140", "1793091000", "1793095300",
"abc", "1793095259"]) {
console.log(a, vaqtniTekshir(a, hozir));
}1793095140 ok
1793091000 eskirgan
1793095300 kelajak
abc format
1793095259 ok1793091000 — 4 200 s oldin, 1 soatdan ko'p. 1793095259 — 59 s oldinda, soatlar farqi uchun ruxsat.
3-mashq (qiyin): idempotent to'lov qabul qiluvchi
tolovQabul(holat, p) yozing: holat — { tolovlar: Map, buyurtmalar: Map }, p — { invoice_payload, total_amount, telegram_payment_charge_id }. Qaytarsin: "takror" (charge_id bor), "noma'lum buyurtma", "summa mos emas" (pul olindi, lekin summa farq — qo'lda ko'rib chiqish), yoki "yoqildi" (to'lov yoziladi, buyurtma tolangan). To'rt update: to'g'ri, o'sha takror, boshqa charge_id bilan noma'lum payload, summasi farqli. Oxirida to'lovlar sonini chiqaring.
Yechim
function tolovQabul(holat, p) {
const id = p.telegram_payment_charge_id;
if (holat.tolovlar.has(id)) return "takror";
const b = holat.buyurtmalar.get(p.invoice_payload);
holat.tolovlar.set(id, p);
if (!b) return "noma'lum buyurtma";
if (b.summa !== p.total_amount) return "summa mos emas";
b.holat = "tolangan";
return "yoqildi";
}
const holat = {
tolovlar: new Map(),
buyurtmalar: new Map([
["prem-1", { summa: 50, holat: "kutilmoqda" }],
["prem-2", { summa: 50, holat: "kutilmoqda" }],
]),
};
const updatelar = [
{ invoice_payload: "prem-1", total_amount: 50,
telegram_payment_charge_id: "c1" },
{ invoice_payload: "prem-1", total_amount: 50,
telegram_payment_charge_id: "c1" },
{ invoice_payload: "yoq", total_amount: 50,
telegram_payment_charge_id: "c2" },
{ invoice_payload: "prem-2", total_amount: 5,
telegram_payment_charge_id: "c3" },
];
for (const u of updatelar) console.log(tolovQabul(holat, u));
console.log("to'lovlar:", holat.tolovlar.size);yoqildi
takror
noma'lum buyurtma
summa mos emas
to'lovlar: 3Muhim qaror: noma'lum va summasi farqli to'lov ham yoziladi — pul allaqachon olingan, u yo'qolmasligi kerak (refund yoki qo'lda hal qilish uchun charge_id kerak). Faqat takror yozilmaydi.
9. Loyiha qadami: miniapp.ts moduli
Kanon modullar/src/telegram/miniapp.ts — initData tekshiruvi va Express middleware (node:crypto, Express 5.3.0, TypeScript 6.0.3). Telefon uchun formatladik va izohlarni qisqartirdik; kod ma'nosi kanondagidek, nusxa tsc, eslint va testlardan o'tdi (biz tekshirdik):
// miniapp.ts — Telegram Mini App backend: `initData` imzosini
// tekshirish (core.telegram.org/bots/webapps):
// secret = HMAC_SHA256(key = "WebAppData", data = bot_token);
// hash = hex(HMAC_SHA256(key = secret, data = data_check_string)),
// data_check_string — `hash` dan boshqa barcha maydonlar, kalit
// bo'yicha alifbo tartibida, "kalit=qiymat", "\n" bilan.
// Haqiqiy bot tokeni bilan SINALMAGAN — soxta token bilan imzolangan.
import { createHmac, timingSafeEqual } from "node:crypto";
import type { NextFunction, Request, Response } from "express";
export type MiniAppFoydalanuvchi = {
id: number; first_name: string; last_name?: string;
username?: string; language_code?: string;
};
export type InitNatija =
| {
ok: true; foydalanuvchi: MiniAppFoydalanuvchi; authDate: Date;
maydonlar: Record<string, string>;
}
| {
ok: false;
sabab: "hash-yoq" | "imzo" | "eskirgan" | "kelajak"
| "foydalanuvchi-yoq" | "format";
};
const dataCheckString = (p: URLSearchParams) =>
[...p.entries()]
.filter(([k]) => k !== "hash")
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([k, v]) => `${k}=${v}`)
.join("\n");
const secretKey = (botToken: string) =>
createHmac("sha256", "WebAppData").update(botToken).digest();
export function initDataTekshir(
initData: string,
botToken: string,
o: { maksYoshSoniya?: number; hozir?: Date } = {},
): InitNatija {
let p: URLSearchParams;
try {
p = new URLSearchParams(initData);
} catch {
return { ok: false, sabab: "format" };
}
const hash = p.get("hash");
if (!hash || !/^[0-9a-f]{64}$/.test(hash)) {
return { ok: false, sabab: "hash-yoq" };
}
const kutilgan = createHmac("sha256", secretKey(botToken))
.update(dataCheckString(p)).digest();
if (!timingSafeEqual(kutilgan, Buffer.from(hash, "hex"))) {
return { ok: false, sabab: "imzo" };
}
const auth = Number(p.get("auth_date"));
const hozir = (o.hozir ?? new Date()).getTime() / 1000;
if (!Number.isInteger(auth)) return { ok: false, sabab: "format" };
if (auth > hozir + 60) return { ok: false, sabab: "kelajak" };
// eski initData — takroriy hujum (replay) oynasi
if (hozir - auth > (o.maksYoshSoniya ?? 3600)) {
return { ok: false, sabab: "eskirgan" };
}
const u = p.get("user");
if (!u) return { ok: false, sabab: "foydalanuvchi-yoq" };
let foydalanuvchi: MiniAppFoydalanuvchi;
try {
foydalanuvchi = JSON.parse(u) as MiniAppFoydalanuvchi;
} catch {
return { ok: false, sabab: "format" };
}
if (!Number.isSafeInteger(foydalanuvchi.id)) {
return { ok: false, sabab: "format" };
}
return {
ok: true, foydalanuvchi, authDate: new Date(auth * 1000),
maydonlar: Object.fromEntries(p.entries()),
};
}
/** Test/mock uchun: Telegram qiladigan imzoni o'zimiz qo'yamiz
* (SOXTA token bilan). */
export function initDataImzola(
maydonlar: Record<string, string>, botToken: string,
): string {
const p = new URLSearchParams(maydonlar);
p.delete("hash");
p.set("hash", createHmac("sha256", secretKey(botToken))
.update(dataCheckString(p)).digest("hex"));
return p.toString();
}
/** Express: `Authorization: tma <initData>` → `res.locals.tg`. */
export function miniAppAuth(botToken: string, maksYoshSoniya = 3600) {
return (req: Request, res: Response, next: NextFunction): void => {
const m = /^tma (.+)$/.exec(req.get("authorization") ?? "");
const n = m?.[1]
? initDataTekshir(m[1], botToken, { maksYoshSoniya })
: ({ ok: false, sabab: "hash-yoq" } as const);
if (!n.ok) {
res.status(401).type("application/problem+json").json({
type: "about:blank", title: "Unauthorized", status: 401,
detail: `initData: ${n.sabab}`,
});
return;
}
res.locals["tg"] = n;
next();
};
}Darsdagi Web Crypto misolidan farqlar bor. Solishtirish — timingSafeEqual (misolda === edi), hash formati regex bilan tekshiriladi. Natija — ok bo'yicha discriminated union tur, sabab bilan. timingSafeEqual ikkala bufer uzunligi teng bo'lmasa xato tashlaydi — regex aynan 64 hex (32 bayt) ni kafolatlaydi, shuning uchun bu holat bo'lmaydi.
Kanon izohida tma sarlavhasi "Telegram hujjatidagi tavsiya shakli" deb yozilgan edi. Biz uni rasmiy hujjatda topmadik, shuning uchun darsdagi nusxada bu so'zlarni olib tashladik: tma — hamjamiyat kelishuvi, Telegram talabi emas.
Testlar — modullar/test/miniapp.test.ts, to'rtta. Imzolangan initData ni initDataImzola soxta token bilan yasaydi. Parcha (formatlangan):
test("o'zgartirilgan user, boshqa token, hash'siz, eski, kelajak",
() => {
const p = new URLSearchParams(toza);
p.set("user", JSON.stringify({ id: 1, first_name: "Admin" }));
assert.deepEqual(initDataTekshir(p.toString(), TOKEN, { hozir }),
{ ok: false, sabab: "imzo" });
assert.deepEqual(initDataTekshir(toza, "999:BOSHQA", { hozir }),
{ ok: false, sabab: "imzo" });
// ... hash'siz → hash-yoq; 2 soatdan keyin → eskirgan;
// auth_date + 1 soat → kelajak
});Testlarni biz ishga tushirdik (kanon nusxasi, Node 24.21.0):
node --test test/miniapp.test.ts✔ to'g'ri imzo — foydalanuvchi olinadi (2.4341ms)
✔ o'zgartirilgan user, boshqa token, hash'siz, eski, kelajak (17.7159ms)
✔ maydonlar tartibi va URL-kodlash imzoga ta'sir qilmaydi (qiymatlar decode qilinib tekshiriladi) (6.7875ms)
✔ Express middleware: Authorization: tma … (203.9404ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 889.4201Millisekundlar har safar boshqacha. To'rtinchi test haqiqiy Express serverni listen(0) bilan ochadi: to'g'ri sarlavha — 200 va id: 501; bitta harfi o'zgargan initData — 401 va application/problem+json; charset=utf-8.
Stars oqimi modulga kirmadi: u kurs tajribasi. To'lovlar tolov/ alohida modulida va 38-qism bozor loyihasida ishlatiladi; vazifalar da Premium yo'q.
Commit xabari:
git commit -m "feat(telegram): Mini App initData tekshiruvi"10. Real ishda
- Telegram ichidagi do'konlar, o'yinlar, bron qilish xizmatlari — Mini App; backend har so'rovni
initDatayoki undan olingan sessiya bilan tekshiradi. - Raqamli obunalar (kurs, premium funksiya, kontent) Telegram'da Stars bilan sotiladi; jismoniy tovar — mahalliy to'lov tizimlari.
- Xavfsizlik auditlarida "initData tekshirilmagan,
initDataUnsafega ishonilgan" — keng tarqalgan topilma. - Intervyuda: "Mini App foydalanuvchisini serverda qanday aniqlaysiz?", "To'lov xabari ikki marta kelsa nima qilasiz?"
Xulosa
- Mini App — Telegram ichidagi web sahifa;
initDataga imzo tekshirilmaguncha ishonilmaydi. secret = HMAC("WebAppData", token),hash = HMAC(secret, data_check_string)— ochilgan qiymatlar, alifbo tartibi,\n.- Imzodan keyin:
auth_date(eskirgan, kelajak),timingSafeEqual,userformati. - Stars (
XTR):provider_tokenbo'sh, bitta narx;pre_checkout_query— 10 s ichida tekshiruv. - Tovar
successful_paymentda;telegram_payment_charge_id— idempotentlik va refund kaliti.
Keyingi dars: Pul bilan ishlash JS'da — to'lov tizimlariga o'tishdan oldin summani tiyinda, butun son sifatida saqlashni o'rganamiz.
Manbalar
- Telegram Mini Apps, ma'lumotni tekshirish: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app (2026-10-28 da o'qildi;
tmasarlavhasi bu sahifada yo'q) - Telegram Stars (raqamli tovarlar): https://core.telegram.org/bots/payments-stars
- Bot API:
sendInvoice(Stars uchunprovider_tokenbo'sh, bitta narx),answerPreCheckoutQuery(10 s),refundStarPayment,answerWebAppQuery— https://core.telegram.org/bots/api - Node
crypto.timingSafeEqual: https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b - Kanon tajribasi (IlmHamroh, 2026-10-27): grammy 1.46.0, Node 24.21.0, lokal soxta Bot API serveri. Modul testlari va Web Crypto/Node imzo solishtiruvi — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!