Path: blob/main/components/public-api-server/pkg/webhooks/stripe.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package webhooks56import (7"io"8"net/http"910"github.com/gitpod-io/gitpod/common-go/log"11"github.com/gitpod-io/gitpod/public-api-server/pkg/billingservice"12"github.com/sirupsen/logrus"13"github.com/stripe/stripe-go/v72/webhook"14)1516const maxBodyBytes = int64(65536)1718const (19InvoiceFinalizedEventType = "invoice.finalized"20CustomerSubscriptionDeletedEventType = "customer.subscription.deleted"21ChargeDisputeCreatedEventType = "charge.dispute.created"22CustomerUpdatedEventType = "customer.updated"23)2425type webhookHandler struct {26billingService billingservice.Interface27stripeWebhookSignature string28}2930func NewStripeWebhookHandler(billingService billingservice.Interface, stripeWebhookSignature string) *webhookHandler {31return &webhookHandler{32billingService: billingService,33stripeWebhookSignature: stripeWebhookSignature,34}35}3637func (h *webhookHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {38if req.Method != http.MethodPost {39log.Errorf("Bad HTTP method: %s", req.Method)40w.WriteHeader(http.StatusMethodNotAllowed)41return42}4344stripeSignature := req.Header.Get("Stripe-Signature")45if stripeSignature == "" {46w.WriteHeader(http.StatusBadRequest)47return48}4950req.Body = http.MaxBytesReader(w, req.Body, maxBodyBytes)5152payload, err := io.ReadAll(req.Body)53if err != nil {54log.WithError(err).Error("Failed to read payload body.")55w.WriteHeader(http.StatusBadRequest)56return57}5859// https://stripe.com/docs/webhooks/signatures#verify-official-libraries60event, err := webhook.ConstructEvent(payload, stripeSignature, h.stripeWebhookSignature)61if err != nil {62log.WithError(err).Error("Failed to verify webhook signature.")63w.WriteHeader(http.StatusBadRequest)64return65}6667logger := log.WithFields(logrus.Fields{"event": event.ID})6869switch event.Type {70case InvoiceFinalizedEventType:71logger.Info("Handling invoice finalization")72invoiceID, ok := event.Data.Object["id"].(string)73if !ok {74logger.Error("failed to find invoice id in Stripe event payload")75w.WriteHeader(http.StatusBadRequest)76return77}7879err = h.billingService.FinalizeInvoice(req.Context(), invoiceID)80if err != nil {81logger.WithError(err).Error("Failed to finalize invoice")82w.WriteHeader(http.StatusInternalServerError)83return84}85case CustomerSubscriptionDeletedEventType:86logger.Info("Handling subscription cancellation")87subscriptionID, ok := event.Data.Object["id"].(string)88if !ok {89logger.Error("failed to find subscriptionId id in Stripe event payload")90w.WriteHeader(http.StatusBadRequest)91return92}93err = h.billingService.CancelSubscription(req.Context(), subscriptionID)94if err != nil {95logger.WithError(err).Error("Failed to cancel subscription")96w.WriteHeader(http.StatusInternalServerError)97return98}99case ChargeDisputeCreatedEventType:100logger.Info("Handling charge dispute")101disputeID, ok := event.Data.Object["id"].(string)102if !ok {103logger.Error("Failed to identify dispute ID from Stripe webhook.")104w.WriteHeader(http.StatusBadRequest)105return106}107108if err := h.billingService.OnChargeDispute(req.Context(), disputeID); err != nil {109logger.WithError(err).Errorf("Failed to handle charge dispute event for dispute ID: %s", disputeID)110w.WriteHeader(http.StatusInternalServerError)111return112}113case CustomerUpdatedEventType:114logger.Info("Handling CustomerUpdatedEventType")115customerID, ok := event.Data.Object["id"].(string)116if !ok {117logger.Error("Failed to identify customer ID from Stripe webhook.")118w.WriteHeader(http.StatusBadRequest)119return120}121122if _, ok := event.Data.PreviousAttributes["address"]; ok {123logger.Infof("Customer with ID %s address updated", customerID)124125err = h.billingService.UpdateCustomerSubscriptionsTaxState(req.Context(), customerID)126if err != nil {127logger.WithError(err).Error("Failed to cancel subscription")128w.WriteHeader(http.StatusInternalServerError)129return130}131}132default:133logger.Errorf("Unexpected Stripe event type: %s", event.Type)134w.WriteHeader(http.StatusBadRequest)135return136}137138}139140141