14 β To'lovlar va Telegram Stars¶
β¬ οΈ Oldingi: 13 β Webhook va running mode Β· π README Β· Keyingi: 15 β Rejalashtirilgan vazifalar va broadcast β‘οΈ
Bu bobda: Telegram bot orqali pul ishlash β to'lovlarni qabul qilish. Telegram'da to'lov uch qadamli oqim bilan ishlaydi: bot invoice (hisob-faktura) yuboradi (
sendInvoice), foydalanuvchi "To'lash" tugmasini bossa Telegram botga pre-checkout query yuboradi va bot 10 soniya ichidaanswerPreCheckoutQuerybilan tasdiqlashi kerak, so'ng to'lov amalga oshgachsuccessful_paymentxabari keladi (onSuccessfulPayment). Quyidagilarni o'rganamiz: ikki xil to'lov turi β Telegram Stars (XTR) (raqamli tovar uchun, provider token KERAK EMAS) va provider token (jismoniy tovar / haqiqiy valyuta, BotFather orqali ulanadi);LabeledPriceva narxni eng kichik birlikda ko'rsatish;onPreCheckoutQuery/answerPreCheckoutQuery(tasdiq va rad etish);onSuccessfulPaymentvatelegram_payment_charge_id'ni bazaga saqlash;refundStarPaymentbilan Stars'ni qaytarish;createInvoiceLinkbilan ulashiladigan to'lov havolasi;getMyStarBalance/getStarTransactionsbilan bot balansini ko'rish; payload validatsiyasi va xavfsizlik (narxni hech qachon foydalanuvchidan olma).Halol eslatma: bu bobdagi to'lov oqimi va handlerlari (
sendInvoicechaqiruvi,onPreCheckoutQuery->answerPreCheckoutQuery,onSuccessfulPayment,refundStarPayment,createInvoiceLink,getMyStarBalance, payload validatsiyasi va charge_id'ni SQLite'ga saqlash)Nutgram::fake()(FakeNutgram) bilan offline, tarmoqsiz va tokensiz HAQIQATAN ishga tushirilib tekshirilgan β natijalar quyida. Lekin haqiqiy to'lov β illustrativ: u jonli provider token (BotFather'da ulangan) va/yoki real Stars hisobi, jonli Telegram mijozini talab qiladi. Pul aslida o'tishi, "to'lov muvaffaqiyatli" degan haqiqiy natija faqat jonli botda ko'rinadi; bu yerda kod va mantiq to'g'ri, faqat pul harakatining o'zi illustrativ.
Telegram to'lovlari qanday ishlaydi?¶
Telegram'ning to'lov tizimi β bu bot foydalanuvchiga invoice (hisob-faktura) yuboradigan, foydalanuvchi esa Telegram interfeysi ichida to'lashi mumkin bo'lgan mexanizm. Bot pulni o'zi qabul qilmaydi β Telegram vositachi bo'ladi. Butun oqim shunday:
- Bot
sendInvoice(...)yuboradi β foydalanuvchi chatida "To'lash" tugmali maxsus xabar paydo bo'ladi. - Foydalanuvchi "To'lash"ni bosadi, karta/Stars ma'lumotini kiritadi.
- Telegram botga
pre_checkout_queryyuboradi β bu "so'nggi tekshiruv": tovar hali mavjudmi, narx to'g'rimi? Bot 10 soniya ichidaanswerPreCheckoutQuery(true)bilan javob berishi SHART. - Bot tasdiqlasa, Telegram pulni yechadi va botga
successful_paymentmaydoni bilan xabar yuboradi. - Bot tovarni beradi (masalan, obunani yoqadi) va
telegram_payment_charge_id'ni saqlaydi.
Bu oqim Python aiogram kitobidagi to'lovlar bilan deyarli aynan bir xil β agar uni ko'rgan bo'lsangiz (qarang ../tgbot-python/README.md), Nutgram'da faqat sintaksis o'zgaradi, mantiq bir xil.
Ikki xil to'lov: Stars va provider token¶
Telegram'da to'lovning ikki yo'li bor, va ular butunlay boshqacha maqsadlar uchun:
| Telegram Stars (XTR) | Provider token | |
|---|---|---|
| Nima uchun | Raqamli tovar/xizmat (obuna, kontent, o'yin ichidagi narsalar) | Jismoniy tovar, haqiqiy valyutadagi xizmat |
provider_token |
'' (bo'sh string) |
BotFather'dan olingan token, masalan '111:TEST:abc' |
currency |
'XTR' |
'UZS', 'USD', 'EUR' ... |
| BotFather sozlash | KERAK EMAS | Provider (Stripe, ...) ulash KERAK |
| Refund | refundStarPayment orqali botning o'zi |
Provider tomonida |
| Cheklov | Faqat raqamli mahsulot (Apple/Google qoidasi) | Davlat/provider cheklovlari |
Tavsiya: raqamli mahsulot (obuna, premium funksiya) sotyapsizmi β Stars ishlating, u eng sodda yo'l: hech qanday provider ulash, hujjat to'ldirish kerak emas, dunyoning hamma joyida ishlaydi. Jismoniy tovar yoki haqiqiy pul kerak bo'lsagina provider token bilan ovora bo'ling.
Diqqat β App Store / Google Play qoidasi: iOS/Android'dagi Telegram ilovasida raqamli tovarni faqat Stars orqali sotish mumkin. Raqamli mahsulot uchun haqiqiy valyuta (provider token) ishlatsangiz, mobil mijozda to'lov ko'rsatilmaydi. Shu sababli raqamli narsa = Stars.
LabeledPrice va narx β eng kichik birlik¶
Narx har doim eng kichik birlikda, butun son sifatida beriladi β float emas:
- Stars (XTR) uchun
amount= Stars soni.150-> "150 Stars". - Valyuta uchun
amount= eng kichik birlik. UZS uchun bu tiyin, USD uchun sent.50000.00 UZS->5000000.1.45 USD->145.
<?php
use SergiX44\Nutgram\Telegram\Types\Payment\LabeledPrice;
// 150 Stars
$prices = [LabeledPrice::make('Pro obuna', 150)];
// 50 000.00 UZS (50000 * 100 tiyin)
$pricesFiat = [LabeledPrice::make('Kitob', 5000000)];
// Bir nechta qator (mahsulot + soliq + chegirma)
$pricesMulti = [
LabeledPrice::make('Mahsulot', 1000),
LabeledPrice::make('Soliq', 120),
LabeledPrice::make('Chegirma', -200), // manfiy ham mumkin
];
prices β bu massiv: har bir element foydalanuvchiga ko'rinadigan bir qator. Ularning yig'indisi yakuniy summa bo'ladi. Nutgram bu massivni avtomatik JSON'ga aylantirib yuboradi.
Invoice yuborish: sendInvoice¶
Eng oddiy Stars invoice'i. provider_token: '' va currency: 'XTR' β bu Stars to'lovi degani:
<?php
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Types\Payment\LabeledPrice;
$bot = new Nutgram($_ENV['TELEGRAM_TOKEN']);
$bot->onCommand('buy', function (Nutgram $bot) {
$bot->sendInvoice(
title: 'Pro obuna', // 1-32 belgi
description: '30 kun reklamasiz', // 1-255 belgi
payload: 'sub_pro_30', // ICHKI identifikator (foydalanuvchiga ko'rinmaydi)
provider_token: '', // Stars uchun BO'SH
currency: 'XTR',
prices: [LabeledPrice::make('Pro obuna', 150)],
);
});
$bot->run();
payload β bu eng muhim maydon: u foydalanuvchiga ko'rinmaydi, lekin keyingi qadamlarda (pre-checkout va successful_payment) qaytib keladi. Bu yerga "qaysi mahsulot, qaysi tarif, qaysi buyurtma" degan ichki ma'lumotni joylashtiramiz β masalan sub_pro_30, order_42, yoki JSON.
OFFLINE tekshirildi: FakeNutgram bilan
/buychaqirildi ->sendInvoiceaynancurrency: 'XTR',provider_token: '',payload: 'sub_pro_30'bilan jo'natildi;pricesJSON'ga to'g'ri serializatsiya bo'lib,amount: 150qiymati saqlandi.
Provider token bilan (haqiqiy valyuta) β faqat provider_token va currency o'zgaradi:
<?php
$bot->onCommand('buyfiat', function (Nutgram $bot) {
$bot->sendInvoice(
title: 'Kitob (qogoz nusxa)',
description: 'Yetkazib berish bilan',
payload: 'book_42',
provider_token: $_ENV['PROVIDER_TOKEN'], // BotFather'dan, .env'da
currency: 'UZS',
prices: [LabeledPrice::make('Kitob', 5000000)], // 50 000.00 UZS
need_shipping_address: true, // jismoniy tovar -> manzil so'rash
need_phone_number: true,
);
});
HALOL:
provider_tokenBotFather'da to'lov providerini (masalan, bank yoki Stripe) ulashni talab qiladi va jonli token bilan ishlaydi β uni kodga yozmang,.env'dan o'qing (qarang 11-bob). Bu chaqiruvning o'zi (parametrlar to'g'riligi) offline tekshirildi; real to'lov jonli token bilan ishlaganda yuz beradi.
Pre-checkout: onPreCheckoutQuery va answerPreCheckoutQuery¶
Foydalanuvchi "To'lash"ni bosgach, Telegram botga pre_checkout_query yuboradi. Bu β so'nggi imkoniyat to'lovni to'xtatish uchun: tovar hali bormi? Narx o'zgarmadimi? Bot 10 soniya ichida javob berishi shart, aks holda Telegram to'lovni bekor qiladi.
<?php
$bot->onPreCheckoutQuery(function (Nutgram $bot) {
$query = $bot->preCheckoutQuery();
// payload orqali mahsulotni aniqlaymiz
if ($query->invoice_payload === 'sub_pro_30') {
// hammasi joyida -> tasdiqlaymiz
$bot->answerPreCheckoutQuery(true);
} else {
// noma'lum yoki mavjud bo'lmagan mahsulot -> rad etamiz
$bot->answerPreCheckoutQuery(
ok: false,
error_message: 'Kechirasiz, bu mahsulot endi mavjud emas.',
);
}
});
$bot->preCheckoutQuery() orqali PreCheckoutQuery obyektiga kiramiz. Foydali maydonlari:
->invoice_payloadβ biz yuborgan ichki payload;->currencyva->total_amountβ valyuta va summa (eng kichik birlikda);->fromβ to'layotgan foydalanuvchi.
Eng muhim qoida:
answerPreCheckoutQuery(true)ni faqat tovarni HAQIQATAN bera olsangiz chaqiring. Tasdiqlagandan keyin pul yechiladi β agar shundan keyin tovaringiz tugab qolsa, foydalanuvchidan pul olib, hech narsa bermagan bo'lasiz. Tekshiruvni shu yerda qiling.OFFLINE tekshirildi:
pre_checkout_query(payloadsub_pro_30) yuborildi -> botanswerPreCheckoutQuery(ok: true)chaqirdi. Noma'lum payload (NOMALUM) yuborilganda esaanswerPreCheckoutQuery(ok: false, error_message: 'Mahsulot mavjud emas')chaqirildi β ya'ni rad etish ham ishlaydi.
Agar bir nechta mahsulotingiz bo'lsa, payload'ni naqsh (regex) bo'yicha ushlash uchun onPreCheckoutQueryPayload qulay:
<?php
// payload 'sub_' bilan boshlanadigan barcha obunalar uchun
$bot->onPreCheckoutQueryPayload('sub_(.*)', function (Nutgram $bot) {
$bot->answerPreCheckoutQuery(true);
});
OFFLINE tekshirildi:
onPreCheckoutQueryPayload('sub_(.*)')sub_pro_30payloadiga mos keldi vaanswerPreCheckoutQuery(true)chaqirildi.
To'lov muvaffaqiyatli: onSuccessfulPayment¶
Pre-checkout tasdiqlangach va pul yechilgach, Telegram botga oddiy xabar yuboradi, lekin unda successful_payment maydoni bo'ladi. Buni onSuccessfulPayment ushlaydi:
<?php
$bot->onSuccessfulPayment(function (Nutgram $bot) {
$payment = $bot->message()->successful_payment;
// 1) Tovarni beramiz (obunani yoqamiz, kontentni ochamiz, ...)
// 2) charge_id'ni saqlaymiz β keyin REFUND uchun kerak bo'ladi
$chargeId = $payment->telegram_payment_charge_id;
$payload = $payment->invoice_payload; // qaysi mahsulot
$amount = $payment->total_amount; // qancha to'landi
// ... bazaga yozish (quyida) ...
$bot->sendMessage("To'lovingiz uchun rahmat! Pro obuna faollashtirildi. π");
});
successful_payment ning muhim maydonlari:
->telegram_payment_charge_idβ to'lov ID'si. Buni saqlash SHART β refund (qaytarish) faqat shu ID bilan mumkin.->invoice_payloadβ biz yuborgan payload (qaysi mahsulot to'landi).->total_amountva->currencyβ qancha va qaysi valyutada.->is_recurring,->subscription_expiration_dateβ obuna (recurring) to'lovlari uchun.
OFFLINE tekshirildi:
successful_paymentmaydonli xabar yuborildi -> handlertelegram_payment_charge_id = 'tg_charge_777',total_amount = 150,invoice_payload = 'sub_pro_30'qiymatlarini to'g'ri o'qidi va "To'lov qabul qilindi! Rahmat." javobini qaytardi.
charge_id'ni bazaga saqlash (refund uchun)¶
Refund qilish kerak bo'lganda telegram_payment_charge_id bo'lishi shart. Shuning uchun har bir to'lovni bazaga yozamiz (DB asoslari uchun 10-bob):
<?php
$bot->onSuccessfulPayment(function (Nutgram $bot) use ($pdo) {
$sp = $bot->message()->successful_payment;
$stmt = $pdo->prepare(
'INSERT INTO purchases (user_id, payload, charge_id, amount, created_at)
VALUES (?, ?, ?, ?, ?)'
);
$stmt->execute([
$bot->userId(),
$sp->invoice_payload,
$sp->telegram_payment_charge_id,
$sp->total_amount,
time(),
]);
$bot->sendMessage("To'lov saqlandi. Rahmat!");
});
OFFLINE tekshirildi: SQLite (
:memory:) jadvaligasuccessful_paymentma'lumotlari yozildi va qayta o'qildi βcharge_id = 'tg_charge_777',amount = 150,user_id = 555to'g'ri saqlandi.
Pulni qaytarish: refundStarPayment¶
Stars to'lovini qaytarish β botning o'zi refundStarPayment bilan qila oladi. Saqlangan charge_id va user_id kerak:
<?php
$bot->onCommand('refund', function (Nutgram $bot) use ($pdo) {
// Oxirgi to'lovni bazadan topamiz
$stmt = $pdo->prepare('SELECT charge_id FROM purchases WHERE user_id = ? ORDER BY created_at DESC LIMIT 1');
$stmt->execute([$bot->userId()]);
$chargeId = $stmt->fetchColumn();
if (!$chargeId) {
$bot->sendMessage("To'lov topilmadi.");
return;
}
$bot->refundStarPayment(
telegram_payment_charge_id: $chargeId,
user_id: $bot->userId(),
);
$bot->sendMessage('Stars qaytarildi.');
});
OFFLINE tekshirildi:
refundStarPaymentaynantelegram_payment_charge_id: 'tg_charge_777'vauser_id: 555bilan chaqirildi.HALOL: Refund haqiqatan Stars'ni foydalanuvchiga qaytarishi β jonli, real Stars hisobi va Telegram serveri bilan amalga oshadi (illustrativ). Bu yerda chaqiruv to'g'riligi tasdiqlangan; pul harakatining o'zi jonli botda ko'rinadi. Provider token (valyuta) to'lovlarida refund esa odatda provider tomonida qilinadi.
Ulashiladigan havola: createInvoiceLink¶
sendInvoice invoice'ni to'g'ridan-to'g'ri chatga yuboradi. Agar to'lov havolasini (URL) yaratib, uni boshqa joyda (kanal, sayt, tugma) ulashmoqchi bo'lsangiz β createInvoiceLink ishlatiladi. U Message emas, balki string (URL) qaytaradi:
<?php
$bot->onCommand('donate', function (Nutgram $bot) {
$link = $bot->createInvoiceLink(
title: "Loyihani qo'llab-quvvatlash",
description: 'Donat β rahmat!',
payload: 'donate_50',
provider_token: '', // Stars
currency: 'XTR',
prices: [LabeledPrice::make('Donat', 50)],
);
$bot->sendMessage("Donat havolasi:\n{$link}");
});
Bu havolani istalgan joyga qo'yish mumkin β masalan inline tugmaga url: sifatida. Pre-checkout va successful_payment oqimi sendInvoice'dagi bilan aynan bir xil bo'ladi.
OFFLINE tekshirildi:
createInvoiceLinkcurrency: 'XTR'vapayload: 'donate_50'bilan chaqirildi (havolaning o'zi Telegram'dan keladi β jonli qism).
Bot balansi va tranzaksiyalar¶
Stars to'lovlaridan tushgan mablag'ni va tranzaksiyalar tarixini ko'rish:
<?php
$bot->onCommand('balance', function (Nutgram $bot) {
$balance = $bot->getMyStarBalance(); // StarAmount
$bot->sendMessage("Bot balansi: {$balance->amount} Stars");
});
$bot->onCommand('history', function (Nutgram $bot) {
$tx = $bot->getStarTransactions(offset: 0, limit: 10); // StarTransactions
$count = count($tx->transactions ?? []);
$bot->sendMessage("Oxirgi tranzaksiyalar soni: {$count}");
});
OFFLINE tekshirildi:
getMyStarBalance()chaqirildi va soxta javob (amount: 1250)StarAmountobyektiga hidratsiya qilinib,->amount1250 sifatida o'qildi.
Xavfsizlik: narxni hech qachon foydalanuvchidan olma¶
To'lovda eng muhim xavfsizlik qoidasi:
- Narxni doim serverda aniqlang.
payload'ga "qaysi mahsulot" degan kalitni qo'ying (masalansub_pro_30), narxni esa o'zingiz mahsulot katalogidan oling. Hech qachon callback_data yoki foydalanuvchi yuborgan matndan narx olmang β aks holda kimdir narxni 1 Star qilib o'zgartirishi mumkin.
<?php
// XAVFLI: narx tashqaridan keladi -> manipulyatsiya mumkin
// $price = $userInput;
// TO'G'RI: payload -> katalogdan narx
$catalog = [
'sub_pro_30' => ['nom' => 'Pro obuna', 'narx' => 150],
'sub_pro_365' => ['nom' => 'Pro yillik', 'narx' => 1500],
];
$bot->onCommand('buy', function (Nutgram $bot) use ($catalog) {
$item = $catalog['sub_pro_30'];
$bot->sendInvoice(
title: $item['nom'],
description: '...',
payload: 'sub_pro_30',
provider_token: '',
currency: 'XTR',
prices: [LabeledPrice::make($item['nom'], $item['narx'])],
);
});
- Pre-checkout'da mavjudlikni tekshiring va faqat tovar bor bo'lsa
trueqaytaring. total_amount'ni tekshiring (ixtiyoriy, lekin foydali): pre-checkout/successful_payment'da kelgan summa kutilgan narxga mosligini solishtiring.charge_id'ni saqlang β refund va qo'llab-quvvatlash uchun.- Idempotentlik: bitta to'lov ikki marta qayta ishlanmasligi uchun
charge_idbo'yicha takror tekshiring (bazagaUNIQUEqo'ying).
Mashqlar¶
Oson¶
/buybuyrug'i 100 Stars'lik "Premium" invoice'ini yuborsin (sendInvoice,currency: 'XTR',provider_token: '').onPreCheckoutQueryhandler yozing: payloadpremiumbo'lsaanswerPreCheckoutQuery(true), aks holdafalse.onSuccessfulPaymenthandler yozing vatelegram_payment_charge_id'nierror_logorqali chiqaring.LabeledPricebilan 49 999.00 UZS narxni to'g'riamount(eng kichik birlik) qilib yozing.createInvoiceLinkbilan 25 Stars'lik "Kofe" donat havolasini yarating va matnda yuboring./balancebuyrug'igetMyStarBalance()->amountni ko'rsatsin.
O'rta¶
- Mahsulot katalogini massiv qilib tuzing (3 ta tovar) va
/shopbuyrug'i har bir tovar uchun callback tugma chiqarsin; tugma bosilganda mossendInvoiceyuborilsin (narx katalogdan, payload'da mahsulot kaliti). onPreCheckoutQueryPayloadbilanorder_(\d+)naqshi orqali buyurtma to'lovlarini ushlang.onSuccessfulPayment'datotal_amountkutilgan narxga mos kelmasa, to'lovni log qilib, foydalanuvchiga ogohlantirish yuboring.- SQLite jadval yarating (
purchases,charge_idUNIQUE) va har bir to'lovni saqlang; takrorcharge_idkelganda qayta ishlamang (idempotentlik). /refundbuyrug'i foydalanuvchining oxirgi to'lovini bazadan topib,refundStarPaymentchaqirsin.- Provider token bilan (UZS) jismoniy tovar invoice'i yuboring β
need_shipping_address: truevaneed_phone_number: truebilan.
Qiyin¶
- To'liq "obuna" oqimini yozing:
/subscribe-> invoice -> pre-checkout (tekshiruv) -> successful_payment -> bazadaexpires_at = time() + 30*86400saqlash;/statusobuna muddati tugaganini tekshirib ko'rsatsin. - FakeNutgram bilan to'liq oqim testi yozing (
onPreCheckoutQuery+onSuccessfulPayment):hearUpdateType(UpdateType::PRE_CHECKOUT_QUERY, ...)vasuccessful_paymentli xabar yuborib,assertReply/assertCalledbilan tekshiring. - Stars va provider token to'lovlarini bitta botda birga ishlating: payload prefiksi (
stars_/fiat_) bo'yicha qaysi turini aniqlab, mosprovider_token/currencybilan invoice yuborishni real qiling.
Yechimlar
1.
$bot->onCommand('buy', function (Nutgram $bot) {
$bot->sendInvoice(
title: 'Premium',
description: 'Premium imkoniyatlar',
payload: 'premium',
provider_token: '',
currency: 'XTR',
prices: [LabeledPrice::make('Premium', 100)],
);
});
2.
$bot->onPreCheckoutQuery(function (Nutgram $bot) {
$ok = $bot->preCheckoutQuery()->invoice_payload === 'premium';
$bot->answerPreCheckoutQuery(
ok: $ok,
error_message: $ok ? null : 'Mahsulot mavjud emas',
);
});
3.
$bot->onSuccessfulPayment(function (Nutgram $bot) {
$id = $bot->message()->successful_payment->telegram_payment_charge_id;
error_log("Charge ID: {$id}");
$bot->sendMessage('Rahmat!');
});
4. 49 999.00 UZS β tiyinda: 4999900.
5.
$bot->onCommand('coffee', function (Nutgram $bot) {
$link = $bot->createInvoiceLink(
title: 'Kofe', description: 'Menga kofe oling',
payload: 'coffee_25', provider_token: '', currency: 'XTR',
prices: [LabeledPrice::make('Kofe', 25)],
);
$bot->sendMessage("Havola: {$link}");
});
6.
$bot->onCommand('balance', function (Nutgram $bot) {
$bal = $bot->getMyStarBalance();
$bot->sendMessage("Balans: {$bal->amount} Stars");
});
7.
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardMarkup;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardButton;
$catalog = [
'p_pro' => ['nom' => 'Pro', 'narx' => 150],
'p_team' => ['nom' => 'Team', 'narx' => 500],
'p_extra' => ['nom' => 'Extra', 'narx' => 50],
];
$bot->onCommand('shop', function (Nutgram $bot) use ($catalog) {
$kb = InlineKeyboardMarkup::make();
foreach ($catalog as $key => $item) {
$kb->addRow(InlineKeyboardButton::make(
"{$item['nom']} β {$item['narx']} β",
callback_data: "buy:{$key}"
));
}
$bot->sendMessage('Tovarni tanlang:', reply_markup: $kb);
});
$bot->onCallbackQueryData('buy:(.*)', function (Nutgram $bot, string $key) use ($catalog) {
$bot->answerCallbackQuery();
$item = $catalog[$key] ?? null;
if (!$item) { return; }
$bot->sendInvoice(
title: $item['nom'], description: '...',
payload: $key, provider_token: '', currency: 'XTR',
prices: [LabeledPrice::make($item['nom'], $item['narx'])],
);
});
8.
$bot->onPreCheckoutQueryPayload('order_(\d+)', function (Nutgram $bot, string $orderId) {
// $orderId β buyurtma raqami; bazadan mavjudligini tekshir
$bot->answerPreCheckoutQuery(true);
});
9.
$expected = ['sub_pro_30' => 150];
$bot->onSuccessfulPayment(function (Nutgram $bot) use ($expected) {
$sp = $bot->message()->successful_payment;
$want = $expected[$sp->invoice_payload] ?? null;
if ($want !== null && $sp->total_amount !== $want) {
error_log("Summa mos emas: {$sp->total_amount} != {$want}");
$bot->sendMessage("To'lov tekshirilmoqda, qo'llab-quvvatlashga murojaat qiling.");
return;
}
$bot->sendMessage('Rahmat!');
});
10.
$pdo->exec('CREATE TABLE IF NOT EXISTS purchases (
charge_id TEXT UNIQUE, user_id INTEGER, payload TEXT, amount INTEGER, created_at INTEGER
)');
$bot->onSuccessfulPayment(function (Nutgram $bot) use ($pdo) {
$sp = $bot->message()->successful_payment;
// takror to'lovmi?
$check = $pdo->prepare('SELECT 1 FROM purchases WHERE charge_id = ?');
$check->execute([$sp->telegram_payment_charge_id]);
if ($check->fetchColumn()) {
return; // allaqachon ishlangan β idempotent
}
$pdo->prepare('INSERT INTO purchases (charge_id, user_id, payload, amount, created_at) VALUES (?,?,?,?,?)')
->execute([$sp->telegram_payment_charge_id, $bot->userId(), $sp->invoice_payload, $sp->total_amount, time()]);
$bot->sendMessage('Rahmat!');
});
11.
$bot->onCommand('refund', function (Nutgram $bot) use ($pdo) {
$stmt = $pdo->prepare('SELECT charge_id FROM purchases WHERE user_id = ? ORDER BY created_at DESC LIMIT 1');
$stmt->execute([$bot->userId()]);
$id = $stmt->fetchColumn();
if (!$id) { $bot->sendMessage("To'lov yo'q."); return; }
$bot->refundStarPayment(telegram_payment_charge_id: $id, user_id: $bot->userId());
$bot->sendMessage('Qaytarildi.');
});
12.
$bot->onCommand('order', function (Nutgram $bot) {
$bot->sendInvoice(
title: 'Futbolka', description: 'Yetkazib berish bilan',
payload: 'fiat_shirt_1', provider_token: $_ENV['PROVIDER_TOKEN'],
currency: 'UZS', prices: [LabeledPrice::make('Futbolka', 12000000)],
need_shipping_address: true, need_phone_number: true,
);
});
13.
$pdo->exec('CREATE TABLE IF NOT EXISTS subs (user_id INTEGER PRIMARY KEY, expires_at INTEGER)');
$bot->onCommand('subscribe', function (Nutgram $bot) {
$bot->sendInvoice(
title: 'Pro obuna (30 kun)', description: 'Reklamasiz',
payload: 'sub_pro_30', provider_token: '', currency: 'XTR',
prices: [LabeledPrice::make('Pro obuna', 150)],
);
});
$bot->onPreCheckoutQueryPayload('sub_(.*)', fn (Nutgram $bot) => $bot->answerPreCheckoutQuery(true));
$bot->onSuccessfulPayment(function (Nutgram $bot) use ($pdo) {
$expires = time() + 30 * 86400;
$pdo->prepare('INSERT INTO subs (user_id, expires_at) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET expires_at = excluded.expires_at')
->execute([$bot->userId(), $expires]);
$bot->sendMessage('Obuna 30 kunga faollashtirildi!');
});
$bot->onCommand('status', function (Nutgram $bot) use ($pdo) {
$stmt = $pdo->prepare('SELECT expires_at FROM subs WHERE user_id = ?');
$stmt->execute([$bot->userId()]);
$exp = $stmt->fetchColumn();
if ($exp && $exp > time()) {
$kun = ceil(($exp - time()) / 86400);
$bot->sendMessage("Obuna faol. Qolgan: {$kun} kun.");
} else {
$bot->sendMessage("Obuna yo'q yoki muddati tugagan.");
}
});
14.
use SergiX44\Nutgram\Telegram\Properties\UpdateType;
// pre-checkout
$bot = Nutgram::fake();
$bot->onPreCheckoutQuery(fn (Nutgram $bot) => $bot->answerPreCheckoutQuery(true));
$bot->hearUpdateType(UpdateType::PRE_CHECKOUT_QUERY, [
'id' => 'q1', 'from' => ['id' => 1, 'is_bot' => false, 'first_name' => 'A'],
'currency' => 'XTR', 'total_amount' => 150, 'invoice_payload' => 'sub_pro_30',
])->reply();
$bot->assertReply('answerPreCheckoutQuery', ['ok' => true]);
// successful payment
$bot2 = Nutgram::fake();
$bot2->onSuccessfulPayment(fn (Nutgram $bot) => $bot->sendMessage('Rahmat!'));
$bot2->hearUpdateType(UpdateType::MESSAGE, [
'from' => ['id' => 1, 'is_bot' => false, 'first_name' => 'A'],
'chat' => ['id' => 1, 'type' => 'private'],
'successful_payment' => [
'currency' => 'XTR', 'total_amount' => 150, 'invoice_payload' => 'sub_pro_30',
'telegram_payment_charge_id' => 'c1', 'provider_payment_charge_id' => '',
],
])->reply();
$bot2->assertReplyText('Rahmat!');
15.
$catalog = [
'stars_pro' => ['nom' => 'Pro', 'narx' => 150, 'token' => '', 'cur' => 'XTR'],
'fiat_book' => ['nom' => 'Kitob','narx' => 5000000, 'token' => $_ENV['PROVIDER_TOKEN'], 'cur' => 'UZS'],
];
$bot->onCallbackQueryData('buy:(.*)', function (Nutgram $bot, string $key) use ($catalog) {
$bot->answerCallbackQuery();
$item = $catalog[$key] ?? null;
if (!$item) { return; }
$bot->sendInvoice(
title: $item['nom'], description: '...',
payload: $key, provider_token: $item['token'],
currency: $item['cur'],
prices: [LabeledPrice::make($item['nom'], $item['narx'])],
// fiat tovar bo'lsa manzil so'rash
need_shipping_address: str_starts_with($key, 'fiat_') ? true : null,
);
});
β¬ οΈ Oldingi: 13 β Webhook va running mode Β· π README Β· Keyingi: 15 β Rejalashtirilgan vazifalar va broadcast β‘οΈ