Как принимать Telegram Stars в боте или Mini App
Практика3 минРедакция Innovialabs
Коротко
Бот выставляет инвойс в валюте XTR без платежного провайдера, подтверждает pre_checkout_query, а товар выдает только после successful_payment, сохраняя telegram_payment_charge_id для идемпотентности и возвратов через refundStarPayment.
Короткий ответ
Telegram Stars — внутренняя валюта Telegram, через которую боты и Mini App продают цифровые товары и услуги. Схема работы:
- Бот создает инвойс с валютой
XTR. Платежный провайдер не нужен, полеprovider_tokenостается пустым. - Пользователь подтверждает оплату, бот получает
pre_checkout_queryи отвечает «можно» или «нельзя». - После списания приходит сообщение с
successful_payment. Только в этот момент вы выдаете товар. - При необходимости вы делаете возврат методом
refundStarPayment.
Где Stars обязательны
По правилам Telegram цифровые товары и услуги, которые потребляются внутри Telegram, продаются за Stars: подписки на контент, премиум-функции бота, внутриигровые предметы, доступ к материалам. Физические товары и офлайн-услуги по-прежнему оплачиваются через обычных платежных провайдеров.
Инвойс в боте
Цена задается одной позицией: для XTR в prices передается ровно один элемент, а сумма указывается в целых звездах.
from aiogram import Bot, F, Router
from aiogram.types import LabeledPrice, Message, PreCheckoutQuery
router = Router()
@router.message(F.text == "/premium")
async def sell(message: Message):
order_id = await create_order(message.from_user.id, product="premium_30d")
await message.answer_invoice(
title="Премиум на 30 дней",
description="Расширенные функции бота",
payload=f"order:{order_id}",
currency="XTR",
prices=[LabeledPrice(label="Премиум", amount=100)],
)
@router.pre_checkout_query()
async def pre_checkout(query: PreCheckoutQuery):
ok = await order_is_open(query.invoice_payload)
await query.answer(ok=ok, error_message=None if ok else "Заказ уже недоступен")
@router.message(F.successful_payment)
async def paid(message: Message):
sp = message.successful_payment
await deliver_once(
payload=sp.invoice_payload,
charge_id=sp.telegram_payment_charge_id,
user_id=message.from_user.id,
)
Оплата в Mini App
В Mini App инвойс открывается без отправки сообщения в чат:
- Сервер вызывает
createInvoiceLinkс теми же параметрами (currency="XTR", одна цена,payloadс ID заказа) и отдает ссылку фронтенду. - Фронтенд вызывает
Telegram.WebApp.openInvoice(url, callback). - Callback получает статус:
paid,cancelled,failedилиpending.
Статус в callback нужен только для интерфейса: показать экран «Спасибо» или вернуть кнопку. Выдавать товар по нему нельзя. Источник истины — обновление successful_payment, которое бот получает на сервере. Mini App после оплаты просто запрашивает у сервера актуальный статус заказа.
Надежная выдача товара
Самая частая проблема — товар выдан дважды или не выдан вовсе. Чтобы этого не было:
- Создавайте заказ до инвойса и кладите его ID в
payload. Не храните вpayloadцену или состав, их легко перепутать. - Сохраняйте
telegram_payment_charge_idс уникальным индексом. Повторное обновление с тем же ID просто игнорируется. - Выдавайте товар в транзакции: отметка «оплачено» и начисление доступа должны пройти вместе или не пройти вовсе.
- Проверяйте в
pre_checkout_queryвсе, что может сорвать выдачу: заказ не оплачен ранее, товар есть, пользователь не заблокирован. Отвечайте быстро, иначе платеж отменится. - Выдачу делайте повторяемой: если отправка сообщения с товаром упала, задача должна перезапуститься, не создавая новый платеж.
Возвраты
Метод refundStarPayment принимает user_id и telegram_payment_charge_id и возвращает звезды пользователю. Практические правила:
- Перед возвратом отзовите выданный доступ и пометьте заказ как возвращенный.
- Повторный возврат по тому же платежу невозможен, поэтому проверяйте статус заказа у себя.
- Опишите правила возврата в боте и дайте понятную команду для вопросов по оплате. Telegram ожидает, что бот, принимающий платежи, отвечает на обращения пользователей по ним (для этого обычно делают команду
/paysupport).
Для сверки используйте getStarTransactions: метод возвращает историю входящих и исходящих операций бота, и по ней удобно находить расхождения с вашей базой.
Частые ошибки
- Несколько позиций в
pricesдляXTR: Telegram вернет ошибку. - Выдача товара в обработчике
pre_checkout_queryили по callback Mini App. - Отсутствие уникальности по
telegram_payment_charge_id. - Тесты только на боевом окружении. Для проверки сценариев используйте тестовый сервер Telegram.
Подробности — в документации Telegram о платежах за Stars.