14 β To'lovlar va Telegram Stars¶
β¬ οΈ Oldingi: 13 β Webhook va aiohttp server Β· π README Β· Keyingi: 15 β Rejalashtirilgan vazifalar va broadcast β‘οΈ
Bu bobda: Botingiz orqali pul (yoki Telegram Stars) yig'ishni o'rganamiz. To'lov oqimining uchta qadamini batafsil ko'ramiz:
send_invoicebilan hisob-faktura (invoice) yuborish,pre_checkout_querynianswer_pre_checkout_querybilan tasdiqlash (10 soniyalik muddat!), vasuccessful_paymenthandler bilan pulni qabul qilib mahsulotni topshirish. Ikki xil to'lov turini taqqoslaymiz: tashqi provider token (BotFather orqali, karta to'lovi) va Telegram Stars (XTRvalyutasi, raqamli tovar uchun eng oson yo'l). Stars'daprovider_token=""(bo'sh string) ekanligini,refund_star_paymentbilan pulni qaytarishni,create_invoice_linkbilan havola yaratishni va SQLite'da xaridni idempotent saqlashni ko'rib chiqamiz.Halol eslatma: Handler routing (
pre_checkout_query,successful_payment), filtrlar,pay=Trueklaviatura,CallbackData, SQLite xarid yozish varefund_star_paymentimzosifeed_updatemock pattern bilan offline tekshirilgan (aiogram 3.28.2, Python 3.14.2). Lekin haqiqiy to'lov (real Stars yoki karta) provider token + jonli Telegram + foydalanuvchi hamyoni talab qiladi β bu bobdagi "bu jonli botda shunday ko'rinadi" deb belgilangan bloklar illustrativ, ulardagi natija (haqiqatan pul tushishi) tokensiz tekshirib bo'lmaydi, lekin kod va oqim to'g'ri.
To'lov nima va u qanday ishlaydi?¶
Telegram botlari foydalanuvchidan chat ichida pul yoki Telegram Stars qabul qila oladi. Foydalanuvchi botingizdan chiqmaydi β karta raqamini terib o'tirmaydi, balki Telegram'ning ichki, tanish interfeysida bir tugma bosadi.
Bu jarayon doim uchta qadamdan iborat. Tartib qat'iy va o'zgarmaydi:
- Hisob-faktura (invoice) yuborish β bot
send_invoice()chaqiradi. Chatda mahsulot nomi, narxi va "To'lash" tugmasi bo'lgan maxsus xabar paydo bo'ladi. - Tasdiqlash so'rovi (pre_checkout_query) β foydalanuvchi "To'lash" bosgach, Telegram botingizga "shu mahsulotni shu narxga sotaverasanmi?" deb so'raydi. Bot 10 soniya ichida "ha" (yoki "yo'q") deb javob berishi shart.
- Muvaffaqiyatli to'lov (successful_payment) β pul tushgach, Telegram botga maxsus xabar yuboradi. Endi bot mahsulotni topshiradi (PDF beradi, obunani yoqadi va h.k.).
Bu uchta qadam uchun bizda uchta alohida handler bo'ladi:
Agar 2-qadam (pre_checkout) o'tmasa β masalan bot 10 soniyada javob bermasa yoki
ok=Falseqaytarsa β 3-qadam hech qachon kelmaydi va foydalanuvchidan pul yechilmaydi. Bu xavfsizlik mexanizmi: pul yechilishidan oldin bot oxirgi marta "rozimisan" deb so'raladi.
Stars (XTR) va tashqi provider: qaysi birini tanlash?¶
Telegram'da to'lovning ikki turi bor. Kod oqimi (uchala qadam) ikkalasida ham bir xil, faqat send_invoice ning ikki parametri farq qiladi: currency va provider_token.
Telegram Stars (XTR) β raqamli tovar uchun¶
Telegram Stars β bu Telegram'ning ichki "yulduz" valyutasi (β). Foydalanuvchi avval Stars sotib oladi (App Store/Google Play orqali), keyin botingizda ularni sarflaydi.
currency="XTR"provider_token=""(bo'sh string β bu shart!)amount= to'g'ridan-to'g'ri yulduzlar soni (50=> 50 β). Bu yerda100ga bo'lish YO'Q.- Provider ro'yxatdan o'tish kerak emas β token olishning hojati yo'q.
- Faqat raqamli tovar/xizmat uchun: PDF, kurs, obuna, kontent ochish, donat.
- Telegram qoidasi bo'yicha raqamli tovar uchun Stars ishlatish majburiy.
Bu β boshlash uchun eng oson yo'l, shuning uchun bobda asosan Stars'ga e'tibor beramiz.
Tashqi provider token β karta to'lovi uchun¶
Jismoniy tovar (kitobni pochta orqali jo'natish, ovqat yetkazib berish) yoki haqiqiy valyutada to'lov uchun tashqi provider kerak (masalan Stripe va boshqalar β mintaqaga bog'liq).
currency="USD","EUR","UZS"va h.k. (ISO 4217 kodi)provider_token="..."β bu tokenni BotFather orqali olasiz (pastda ko'ramiz).amount= eng kichik birlik.1.00 USD=>100(sentlar).25000 UZS=>2500000(tiyinlar).- Test rejimi uchun alohida token beriladi.
Bu bobdagi runnable kodda biz Stars (XTR) dan foydalanamiz, chunki uni provider tokensiz tushuntirish mumkin va kod toza. Provider uchun esa faqat
currencyvaprovider_tokeno'zgaradi β qolgani aynan bir xil.
Tokenni qayerdan olish (BotFather)¶
Provider token bilan ishlamoqchi bo'lsangiz, uni botingizning asosiy BOT_TOKEN dan ALOHIDA olasiz:
1. @BotFather ga kiring.
2. /mybots -> botingizni tanlang -> "Payments" tugmasi.
3. To'lov providerini (mintaqangizdagi birini) tanlang va ulang.
4. Provider sizga PROVIDER TOKEN beradi (test va jonli alohida).
5. Bu provider tokenni .env ga PAYMENT_PROVIDER_TOKEN sifatida saqlang.
Stars (XTR) uchun esa hech narsa qilish shart emas β provider_token="" yetarli.
.env faylimiz (3-bobdagi pattern davomi):
BOT_TOKEN=123456789:AA... # botning asosiy tokeni
PAYMENT_PROVIDER_TOKEN= # Stars uchun bo'sh; provider uchun to'ldiriladi
Eslatma: provider token ham, bot token ham β maxfiy. Hech qachon kodga yozmang,
.envda saqlang va.gitignorega.envni qo'shing. Bu haqda 3-bobda batafsil gaplashganmiz.
Birinchi qadam: hisob-faktura yuborish (send_invoice)¶
Foydalanuvchi /buy yozsa, unga "To'lash" tugmali invoice yuboramiz.
send_invoice ning majburiy parametrlari:
| Parametr | Tavsif |
|---|---|
chat_id |
Kimga yuborilsin |
title |
Mahsulot nomi (1-32 belgi) |
description |
Tavsif (1-255 belgi) |
payload |
Botning ichki yorlig'i (1-128 bayt) β pulni keyin shu orqali taniymiz |
currency |
"XTR" (Stars) yoki ISO 4217 kodi |
prices |
LabeledPrice ro'yxati |
LabeledPrice da ikki maydon bor: label (matn) va amount (son).
from aiogram import Bot, Router
from aiogram.filters import Command
from aiogram.types import LabeledPrice, Message
router = Router()
PREMIUM_PRICE = 50 # 50 Telegram Stars
@router.message(Command("buy"))
async def cmd_buy(message: Message, bot: Bot):
await bot.send_invoice(
chat_id=message.chat.id,
title="Premium kontent",
description="Maxsus PDF qo'llanma β barcha boblar bir faylda.",
payload="premium_pdf_v1", # keyin shu orqali taniymiz
currency="XTR", # Telegram Stars
prices=[LabeledPrice(label="Premium PDF", amount=PREMIUM_PRICE)],
provider_token="", # Stars uchun MAJBURAN bo'sh string
)
Stars'da
provider_token=""(bo'sh string) bo'lishi shart. Agar bu yergaNoneyoki provider token qo'ysangiz, Telegram xatolik qaytaradi. Provider to'lovida esa bu yerga.envdagiPAYMENT_PROVIDER_TOKENqo'yiladi.Muhim cheklov: XTR (Stars) uchun
pricesro'yxatida aynan bittaLabeledPricebo'lishi shart. Oddiy valyutadagidek bir nechta narx qatori (masalan "tovar + yetkazib berish") qo'shib bo'lmaydi β Stars'da yagona summa beriladi.
Bu jonli botda shunday ko'rinadi (illustrativ β token + internet kerak): foydalanuvchi chatda "Premium kontent β 50 β" yozuvli karta va sariq "To'lash" tugmasini ko'radi.
message.answer_invoice qisqartmasi¶
Aynan shu narsani Message obyektining qisqartmasi orqali ham yozish mumkin β chat_id ni o'zi oladi:
@router.message(Command("buy"))
async def cmd_buy(message: Message):
await message.answer_invoice(
title="Premium kontent",
description="Maxsus PDF qo'llanma.",
payload="premium_pdf_v1",
currency="XTR",
prices=[LabeledPrice(label="Premium PDF", amount=PREMIUM_PRICE)],
provider_token="",
)
Ikkalasi bir xil ishlaydi β uslub masalasi.
Ikkinchi qadam: pre_checkout_query ni tasdiqlash¶
Foydalanuvchi "To'lash" bosgach, Telegram botga pre_checkout_query update'ini yuboradi. Bu β botning oxirgi imkoniyati to'lovni to'xtatish uchun. Masalan: mahsulot tugagan, narx o'zgargan, foydalanuvchi bloklangan β shu yerda tekshirib ok=False qaytarish mumkin.
Eng muhim qoida: 10 soniya ichida javob bering. Aks holda Telegram to'lovni bekor qiladi.
pre_checkout_query.answer() parametrlari:
ok=Trueβ hammasi joyida, to'lovni davom ettir.ok=False, error_message="..."β to'lovni rad et, sababni foydalanuvchiga ko'rsat (error_messageok=Falseda MAJBURIY).
from aiogram.types import PreCheckoutQuery
@router.pre_checkout_query()
async def process_pre_checkout(query: PreCheckoutQuery):
# Validatsiya: payload va summa biz kutgandekmi?
if query.invoice_payload == "premium_pdf_v1" and query.total_amount == PREMIUM_PRICE:
await query.answer(ok=True)
else:
await query.answer(
ok=False,
error_message="Kechirasiz, bu mahsulot hozir mavjud emas.",
)
@router.pre_checkout_query()β bu maxsus dekorator, oddiy@router.messageEMAS. U faqatpre_checkout_queryupdate'ini ushlaydi.
PreCheckoutQuery ichida nimalar bor (offline tasdiqlangan maydonlar):
query.id # so'rov identifikatori
query.from_user # User β kim sotib olmoqchi
query.currency # "XTR" yoki valyuta kodi
query.total_amount # narx (Stars'da yulduzlar soni)
query.invoice_payload # send_invoice'dagi payload β mahsulotni shu orqali taniymiz
Diqqat:
pre_checkout_querydaok=Trueqaytarish β siz pulni olishga rozisiz degani. Lekin pul aslida 3-qadamda tushadi. Shuning uchun mahsulotni shu yerda topshirmang β faqatsuccessful_paymentda topshiring.
Uchinchi qadam: successful_payment β pul tushdi¶
Pul muvaffaqiyatli yechilsa, Telegram chatga successful_payment maydonli oddiy Message yuboradi. Buni F.successful_payment magic-filtri bilan ushlaymiz.
from aiogram import F
from aiogram.types import Message
@router.message(F.successful_payment)
async def on_successful_payment(message: Message):
sp = message.successful_payment
# MANA shu yerda mahsulotni topshiramiz:
await message.answer(
f"Rahmat! To'lov qabul qilindi.\n"
f"Summa: {sp.total_amount} {sp.currency}\n"
f"Mana sizning PDF havolangiz: https://example.com/premium.pdf"
)
SuccessfulPayment ning muhim maydonlari (offline tasdiqlangan):
sp.currency # "XTR"
sp.total_amount # 50
sp.invoice_payload # "premium_pdf_v1" β qaysi mahsulot edi
sp.telegram_payment_charge_id # to'lov ID'si β refund/saqlash uchun MUHIM
sp.provider_payment_charge_id # provider tomonidagi ID (Stars'da bo'sh)
telegram_payment_charge_idβ eng qimmatli maydon. Uni albatta saqlang: keyinroq pulni qaytarish (refund_star_payment) shu ID orqali bo'ladi va xaridni qayd qilish uchun ham kerak.
Hammasini birga: to'liq, ishlaydigan to'lov boti¶
Mana uchala handler bitta routerda. Bu kod offline feed_update pattern bilan tekshirilgan β handler routing, validatsiya va successful_payment o'qish to'g'ri ishlaydi.
# payments.py
from aiogram import Bot, F, Router
from aiogram.filters import Command
from aiogram.types import (
LabeledPrice, Message, PreCheckoutQuery,
)
router = Router()
PREMIUM_PRICE = 50
PAYLOAD = "premium_pdf_v1"
@router.message(Command("buy"))
async def cmd_buy(message: Message, bot: Bot):
await bot.send_invoice(
chat_id=message.chat.id,
title="Premium kontent",
description="Maxsus PDF qo'llanma.",
payload=PAYLOAD,
currency="XTR",
prices=[LabeledPrice(label="Premium PDF", amount=PREMIUM_PRICE)],
provider_token="",
)
@router.pre_checkout_query()
async def process_pre_checkout(query: PreCheckoutQuery):
if query.invoice_payload == PAYLOAD and query.total_amount == PREMIUM_PRICE:
await query.answer(ok=True)
else:
await query.answer(ok=False, error_message="Mahsulot mavjud emas.")
@router.message(F.successful_payment)
async def on_successful_payment(message: Message):
sp = message.successful_payment
await message.answer(
"Rahmat! To'lov qabul qilindi.\n"
"Mana havolangiz: https://example.com/premium.pdf"
)
Asosiy main.py (jonli ishga tushirish β bu qismi token + internet talab qiladi):
# main.py β illustrativ: jonli polling BotFather token + internet talab qiladi
import asyncio
import os
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from payments import router
async def main():
bot = Bot(
token=os.environ["BOT_TOKEN"],
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
dp = Dispatcher(storage=MemoryStorage())
dp.include_router(router)
await dp.start_polling(bot) # <- jonli Telegram'ga ulanadi
if __name__ == "__main__":
asyncio.run(main())
start_pollingjonli Telegram serveriga ulanadi β buni tokensiz ishga tushirib bo'lmaydi. Quyida esa tokensiz, offline tekshirish patternini ko'ramiz.
Tokensiz offline tekshirish (haqiqatan ishlaydi)¶
Spec bo'yicha handlerlarni jonli Telegram'siz, soxta token bilan feed_update orqali tekshiramiz. bot.session ni mock qilamiz β shunda send_invoice kabi chaqiruvlar tarmoqqa chiqmaydi, faqat handler routing va biznes-logikani tekshiramiz. Bu kodni o'zim ishga tushirib tasdiqladim.
# test_payments.py β OFFLINE, token shart emas
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import (
Chat, PreCheckoutQuery, SuccessfulPayment, Message, Update, User,
)
from payments import router, PREMIUM_PRICE, PAYLOAD
FAKE_TOKEN = "123456:AAH-FakeTest_abc"
async def main():
bot = Bot(token=FAKE_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML))
bot.session = AsyncMock() # <- tarmoqqa chiqmaydi
dp = Dispatcher(storage=MemoryStorage())
dp.include_router(router)
chat = Chat(id=111, type="private")
user = User(id=222, is_bot=False, first_name="Oqil")
# 1) /buy
msg = Message(message_id=1, date=datetime.now(), chat=chat,
from_user=user, text="/buy")
await dp.feed_update(bot, Update(update_id=1, message=msg))
# 2) pre_checkout (to'g'ri)
pcq = PreCheckoutQuery(id="pcq1", from_user=user, currency="XTR",
total_amount=PREMIUM_PRICE, invoice_payload=PAYLOAD)
await dp.feed_update(bot, Update(update_id=2, pre_checkout_query=pcq))
# 3) successful_payment
sp = SuccessfulPayment(currency="XTR", total_amount=PREMIUM_PRICE,
invoice_payload=PAYLOAD,
telegram_payment_charge_id="charge_abc123",
provider_payment_charge_id="")
msg2 = Message(message_id=2, date=datetime.now(), chat=chat,
from_user=user, successful_payment=sp)
await dp.feed_update(bot, Update(update_id=4, message=msg2))
await bot.session.close()
print("Hamma handler ishladi (offline routing)")
if __name__ == "__main__":
asyncio.run(main())
Ishga tushirilganda chiqqan natija (men o'zim ishga tushirib oldim):
Uchala update (/buy, pre_checkout_query, successful_payment) xato bermay tegishli handlerga yetib bordi β agar biror handler topilmasa yoki kodda xato bo'lsa, feed_update istisno (exception) ko'tarib, bu satr umuman chop etilmas edi.
Bu test handler routing va logika to'g'riligini isbotlaydi. Lekin "pul tushdi" qismi soxta
SuccessfulPaymentobyekti β haqiqiy pul tushishi faqat jonli botda, real foydalanuvchi hamyoni bilan bo'ladi.
Xaridni saqlash: SQLite va idempotentlik¶
Pul tushgach, xaridni bazaga yozish kerak β kim, qachon, qancha to'ladi. telegram_payment_charge_id ni PRIMARY KEY qilsak, bir xil to'lov ikki marta yozilmaydi (idempotentlik). Bu kodni offline tekshirdim.
import aiosqlite
async def init_db():
async with aiosqlite.connect("shop.db") as db:
await db.execute("""
CREATE TABLE IF NOT EXISTS purchases(
charge_id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
payload TEXT NOT NULL,
amount INTEGER NOT NULL,
currency TEXT NOT NULL
)
""")
await db.commit()
async def save_purchase(sp, user_id: int):
async with aiosqlite.connect("shop.db") as db:
try:
await db.execute(
"INSERT INTO purchases VALUES (?, ?, ?, ?, ?)",
(sp.telegram_payment_charge_id, user_id,
sp.invoice_payload, sp.total_amount, sp.currency),
)
await db.commit()
return True # yangi xarid
except aiosqlite.IntegrityError:
return False # bu charge_id allaqachon bor (takror)
Endi successful_payment handlerini bog'laymiz:
@router.message(F.successful_payment)
async def on_successful_payment(message: Message):
sp = message.successful_payment
is_new = await save_purchase(sp, message.from_user.id)
if is_new:
await message.answer("Rahmat! Mana havolangiz: https://example.com/premium.pdf")
else:
await message.answer("Bu to'lov allaqachon qayd etilgan.")
PRIMARY KEYustidagiIntegrityErrorβ bizning idempotentlik qalqonimiz. SQLite bo'limini ko'proq o'rganish uchun: SQL bobiga qarang.
Pulni qaytarish: refund_star_payment¶
Telegram Stars'ni foydalanuvchiga qaytarish mumkin (provider to'lovida bu provider qoidalariga bog'liq). Buning uchun telegram_payment_charge_id kerak β shuning uchun uni saqlash muhim edi.
refund_star_payment parametrlari (offline tasdiqlangan): user_id va telegram_payment_charge_id.
@router.message(Command("refund"))
async def cmd_refund(message: Message, bot: Bot):
# Misol uchun bazadan oxirgi charge_id ni olgandirsiz
charge_id = "charge_abc123"
await bot.refund_star_payment(
user_id=message.from_user.id,
telegram_payment_charge_id=charge_id,
)
await message.answer("Stars qaytarildi.")
Bu jonli botda shunday ishlaydi (illustrativ β token + real to'lov kerak): foydalanuvchi yulduzlarni qaytarib oladi va botdan xabar keladi. Tokensiz biz faqat metod imzosi to'g'riligini tasdiqladik.
To'lov havolasi: create_invoice_link¶
Ba'zan invoice'ni chatga emas, balki havola (link) sifatida yaratish kerak β masalan kanalda tugma yoki saytda joylash uchun. create_invoice_link xuddi send_invoice kabi argumentlar oladi, lekin chat_id o'rniga URL qaytaradi.
@router.message(Command("link"))
async def cmd_link(message: Message, bot: Bot):
link = await bot.create_invoice_link(
title="Donat",
description="Loyihani qo'llab-quvvatlash.",
payload="donate_100",
currency="XTR",
prices=[LabeledPrice(label="Donat", amount=100)],
provider_token="",
)
await message.answer(f"To'lov havolasi: {link}")
Bu havolani bossangiz, o'sha invoice ochiladi va to'lovning xuddi shu uchta qadami ishlaydi. Argument formatini offline (mock session bilan) tekshirdim.
Invoice xabariga "To'lash" tugmasini qo'shish (pay=True)¶
send_invoice o'zi standart "To'lash" tugmasini chiqaradi. Lekin agar siz reply_markup bilan o'z klaviaturangizni bersangiz, birinchi tugma pay=True bo'lishi shart β bu maxsus to'lov tugmasi. Boshqa tugmalar (masalan "Bekor qilish") oddiy callback_data bilan bo'lishi mumkin.
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
def pay_keyboard() -> "InlineKeyboardMarkup":
kb = InlineKeyboardBuilder()
kb.row(InlineKeyboardButton(text="50 β to'lash", pay=True)) # MAJBURIY birinchi
kb.row(InlineKeyboardButton(text="Bekor qilish", callback_data="cancel"))
return kb.as_markup()
Va uni invoice'ga ulaymiz:
await message.answer_invoice(
title="Premium kontent",
description="Maxsus PDF.",
payload=PAYLOAD,
currency="XTR",
prices=[LabeledPrice(label="Premium PDF", amount=PREMIUM_PRICE)],
provider_token="",
reply_markup=pay_keyboard(),
)
pay=Truetugmasi vaas_markup()ni offline tekshirdim β birinchi tugmapay=True, ikkinchisicallback_data="cancel"bo'lib to'g'ri quriladi. Klaviatura quruvchilarni 6-bobda ko'rgan edik: klaviaturalar.
Keng tarqalgan xatolar (va to'g'risi)¶
# β ESKI (aiogram 2.x) β ISHLATMANG
@dp.pre_checkout_query_handler(lambda q: True)
async def pcq(query):
await bot.answer_pre_checkout_query(query.id, ok=True)
# β
TO'G'RI (aiogram 3.x)
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
await query.answer(ok=True)
# β XATO β Stars'da provider_token=None yoki real token
await bot.send_invoice(..., currency="XTR", provider_token=None)
await bot.send_invoice(..., currency="XTR", provider_token="real_token")
# β
TO'G'RI β Stars'da provider_token bo'sh string
await bot.send_invoice(..., currency="XTR", provider_token="")
# β XATO β mahsulotni pre_checkout'da topshirish (pul hali tushmagan!)
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
await query.answer(ok=True)
await give_product(query.from_user.id) # ERTA! pul hali yo'q
# β
TO'G'RI β mahsulotni faqat successful_payment'da
@router.message(F.successful_payment)
async def paid(message: Message):
await give_product(message.from_user.id) # pul tushdi
# β XATO β pre_checkout'ga sekin javob (10 soniyadan oshsa to'lov bekor)
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
await slow_external_api_call() # bu uzoq cho'zilsa to'lov bekor bo'ladi
await query.answer(ok=True)
# β
TO'G'RI β avval tez javob, og'ir ishni keyin (successful_payment'da)
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
await query.answer(ok=True) # darrov tasdiqlash
Eslatma: bu kitob Python biladi deb faraz qiladi.
async/await, dekorator, type hints kabilar yangi bo'lsa: Python bobiga qarang. Node.js'da botda to'lov qanday ko'rinishini solishtirish uchun: Node.js bobiga.
Mashqlar¶
Oson¶
send_invoicening majburiy oltita parametrini sanab bering va har biri nima uchun kerakligini bir jumlada yozing.- Telegram Stars uchun
currencyvaprovider_tokenqiymatlari qanday bo'lishi kerak? Provider to'lovi uchun-chi? successful_paymentxabaridan qaysi maydonni albatta saqlash kerak va nima uchun?- Quyidagi kodda xatoni toping va tuzating:
- To'lov oqimining uchta qadamini to'g'ri tartibda yozing va har birida qaysi handler ishlashini ayting.
pre_checkout_queryga javob berish uchun necha soniya bor? Javob bermasa nima bo'ladi?
O'rta¶
/buyhandlerini yozing: foydalanuvchiga100Stars'lik "Yillik obuna" invoice'sini yuborsin (payload="sub_year").pre_checkout_queryhandlerini yozing: faqatpayload == "sub_year"bo'lsaok=True, aks holdaok=Falseva xato xabari qaytarsin.successful_paymenthandlerini yozing: foydalanuvchiga "Obuna 1 yilga faollashtirildi" deb javob bersin vatotal_amountni xabarda ko'rsatsin.pay=Truetugmali inline klaviatura quruvchi funksiya yozing: birinchi tugma "100 β to'lash" (to'lov tugmasi), ikkinchisi "Yordam" (callback_data="help").create_invoice_linkishlatib,/donatebuyrug'iga javoban25Stars'lik donat havolasini yuboruvchi handler yozing.- SQLite jadval sxemasini yozing:
purchases(charge_id, user_id, payload, amount, currency)βcharge_idPRIMARY KEY bo'lsin. Nima uchun aynancharge_idni kalit qilamiz?
Qiyin¶
- To'liq to'lov botini yozing:
/buy-> invoice (50 β),pre_checkout-> validatsiya,successful_payment-> xaridniaiosqlitega idempotent yozish (takrorcharge_idqayta yozilmasin) va foydalanuvchiga javob. Keyinfeed_updatemock pattern bilan offline test yozib, uchala handler ishlashini tekshiring. /refund <charge_id>buyrug'ini yozing: argumentdancharge_idni ajratib oling (Commandfiltri yoki matnni parslash bilan) varefund_star_paymentchaqiring. Argument bo'lmasa, foydalanuvchiga to'g'ri ishlatishni ko'rsating. Xato holatlarni (try/except) ham qo'shing.- Bir nechta mahsulotli do'kon qiling:
CallbackDatafactory (prefix="buy", maydonproduct_id: int) bilan mahsulot tanlanadigan inline menyu, tanlangan mahsulot bo'yicha to'g'ri narx vapayloadbilan invoice yuborilsin.pre_checkoutda narxni mahsulot bazasiga solishtirib tekshiring.
Yechimlar
Oson¶
1. Majburiy oltita parametr:
chat_idβ invoice kimga yuborilishini bildiradi.titleβ mahsulot nomi, foydalanuvchi shuni ko'radi.descriptionβ mahsulot tavsifi.payloadβ botning ichki yorlig'i, pulni keyin shu orqali taniydi.currencyβ valyuta ("XTR"yoki ISO 4217 kodi).pricesβ narxlar ro'yxati (LabeledPrice).
2. Telegram Stars uchun: currency="XTR", provider_token="" (bo'sh string). Provider to'lovi uchun: currency valyuta kodi (masalan "USD"), provider_token BotFather'dan olingan haqiqiy token.
3. telegram_payment_charge_id ni saqlash kerak. Chunki pulni qaytarish (refund_star_payment) shu ID orqali bo'ladi va xaridni qayd qilishda takrorlanishni oldini olish uchun (idempotentlik) kalit sifatida ishlatiladi.
4. Xato: ok=False doim qaytarilmoqda β hech kim sotib ololmaydi. Bundan tashqari ok=False da error_message MAJBURIY. Tuzatish:
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
await query.answer(ok=True) # normal holatda tasdiqlash
(Agar haqiqatan rad etmoqchi bo'lsangiz: await query.answer(ok=False, error_message="Sabab...").)
5. Tartib:
send_invoice->@router.message(...)handleri (masalan/buy).pre_checkout_query->@router.pre_checkout_query()handleri.successful_payment->@router.message(F.successful_payment)handleri.
6. 10 soniya bor. Javob bermasangiz, Telegram to'lovni avtomatik bekor qiladi va foydalanuvchidan pul yechilmaydi.
O'rta¶
7.
@router.message(Command("buy"))
async def cmd_buy(message: Message, bot: Bot):
await bot.send_invoice(
chat_id=message.chat.id,
title="Yillik obuna",
description="Botning premium imkoniyatlari 1 yilga.",
payload="sub_year",
currency="XTR",
prices=[LabeledPrice(label="Yillik obuna", amount=100)],
provider_token="",
)
8.
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
if query.invoice_payload == "sub_year":
await query.answer(ok=True)
else:
await query.answer(ok=False, error_message="Bu mahsulot mavjud emas.")
9.
@router.message(F.successful_payment)
async def paid(message: Message):
sp = message.successful_payment
await message.answer(
f"Obuna 1 yilga faollashtirildi! To'langan: {sp.total_amount} {sp.currency}."
)
10.
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
def pay_kb():
kb = InlineKeyboardBuilder()
kb.row(InlineKeyboardButton(text="100 β to'lash", pay=True)) # birinchi - to'lov
kb.row(InlineKeyboardButton(text="Yordam", callback_data="help"))
return kb.as_markup()
11.
@router.message(Command("donate"))
async def cmd_donate(message: Message, bot: Bot):
link = await bot.create_invoice_link(
title="Donat",
description="Loyihaga qo'llab-quvvatlash.",
payload="donate_25",
currency="XTR",
prices=[LabeledPrice(label="Donat", amount=25)],
provider_token="",
)
await message.answer(f"Donat havolasi: {link}")
12.
CREATE TABLE IF NOT EXISTS purchases(
charge_id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
payload TEXT NOT NULL,
amount INTEGER NOT NULL,
currency TEXT NOT NULL
);
charge_id ni PRIMARY KEY qilamiz, chunki u har bir to'lov uchun noyob. Agar Telegram bir successful_payment ni qaytadan yuborsa (tarmoq nuqsoni, qayta urinish), INSERT IntegrityError beradi va biz mahsulotni ikki marta bermaymiz β bu idempotentlik.
Qiyin¶
13. To'liq bot + offline test:
# shop.py
import aiosqlite
from aiogram import Bot, F, Router
from aiogram.filters import Command
from aiogram.types import LabeledPrice, Message, PreCheckoutQuery
router = Router()
PRICE = 50
PAYLOAD = "premium_pdf_v1"
DB = "shop.db"
async def init_db():
async with aiosqlite.connect(DB) as db:
await db.execute("""
CREATE TABLE IF NOT EXISTS purchases(
charge_id TEXT PRIMARY KEY, user_id INTEGER NOT NULL,
payload TEXT NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL)
""")
await db.commit()
async def save_purchase(sp, user_id):
async with aiosqlite.connect(DB) as db:
try:
await db.execute("INSERT INTO purchases VALUES (?,?,?,?,?)",
(sp.telegram_payment_charge_id, user_id, sp.invoice_payload,
sp.total_amount, sp.currency))
await db.commit()
return True
except aiosqlite.IntegrityError:
return False
@router.message(Command("buy"))
async def cmd_buy(message: Message, bot: Bot):
await bot.send_invoice(
chat_id=message.chat.id, title="Premium", description="PDF qo'llanma.",
payload=PAYLOAD, currency="XTR",
prices=[LabeledPrice(label="Premium", amount=PRICE)], provider_token="")
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
if query.invoice_payload == PAYLOAD and query.total_amount == PRICE:
await query.answer(ok=True)
else:
await query.answer(ok=False, error_message="Mavjud emas.")
@router.message(F.successful_payment)
async def paid(message: Message):
sp = message.successful_payment
is_new = await save_purchase(sp, message.from_user.id)
if is_new:
await message.answer("Rahmat! Havola: https://example.com/premium.pdf")
else:
await message.answer("Bu to'lov allaqachon qayd etilgan.")
Offline test (tokensiz, men ishga tushirib tekshirgan pattern):
# test_shop.py
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import Chat, PreCheckoutQuery, SuccessfulPayment, Message, Update, User
from shop import router, init_db, PRICE, PAYLOAD
async def main():
await init_db()
bot = Bot(token="123456:AAH-FakeTest_abc",
default=DefaultBotProperties(parse_mode=ParseMode.HTML))
bot.session = AsyncMock()
dp = Dispatcher(storage=MemoryStorage())
dp.include_router(router)
chat = Chat(id=1, type="private")
user = User(id=2, is_bot=False, first_name="T")
msg = Message(message_id=1, date=datetime.now(), chat=chat, from_user=user, text="/buy")
await dp.feed_update(bot, Update(update_id=1, message=msg))
pcq = PreCheckoutQuery(id="p1", from_user=user, currency="XTR",
total_amount=PRICE, invoice_payload=PAYLOAD)
await dp.feed_update(bot, Update(update_id=2, pre_checkout_query=pcq))
sp = SuccessfulPayment(currency="XTR", total_amount=PRICE, invoice_payload=PAYLOAD,
telegram_payment_charge_id="ch1", provider_payment_charge_id="")
msg2 = Message(message_id=2, date=datetime.now(), chat=chat, from_user=user,
successful_payment=sp)
await dp.feed_update(bot, Update(update_id=3, message=msg2))
# idempotentlik: bir xil charge_id qayta -> ikkinchi marta yozilmaydi
await dp.feed_update(bot, Update(update_id=4, message=msg2))
await bot.session.close()
print("OK")
asyncio.run(main())
14.
@router.message(Command("refund"))
async def cmd_refund(message: Message, command, bot: Bot):
# Command filtri argumentni command.args ga beradi: "/refund ch1" -> "ch1"
charge_id = (command.args or "").strip()
if not charge_id:
await message.answer("Ishlatish: /refund <charge_id>")
return
try:
await bot.refund_star_payment(
user_id=message.from_user.id,
telegram_payment_charge_id=charge_id,
)
await message.answer("Stars qaytarildi.")
except Exception as e:
await message.answer(f"Qaytarib bo'lmadi: {e}")
Command filtri handlerga command argumentini beradi (CommandObject), uning .args maydoni buyruqdan keyingi matnni ushlaydi. Agar argument bo'sh bo'lsa, foydalanuvchiga ko'rsatma beramiz.
15. Ko'p mahsulotli do'kon:
from aiogram import Bot, F, Router
from aiogram.filters import Command
from aiogram.filters.callback_data import CallbackData
from aiogram.types import (
CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder
router = Router()
# Mahsulot bazasi: id -> (nom, narx Stars)
PRODUCTS = {1: ("PDF qo'llanma", 50), 2: ("Video kurs", 150), 3: ("Konsultatsiya", 300)}
class BuyCD(CallbackData, prefix="buy"):
product_id: int
@router.message(Command("shop"))
async def shop(message: Message):
kb = InlineKeyboardBuilder()
for pid, (name, price) in PRODUCTS.items():
kb.row(InlineKeyboardButton(
text=f"{name} β {price} β",
callback_data=BuyCD(product_id=pid).pack(),
))
await message.answer("Mahsulotni tanlang:", reply_markup=kb.as_markup())
@router.callback_query(BuyCD.filter())
async def choose(callback: CallbackQuery, callback_data: BuyCD, bot: Bot):
pid = callback_data.product_id
name, price = PRODUCTS[pid]
await bot.send_invoice(
chat_id=callback.from_user.id,
title=name, description=f"{name} xaridi.",
payload=f"prod_{pid}", currency="XTR",
prices=[LabeledPrice(label=name, amount=price)],
provider_token="",
)
await callback.answer()
@router.pre_checkout_query()
async def pcq(query: PreCheckoutQuery):
# payload: "prod_<id>" -> bazadagi narxga solishtiramiz
try:
pid = int(query.invoice_payload.removeprefix("prod_"))
_, price = PRODUCTS[pid]
except (ValueError, KeyError):
await query.answer(ok=False, error_message="Noto'g'ri mahsulot.")
return
if query.total_amount == price:
await query.answer(ok=True)
else:
await query.answer(ok=False, error_message="Narx mos kelmadi.")
@router.message(F.successful_payment)
async def paid(message: Message):
sp = message.successful_payment
pid = int(sp.invoice_payload.removeprefix("prod_"))
name, _ = PRODUCTS[pid]
await message.answer(f"Rahmat! '{name}' sotib olindi.")
CallbackData factory bilan tugmaga mahsulot id sini "joylab", pre_checkout da narxni bazaga solishtirib tekshiramiz β bu foydalanuvchi narxni o'zgartirib yuborolmasligi uchun muhim xavfsizlik qadami. CallbackData.pack()/unpack() ni 7-bobda (callback va inline) ko'rgan edik.
β¬ οΈ Oldingi: 13 β Webhook va aiohttp server Β· π README Β· Keyingi: 15 β Rejalashtirilgan vazifalar va broadcast β‘οΈ