# Recipe: Telegram bot

> Sell bot commands, a role or a private group. Each user gets their own invoice; a signed webhook unlocks them.

The bot never sees money. On `/buy` it sends the user their own invoice link; your server hears `invoice.fulfilled` and unlocks. Every paid command asks Perly Pay, so a restart loses nothing. Discord works the same way, with a role.

> **Note:** With an assistant: the `sell-bot-access` skill of the Perly Pay plugin for Claude Code does this recipe in your project — the plugin and its example, `examples/telegram-bot` (MIT), are on their way. Until then, any assistant can read the same skill from `/.well-known/skills/perly-pay-sell-bot-access/SKILL.md`.

## How it works

1. **A product and price** — One-time or lifetime for "forever", fixed term for passes, subscription for memberships renewed by invoice.
2. **A customer per chat user** — `external_id: "tg:<user id>"`. Ask for an email only for subscriptions: renewals go there.
3. **/buy** — An invoice with the chat id in `metadata`, sent as a button. One invoice per user per day, by idempotency key.
4. **Unlock** — On `invoice.fulfilled`, read the chat id from `metadata` and answer: a message, a one-time invite link, a role.
5. **Paid commands** — `entitlements.check({ customer_external_id: "tg:<id>", product })`.

**/buy**

```js
const invoice = await pay.invoices.create(
  { line_items: [{ price: PRICE }], customer: customer.id, metadata: { chat_id: String(chatId) } },
  { idempotencyKey: `buy-${userId}-${today}` },
);
await tg("sendMessage", { chat_id: chatId, text: "Pay:", reply_markup: { inline_keyboard: [[{ text: "Pay", url: invoice.url }]] } });
```

## Run the example

1. **Install** — `npm install` in `examples/telegram-bot` (Node 20.12+).
2. **Key** — Copy `.env.example` to `.env` and put a test key (`sk_test_…`) in `PERLY_PAY_SECRET_KEY`.
3. **Set up** — `npm run setup` makes the product and price with your key and prints their ids for `.env`.
4. **Run** — `npm start` (or `npm run dev`), as its README says.
5. **Pay without a wallet** — Message the bot `/buy`, open Pay and press **Simulate payment**: the bot says "Payment confirmed" and `/secret` answers.

## Pitfalls

- One shared payment link for everyone: you cannot tell who paid. An invoice per user, with the chat id.
- Unlocking when the user taps "I paid". Only the webhook or `entitlements.check` unlocks.
- Access kept in the bot's memory. It is gone after a restart; ask Perly Pay.
