- commit
- d22ca60
- parent
- f350303
- author
- Eric Bower
- date
- 2024-03-17 15:53:52 +0000 UTC
feat(auth): stripe webhook integration (#101)
3 files changed,
+79,
-0
+75,
-0
1@@ -5,6 +5,7 @@ import (
2 "encoding/json"
3 "fmt"
4 "html/template"
5+ "io"
6 "log/slog"
7 "net/http"
8 "net/url"
9@@ -15,6 +16,7 @@ import (
10 "github.com/picosh/pico/db"
11 "github.com/picosh/pico/db/postgres"
12 "github.com/picosh/pico/shared"
13+ stripe "github.com/stripe/stripe-go/v75"
14 )
15
16 type Client struct {
17@@ -393,6 +395,78 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
18 }
19 }
20
21+func stripeWebhookHandler(w http.ResponseWriter, r *http.Request) {
22+ client := getClient(r)
23+ dbpool := client.Dbpool
24+ logger := client.Logger
25+ const MaxBodyBytes = int64(65536)
26+ r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes)
27+ payload, err := io.ReadAll(r.Body)
28+ if err != nil {
29+ logger.Error("error reading request body", "err", err.Error())
30+ w.WriteHeader(http.StatusServiceUnavailable)
31+ return
32+ }
33+
34+ event := stripe.Event{}
35+
36+ if err := json.Unmarshal(payload, &event); err != nil {
37+ logger.Error("failed to parse webhook body JSON", "err", err.Error())
38+ w.WriteHeader(http.StatusBadRequest)
39+ return
40+ }
41+
42+ switch event.Type {
43+ case "checkout.session.completed":
44+ var checkout stripe.CheckoutSession
45+ err := json.Unmarshal(event.Data.Raw, &checkout)
46+ if err != nil {
47+ logger.Error("error parsing webhook JSON", "err", err.Error())
48+ w.WriteHeader(http.StatusBadRequest)
49+ return
50+ }
51+ username := checkout.ClientReferenceID
52+ txId := ""
53+ if checkout.PaymentIntent != nil {
54+ txId = checkout.PaymentIntent.ID
55+ }
56+ email := ""
57+ if checkout.CustomerDetails != nil {
58+ email = checkout.CustomerDetails.Email
59+ }
60+ created := checkout.Created
61+ status := checkout.PaymentStatus
62+
63+ log := logger.With(
64+ "username", username,
65+ "email", email,
66+ "created", created,
67+ "paymentStatus", status,
68+ "txId", txId,
69+ )
70+ log.Info(
71+ "stripe:checkout.session.completed",
72+ )
73+
74+ if username == "" {
75+ log.Error("no `?client_reference_id={username}` found in URL, cannot add pico+ membership")
76+ w.WriteHeader(http.StatusOK)
77+ return
78+ }
79+
80+ err = dbpool.AddPicoPlusUser(username, "stripe", txId)
81+ if err != nil {
82+ log.Error("failed to add pico+ user", "err", err)
83+ } else {
84+ log.Info("successfully added pico+ user")
85+ }
86+ default:
87+ // logger.Info("unhandled event type", "type", event.Type)
88+ }
89+
90+ w.WriteHeader(http.StatusOK)
91+}
92+
93 func createMainRoutes() []shared.Route {
94 fileServer := http.FileServer(http.Dir("auth/public"))
95
96@@ -404,6 +478,7 @@ func createMainRoutes() []shared.Route {
97 shared.NewRoute("POST", "/key", keyHandler),
98 shared.NewRoute("GET", "/rss/(.+)", rssHandler),
99 shared.NewRoute("POST", "/redirect", redirectHandler),
100+ shared.NewRoute("POST", "/webhook", stripeWebhookHandler),
101 shared.NewRoute("GET", "/main.css", fileServer.ServeHTTP),
102 shared.NewRoute("GET", "/card.png", fileServer.ServeHTTP),
103 shared.NewRoute("GET", "/favicon-16x16.png", fileServer.ServeHTTP),
M
go.mod
+1,
-0
1@@ -32,6 +32,7 @@ require (
2 github.com/picosh/ptun v0.0.0-20240313192814-d0ca401937fe
3 github.com/picosh/send v0.0.0-20240217194807-77b972121e63
4 github.com/sendgrid/sendgrid-go v3.13.0+incompatible
5+ github.com/stripe/stripe-go/v75 v75.11.0
6 github.com/yuin/goldmark v1.6.0
7 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
8 github.com/yuin/goldmark-meta v1.1.0
M
go.sum
+3,
-0
1@@ -243,6 +243,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
2 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
3 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
4 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
5+github.com/stripe/stripe-go/v75 v75.11.0 h1:jLbHQGRrptDS815sMKFFbTqVtrh+ugzO39zRVaU1Xe8=
6+github.com/stripe/stripe-go/v75 v75.11.0/go.mod h1:wT44gah+eCY8Z0aSpY/vQlYYbicU9uUAbAqdaUxxDqE=
7 github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
8 github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
9 github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
10@@ -280,6 +282,7 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/
11 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
12 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
13 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
14+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
15 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
16 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
17 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=