iFragment

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 qadamiAvval (o'z kodingiz)Hozir (iFragment)
Username'ni tekshirishcookie'lar bilan Fragment'da searchStarsRecipientGET /v1/recipients/lookup
To'lovdan keyin sotib olishinitBuyStarsRequest → getBuyStarsLink → TON imzolash va yuborishexternalId = buyurtmangiz id'si bilan POST /v1/orders
Natijani bilishseqno'ni so'rash, tranzaksiyalarni solishtirish, qo'lda tekshirishorder.succeeded / order.failed vebxuki
Sessiya, hamyon, trottling, qayta urinishlarsiznikibizniki (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:

ifragment.ts
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:

on-paid.ts
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:

webhook.ts
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

  1. Project kaliti va vebxuk sirini oling; bot manzili uchun IP allowlist'ni yoqing.
  2. Agar o'z akkauntlaringiz bilan qolsangiz, sessiya va hamyonni kabinet orqali yuboring (OWN rejimi). Shundan keyin hamyondan faqat iFragment orqali foydalaning.
  3. Qabul qiluvchini qidirishni GET /v1/recipients/lookup bilan almashtiring.
  4. Sotib olishni externalId = bot buyurtmasi id'si bilan POST /v1/orders ga almashtiring.
  5. Vebxuk endpoint'ini, imzo tekshiruvini va event.id bo'yicha dublikatlarni saralashni qo'shing.
  6. Fragment/TON kodini o'chiring: cookie'lar, mnemonika, o'tkazmalarni imzolash, seqno'ni so'rash.

Ushbu sahifada