Как подключить оплату Telegram Stars в бота на aiogram 3: пошаговая инструкция с кодом
Приём оплаты звёздами Telegram в боте на aiogram 3: счёт, pre_checkout_query, успешный платёж, возвраты, ссылка на оплату и вывод звёзд. Полный рабочий код с пояснениями.
Telegram Stars — встроенная валюта Telegram. Пользователь покупает звёзды прямо в приложении, а потом тратит их в ботах в два касания, без ввода карты и перехода на сторонние сайты. Для владельца бота это самый простой способ принимать оплату: не нужен платёжный провайдер, договор и токен оплаты — всё работает через обычный Bot API.
По правилам Telegram цифровые товары и услуги в ботах — доступы, подписки, файлы, внутриигровые предметы — нужно продавать именно за Stars. Ниже — рабочий бот на aiogram 3, который выставляет счёт, принимает оплату, выдаёт товар и умеет делать возвраты.
Как проходит оплата звёздами
- Бот отправляет пользователю счёт (invoice) с валютой XTR — так в Bot API обозначаются звёзды.
- Пользователь нажимает «Оплатить», и 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, которому разрешены возвраты.
Полный код бота
Сохраните код в файл 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 (раздел с балансом звёзд) — вывод идёт через Fragment в TON. Звёзды становятся доступны к выводу не сразу, а после периода удержания; актуальные сроки и минимальную сумму смотрите в самом Telegram — условия периодически меняются.
Частые ошибки
- Бот выдаёт товар в pre_checkout_query. Оплата ещё не прошла — переносите выдачу в successful_payment.
- Обработчик pre_checkout_query не отвечает или работает дольше 10 секунд. Оплата отменяется с ошибкой у пользователя. Не делайте здесь долгих запросов.
- Несколько позиций в prices. Для XTR допускается только одна.
- Нет записи charge_id. Без него невозможно сделать возврат.
- Токен в коде. Храните его в .env и не публикуйте в репозитории.
Приём оплаты другими способами — криптовалютой через CryptoBot или xRocket — разобран в отдельных инструкциях, а обзор всех вариантов — в статье «Как принимать оплату в Telegram-боте».
Частые вопросы
Нужен ли provider_token для оплаты звёздами?
Нет. Для валюты XTR платёжный провайдер не нужен, параметр provider_token не передаётся.
Как протестировать оплату Stars, не тратя деньги?
Купите немного звёзд, поставьте цену 1 ⭐, оплатите и сделайте возврат командой /refund — звёзды вернутся на ваш баланс.
Можно ли принимать звёзды за физические товары?
Звёзды предназначены для цифровых товаров и услуг. Для физических товаров используйте платёжных провайдеров или другие способы оплаты.
Как вывести звёзды из бота?
Через баланс звёзд в профиле бота — вывод идёт через Fragment в TON после периода удержания. Актуальные условия смотрите в Telegram.
Почему оплата отменяется сразу после нажатия «Оплатить»?
Бот не ответил на pre_checkout_query за 10 секунд или ответил ok=False. Проверьте, что обработчик подключён и работает быстро.
Готовые решения по теме
БОТ⭐️ Auto Buy Stars WEB APP
Бот для покупки Stars/Premium/Пополнением Steam в автоматическом режиме с Fragment Установка производится покупателем
БОТ📲 AI монтаж видео
Мощный комбайн для AI монтажа видео из 6 агентов, работает с Claude
БОТ✍🏻 Автокомментинг ИИ 2.0
ИИ Автокомментинг новой версии с поддержкой мультиаккаунтов
БОТ