June 25

How to integrate Telegram Stars Payment to your bot

Telegram Stars is a new in-app currency introduced by Telegram for purchasing digital goods and services. Users can buy Stars directly within Telegram using Apple Pay or Google Pay. This article is a step-by-step guide on how to connect Stars payments to your bot, as well as GitHub example. While the code will be in Node.js, I'll show you the general ideas for accepting Stars payments so you can apply them to any language and framework.

Step 1. Create a bot

If you haven't created a bot yet, go to BotFather, create one, and grab your API bot token.

Step 2. Set up a project

You'll need a project that uses Telegram's bot API to issue invoices and handle successful payments. We'll use grammY, a bot framework for Node.js. Run the following commands:

mkdir telegram-stars-payment && cd telegram-stars-payment
npm i grammy

Copy the following code to your index.mjs and insert your bot token:

import { Bot } from "grammy";

const bot = new Bot(""); // <-- put your bot token between the ""

bot.command("start", (ctx) => ctx.reply("Welcome! Up and running."));

bot.start();

Now run the bot via node index.mjs. The bot should start responding to the start command.

Step 3. Create an invoice

In terms of Telegram, an invoice is a special message type used in the Telegram Bot API to make payments within the chat. Regardless of which framework you're using, you can find these two methods for creating invoices:

  1. sendInvoice: The bot sends a message with a payment interface to the user, allowing them to complete the transaction within the chat. It includes details like the title, description, currency, prices, and other optional parameters such as photos and payload.
  2. createInvoiceLink: This command generates a link to an invoice. When users click the link, they are redirected to a payment interface to complete the transaction. This method is useful for sharing the payment link outside the Telegram chat, such as in Telegram Mini Apps, emails, or on websites.

We'll stick to the first method for this example. Let's add a command to send invoices:

bot.command("pay", (ctx) => {
  return ctx.replyWithInvoice(
    "Test Product", // Product title
    "Test description", // Product description
    "{}", // Product payload, not required for now
    "XTR", // Stars Currency 
    [{ amount: 1, label: "Test Product" }, // Product variants
  ]);
});

You can now send /pay to the bot, and it will respond with an invoice:

The result of calling `replyWithInvoice`

Step 4. Handle pre_checkout_query

Now we need to handle the payment. When a user buys Stars from Telegram and presses the pay button, the Bot API sends an update to the bot with all the order details in a field called pre_checkout_query. The bot must respond with answerPreCheckoutQuery within 10 seconds, or the transaction will be canceled.

If the bot can't process the order, it should send an error message explaining why in plain language (e.g., "Sorry, the item is out of stock. Would you like to order something else?"). Telegram will show this message to the user. For our example, we don't need that, so we'll just answer via answerPreCheckoutQuery:

bot.on("pre_checkout_query", (ctx) => {
  return ctx.answerPreCheckoutQuery(true).catch(() => {
    console.error("answerPreCheckoutQuery failed");
  });
});

Only after that Telegram will proceed with the payment.

Step 5. Handle successful payment

At this point, the user has already paid, and their money has been sent to you. Telegram will notify you who paid and for what, so you can mark this user as paid in your database. To get notified, you only need to search for an event like successful_payment in your bot framework API.

// Map is used for simplicity. For production use a database
const paidUsers = new Map();

bot.on("message:successful_payment", (ctx) => {
  if (!ctx.message || !ctx.message.successful_payment || !ctx.from) {
    return;
  }

  paidUsers.set(
    ctx.from.id,
    ctx.message.successful_payment.telegram_payment_charge_id,
  );

  console.log(ctx.message.successful_payment);
});

bot.command("status", (ctx) => {
  const message = paidUsers.has(ctx.from.id)
    ? "You have paid"
    : "You have not paid yet";
  return ctx.reply(message);
});

The basic version is ready! We've saved the information about successful payments into a JavaScript Map. Make sure to use a real database for production so the data is persistent between app reloads. We've saved the user's information alongside the telegram_payment_charge_id. It's needed for refunds. Let's implement them:

bot.command("refund", (ctx) => {
  const userId = ctx.from.id;
  if (!paidUsers.has(userId)) {
    return ctx.reply("You have not paid yet, there is nothing to refund");
  }

  ctx.api
    .refundStarPayment(userId, paidUsers.get(userId))
    .then(() => {
      paidUsers.delete(userId);
      return ctx.reply("Refund successful");
    })
    .catch(() => ctx.reply("Refund failed"));
});

The full code with instructions on how to run it is available on GitHub: https://github.com/kubk/telegram-stars-example

Common Questions

How to buy Telegram Stars?

You can buy Telegram Stars in the conversation with the bot you've just created while following the article. You can also purchase Stars via @PremiumBot on the direct version of Telegram for Android, as well as Telegram’s Web and Desktop apps.

How to withdraw Stars that I got from users?

You can withdraw them using Fragment platform. Make sure your Telegram is up-to-date, go to your bot, click Edit > Balance. More details. You can only withdraw after 3 weeks following the stars payment. This feature is likely designed for users who would like to request a refund from you for the stars payment.

Why did you use real payments for testing?

Because it's much easier than setting up a separate Telegram test account. 50 Telegram stars is around $1, and you can still refund them.

How to use Telegram Stars in Telegram Mini Apps?

The idea is completely the same; you just need to use createInvoiceLink instead of sendInvoice. The method createInvoiceLink will generate a link that you can open using Telegram's Mini App SDK:

Any tips for production?

The article shows a simple example so you can get the basic idea. For production, I'd suggest a real database like PostgreSQL with 2 separate entities:

  • Product with fields like name, price
  • Order with fields like user_id (1-to-many), status ('initial', 'paid', 'failed'), product_id, product_price, product_name, telegram_payload for storing the Telegram data needed for refunds. Why denormalize the product's price and name? Because they may change in the future, and you want to save the real value at the moment of purchase to avoid confusion about how much users paid in the past. It's a common practice in such applications.

About the author

I am Egor, full-stack developer and a contributor to many popular open source libraries. I've built MemoCard - Award-winning Telegram mini app for improving memory with spaced repetition and AI. Give it a shot, it also accepts Telegram Stars.