Werogram/Документация/Telegram Premium paid with Stars
RUWEROGRAM DOCS

Telegram Premium paid with Stars

This implementation provides a Layer 228 Premium storefront, direct Premium gifts, and the HTTP Bot API giftPremiumSubscription method on top of the existing users, messages, updates, and Stars ledger.

Supported entry points

  • help.getPremiumPromo and help.getAppConfig advertise the configured built-in Premium bot. The Stars-only catalog is opened inside that bot.
  • @premiumbot supports /start, /premium, /gift, /status, /history, /terms, and /help. /gift opens a regular-user peer picker, then presents the current server catalog and an invoice. A fallback command has the form /gift <user_id> <months> [greeting]. Menu and plan buttons are callback buttons handled by the local server; they do not navigate to the public t.me service. Every incoming message and callback also carries the physical auth-key, session id, lang_code, system_lang_code, and lang_pack from the exact initConnection request. The copy is Russian for ru, English for all other reported languages, and retains Russian only for legacy internal calls that have no client metadata.
  • payments.getPremiumGiftCodeOptions returns ordered, enabled, one-user XTR plans. A supplied boost_peer is validated and then rejected with BOOST_PEER_INVALID; Premium giveaways and multi-user gift codes are not implemented.
  • payments.getPaymentForm accepts inputInvoicePremiumGiftStars, an @premiumbot invoice message, and the native Premium subscription store purpose. Because that constructor has no product/month field, a normal native purchase maps to the shortest enabled plan and its upgrade variant maps to the longest enabled plan. An unverifiable external-store restore request is rejected instead of minting Premium locally.
  • payments.sendStarsForm settles a form and returns the existing payments.paymentResult update container.
  • The local HTTP Bot API exposes giftPremiumSubscription with user_id, month_count, star_count, optional text, text_parse_mode, and text_entities. It validates month_count and star_count against the same catalog used by MTProto.

Bot API callers can supply an Idempotency-Key HTTP header or the local request_id extension. Reusing the same key with the same request returns the stored successful result; reusing it with different recipient, plan, price, or text is rejected. Calls without either field remain compatible with the official method but receive a new server-generated request key, so an application that retries requests should always send Idempotency-Key.

Layer 228 wire contract

The implementation uses constructors from the vendored Layer 228 schema:

Constructor ID Relevant flags
inputInvoicePremiumGiftStars 0xdabab2ef message: bit 0
payments.paymentFormStars 0x7bf6b15c schema-generated
premiumGiftCodeOption 0x257e962b store product: bit 0; quantity: bit 1 (both absent for XTR)
messageActionGiftPremium 0x48e91302 crypto pair: bit 0; message: bit 1
starsTransactionPeerPremiumBot 0x250dbaf8 no fields
inputStorePaymentPremiumSubscription 0xa6751e66 restore: bit 0

The direct gift parser verifies InputUser.access_hash, rejects self-gifts and bot/deleted/system recipients, bounds the greeting to 128 Unicode characters, allows only bold, italic, underline, strikethrough, spoiler, and valid custom emoji entities, and verifies every entity against UTF-16 boundaries, including surrogate-pair boundaries. The recipient's persisted disallow_premium_gifts setting is checked both while creating the form and again during settlement.

Catalog and configuration

The authoritative catalog is premium_plans. A client never supplies the charged price. Every form snapshots the amount, duration, and plan version, and settlement checks the snapshot against the current enabled catalog again.

TELESRV_PREMIUM_PLANS seeds and synchronizes config-owned rows. Saving a row through the protected admin UI/API changes its ownership to admin, so a restart does not overwrite the operator's price, duration, label, order, or enabled state. Plans are disabled rather than deleted, and the last enabled plan cannot be disabled. Existing checkout forms retain their original snapshot but fail settlement after a plan version changes.

Environment variable Default Meaning
TELESRV_PREMIUM_BOT_USERNAME premiumbot Reserved built-in storefront username, without @.
TELESRV_PREMIUM_BOT_USER_ID 1250000015 Stable positive system-user ID that must not collide with another built-in account.
TELESRV_PREMIUM_PLANS 3:90:750,6:180:1300,12:365:2400 Comma-separated months:duration_days:amount_stars catalog.
TELESRV_PREMIUM_SWEEP_INTERVAL 1m Expiration worker interval.
TELESRV_PREMIUM_SWEEP_BATCH 500 Expired entitlement rows processed per transaction.

Durations use the catalog's fixed duration_days everywhere: invoice, entitlement, service message, and expiration. Extension is max(now, premium_until) + duration_days.

Atomic settlement

Before issuing a form, the application reads the existing Stars account through the normal Stars service, which also applies the configured one-time starting grant. @premiumbot shows the resulting balance alongside the exact catalog price. Settlement then runs in one PostgreSQL transaction:

  1. locks buyer and recipient users in stable ID order through the existing private-message state machine;
  2. locks the PaymentIntent and revalidates owner, status, expiry, invoice snapshot, plan version, recipient, and the latest gift-privacy restriction;
  3. locks and debits the buyer's existing Stars balance without allowing it to become negative;
  4. inserts one immutable negative Stars transaction linked to the PaymentIntent;
  5. inserts one entitlement and extends users.premium_expires_at;
  6. marks the intent paid and stores transaction/message IDs;
  7. inserts the purchase audit event;
  8. writes both private message boxes, dialogs, PTS events, and dispatch outbox rows through the existing message transaction hooks.

