Skip to content

Commit 4dc233e

Browse files
ernadoclaude
andcommitted
feat: add GiftPremiumSubscription
Gifts a Telegram Premium subscription to a user via the Telegram Stars payment flow with inputInvoicePremiumGiftStars. The Stars cost is determined by the payment form for the chosen month count, so it is not a parameter. Generalizes payStarsForm to accept any star payment form (starsFormID handles both paymentFormStarGift and paymentFormStars), reusing the same two-step flow introduced for SendGift. Text is resolved through the shared giftMessage helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4f452a8 commit 4dc233e

2 files changed

Lines changed: 98 additions & 4 deletions

File tree

send_gift.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,39 @@ func (b *Bot) SendGift(ctx context.Context, target ChatID, giftID string, opts .
7878
return b.payStarsForm(ctx, invoice)
7979
}
8080

81+
// GiftPremiumSubscription gifts a Telegram Premium subscription to a user for
82+
// the given number of months. The Telegram Stars cost is determined by the
83+
// payment form for the chosen duration. WithGiftPayForUpgrade has no effect
84+
// here.
85+
func (b *Bot) GiftPremiumSubscription(ctx context.Context, userID int64, months int, opts ...GiftOption) error {
86+
var cfg giftConfig
87+
88+
for _, opt := range opts {
89+
opt(&cfg)
90+
}
91+
92+
user, err := b.resolveInputUser(ctx, userID)
93+
if err != nil {
94+
return err
95+
}
96+
97+
invoice := &tg.InputInvoicePremiumGiftStars{
98+
UserID: user,
99+
Months: months,
100+
}
101+
102+
if cfg.text != "" {
103+
text, entities, err := b.giftMessage(ctx, cfg)
104+
if err != nil {
105+
return err
106+
}
107+
108+
invoice.SetMessage(tg.TextWithEntities{Text: text, Entities: entities})
109+
}
110+
111+
return b.payStarsForm(ctx, invoice)
112+
}
113+
81114
// giftMessage resolves the gift text into a (text, entities) pair: explicit
82115
// entities take precedence over the parse mode.
83116
func (b *Bot) giftMessage(ctx context.Context, cfg giftConfig) (string, []tg.MessageEntityClass, error) {
@@ -98,17 +131,31 @@ func (b *Bot) payStarsForm(ctx context.Context, invoice tg.InputInvoiceClass) er
98131
return asAPIError(err)
99132
}
100133

101-
stars, ok := form.(*tg.PaymentsPaymentFormStarGift)
102-
if !ok {
103-
return &Error{Code: 500, Description: "Internal Server Error: unexpected payment form"}
134+
formID, err := starsFormID(form)
135+
if err != nil {
136+
return err
104137
}
105138

106139
if _, err := b.raw.PaymentsSendStarsForm(ctx, &tg.PaymentsSendStarsFormRequest{
107-
FormID: stars.FormID,
140+
FormID: formID,
108141
Invoice: invoice,
109142
}); err != nil {
110143
return asAPIError(err)
111144
}
112145

113146
return nil
114147
}
148+
149+
// starsFormID extracts the form id from a Telegram Stars payment form. Star
150+
// gifts return a starGift form; subscriptions and other star purchases return a
151+
// plain stars form.
152+
func starsFormID(form tg.PaymentsPaymentFormClass) (int64, error) {
153+
switch f := form.(type) {
154+
case *tg.PaymentsPaymentFormStarGift:
155+
return f.FormID, nil
156+
case *tg.PaymentsPaymentFormStars:
157+
return f.FormID, nil
158+
default:
159+
return 0, &Error{Code: 500, Description: "Internal Server Error: unexpected payment form"}
160+
}
161+
}

send_gift_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,53 @@ func TestSendGift(t *testing.T) {
6363
}
6464
}
6565

66+
func TestGiftPremiumSubscription(t *testing.T) {
67+
inv := newMockInvoker()
68+
inv.reply(tg.PaymentsGetPaymentFormRequestTypeID, &tg.PaymentsPaymentFormStars{
69+
FormID: 555,
70+
Invoice: tg.Invoice{Currency: "XTR"},
71+
})
72+
inv.reply(tg.PaymentsSendStarsFormRequestTypeID, &tg.PaymentsPaymentResult{
73+
Updates: &tg.Updates{},
74+
})
75+
76+
bot := newMockBot(inv)
77+
78+
err := bot.GiftPremiumSubscription(context.Background(), 42, 6, WithGiftText("enjoy"))
79+
if err != nil {
80+
t.Fatalf("GiftPremiumSubscription: %v", err)
81+
}
82+
83+
var form tg.PaymentsGetPaymentFormRequest
84+
85+
inv.decode(t, tg.PaymentsGetPaymentFormRequestTypeID, &form)
86+
87+
invoice, ok := form.Invoice.(*tg.InputInvoicePremiumGiftStars)
88+
if !ok {
89+
t.Fatalf("invoice = %#v, want premium gift stars", form.Invoice)
90+
}
91+
92+
if invoice.Months != 6 {
93+
t.Fatalf("months = %d, want 6", invoice.Months)
94+
}
95+
96+
if invoice.Message.Text != "enjoy" {
97+
t.Fatalf("message = %q", invoice.Message.Text)
98+
}
99+
100+
if _, ok := invoice.UserID.(*tg.InputUser); !ok {
101+
t.Fatalf("user = %#v, want input user", invoice.UserID)
102+
}
103+
104+
var send tg.PaymentsSendStarsFormRequest
105+
106+
inv.decode(t, tg.PaymentsSendStarsFormRequestTypeID, &send)
107+
108+
if send.FormID != 555 {
109+
t.Fatalf("form id = %d, want 555", send.FormID)
110+
}
111+
}
112+
66113
func TestSendGiftInvalidID(t *testing.T) {
67114
inv := newMockInvoker()
68115

0 commit comments

Comments
 (0)