Skip to content

Commit e0be7dd

Browse files
Payments bot sample (#175)
* set up payments bot sample * Regen docs + add paysupport command * lint
1 parent 10c3bf6 commit e0be7dd

File tree

5 files changed

+161
-2
lines changed

5 files changed

+161
-2
lines changed

samples/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ could be collected.
5959
This bot shows how to effectively use middlewares to modify and intercept HTTP requests to the bot API server.
6060
In this example, the middleware sets the allow_sending_without_reply to certain methods, as well as make sure to log all error messages.
6161

62+
## samples/paymentsBot
63+
64+
This bot demonstrates how to provide invoices, checkouts, and successful payments through telegram's in-app purchase
65+
methods.
66+
Use this if you want an example of how to sell things through telegram. The example targets Telegram Stars, which
67+
allows bot developers to sell digital products through Telegram.
68+
6269
## samples/statefulClientBot
6370

6471
This bot demonstrates how to pass around variables to all handlers without changing any function signatures.

samples/commandBot/main.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ func source(b *gotgbot.Bot, ctx *ext.Context) error {
9999

100100
// start introduces the bot.
101101
func start(b *gotgbot.Bot, ctx *ext.Context) error {
102-
_, err := ctx.EffectiveMessage.Reply(b, fmt.Sprintf("Hello, I'm @%s. I <b>repeat</b> all your messages.", b.User.Username), &gotgbot.SendMessageOpts{
103-
ParseMode: "html",
102+
_, err := ctx.EffectiveMessage.Reply(b, fmt.Sprintf("Hello, I'm @%s.\nI am a sample bot to demonstrate how file sending works.\n\nTry the /source command!", b.User.Username), &gotgbot.SendMessageOpts{
103+
ParseMode: "HTML",
104104
})
105105
if err != nil {
106106
return fmt.Errorf("failed to send start message: %w", err)

samples/paymentsBot/go.mod

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module github.com/PaulSonOfLars/gotgbot/samples/paymentsBot
2+
3+
go 1.19
4+
5+
require (
6+
github.com/PaulSonOfLars/gotgbot/v2 v2.99.99
7+
github.com/google/uuid v1.6.0
8+
)
9+
10+
replace github.com/PaulSonOfLars/gotgbot/v2 => ../../

samples/paymentsBot/go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

samples/paymentsBot/main.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
11+
"github.com/PaulSonOfLars/gotgbot/v2"
12+
"github.com/PaulSonOfLars/gotgbot/v2/ext"
13+
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
14+
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/message"
15+
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/precheckoutquery"
16+
)
17+
18+
// This bot demonstrates how to provide invoices, checkouts, and successful payments through telegram's in-app purchase
19+
// methods.
20+
// Use this if you want an example of how to sell things through telegram. The example targets Telegram Stars, which
21+
// allows bot developers to sell digital products through Telegram.
22+
func main() {
23+
// Get token from the environment variable
24+
token := os.Getenv("TOKEN")
25+
if token == "" {
26+
panic("TOKEN environment variable is empty")
27+
}
28+
29+
// Create bot from environment value.
30+
b, err := gotgbot.NewBot(token, nil)
31+
if err != nil {
32+
panic("failed to create new bot: " + err.Error())
33+
}
34+
35+
// Create updater and dispatcher.
36+
dispatcher := ext.NewDispatcher(&ext.DispatcherOpts{
37+
// If an error is returned by a handler, log it and continue going.
38+
Error: func(b *gotgbot.Bot, ctx *ext.Context, err error) ext.DispatcherAction {
39+
log.Println("an error occurred while handling update:", err.Error())
40+
return ext.DispatcherActionNoop
41+
},
42+
MaxRoutines: ext.DefaultMaxRoutines,
43+
})
44+
updater := ext.NewUpdater(dispatcher, nil)
45+
46+
// /start command to introduce the bot
47+
dispatcher.AddHandler(handlers.NewCommand("start", start))
48+
// PreCheckout to handle the step right before payment. Must be handled within 10s, or the checkout will be abandoned by telegram.
49+
dispatcher.AddHandler(handlers.NewPreCheckoutQuery(precheckoutquery.All, preCheckout))
50+
// Payment received; send/provide product to customer.
51+
dispatcher.AddHandler(handlers.NewMessage(message.SuccessfulPayment, paymentComplete))
52+
// Bots selling on telegram must be able to provide refunds; do so through the paysupport command, as mentioned in
53+
// the TOS: https://telegram.org/tos/stars#3-1-disputing-purchases
54+
dispatcher.AddHandler(handlers.NewCommand("paysupport", paySupport))
55+
56+
// Start receiving updates.
57+
err = updater.StartPolling(b, &ext.PollingOpts{
58+
DropPendingUpdates: true,
59+
GetUpdatesOpts: &gotgbot.GetUpdatesOpts{
60+
Timeout: 9,
61+
RequestOpts: &gotgbot.RequestOpts{
62+
Timeout: time.Second * 10,
63+
},
64+
},
65+
})
66+
if err != nil {
67+
panic("failed to start polling: " + err.Error())
68+
}
69+
log.Printf("%s has been started...\n", b.User.Username)
70+
71+
// Idle, to keep updates coming in, and avoid bot stopping.
72+
updater.Idle()
73+
}
74+
75+
// start introduces the bot and sends an initial invoice (in Telegram stars; denoted as XTR).
76+
func start(b *gotgbot.Bot, ctx *ext.Context) error {
77+
if ctx.EffectiveChat.Type != "private" {
78+
// Only reply in private chats.
79+
return nil
80+
}
81+
82+
// Introduce the bot.
83+
_, err := ctx.EffectiveMessage.Reply(b, fmt.Sprintf("Hello, I'm @%s. I demonstrate how telegram payments might work.", b.User.Username), &gotgbot.SendMessageOpts{
84+
ParseMode: "HTML",
85+
})
86+
if err != nil {
87+
return fmt.Errorf("failed to send start message: %w", err)
88+
}
89+
90+
// Generate a unique payload for this checkout.
91+
// In a production environment, you could create a database containing any additional information you might need to
92+
// complete this transaction, and refer to it at the preCheckout and successful payment stages.
93+
// For example, you may want to store the invoice creator's ID to ensure that only the creator can pay, and any
94+
// other necessary data you have collected.
95+
payload := uuid.NewString()
96+
97+
// Send the invoice. XTR == Telegram stars, for selling digital products.
98+
_, err = b.SendInvoice(ctx.EffectiveChat.Id, "Product Name", "Some detailed description", payload, "XTR", []gotgbot.LabeledPrice{{
99+
Label: "Some product",
100+
Amount: 100, // 100 stars.
101+
}}, &gotgbot.SendInvoiceOpts{
102+
ProtectContent: true, // Stop people from forwarding this invoice to others.
103+
})
104+
if err != nil {
105+
return fmt.Errorf("failed to generate invoice: %w", err)
106+
}
107+
108+
return nil
109+
}
110+
111+
func preCheckout(b *gotgbot.Bot, ctx *ext.Context) error {
112+
// Do any required preCheckout validation here. If anything failed, we should answer the query with "ok: False",
113+
// and populate the ErrorMessage field in the opts.
114+
// For example, you may want to ensure that the user who requested the invoice is the same person as the person who
115+
// is checking out; but this would require storage, so isn't shown here.
116+
117+
// Answer true once checks have passed.
118+
_, err := ctx.PreCheckoutQuery.Answer(b, true, nil)
119+
if err != nil {
120+
return fmt.Errorf("failed to answer precheckout query: %w", err)
121+
}
122+
return nil
123+
}
124+
125+
func paymentComplete(b *gotgbot.Bot, ctx *ext.Context) error {
126+
// Payment has been received; a real bot would now provide the user with the product.
127+
_, err := ctx.EffectiveMessage.Reply(b, "Payment complete - in a real bot, this is where you would provision the product that has been paid for.", nil)
128+
if err != nil {
129+
return fmt.Errorf("failed to send payment complete message: %w", err)
130+
}
131+
return nil
132+
}
133+
134+
func paySupport(b *gotgbot.Bot, ctx *ext.Context) error {
135+
_, err := ctx.EffectiveMessage.Reply(b, "Explain your refund process here.", nil)
136+
if err != nil {
137+
return fmt.Errorf("failed to describe refund process: %w", err)
138+
}
139+
return nil
140+
}

0 commit comments

Comments
 (0)