Skip to content

Commit d3c36fb

Browse files
ernadoclaude
andcommitted
feat(payments): implement SendInvoice
Send invoices via messages.sendMedia with InputMediaInvoice. InvoiceParams carries the invoice fields (prices, tips, provider data, photo, the need_*/send_*_to_provider/flexible flags); standard SendOptions apply markup/silent/reply. Share pricesToTg with the shipping answer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 53506a7 commit d3c36fb

3 files changed

Lines changed: 109 additions & 6 deletions

File tree

conformance_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ var coveredByOtherMeans = map[string]string{
2121
// implemented. Each is an acknowledged gap tracked in docs/roadmap.md; the
2222
// conformance test allows them so it can still catch *unacknowledged* drift.
2323
var deferredMethods = map[string]string{
24-
"sendInvoice": "payments — deferred",
2524
"setPassportDataErrors": "Telegram Passport — deferred",
2625
}
2726

payments.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,7 @@ func WithPreCheckoutError(message string) PreCheckoutAnswerOption {
9797
func shippingOptionsToTg(options []ShippingOption) []tg.ShippingOption {
9898
out := make([]tg.ShippingOption, 0, len(options))
9999
for _, o := range options {
100-
prices := make([]tg.LabeledPrice, 0, len(o.Prices))
101-
for _, p := range o.Prices {
102-
prices = append(prices, tg.LabeledPrice{Label: p.Label, Amount: int64(p.Amount)})
103-
}
104-
out = append(out, tg.ShippingOption{ID: o.ID, Title: o.Title, Prices: prices})
100+
out = append(out, tg.ShippingOption{ID: o.ID, Title: o.Title, Prices: pricesToTg(o.Prices)})
105101
}
106102
return out
107103
}

send_invoice.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package botapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/gotd/td/telegram/message"
7+
"github.com/gotd/td/tg"
8+
)
9+
10+
// InvoiceParams describes an invoice to send with SendInvoice.
11+
type InvoiceParams struct {
12+
Title string `json:"title"`
13+
Description string `json:"description"`
14+
Payload string `json:"payload"` // bot-defined, not shown to the user
15+
ProviderToken string `json:"provider_token"`
16+
Currency string `json:"currency"`
17+
Prices []LabeledPrice `json:"prices"`
18+
19+
MaxTipAmount int `json:"max_tip_amount,omitempty"`
20+
SuggestedTipAmounts []int `json:"suggested_tip_amounts,omitempty"`
21+
StartParameter string `json:"start_parameter,omitempty"`
22+
ProviderData string `json:"provider_data,omitempty"` // JSON for the payment provider
23+
24+
PhotoURL string `json:"photo_url,omitempty"`
25+
PhotoSize int `json:"photo_size,omitempty"`
26+
PhotoWidth int `json:"photo_width,omitempty"`
27+
PhotoHeight int `json:"photo_height,omitempty"`
28+
29+
NeedName bool `json:"need_name,omitempty"`
30+
NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
31+
NeedEmail bool `json:"need_email,omitempty"`
32+
NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
33+
34+
SendPhoneNumberToProvider bool `json:"send_phone_number_to_provider,omitempty"`
35+
SendEmailToProvider bool `json:"send_email_to_provider,omitempty"`
36+
IsFlexible bool `json:"is_flexible,omitempty"`
37+
}
38+
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+
for _, o := range opts {
43+
o(&cfg)
44+
}
45+
46+
peer, err := b.resolveInputPeer(ctx, chat)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
suggested := make([]int64, len(params.SuggestedTipAmounts))
52+
for i, a := range params.SuggestedTipAmounts {
53+
suggested[i] = int64(a)
54+
}
55+
invoice := tg.Invoice{
56+
NameRequested: params.NeedName,
57+
PhoneRequested: params.NeedPhoneNumber,
58+
EmailRequested: params.NeedEmail,
59+
ShippingAddressRequested: params.NeedShippingAddress,
60+
Flexible: params.IsFlexible,
61+
PhoneToProvider: params.SendPhoneNumberToProvider,
62+
EmailToProvider: params.SendEmailToProvider,
63+
Currency: params.Currency,
64+
Prices: pricesToTg(params.Prices),
65+
MaxTipAmount: int64(params.MaxTipAmount),
66+
SuggestedTipAmounts: suggested,
67+
}
68+
69+
media := &tg.InputMediaInvoice{
70+
Title: params.Title,
71+
Description: params.Description,
72+
Invoice: invoice,
73+
Payload: []byte(params.Payload),
74+
Provider: params.ProviderToken,
75+
StartParam: params.StartParameter,
76+
}
77+
if params.ProviderData != "" {
78+
media.ProviderData = tg.DataJSON{Data: params.ProviderData}
79+
}
80+
if params.PhotoURL != "" {
81+
media.SetPhoto(tg.InputWebDocument{
82+
URL: params.PhotoURL,
83+
Size: params.PhotoSize,
84+
MimeType: "image/jpeg",
85+
Attributes: []tg.DocumentAttributeClass{
86+
&tg.DocumentAttributeImageSize{W: params.PhotoWidth, H: params.PhotoHeight},
87+
},
88+
})
89+
}
90+
91+
builder := &b.sender.To(peer).Builder
92+
builder, err = b.applySendConfig(builder, cfg)
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
resp, err := builder.Media(ctx, message.Media(media))
98+
return b.sentMessage(ctx, peer, resp, err)
99+
}
100+
101+
// pricesToTg converts Bot API labeled prices to MTProto.
102+
func pricesToTg(prices []LabeledPrice) []tg.LabeledPrice {
103+
out := make([]tg.LabeledPrice, 0, len(prices))
104+
for _, p := range prices {
105+
out = append(out, tg.LabeledPrice{Label: p.Label, Amount: int64(p.Amount)})
106+
}
107+
return out
108+
}

0 commit comments

Comments
 (0)