package server import ( "context" "encoding/json" "io" "net/http" "github.com/stripe/stripe-go/v83" "github.com/stripe/stripe-go/v83/webhook" ) type stripeEventService interface { HandlePaymentIntentSucceeded(ctx context.Context, pi *stripe.PaymentIntent) error HandleChargeRefunded(ctx context.Context, ch *stripe.Charge) error } var constructEvent = webhook.ConstructEvent func handleStripeWebhook(bs stripeEventService, secret string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if secret == "" { http.Error(w, "stripe webhook secret not configured", http.StatusServiceUnavailable) return } payload, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "unable to read request body", http.StatusBadRequest) return } sig := r.Header.Get("Stripe-Signature") if sig == "" { http.Error(w, "missing Stripe-Signature header", http.StatusBadRequest) return } event, err := constructEvent(payload, sig, secret) if err != nil { http.Error(w, "invalid webhook signature", http.StatusBadRequest) return } switch event.Type { case stripe.EventTypePaymentIntentSucceeded: var pi stripe.PaymentIntent if err := json.Unmarshal(event.Data.Raw, &pi); err != nil { http.Error(w, "invalid payment intent payload", http.StatusBadRequest) return } if err := bs.HandlePaymentIntentSucceeded(r.Context(), &pi); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } case stripe.EventTypeChargeRefunded: var ch stripe.Charge if err := json.Unmarshal(event.Data.Raw, &ch); err != nil { http.Error(w, "invalid charge payload", http.StatusBadRequest) return } if err := bs.HandleChargeRefunded(r.Context(), &ch); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } default: // Acknowledge events we don't actively process. } w.WriteHeader(http.StatusOK) } }