Перейти к содержанию
Innovialabs

Как принимать Telegram Stars в боте или Mini App

Практика3 мин

Коротко

Бот выставляет инвойс в валюте XTR без платежного провайдера, подтверждает pre_checkout_query, а товар выдает только после successful_payment, сохраняя telegram_payment_charge_id для идемпотентности и возвратов через refundStarPayment.

Короткий ответ

Telegram Stars — внутренняя валюта Telegram, через которую боты и Mini App продают цифровые товары и услуги. Схема работы:

  1. Бот создает инвойс с валютой XTR. Платежный провайдер не нужен, поле provider_token остается пустым.
  2. Пользователь подтверждает оплату, бот получает pre_checkout_query и отвечает «можно» или «нельзя».
  3. После списания приходит сообщение с successful_payment. Только в этот момент вы выдаете товар.
  4. При необходимости вы делаете возврат методом 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 инвойс открывается без отправки сообщения в чат:

  1. Сервер вызывает createInvoiceLink с теми же параметрами (currency="XTR", одна цена, payload с ID заказа) и отдает ссылку фронтенду.
  2. Фронтенд вызывает Telegram.WebApp.openInvoice(url, callback).
  3. 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.

Частые вопросы

Stars предназначены для цифровых товаров и услуг. Для доставки, офлайн-услуг и физических товаров подключают обычного платежного провайдера.

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.