Skip to content

Commit 7cef8e1

Browse files
Only handle known event type with intended price ID
1 parent 1f59f8f commit 7cef8e1

File tree

2 files changed

+95
-70
lines changed

2 files changed

+95
-70
lines changed

cmd/cli/main.go

Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"context"
5-
"encoding/base64"
65
"encoding/json"
76
"errors"
87
"fmt"
@@ -19,7 +18,6 @@ import (
1918
"github.com/joho/godotenv"
2019
slogmulti "github.com/samber/slog-multi"
2120
"github.com/spf13/cobra"
22-
"github.com/stripe/stripe-go/v82"
2321
"github.com/stripe/stripe-go/v82/client"
2422
"gopkg.in/gomail.v2"
2523

@@ -315,8 +313,17 @@ func Handler_v1_stripe_webhook(w http.ResponseWriter, r *http.Request) {
315313
logger := slogging.GetLogger(ctx)
316314
deps := GetDependencies(ctx)
317315

318-
e, err := pkgstripe.ConstructEvent(r, deps.StripeWebhookSigningSecret)
316+
e, err := pkgstripe.ConstructEvent(ctx, deps.StripeClient, r, pkgstripe.ConstructEventOptions{
317+
SigningSecret: deps.StripeWebhookSigningSecret,
318+
PriceID: deps.StripeCheckoutSessionPriceID,
319+
})
319320
if err != nil {
321+
if errors.Is(err, pkgstripe.ErrUnknownEvent) {
322+
// Ignore the event by returning 200
323+
slogging.Error(ctx, logger, "ignore unknown event", "stripe_event_id", e.ID)
324+
return
325+
}
326+
320327
slogging.Error(ctx, logger, "failed to construct webhook event",
321328
"error", err)
322329
if !pkgstripe.IsWebhookClientError(err) {
@@ -326,73 +333,61 @@ func Handler_v1_stripe_webhook(w http.ResponseWriter, r *http.Request) {
326333
}
327334
return
328335
}
336+
logger = logger.With("stripe_event_id", e.ID)
337+
slogging.Info(ctx, logger, "handling event")
329338

330-
switch e.Type {
331-
case stripe.EventTypeCheckoutSessionCompleted:
332-
b, err := json.Marshal(e)
333-
if err != nil {
334-
panic(err)
335-
}
336-
logger = logger.With("stripe_event_json_base64url", base64.RawURLEncoding.EncodeToString(b))
337-
338-
checkoutSessionID, ok := pkgstripe.GetCheckoutSessionID(e)
339-
if !ok {
340-
slogging.Error(ctx, logger, "checkout session ID not found")
341-
http.Error(w, "checkout session ID not found", http.StatusInternalServerError)
342-
return
343-
}
339+
checkoutSessionID := pkgstripe.GetEventDataID(e)
340+
logger = logger.With("stripe_checkout_session_id", checkoutSessionID)
344341

345-
customerID, ok := pkgstripe.GetCustomerID(e)
346-
if !ok {
347-
slogging.Error(ctx, logger, "customer id not found")
348-
http.Error(w, "customer id not found", http.StatusInternalServerError)
349-
return
350-
}
342+
customerID, ok := pkgstripe.GetCustomerID(e)
343+
if !ok {
344+
slogging.Error(ctx, logger, "customer id not found")
345+
http.Error(w, "customer id not found", http.StatusInternalServerError)
346+
return
347+
}
348+
logger = logger.With("stripe_customer_id", customerID)
351349

352-
email, ok := pkgstripe.GetCustomerEmail(e)
353-
if !ok {
354-
slogging.Error(ctx, logger, "customer email not found")
355-
http.Error(w, "customer email not found", http.StatusInternalServerError)
356-
return
357-
}
350+
email, ok := pkgstripe.GetCustomerEmail(e)
351+
if !ok {
352+
slogging.Error(ctx, logger, "customer email not found")
353+
http.Error(w, "customer email not found", http.StatusInternalServerError)
354+
return
355+
}
358356

359-
licenseKey, err := keygen.CreateLicenseKey(ctx, deps.HTTPClient, keygen.CreateLicenseKeyOptions{
360-
KeygenConfig: deps.KeygenConfig,
361-
StripeCheckoutSessionID: checkoutSessionID,
362-
StripeCustomerID: customerID,
363-
})
364-
if err != nil {
365-
slogging.Error(ctx, logger, "failed to create license key",
366-
"error", err)
367-
http.Error(w, "failed to create license key", http.StatusInternalServerError)
368-
return
369-
}
357+
licenseKey, err := keygen.CreateLicenseKey(ctx, deps.HTTPClient, keygen.CreateLicenseKeyOptions{
358+
KeygenConfig: deps.KeygenConfig,
359+
StripeCheckoutSessionID: checkoutSessionID,
360+
StripeCustomerID: customerID,
361+
})
362+
if err != nil {
363+
slogging.Error(ctx, logger, "failed to create license key",
364+
"error", err)
365+
http.Error(w, "failed to create license key", http.StatusInternalServerError)
366+
return
367+
}
370368

371-
u := ConstructFullURL(r)
372-
u.Path = fmt.Sprintf("/install/%v", licenseKey)
369+
u := ConstructFullURL(r)
370+
u.Path = fmt.Sprintf("/install/%v", licenseKey)
373371

374-
htmlBody := emailtemplate.RenderInstallationEmail(emailtemplate.InstallationEmailData{
375-
InstallationOneliner: fmt.Sprintf(`/bin/sh -c "$(curl -fsSL %v)"`, u.String()),
376-
})
372+
htmlBody := emailtemplate.RenderInstallationEmail(emailtemplate.InstallationEmailData{
373+
InstallationOneliner: fmt.Sprintf(`/bin/sh -c "$(curl -fsSL %v)"`, u.String()),
374+
})
377375

378-
opts := smtp.EmailOptions{
379-
Sender: deps.SMTPSender,
380-
Subject: "Installing Authgear once",
381-
HTMLBody: htmlBody,
382-
To: email,
383-
}
376+
opts := smtp.EmailOptions{
377+
Sender: deps.SMTPSender,
378+
Subject: "Installing Authgear once",
379+
HTMLBody: htmlBody,
380+
To: email,
381+
}
384382

385-
err = smtp.SendEmail(deps.SMTPDialer, opts)
386-
if err != nil {
387-
slogging.Error(ctx, logger, "failed to send email",
388-
"error", err)
389-
http.Error(w, "failed to send email", http.StatusInternalServerError)
390-
} else {
391-
slogging.Info(ctx, logger, "sent installation to checkout session",
392-
"checkout_session_id", checkoutSessionID,
393-
"customer_id", customerID)
394-
// Return 200 implicitly.
395-
}
383+
err = smtp.SendEmail(deps.SMTPDialer, opts)
384+
if err != nil {
385+
slogging.Error(ctx, logger, "failed to send email",
386+
"error", err)
387+
http.Error(w, "failed to send email", http.StatusInternalServerError)
388+
} else {
389+
slogging.Info(ctx, logger, "sent installation to checkout session")
390+
// Return 200 implicitly.
396391
}
397392
}
398393

pkg/stripe/webhook.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
11
package stripe
22

33
import (
4+
"context"
45
"errors"
6+
"fmt"
57
"io"
68
"net/http"
79

810
"github.com/stripe/stripe-go/v82"
11+
"github.com/stripe/stripe-go/v82/client"
912
"github.com/stripe/stripe-go/v82/webhook"
1013
)
1114

12-
func ConstructEvent(r *http.Request, signingSecret string) (*stripe.Event, error) {
15+
var ErrUnknownEvent = errors.New("pkgstripe: unknown event")
16+
17+
type ConstructEventOptions struct {
18+
SigningSecret string
19+
PriceID string
20+
}
21+
22+
func ConstructEvent(ctx context.Context, client *client.API, r *http.Request, opts ConstructEventOptions) (*stripe.Event, error) {
1323
body, err := io.ReadAll(r.Body)
1424
if err != nil {
1525
return nil, err
1626
}
1727

1828
sig := r.Header.Get("Stripe-Signature")
19-
e, err := webhook.ConstructEvent(body, sig, signingSecret)
29+
e, err := webhook.ConstructEvent(body, sig, opts.SigningSecret)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
if e.Type != stripe.EventTypeCheckoutSessionCompleted {
35+
return &e, ErrUnknownEvent
36+
}
37+
38+
checkoutSessionID := GetEventDataID(&e)
39+
checkoutSession, err := client.CheckoutSessions.Get(checkoutSessionID, &stripe.CheckoutSessionParams{
40+
Expand: []*string{
41+
stripe.String("line_items"),
42+
},
43+
})
2044
if err != nil {
2145
return nil, err
2246
}
2347

24-
return &e, nil
48+
for _, lineItem := range checkoutSession.LineItems.Data {
49+
if lineItem.Price.ID == opts.PriceID {
50+
return &e, nil
51+
}
52+
}
53+
54+
return &e, ErrUnknownEvent
2555
}
2656

2757
func IsWebhookClientError(err error) bool {
@@ -196,10 +226,10 @@ func GetCustomerEmail(e *stripe.Event) (string, bool) {
196226
return email, true
197227
}
198228

199-
func GetCheckoutSessionID(e *stripe.Event) (string, bool) {
200-
id, ok := e.Data.Object["id"].(string)
201-
if !ok {
202-
return "", false
229+
func GetEventDataID(e *stripe.Event) string {
230+
id := e.Data.Object["id"].(string)
231+
if id == "" {
232+
panic(fmt.Errorf("stripe event data has no ID"))
203233
}
204-
return id, true
234+
return id
205235
}

0 commit comments

Comments
 (0)