Gift purchases create messageActionGiftPremium between buyer and recipient. Self purchases create a confirmation from @premiumbot. Stars are burned by the platform: the buyer receives a negative ledger entry whose peer projects as starsTransactionPeerPremiumBot; the Premium bot is not credited.

form_id, PaymentIntent idempotency keys, entitlement payment IDs, and linked transaction IDs are unique. Concurrent retries therefore cannot double debit, extend twice, or create a second service message.

Storage

Migration 0167_premium_stars adds:

  • premium_plans;
  • premium_payment_intents;
  • premium_entitlements;
  • premium_audit_events;
  • Premium linkage columns on stars_transactions;
  • users.premium_updated_at and the Layer 228 disallowed-gift switches in account_settings;
  • the built-in Premium bot user, bot profile, reserved username, and read-model versions.

Migration 0168_premium_plan_management records whether a catalog row is managed by startup configuration or by an operator. Apply both migrations before starting a build that includes the Premium catalog editor.

Indexes cover plan order, form/idempotency/transaction uniqueness, pending form expiry, recipient history, entitlement user/expiry/status, payment and transaction uniqueness, and audit lookup by target/payment/command.

Expiration is lazy on every User projection and durable through a restart-safe worker. The worker expires rows in batches, recomputes the maximum remaining active entitlement, writes audit events, invalidates User/UserFull caches, and pushes updateUser.

Refunds preserve the original debit and add a compensating Stars credit. The associated entitlement becomes refunded, later entitlement windows are compacted without destroying time from unrelated grants, the aggregate expiration is recomputed, and the command is idempotent.

Protected operator API

All Premium endpoints require a master token or a scoped token with premium.manage:

  • GET /v1/premium/users/{id}/entitlements?limit=50
  • GET /v1/premium/payments/{id} — returns the PaymentIntent, linked entitlement, and original ledger transaction
  • GET /v1/premium/plans
  • POST /v1/premium/plans/upsert
  • POST /v1/accounts/grant-premium — months: 0 revokes
  • POST /v1/accounts/refund-premium

Mutations require the existing command metadata (command_id, actor, reason, and optional dry_run) and are written to both the admin command log and Premium audit log.

The browser admin exposes one Stars & Premium (/monetization) workspace. It shows the native/bot channels, supports manual Stars and Premium grants, and edits the shared Premium catalog: Stars price, fixed duration, label, sort order, and enabled state. The legacy /premium address remains an alias. Browser reads and writes are protected by premium.manage; users with a wildcard permission also have access.

Stars-only promo compatibility

premiumSubscriptionOption.currency is an ISO 4217 fiat currency field. XTR is valid for Premium gift-code options and Stars payment forms, but not for that constructor. Publishing XTR in help.premiumPromo.period_options makes official clients render duplicate Star icons or missing-glyph boxes. The Stars-only server therefore leaves period_options empty and relies on premium_bot_username; the bot displays the live Stars catalog without ambiguous symbols.

Verification

Run unit tests:

go test ./...

Run the real PostgreSQL bootstrap/concurrency/privacy/refund/expiry tests against a dedicated database whose name contains test:

$env:TELESRV_TEST_POSTGRES_DSN = "postgres://user:[email protected]:5432/telesrv_test?sslmode=disable"
go test ./internal/store/postgres -run "TestPremiumPurchaseAtomicConcurrentIdempotentRefundAndExpiry|TestAccountSettingsRoundTripPostgres" -count=1 -v

Manual Telegram Desktop check:

  1. apply migrations and start the server;
  2. open Settings → Telegram Premium and confirm there are no malformed fiat rows or duplicate Star glyphs;
  3. open @premiumbot, press each menu callback, buy one plan, and verify the Stars balance/history and Premium badge;
  4. use /gift, choose a regular user, pay the invoice, and verify messageActionGiftPremium in both dialog views;
  5. disable Premium gifts in the recipient's gift privacy, verify USER_PRIVACY_RESTRICTED, then re-enable them;
  6. retry the same sendStarsForm and confirm there is no second debit, extension, or message;
  7. disconnect the recipient before gifting, reconnect, and confirm updates.getDifference restores the service message;
  8. restart the server and confirm Premium persists;
  9. edit a plan in Stars & Premium, restart the server, and confirm the operator-managed value remains;
  10. configure a short test plan or admin grant, wait for expiry, and confirm the badge disappears after the worker or immediately on a fresh User read.

Deliberate non-goals

  • boost_peer, Premium giveaways, multi-user gift codes, fiat/store purchases, recurring renewal, and external payment providers are not implemented.
  • Premium promo videos remain optional and use the existing TELESRV_PREMIUM_PROMO_SEED_DIR; an absent seed keeps the no-video response.
  • No server-side inputStickerSetPremiumGifts asset is seeded; official clients may use their own built-in rendering for messageActionGiftPremium.
  • Built-in bot copy currently ships in Russian and English; unsupported client languages fall back to English. Protocol responses and purchase data remain language-neutral, and the request-scoped session metadata path is reusable by future service bots.

Protocol references: Layer schema, Premium API, Stars API, payment flow, inputInvoicePremiumGiftStars, premiumSubscriptionOption, payments.getPremiumGiftCodeOptions, payments.getPaymentForm, payments.sendStarsForm, and messageActionGiftPremium. The HTTP surface follows the official giftPremiumSubscription parameter set and adds only the optional local idempotency key described above.