EvolCode
треки/Телеграм-боты·01 / 06
14 мин чтения

Принимаем платежи прямо в боте

Раньше чтобы продать что-то через бота, надо было редиректить пользователя на сайт с формой оплаты. Это убивало конверсию. Теперь Telegram Payments: пользователь нажимает кнопку «Оплатить», вводит карту прямо в Telegram (один раз — потом сохраняется), и через 5 секунд деньги у тебя на счету. Конверсия как в App Store. Разберём как это работает технически и какие есть ловушки.

Как устроены платежи в Telegram

Telegram сам платежи НЕ обрабатывает. Он берёт на себя UX (форма ввода карты, 3D Secure, чек), а реальные деньги идут через провайдеров: Stripe, ЮKassa, Tinkoff, Sberbank, Paymaster. У каждого провайдера свой токен, который ты получаешь после регистрации у них и привязываешь к боту через @BotFather. Поток: бот шлёт invoice → юзер нажимает «Заплатить» → Telegram показывает форму → юзер вводит карту → Telegram шлёт боту pre_checkout_query (последний шанс отказать) → если ОК → Telegram проводит платёж через провайдера → присылает боту successful_payment.

Регистрация провайдера

Идёшь к @BotFather, /mybots → твой бот → Payments. Там список провайдеров. Выбираешь например Stripe (для глобального) или ЮKassa (для России). Тебя редиректит на сайт провайдера, регистрируешь юрлицо/самозанятого, получаешь Provider Token — длинная строка вида 1234567:TEST:abcdef. TEST — это тестовый режим, для разработки. Для прода нужен LIVE-токен (после прохождения KYC). Этот токен бот использует при отправке invoice.

Цифровые товары и Telegram Stars

Для цифровых товаров (подписки, разблокировка фич, токены в игре) Apple/Google требуют использовать ИХ платежи на iOS/Android. Чтобы не нарушать правила, Telegram сделал Telegram Stars — внутренняя валюта где Apple/Google уже получили свою долю. Для цифровых товаров используй ТОЛЬКО Stars. Для физических товаров (доставка, услуги) — обычные провайдеры (Stripe и др). Если торгуешь цифрой через Stripe в боте — Apple может забанить пользователя.

pre_checkout_query — последний шанс отказать

Перед фактическим списанием Telegram шлёт боту pre_checkout_query. У тебя 10 секунд чтобы ответить ok=true или ok=false с причиной. Здесь делаешь финальные проверки: товар ещё в наличии? Юзер не в бане? Цена не изменилась? Если ok=false → платёж отменяется, юзеру показывается твоя причина. Если не ответить за 10 секунд — Telegram отменит транзакцию автоматически. Это критично! Никогда не оставляй pre_checkout без обработчика.

Идемпотентность и подписки

Самое важное в платежах — не давать товар дважды. Каждый успешный платёж имеет уникальный telegram_payment_charge_id — записывай его в БД с уникальным constraint. На successful_payment ищешь этот id — если уже есть, ничего не делаешь (уже выдали). Для подписок: Telegram Stars поддерживают recurring (подписку с автосписанием), у внешних провайдеров надо самому управлять — после оплаты ставишь флаг «активна до 2026-05-01», cron каждый день проверяет истечения и просит оплатить снова.

примерtypescript
import { Bot, InlineKeyboard } from 'grammy';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
const bot = new Bot(process.env.BOT_TOKEN!);
const PROVIDER_TOKEN = process.env.PROVIDER_TOKEN!; // от @BotFather

// 1. Юзер запросил оплату
bot.command('buy_pro', async (ctx) => {
  await ctx.replyWithInvoice(
    'Pro подписка',                       // title
    'Безлимит на месяц',                  // description
    `pro_month:${ctx.from!.id}`,        // payload — твой ID транзакции
    'XTR',                                // currency: XTR = Telegram Stars,
                                          // или 'USD', 'RUB' для обычных провайдеров
    [{ label: 'Pro 1 месяц', amount: 100 }] // amount в минимальных единицах:
                                          // для USD это центы, для Stars — штуки
    // если провайдер обычный (не Stars), второй аргумент — provider_token:
    // { provider_token: PROVIDER_TOKEN }
  );
});

// 2. Pre-checkout — последний шанс отказать
bot.on('pre_checkout_query', async (ctx) => {
  // Здесь финальные проверки
  const userId = ctx.from!.id;
  const user = await prisma.user.findUnique({ where: { id: BigInt(userId) } });

  if (user?.banned) {
    return ctx.answerPreCheckoutQuery(false, 'Аккаунт заблокирован');
  }
  // Всё ок — подтверждаем
  return ctx.answerPreCheckoutQuery(true);
});

// 3. Успешный платёж — выдаём товар, идемпотентно
bot.on(':successful_payment', async (ctx) => {
  const payment = ctx.message?.successful_payment;
  if (!payment) return;

  const chargeId = payment.telegram_payment_charge_id;

  // Идемпотентность — проверяем не выдавали ли уже
  const existing = await prisma.payment.findUnique({
    where: { chargeId },
  });
  if (existing) return; // уже обработали

  // Записываем платёж
  await prisma.payment.create({
    data: {
      chargeId,
      userId: BigInt(ctx.from!.id),
      amount: payment.total_amount,
      currency: payment.currency,
      payload: payment.invoice_payload,
    },
  });

  // Активируем Pro на месяц
  const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
  await prisma.user.update({
    where: { id: BigInt(ctx.from!.id) },
    data: { plan: 'pro', planExpiresAt: expiresAt },
  });

  await ctx.reply('🎉 Pro активирован на 30 дней!');
});

bot.start();

Продажа доступа к Pro-плану через Telegram Payments

главное
  • 01Telegram только UX и UI — реальные деньги идут через провайдера (Stripe, ЮKassa, Telegram Stars).
  • 02Цифровые товары — ТОЛЬКО Telegram Stars (currency XTR). Apple/Google требуют этого на мобильных.
  • 03pre_checkout_query обязательно обрабатывать. Не ответил за 10 секунд → платёж отменён.
  • 04telegram_payment_charge_id — уникальный ID. Используй для идемпотентности — не давай товар дважды.
  • 05Подписки Stars — recurring встроен. Внешние провайдеры — управляешь истечением сам через cron.
проверь себя
01Чем отличается currency XTR от RUB или USD?
02Что произойдёт если не ответить на pre_checkout_query за 10 секунд?
03Почему telegram_payment_charge_id критически важен?
следующий урок: Бот с подключённым Claude — без типичных ошибок