Business
Invoices & payments
All purchases in the Telegram storefront are proof-of-payment based: a user picks a plan, uploads a payment screenshot, and an admin approves it in Payments. Approved orders generate a permanent record in Invoices. This page covers both queues end to end.
- 1Configure payment instructions and a QR code in Bot → Content (
paymentInstructions,paymentQrUrl) so buyers know where/how to pay outside the bot — bank transfer, crypto address, etc. - 2A user picks a plan in the bot's Buy flow and the bot shows the configured payment instructions.
- 3The user pays out-of-band, then uploads a screenshot/proof of payment as a photo/document in the chat.
- 4The bot creates an order that lands in Payments → Pending with
status: "pending". - 5An admin reviews the proof image against the plan and amount, then approves, rejects, or holds the order.
- 6Approving auto-provisions the VPN account(s) for the plan and DMs the config back to the user via the bot.
No automated payment gateway
There is no card/gateway integration in this flow — every purchase is manually verified by an admin against a screenshot. This keeps the storefront usable with local bank transfers and crypto wallets that have no programmatic webhook.
§3 — the moderation surface
Payments queue#
Route: /_authed/payments. This is the working queue admins act on; Invoices (below) is the read-only historical ledger generated once a payment is approved.
Payment data model
| Field | Meaning |
|---|---|
| id | Order id. |
| telegramId / telegramName | The buyer. |
| planName / amountCents | What was bought and for how much, in cents. |
| proofUrl | The uploaded payment screenshot/proof image. |
| status | pending | on_hold | approved | rejected. |
| note | Optional admin note, e.g. reason for hold/reject. |
| createdAt | Order timestamp. |
UI and moderation actions
- Tabs: Pending / On hold / Approved / Rejected / All.
- Each pending/on-hold card shows the proof image, buyer name + Telegram id, plan + amount, timestamp, and any admin note.
- Approve — provisions the account(s) immediately and notifies the buyer.
- Reject (with a reason — preset reasons: unclear screenshot, amount mismatch, transaction not found, duplicate payment).
- Hold (with a reason — preset reasons: verifying with bank, need TX ID, awaiting crypto confirmations).
- Contact — send a free-form Telegram message to the buyer without changing status.
- On approved/rejected orders: Message user is still available; approved orders additionally get Revoke — a destructive rollback that removes the provisioned account(s) and reopens the order as pending, with an optional reason shown to the user.
- Every reject/hold/message/revoke action notifies the buyer inside the Telegram bot automatically.
Endpoints (api.payments)
text
list(status?) filter by tab: pending | on_hold | approved | rejected | all
decide(id, "approved"|"rejected"|"on_hold", reason?) the core moderation action
revoke(id, reason?, removeAccount: true) undo an approval; reopens as pending, deletes account
message(id, text) send a free-form Telegram message, no status changeTaking a payment, step by step
- 1Configure payment instructions/QR in Bot → Content so users know where and how to pay.
- 2User buys a plan, uploads a screenshot as proof, order appears in Payments → Pending.
- 3Admin reviews the proof photo and amount.
- 4Click Approve to provision the account and notify the user, or Reject/Hold with a reason.
- 5If a wrong approval happened, use Revoke to roll it back — this deletes the account and returns the order to Pending.
§2 — the historical ledger
Invoices#
Route: /_authed/invoices. A read-only ledger of generated business invoices, one per approved payment/order, with export and Telegram re-send.
Invoice fields
- id / number
- Row id and the invoice number (e.g. shown in mono font).
- issuedAt
- ISO issue date.
- username / telegramId
- The buyer.
- planId / planName
- Links back to the plan (
/plans?open=<planId>for quick cross-reference). - method
- Payment method label (bank/crypto/points/topup — shown uppercase).
- amountCents
- Invoice amount in cents.
Filtering and export
- Client-side period filter: Monthly / Yearly / All-time, computed via periodRange().
- Free-text search across invoice number, username, Telegram id, and plan name.
send(id, "telegram")— re-sends the invoice PDF to the buyer's Telegram (the bot delivers it).pdfUrl(id)— direct download link for a single invoice PDF.exportUrl("xlsx" | "pdf", { start, end })— bulk export of all invoices in the selected period.txns()— the full wallet transaction ledger tagged with telegram_id, used elsewhere (e.g. Users page) to reconstruct financial history per user.
Send requires a linked Telegram id
Clicking Send on an invoice requires
telegramId to be present — invoices without a bot user attached cannot be re-delivered automatically.- There is no live payment-gateway integration; "payment methods" are whatever instructions/QR you configure in Bot → Content — reconciliation is manual, driven by comparing the uploaded proof to your own bank/crypto records.
- Every approved payment eventually shows up in both the Payments → Approved tab and as a row in Invoices — use the invoice's plan link to jump back to the plan, or the Users page's transaction history (
api.invoices.txns()) to see a buyer's full financial trail across all wallet tracks. - For crypto payments specifically, ongoing balance/confirmation tracking after the initial proof lives in Wallet → Wallet Tracks (see the Wallet & tracks page) rather than in Payments itself.
- Use Invoices → Export for period-end reconciliation against your own accounting; XLSX and combined PDF exports are both scoped to a start/end date range.
- Rejecting or holding an order does not delete it — it stays visible under its tab and can still be approved later.
- Revoke is destructive: it deletes the provisioned account(s), not just the order status; only use it to correct a genuine mistake.
- Invoices are generated from approved payments — there is no invoice for a rejected or still-pending order.
- The invoice "method" field is a label only; it does not enforce or validate against a real payment processor.
