Skip to content

Commit 405bacf

Browse files
ernadoclaude
andcommitted
feat: add CreateInvoiceLink and RefundStarPayment
CreateInvoiceLink (payments.exportInvoice, sharing the invoice-media builder with SendInvoice) and RefundStarPayment (payments.refundStarsCharge). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d8e56f6 commit 405bacf

3 files changed

Lines changed: 117 additions & 13 deletions

File tree

payments_extra.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/tg"
7+
)
8+
9+
// CreateInvoiceLink creates a link for an invoice and returns it. The created
10+
// invoice can be paid by any user who follows the link.
11+
func (b *Bot) CreateInvoiceLink(ctx context.Context, params InvoiceParams) (string, error) {
12+
res, err := b.raw.PaymentsExportInvoice(ctx, invoiceMedia(params))
13+
if err != nil {
14+
return "", asAPIError(err)
15+
}
16+
17+
return res.URL, nil
18+
}
19+
20+
// RefundStarPayment refunds a successful payment in Telegram Stars to the given
21+
// user. chargeID is the telegram_payment_charge_id from the successful payment.
22+
func (b *Bot) RefundStarPayment(ctx context.Context, userID int64, chargeID string) error {
23+
user, err := b.resolveInputUser(ctx, userID)
24+
if err != nil {
25+
return err
26+
}
27+
28+
if _, err := b.raw.PaymentsRefundStarsCharge(ctx, &tg.PaymentsRefundStarsChargeRequest{
29+
UserID: user,
30+
ChargeID: chargeID,
31+
}); err != nil {
32+
return asAPIError(err)
33+
}
34+
35+
return nil
36+
}

payments_extra_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
func TestCreateInvoiceLink(t *testing.T) {
11+
inv := newMockInvoker()
12+
inv.reply(tg.PaymentsExportInvoiceRequestTypeID, &tg.PaymentsExportedInvoice{URL: "https://t.me/invoice/abc"})
13+
14+
params := InvoiceParams{
15+
Title: "Item",
16+
Description: "An item",
17+
Payload: "payload",
18+
Currency: "XTR",
19+
Prices: []LabeledPrice{{Label: "Item", Amount: 100}},
20+
}
21+
22+
link, err := newMockBot(inv).CreateInvoiceLink(context.Background(), params)
23+
if err != nil {
24+
t.Fatalf("CreateInvoiceLink: %v", err)
25+
}
26+
27+
if link != "https://t.me/invoice/abc" {
28+
t.Fatalf("link = %q", link)
29+
}
30+
31+
var req tg.PaymentsExportInvoiceRequest
32+
33+
inv.decode(t, tg.PaymentsExportInvoiceRequestTypeID, &req)
34+
35+
media, ok := req.InvoiceMedia.(*tg.InputMediaInvoice)
36+
if !ok || media.Title != "Item" {
37+
t.Fatalf("media = %#v", req.InvoiceMedia)
38+
}
39+
}
40+
41+
func TestRefundStarPayment(t *testing.T) {
42+
inv := newMockInvoker()
43+
inv.reply(tg.PaymentsRefundStarsChargeRequestTypeID, okUpdates())
44+
45+
if err := newMockBot(inv).RefundStarPayment(context.Background(), 99, "charge_123"); err != nil {
46+
t.Fatalf("RefundStarPayment: %v", err)
47+
}
48+
49+
var req tg.PaymentsRefundStarsChargeRequest
50+
51+
inv.decode(t, tg.PaymentsRefundStarsChargeRequestTypeID, &req)
52+
53+
if req.ChargeID != "charge_123" {
54+
t.Fatalf("charge id = %q", req.ChargeID)
55+
}
56+
57+
if _, ok := req.UserID.(*tg.InputUser); !ok {
58+
t.Fatalf("user = %#v", req.UserID)
59+
}
60+
}

send_invoice.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,9 @@ type InvoiceParams struct {
3636
IsFlexible bool `json:"is_flexible,omitempty"`
3737
}
3838

39-
// SendInvoice sends an invoice.
40-
func (b *Bot) SendInvoice(ctx context.Context, chat ChatID, params InvoiceParams, opts ...SendOption) (*Message, error) {
41-
var cfg sendConfig
42-
43-
for _, o := range opts {
44-
o(&cfg)
45-
}
46-
47-
peer, err := b.resolveInputPeer(ctx, chat)
48-
if err != nil {
49-
return nil, err
50-
}
51-
39+
// invoiceMedia builds the MTProto invoice media shared by SendInvoice and
40+
// CreateInvoiceLink.
41+
func invoiceMedia(params InvoiceParams) *tg.InputMediaInvoice {
5242
suggested := make([]int64, len(params.SuggestedTipAmounts))
5343
for i, a := range params.SuggestedTipAmounts {
5444
suggested[i] = int64(a)
@@ -91,6 +81,24 @@ func (b *Bot) SendInvoice(ctx context.Context, chat ChatID, params InvoiceParams
9181
})
9282
}
9383

84+
return media
85+
}
86+
87+
// SendInvoice sends an invoice.
88+
func (b *Bot) SendInvoice(ctx context.Context, chat ChatID, params InvoiceParams, opts ...SendOption) (*Message, error) {
89+
var cfg sendConfig
90+
91+
for _, o := range opts {
92+
o(&cfg)
93+
}
94+
95+
peer, err := b.resolveInputPeer(ctx, chat)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
media := invoiceMedia(params)
101+
94102
builder := &b.sender.To(peer).Builder
95103

96104
builder, err = b.applySendConfig(builder, cfg)

0 commit comments

Comments
 (0)