Skip to content

Stars

@vibegram/stars provides production-oriented helpers for Telegram Stars payments: invoice building, pre-checkout approval/decline, successful payment validation, refunds, gift/business workflows, and paid-update test fixtures.

Use it when a bot sells digital goods, paid media, premium access, gifts, or Mini App features through Telegram Stars.

Official Telegram Mapping

This plugin wraps Telegram Bot API payment behavior without changing it.

Stars invoices are regular Telegram invoices with currency: "XTR", an empty provider_token, and exactly one prices item. The plugin validates these rules before the request reaches Telegram.

Pre-checkout updates arrive as Update.pre_checkout_query and must be answered with answerPreCheckoutQuery. Completed payments arrive as message.successful_payment. Refunds are performed through refundStarPayment. Paid-media purchases arrive as purchased_paid_media.

References: Payments, sendInvoice, createInvoiceLink, answerPreCheckoutQuery, PreCheckoutQuery, SuccessfulPayment, refundStarPayment, sendPaidMedia, and BusinessConnection.

Install

When this official plugin package is published, install it from npm:

bash
npm install vibegram @vibegram/stars

Today, use the repository package as a local file dependency:

json
{
  "dependencies": {
    "vibegram": "^2.1.0",
    "@vibegram/stars": "file:../vibegram/plugins/stars"
  }
}

Setup

typescript
import { Bot } from 'vibegram';
import { stars } from '@vibegram/stars';

const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);

bot.use(stars());

The middleware adds request-scoped ctx.stars helpers and restores any previous value after the handler finishes.

Stars Invoice

typescript
bot.command('buy', ctx => {
    return ctx.stars.invoice({
        title: 'Premium access',
        description: '30 days of premium access',
        payload: `premium:${ctx.from?.id}`,
        amount: 100,
        label: 'Premium',
    });
});

ctx.stars.invoice() sends sendInvoice to the current chat with this payload:

typescript
{
    provider_token: '',
    currency: 'XTR',
    prices: [{ label: 'Premium', amount: 100 }]
}

For subscriptions, pass Telegram's supported 30-day period:

typescript
await ctx.stars.invoice({
    title: 'Premium subscription',
    description: 'Recurring 30-day premium access',
    payload: `sub:${ctx.from?.id}`,
    amount: 500,
    subscriptionPeriod: 2592000,
});
typescript
const link = await ctx.stars.createInvoiceLink({
    title: 'Premium access',
    description: '30 days of premium access',
    payload: 'premium:42',
    amount: 100,
});

Store the payload in your own order table before sending the link.

Pre-Checkout Guard

typescript
bot.on('pre_checkout_query', async ctx => {
    try {
        await ctx.stars.approvePreCheckout({
            payloadPrefix: 'premium:',
            totalAmount: 100,
        });
    } catch {
        await ctx.stars.declinePreCheckout('This order is no longer available.');
    }
});

Validate:

  • currency is XTR
  • total_amount matches the server-side order
  • invoice_payload exists in your database
  • the order is still available and not already fulfilled

Telegram expects the pre-checkout answer quickly. Keep this handler lean.

Successful Payment

typescript
bot.on('message:successful_payment', async ctx => {
    const payment = ctx.stars.requireSuccessfulPayment({
        payloadPrefix: 'premium:',
    });

    await activatePremium({
        payload: payment.invoice_payload,
        chargeId: payment.telegram_payment_charge_id,
        amount: payment.total_amount,
    });

    await ctx.reply('Premium activated.');
});

Fulfill only after successful_payment, not after sending the invoice or approving pre-checkout.

Refunds

typescript
await ctx.stars.refund(ctx.from!.id, 'telegram-payment-charge-id');

When handling a successful-payment update:

typescript
await ctx.stars.refundSuccessfulPayment();

Record refund status in your database so repeated commands do not attempt the same refund repeatedly.

typescript
await ctx.stars.paidMedia({
    starCount: 25,
    payload: `gallery:${ctx.from?.id}`,
    media: [
        { type: 'photo', media: 'https://example.com/photo.jpg' },
    ],
    caption: 'Premium gallery',
});

Paid-media purchase updates can be tested with fixtures:

typescript
import { createPaidMediaPurchasedUpdate } from '@vibegram/stars';

const update = createPaidMediaPurchasedUpdate({
    userId: 42,
    paidMediaPayload: 'gallery:42',
});

Gifts And Business Workflows

typescript
await ctx.stars.gifts.send(ctx.from!.id, 'gift-id', {
    text: 'Thanks for supporting us',
});

await ctx.stars.gifts.premium(ctx.from!.id, 3, 1000, {
    text: 'Premium gift',
});

Business account helpers:

typescript
await ctx.stars.business.getStarBalance('business-connection-id');
await ctx.stars.business.transferStars('business-connection-id', 250);
await ctx.stars.business.convertGiftToStars('business-connection-id', 'owned-gift-id');
await ctx.stars.business.upgradeGift('business-connection-id', 'owned-gift-id', {
    star_count: 0,
});
await ctx.stars.business.transferGift('business-connection-id', 'owned-gift-id', 123456789);

These methods require the matching Telegram business bot rights.

Test Fixtures

typescript
import {
    createPreCheckoutQueryUpdate,
    createSuccessfulPaymentUpdate,
} from '@vibegram/stars';

const preCheckout = createPreCheckoutQueryUpdate({
    payload: 'premium:42',
    totalAmount: 100,
});

const successful = createSuccessfulPaymentUpdate({
    payload: 'premium:42',
    totalAmount: 100,
    telegramPaymentChargeId: 'charge-1',
});

Options And API

Exports include stars(), buildStarsInvoice(), createPreCheckoutQueryUpdate(), createSuccessfulPaymentUpdate(), createPaidMediaPurchasedUpdate(), StarsPluginError, StarsFlavor, and related TypeScript types.

buildStarsInvoice(options) accepts:

OptionTypeDescription
titlestringProduct name, max 32 characters
descriptionstringProduct description, max 255 characters; defaults to title
payloadstringServer-side order payload, max 128 bytes
amountnumberStars amount when using one generated price
labelstringPrice label, defaults to Stars
pricesLabeledPrice[]Explicit price list; Stars requires exactly one item
subscriptionPeriod2592000Optional 30-day subscription period
extraobjectExtra Telegram invoice fields

Security And Reconciliation

  • Build invoices on the server.
  • Treat payload as an opaque order ID, not as trusted user state.
  • Persist the order before sending an invoice.
  • Validate pre-checkout from your database.
  • Fulfill only after successful_payment.
  • Make fulfillment idempotent.
  • Store telegram_payment_charge_id and refund state.
  • Use @vibegram/throttler for broadcast-style paid offers and avoid blindly retrying sendInvoice.

Validation

The plugin is covered by tests for Stars invoice payloads, pre-checkout handling, successful payment validation, refund helpers, gift/business helpers, and paid-media fixtures.

bash
npm run plugins:validate
npm run docs:build

Released under the ISC License.