Integratsiya misoli: do'kon Telegram-boti
StarPay kabi bot o'zining Fragment/TON kodini iFragment API chaqiruvlari bilan qanday almashtiradi.
Odatiy Stars do'kon boti (StarPay kabi) Fragment bilan o'zi ishlaydi: sessiya cookie'larini va TON hamyonining mnemonikasini saqlaydi, o'tkazmalarni imzolaydi, seqno va qayta urinishlarni kuzatadi. iFragment bilan bu qismning hammasi uchta API chaqiruvi va bitta vebxuk bilan almashtiriladi. Xaridor to'lovi, katalog va bot interfeysi sizda qoladi.
Avval va hozir
| Bot qadami | Avval (o'z kodingiz) | Hozir (iFragment) |
|---|---|---|
| Username'ni tekshirish | cookie'lar bilan Fragment'da searchStarsRecipient | GET /v1/recipients/lookup |
| To'lovdan keyin sotib olish | initBuyStarsRequest → getBuyStarsLink → TON imzolash va yuborish | externalId = buyurtmangiz id'si bilan POST /v1/orders |
| Natijani bilish | seqno'ni so'rash, tranzaksiyalarni solishtirish, qo'lda tekshirish | order.succeeded / order.failed vebxuki |
| Sessiya, hamyon, trottling, qayta urinishlar | sizniki | bizniki (Supplier Accounts) |
Agar sizda allaqachon o'z sessiyangiz va hamyoningiz bo'lsa, ularni OWN rejimida Supplier Account sifatida ulang: yetkazib beruvchiga avvalgidek o'zingiz to'laysiz, bajarishni esa platforma o'z zimmasiga oladi.
Oqim
Xaridor ─▶ bot: /buy 500 → @durov
bot ─▶ iFragment: GET /v1/recipients/lookup → ism va avatar, "Topildi"
Xaridor ─▶ sizning to'lovingiz (Payme, Click, Stars, …) → to'landi
bot ─▶ iFragment: POST /v1/orders { externalId: "<bot buyurtmasi id'si>" } → 201 QUEUED
iFragment ─▶ bot: order.succeeded vebxuki → "Tayyor! 500 ⭐ @durov da"Kod (TypeScript, fetch)
iFragment mijozi fetch ustidan bir necha qatordan iborat:
const API = 'https://api.ifragment.com/v1';
async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`${API}${path}`, {
...init,
headers: {
'X-API-Key': process.env.IFRAGMENT_KEY!,
'Content-Type': 'application/json',
'Accept-Language': 'uz',
...init.headers,
},
});
const body = await res.json();
if (!body.success) {
throw Object.assign(new Error(body.error.message), { status: res.status, code: body.error.code });
}
return body.data as T;
}
export const ifragment = {
lookup: (username: string) =>
call<{ found: boolean; name?: string }>(`/recipients/lookup?username=${encodeURIComponent(username)}&product=STARS`),
// externalId = botdagi buyurtma id'si: taymautdan keyingi takror ikkinchi buyurtma yaratmaydi.
createStarsOrder: (shopOrderId: string, username: string, quantity: number) =>
call<{ id: string; status: string }>('/orders', {
method: 'POST',
headers: { 'Idempotency-Key': `shop-order-${shopOrderId}` },
body: JSON.stringify({ product: 'STARS', quantity, username, externalId: shopOrderId }),
}),
};Muvaffaqiyatli to'lovdan keyin botda sotib olish, avvalgi Fragment/TON kodi o'rniga:
import { ifragment } from './ifragment';
export async function onShopOrderPaid(order: { id: string; username: string; quantity: number; chatId: number }) {
try {
await ifragment.createStarsOrder(order.id, order.username, order.quantity);
await db.shopOrders.update(order.id, { status: 'FULFILLING' });
await bot.api.sendMessage(order.chatId, "To'lov qabul qilindi, yulduzlarni yuboryapmiz…");
} catch (error) {
// 422: qabul qiluvchi tekshiruv va to'lov orasida yo'qolgan, pulni xaridorga qaytaring.
// 5xx/tarmoq: keyinroq xuddi shu chaqiruvni takrorlang: externalId va Idempotency-Key dublikatdan himoya qiladi.
await retryQueue.add('create-order', order, { delay: 30_000 });
}
}Vebxuk: imzoni tekshirish va xaridorni xabardor qilish. data.externalId bu sizning buyurtmangiz id'si, xaridorni shu orqali topamiz:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(raw: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
if (!parts.t || !parts.v1 || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) {
return false;
}
const expected = createHmac('sha256', secret).update(`${parts.t}.${raw}`).digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && timingSafeEqual(expected, received);
}
export async function handleWebhook(request: Request): Promise<Response> {
const raw = await request.text(); // xom tana, JSON.parse'dan oldin
if (!verify(raw, request.headers.get('X-IFragment-Signature') ?? '', process.env.IFRAGMENT_WEBHOOK_SECRET!)) {
return new Response('bad signature', { status: 401 });
}
const event = JSON.parse(raw) as { id: string; type: string; data: { externalId: string; quantity: number } };
if (await db.processedEvents.exists(event.id)) {
return new Response('ok'); // qayta yetkazish
}
const order = await db.shopOrders.find(event.data.externalId);
if (event.type === 'order.succeeded') {
await db.shopOrders.update(order.id, { status: 'COMPLETED' });
await bot.api.sendMessage(order.chatId, `Tayyor! ${event.data.quantity} ⭐ allaqachon @${order.username} da`);
} else if (event.type === 'order.failed') {
await db.shopOrders.update(order.id, { status: 'FAILED' });
await bot.api.sendMessage(order.chatId, "Buyurtmani bajarib bo'lmadi, pulingizni bir kun ichida qaytaramiz.");
}
await db.processedEvents.add(event.id);
return new Response('ok');
}Migratsiya bo'yicha nazorat ro'yxati
- Project kaliti va vebxuk sirini oling; bot manzili uchun IP allowlist'ni yoqing.
- Agar o'z akkauntlaringiz bilan qolsangiz, sessiya va hamyonni kabinet orqali yuboring (
OWNrejimi). Shundan keyin hamyondan faqat iFragment orqali foydalaning. - Qabul qiluvchini qidirishni
GET /v1/recipients/lookupbilan almashtiring. - Sotib olishni
externalId= bot buyurtmasi id'si bilanPOST /v1/ordersga almashtiring. - Vebxuk endpoint'ini, imzo tekshiruvini va
event.idbo'yicha dublikatlarni saralashni qo'shing. - Fragment/TON kodini o'chiring: cookie'lar, mnemonika, o'tkazmalarni imzolash, seqno'ni so'rash.