Создашь Stars-подписку на 30 дней через createInvoiceLink с subscription_period и отменишь продление через editUserStarSubscription.
Подписки за Stars
С Bot API 8.0 бот может выставлять рекуррентный инвойс в Stars: пользователь платит раз в период, Telegram списывает Stars автоматически при продлении. Сейчас единственный допустимый период — 2592000 секунд (30 дней). Цена подписки — не больше 10000 Stars.
Подписку создают через createInvoiceLink с полем subscription_period (не через обычный sendInvoice для media в чат — для bot subscription используй invoice link / deep link).
Создаём и обслуживаем подписку
typescript
// Telegram Bot API 8.0+ (подписки Stars)const BOT_TOKEN = process.env.BOT_TOKEN!;const API = `https://api.telegram.org/bot${BOT_TOKEN}`;const PERIOD_30_DAYS = 2_592_000; // единственное допустимое значениеasync function call<T>(method: string, body: Record<string, unknown>): Promise<T> { const res = await fetch(`${API}/${method}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const json = (await res.json()) as { ok: boolean; result: T; description?: string }; if (!json.ok) throw new Error(`${method}: ${json.description}`); return json.result;}/** Ссылка на подписку Pro (100 Stars / 30 дней) */export async function createProSubscriptionLink(userId: number): Promise<string> { return call<string>("createInvoiceLink", { title: "Pro подписка", description: "Доступ к Pro на 30 дней с автопродлением", payload: `sub:pro:${userId}`, provider_token: "", currency: "XTR", prices: [{ label: "Pro / 30 дней", amount: 100 }], subscription_period: PERIOD_30_DAYS, });}type SuccessfulPayment = { currency: string; total_amount: number; invoice_payload: string; telegram_payment_charge_id: string; is_recurring?: true; is_first_recurring?: true; subscription_expiration_date?: number; // unix seconds};export async function onSubscriptionPayment( userId: number, payment: SuccessfulPayment,) { if (payment.currency !== "XTR") return; await upsertSubscription({ userId, plan: "pro", chargeId: payment.telegram_payment_charge_id, expiresAt: payment.subscription_expiration_date ? new Date(payment.subscription_expiration_date * 1000) : null, isRecurring: Boolean(payment.is_recurring), isFirst: Boolean(payment.is_first_recurring), }); // is_first_recurring — первая оплата; последующие продления тоже приходят // как successful_payment с is_recurring await enableProFeatures(userId);}/** Отменить автопродление (доступ остаётся до конца оплаченного периода) */export async function cancelSubscriptionRenewal( userId: number, telegramPaymentChargeId: string,) { return call<boolean>("editUserStarSubscription", { user_id: userId, telegram_payment_charge_id: telegramPaymentChargeId, is_canceled: true, });}/** Разрешить пользователю снова включить продление */export async function allowSubscriptionRenewal( userId: number, telegramPaymentChargeId: string,) { return call<boolean>("editUserStarSubscription", { user_id: userId, telegram_payment_charge_id: telegramPaymentChargeId, is_canceled: false, });}declare function upsertSubscription(row: { userId: number; plan: string; chargeId: string; expiresAt: Date | null; isRecurring: boolean; isFirst: boolean;}): Promise<void>;declare function enableProFeatures(userId: number): Promise<void>;
Для платного канала отдельно существует createChatSubscriptionInviteLink (subscription_period: 2592000, subscription_price 1–10000) — это доступ к каналу, не bot-feature subscription. Не смешивай модели в одной таблице без поля kind.
Как это работает
subscription_period: 2592000 — включает режим подписки; валюта обязана быть XTR.
У одного пользователя может быть несколько активных подписок на одного бота одновременно.
В successful_payment смотри is_recurring, is_first_recurring, subscription_expiration_date.
editUserStarSubscription(is_canceled: true) — останавливает продление, не «мгновенный бан»; до subscription_expiration_date доступ обычно сохраняется (логика продукта — на тебе).
Pre-checkout для подписок тот же: отвечай ≤10 с и валидируй payload/сумму.
Частые ошибки
Ставишь period ≠ 2592000 → Bot API отклонит запрос. Других периодов пока нет.
Ждёшь списания как on-chain invoice в Gram → продление списывает Stars с баланса пользователя в Telegram, это не Gram-транзакция и не TON Connect.
Хранить только первый charge_id и игнорировать продления → каждое успешное продление нужно учитывать (срок, статус, поддержка /paysupport).
Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация