如何在 aiogram 3 的机器人中开通 Telegram Stars 支付功能:附代码的分步指南
在 aiogram 3 机器人中通过 Telegram 星币进行支付:账单、pre_checkout_query、支付成功、退款、支付链接及星币提现。附带说明的完整可运行代码。
Telegram Stars 是 Telegram 的内置虚拟货币。用户可直接在应用内购买星星,随后只需轻点两下即可在机器人中使用,无需输入银行卡信息,也无需跳转至第三方网站。 对于机器人所有者而言,这是最简便的收款方式:无需支付服务商、合同或支付令牌——一切均通过常规的 Bot API 完成。
根据 Telegram 的规定,机器人中的数字商品和服务(如访问权限、订阅、文件、游戏内道具)必须使用 Stars 进行销售。 以下是一个基于 aiogram 3 的可运行机器人,它能够开具账单、接收付款、交付商品并支持退款。
如何使用Stars进行支付
- 机器人向用户发送以XTR为货币单位的账单(invoice)——这是Bot API中对Stars的标识。
- 用户点击“支付”,Telegram会向机器人发送pre_checkout_query——这是扣款前的最后一次请求。机器人必须在10秒内对此作出响应:确认或拒绝。
- 确认后,Telegram 会扣除星币并发送包含 successful_payment 的消息。只有此时,付款才被视为完成——商品也将在此时交付。
所需条件
- 通过 @BotFather 创建的机器人及其令牌。无需在 BotFather 中单独为星币启用支付功能。
- Python 3.10 或更高版本。
- aiogram 3 — 通过一条命令即可安装:
pip install -U aiogram请勿将机器人令牌写入代码中——应通过环境变量或.env文件传递,该文件不会被提交到代码库。在下面的示例中,机器人会读取 BOT_TOKEN 和 ADMIN_ID——即您在 Telegram 上的 ID,该 ID 被授权接收退款。
机器人完整代码
将代码保存到 bot.py 文件中:
import asyncio
import logging
import os
from aiogram import Bot, Dispatcher, F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import Command, CommandObject, CommandStart
from aiogram.types import LabeledPrice, Message, PreCheckoutQuery
ADMIN_ID = int(os.environ["ADMIN_ID"]) # ваш Telegram ID — только ему доступен /refund
# Товар: название, описание, цена в звёздах (целое число) и метка заказа
PRODUCT_TITLE = "Доступ к курсу"
PRODUCT_DESCRIPTION = "Полный доступ ко всем урокам курса навсегда"
PRODUCT_PAYLOAD = "course_full"
PRICE_STARS = 50
router = Router()
@router.message(CommandStart())
async def start(message: Message) -> None:
await message.answer(f"Привет! Курс стоит {PRICE_STARS} ⭐. Купить: /buy")
@router.message(Command("buy"))
async def buy(message: Message) -> None:
await message.answer_invoice(
title=PRODUCT_TITLE,
description=PRODUCT_DESCRIPTION,
payload=PRODUCT_PAYLOAD, # вернётся в pre_checkout_query и successful_payment
currency="XTR", # XTR — Telegram Stars, provider_token не нужен
prices=[LabeledPrice(label=PRODUCT_TITLE, amount=PRICE_STARS)],
)
@router.pre_checkout_query()
async def pre_checkout(query: PreCheckoutQuery) -> None:
# Последняя проверка перед списанием: товар существует и в наличии.
# Ответить нужно за 10 секунд, иначе Telegram отменит оплату.
if query.invoice_payload != PRODUCT_PAYLOAD:
await query.answer(ok=False, error_message="Товар не найден. Создайте счёт заново: /buy")
return
await query.answer(ok=True)
@router.message(F.successful_payment)
async def paid(message: Message) -> None:
payment = message.successful_payment
if payment.invoice_payload != PRODUCT_PAYLOAD or payment.total_amount != PRICE_STARS:
logging.warning("Неожиданный платёж: %s", payment.invoice_payload)
return
# charge_id сохраните в базе вместе с заказом — без него не сделать возврат
logging.info("Оплата %s ⭐ от %s, charge_id=%s",
payment.total_amount, message.from_user.id, payment.telegram_payment_charge_id)
# Здесь выдайте товар: откройте доступ, отправьте файл, начислите баланс
await message.answer("Спасибо за покупку! Доступ к курсу открыт ✅")
@router.message(Command("refund"), F.from_user.id == ADMIN_ID)
async def refund(message: Message, command: CommandObject, bot: Bot) -> None:
# /refund <user_id> <charge_id> — вернуть звёзды покупателю
args = (command.args or "").split()
if len(args) != 2 or not args[0].isdigit():
await message.answer("Формат: /refund <user_id> <charge_id>")
return
try:
await bot.refund_star_payment(user_id=int(args[0]), telegram_payment_charge_id=args[1])
except TelegramBadRequest as e:
await message.answer(f"Возврат не прошёл: {e.message}")
return
await message.answer("Звёзды возвращены покупателю")
@router.message(Command("paysupport"))
async def pay_support(message: Message) -> None:
await message.answer("Вопросы по оплате: напишите @your_support — ответим в течение дня.")
async def main() -> None:
logging.basicConfig(level=logging.INFO)
bot = Bot(token=os.environ["BOT_TOKEN"])
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())在 Linux 或 macOS 上运行:
export BOT_TOKEN="токен_от_BotFather"
export ADMIN_ID="ваш_telegram_id"
python bot.py向机器人发送 /buy — 系统将发送一张附有“支付 50 ⭐”按钮的账单。
代码解析
账单:answer_invoice
- currency="XTR" — 使用星币支付。对于星币,不传递 provider_token 参数。
- prices — 商品列表。对于 Stars,该列表中必须恰好包含一项商品,且 amount 必须是整数个星(不包含分或美分)。
- payload — 您的内部订单标识符。 用户无法看到该标记,Telegram会在支付完成后返回该标记——您可通过它了解具体支付了什么。在本商店中,此处填写的是数据库中的订单号。
pre_checkout_query
此处用于验证商品是否仍可销售:是否已售罄、价格是否发生变化、订单是否已被提前支付。如果出现任何异常,请返回 ok=False 并附上清晰的错误信息,Telegram 会将其显示给用户。
successful_payment
主要处理流程:星币已扣除,需要发货。必须:
- 核对 payload 和金额 是否与数据库中的订单一致;
- 保存 telegram_payment_charge_id — 这是支付编号,用于退款和处理纠纷;
- 仅发放一次商品 — 在数据库中将订单标记为已支付。
退还星星
refund_star_payment 方法会将星星全额退还给买家。在示例中,/refund 命令仅对管理员可用——过滤条件为 F.from_user.id == ADMIN_ID。请勿允许用户自行办理退款:否则可能会出现先收到商品又立即退款的情况。
退款后,请务必收回已发放的商品:在数据库中关闭访问权限或取消购买。
使用支付链接代替消息
如果需要将账单发布在频道、按钮或迷你应用(Web App)中,请创建支付链接:
link = await bot.create_invoice_link(
title="Доступ к курсу",
description="Полный доступ ко всем урокам курса",
payload="course_full",
currency="XTR",
prices=[LabeledPrice(label="Доступ к курсу", amount=50)],
)通过此链接,支付流程同样如下:机器人会收到 pre_checkout_query 和 successful_payment 消息。
/paysupport 命令
Telegram 要求负责收款的机器人必须提供清晰的支付支持说明——为此需要使用 /paysupport 命令。请通过 @BotFather → Edit Bot → Edit Commands 将其与 /buy 命令一起添加到菜单中。
如何提取赚取的星星
星星会累积在机器人的余额中。您可以在 Telegram 中的机器人个人资料(星星余额部分)查看并提取它们——提取通过 TON 中的 Fragment 进行。 星星并非立即可提现,而需经过一段锁定期;具体时限和最低提现金额请查看Telegram应用内——相关条款会不定期调整。
常见错误
- 机器人已在 pre_checkout_query 阶段交付商品。 支付尚未完成——请将交付操作移至 successful_payment 阶段。
- pre_checkout_query 处理程序无响应或运行时间超过 10 秒。 这将导致用户端出现错误并取消支付。请勿在此处执行耗时过长的请求。
- prices 中包含多个商品项。 XTR 仅允许一个商品项。
- 缺少 charge_id 字段。 缺少该字段将无法办理退款。
- 代码中包含令牌。 请将其保存在 .env 文件中,切勿发布到代码仓库。
其他支付方式——通过 CryptoBot 接收加密货币,或 xRocket——相关操作已在单独的指南中详细说明,所有支付方式的概述请参阅文章《如何在 Telegram 机器人中接收付款》。
常见问题
使用Stars支付是否需要provider_token?
不需要。对于XTR货币,无需支付服务商,因此不传递provider_token参数。
如何在不花钱的情况下测试Stars支付?
购买少量星星,将价格设为 1 ⭐,完成支付后使用 /refund 命令申请退款——星星将返还至您的余额中。
能否用星星支付实物商品?
星星专用于数字商品和服务。 若需销售实物商品,请使用支付服务商或其他支付方式。
如何从机器人中提取星星?
通过机器人个人资料中的星星余额进行操作——在锁定期结束后,将通过 TON 中的 Fragment 进行提现。 具体条款请参阅 Telegram。
为什么点击“支付”后交易立即被取消?
机器人未在 10 秒内响应 pre_checkout_query,或返回了 ok=False。请检查处理程序是否已连接且运行迅速。



