mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
[public-api] handle customer.subscription.deleted event
This event is fired by Stripe when a customer cancels their subscription
This commit is contained in:
parent
9c9369b4e5
commit
1b76bca17d
@ -8,13 +8,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gitpod-io/gitpod/usage-api/v1"
|
||||
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
FinalizeInvoice(ctx context.Context, invoiceId string) error
|
||||
CancelSubscription(ctx context.Context, subscriptionId string) error
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@ -38,3 +39,12 @@ func (c *Client) FinalizeInvoice(ctx context.Context, invoiceId string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CancelSubscription(ctx context.Context, subscriptionId string) error {
|
||||
_, err := c.b.CancelSubscription(ctx, &v1.CancelSubscriptionRequest{SubscriptionId: subscriptionId})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed RPC to billing service: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -51,3 +51,17 @@ func (mr *MockInterfaceMockRecorder) FinalizeInvoice(ctx, invoiceId interface{})
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinalizeInvoice", reflect.TypeOf((*MockInterface)(nil).FinalizeInvoice), ctx, invoiceId)
|
||||
}
|
||||
|
||||
// CancelSubscription mocks base method.
|
||||
func (m *MockInterface) CancelSubscription(ctx context.Context, subscriptionId string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CancelSubscription", ctx, subscriptionId)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CancelSubscription indicates an expected call of CancelSubscription.
|
||||
func (mr *MockInterfaceMockRecorder) CancelSubscription(ctx, subscriptionId interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelSubscription", reflect.TypeOf((*MockInterface)(nil).CancelSubscription), ctx, subscriptionId)
|
||||
}
|
||||
|
||||
@ -11,3 +11,7 @@ type NoOpClient struct{}
|
||||
func (c *NoOpClient) FinalizeInvoice(ctx context.Context, invoiceId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NoOpClient) CancelSubscription(ctx context.Context, subscriptionId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@
|
||||
package webhooks
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gitpod-io/gitpod/common-go/log"
|
||||
"github.com/gitpod-io/gitpod/public-api-server/pkg/billingservice"
|
||||
"github.com/stripe/stripe-go/v72/webhook"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const maxBodyBytes = int64(65536)
|
||||
@ -56,12 +57,8 @@ func (h *webhookHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if event.Type != "invoice.finalized" {
|
||||
log.Errorf("Unexpected Stripe event type: %s", event.Type)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch event.Type {
|
||||
case "invoice.finalized":
|
||||
invoiceId, ok := event.Data.Object["id"].(string)
|
||||
if !ok {
|
||||
log.Error("failed to find invoice id in Stripe event payload")
|
||||
@ -74,4 +71,22 @@ func (h *webhookHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case "customer.subscription.deleted":
|
||||
subscriptionId, ok := event.Data.Object["id"].(string)
|
||||
if !ok {
|
||||
log.Error("failed to find subscriptionId id in Stripe event payload")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
err = h.billingService.CancelSubscription(req.Context(), subscriptionId)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to cancel subscription")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Errorf("Unexpected Stripe event type: %s", event.Type)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -8,11 +8,12 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/stripe/stripe-go/v72/webhook"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stripe/stripe-go/v72/webhook"
|
||||
|
||||
"github.com/gitpod-io/gitpod/common-go/baseserver"
|
||||
"github.com/gitpod-io/gitpod/public-api-server/pkg/billingservice"
|
||||
mockbillingservice "github.com/gitpod-io/gitpod/public-api-server/pkg/billingservice/mock_billingservice"
|
||||
@ -25,6 +26,7 @@ const (
|
||||
invoiceUpdatedEventType = "invoice.updated"
|
||||
invoiceFinalizedEventType = "invoice.finalized"
|
||||
customerCreatedEventType = "customer.created"
|
||||
customerSubscriptionDeleted = "customer.subscription.deleted"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -80,6 +82,10 @@ func TestWebhookIgnoresIrrelevantEvents(t *testing.T) {
|
||||
EventType: invoiceFinalizedEventType,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
EventType: customerSubscriptionDeleted,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
EventType: invoiceUpdatedEventType,
|
||||
ExpectedStatusCode: http.StatusBadRequest,
|
||||
@ -134,6 +140,26 @@ func TestWebhookInvokesFinalizeInvoiceRPC(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestWebhookInvokesCancelSubscriptionRPC(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
m := mockbillingservice.NewMockInterface(ctrl)
|
||||
m.EXPECT().CancelSubscription(gomock.Any(), gomock.Eq("in_1LUQi7GadRXm50o36jWK7ehs"))
|
||||
|
||||
srv := baseServerWithStripeWebhook(t, m)
|
||||
|
||||
url := fmt.Sprintf("%s%s", srv.HTTPAddress(), "/webhook")
|
||||
|
||||
payload := payloadForStripeEvent(t, customerSubscriptionDeleted)
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set("Stripe-Signature", generateHeader(payload, testWebhookSecret))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func baseServerWithStripeWebhook(t *testing.T, billingService billingservice.Interface) *baseserver.Server {
|
||||
t.Helper()
|
||||
|
||||
@ -150,19 +176,14 @@ func baseServerWithStripeWebhook(t *testing.T, billingService billingservice.Int
|
||||
func payloadForStripeEvent(t *testing.T, eventType string) []byte {
|
||||
t.Helper()
|
||||
|
||||
if eventType != invoiceFinalizedEventType {
|
||||
return []byte(`{}`)
|
||||
}
|
||||
return []byte(`
|
||||
{
|
||||
return []byte(`{
|
||||
"data": {
|
||||
"object": {
|
||||
"id": "in_1LUQi7GadRXm50o36jWK7ehs"
|
||||
}
|
||||
},
|
||||
"type": "invoice.finalized"
|
||||
}
|
||||
`)
|
||||
"type": "` + eventType + `"
|
||||
}`)
|
||||
}
|
||||
|
||||
func generateHeader(payload []byte, secret string) string {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user