fix: cancel active subscription upon user account deletion

This commit is contained in:
Ferdinand Mütsch 2023-04-09 17:29:57 +02:00
parent 44c481b9e0
commit a20456bb8e
3 changed files with 63 additions and 1 deletions

View File

@ -12,6 +12,7 @@ const (
TopicHeartbeat = "heartbeat.*" TopicHeartbeat = "heartbeat.*"
TopicProjectLabel = "project_label.*" TopicProjectLabel = "project_label.*"
EventUserUpdate = "user.update" EventUserUpdate = "user.update"
EventUserDelete = "user.delete"
EventHeartbeatCreate = "heartbeat.create" EventHeartbeatCreate = "heartbeat.create"
EventProjectLabelCreate = "project_label.create" EventProjectLabelCreate = "project_label.create"
EventProjectLabelDelete = "project_label.delete" EventProjectLabelDelete = "project_label.delete"

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/emvi/logbuch" "github.com/emvi/logbuch"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/leandro-lugaresi/hub"
conf "github.com/muety/wakapi/config" conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models" "github.com/muety/wakapi/models"
@ -16,6 +17,7 @@ import (
stripeCheckoutSession "github.com/stripe/stripe-go/v74/checkout/session" stripeCheckoutSession "github.com/stripe/stripe-go/v74/checkout/session"
stripeCustomer "github.com/stripe/stripe-go/v74/customer" stripeCustomer "github.com/stripe/stripe-go/v74/customer"
stripePrice "github.com/stripe/stripe-go/v74/price" stripePrice "github.com/stripe/stripe-go/v74/price"
stripeSubscription "github.com/stripe/stripe-go/v74/subscription"
"github.com/stripe/stripe-go/v74/webhook" "github.com/stripe/stripe-go/v74/webhook"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -32,8 +34,11 @@ import (
4. Copy the publishable API key (https://dashboard.stripe.com/test/apikeys) and save it to 'stripe_api_key' 4. Copy the publishable API key (https://dashboard.stripe.com/test/apikeys) and save it to 'stripe_api_key'
*/ */
// TODO: move all logic inside this controller into a separate service
type SubscriptionHandler struct { type SubscriptionHandler struct {
config *conf.Config config *conf.Config
eventBus *hub.Hub
userSrvc services.IUserService userSrvc services.IUserService
mailSrvc services.IMailService mailSrvc services.IMailService
keyValueSrvc services.IKeyValueService keyValueSrvc services.IKeyValueService
@ -46,6 +51,7 @@ func NewSubscriptionHandler(
keyValueService services.IKeyValueService, keyValueService services.IKeyValueService,
) *SubscriptionHandler { ) *SubscriptionHandler {
config := conf.Get() config := conf.Get()
eventBus := conf.EventBus()
if config.Subscriptions.Enabled { if config.Subscriptions.Enabled {
stripe.Key = config.Subscriptions.StripeSecretKey stripe.Key = config.Subscriptions.StripeSecretKey
@ -59,13 +65,32 @@ func NewSubscriptionHandler(
logbuch.Info("enabling subscriptions with stripe payment for %s / month", config.Subscriptions.StandardPrice) logbuch.Info("enabling subscriptions with stripe payment for %s / month", config.Subscriptions.StandardPrice)
} }
return &SubscriptionHandler{ handler := &SubscriptionHandler{
config: config, config: config,
userSrvc: userService, userSrvc: userService,
mailSrvc: mailService, mailSrvc: mailService,
keyValueSrvc: keyValueService, keyValueSrvc: keyValueService,
httpClient: &http.Client{Timeout: 10 * time.Second}, httpClient: &http.Client{Timeout: 10 * time.Second},
} }
onUserDelete := eventBus.Subscribe(0, conf.EventUserDelete)
go func(sub *hub.Subscription) {
for m := range sub.Receiver {
user := m.Fields[conf.FieldPayload].(*models.User)
if !user.HasActiveSubscription() {
continue
}
logbuch.Info("cancelling subscription for user '%s' (email '%s', stripe customer '%s') upon account deletion", user.ID, user.Email, user.StripeCustomerId)
if err := handler.cancelUserSubscription(user); err == nil {
logbuch.Info("successfully cancelled subscription for user '%s' (email '%s', stripe customer '%s')", user.ID, user.Email, user.StripeCustomerId)
} else {
conf.Log().Error("failed to cancel subscription for user '%s' (email '%s', stripe customer '%s') - %v", user.ID, user.Email, user.StripeCustomerId, err)
}
}
}(&onUserDelete)
return handler
} }
// https://stripe.com/docs/billing/quickstart?lang=go // https://stripe.com/docs/billing/quickstart?lang=go
@ -325,6 +350,16 @@ func (h *SubscriptionHandler) parseCheckoutSessionEvent(w http.ResponseWriter, r
return &checkoutSession, nil return &checkoutSession, nil
} }
func (h *SubscriptionHandler) cancelUserSubscription(user *models.User) error {
// TODO: directly store subscription id with user object
subscription, err := h.findCurrentStripeSubscription(user.StripeCustomerId)
if err != nil {
return err
}
_, err = stripeSubscription.Cancel(subscription.ID, nil)
return err
}
func (h *SubscriptionHandler) findStripeCustomerByEmail(email string) (*stripe.Customer, error) { func (h *SubscriptionHandler) findStripeCustomerByEmail(email string) (*stripe.Customer, error) {
params := &stripe.CustomerSearchParams{ params := &stripe.CustomerSearchParams{
SearchParams: stripe.SearchParams{ SearchParams: stripe.SearchParams{
@ -344,6 +379,24 @@ func (h *SubscriptionHandler) findStripeCustomerByEmail(email string) (*stripe.C
} }
} }
func (h *SubscriptionHandler) findCurrentStripeSubscription(customerId string) (*stripe.Subscription, error) {
paramStatus := "active"
params := &stripe.SubscriptionListParams{
Customer: &customerId,
Price: &h.config.Subscriptions.StandardPriceId,
Status: &paramStatus,
CurrentPeriodEndRange: &stripe.RangeQueryParams{
GreaterThan: time.Now().Unix(),
},
}
params.Filters.AddFilter("limit", "", "1")
if result := stripeSubscription.List(params); result.Next() {
return result.Subscription(), nil
}
return nil, fmt.Errorf("no active subscription found for customer '%s'", customerId)
}
func (h *SubscriptionHandler) clearSubscriptionNotificationStatus(userId string) { func (h *SubscriptionHandler) clearSubscriptionNotificationStatus(userId string) {
key := fmt.Sprintf("%s_%s", conf.KeySubscriptionNotificationSent, userId) key := fmt.Sprintf("%s_%s", conf.KeySubscriptionNotificationSent, userId)
if err := h.keyValueSrvc.DeleteString(key); err != nil { if err := h.keyValueSrvc.DeleteString(key); err != nil {

View File

@ -214,6 +214,7 @@ func (srv *UserService) Delete(user *models.User) error {
user.ReportsWeekly = false user.ReportsWeekly = false
srv.notifyUpdate(user) srv.notifyUpdate(user)
srv.notifyDelete(user)
return srv.repository.Delete(user) return srv.repository.Delete(user)
} }
@ -232,3 +233,10 @@ func (srv *UserService) notifyUpdate(user *models.User) {
Fields: map[string]interface{}{config.FieldPayload: user}, Fields: map[string]interface{}{config.FieldPayload: user},
}) })
} }
func (srv *UserService) notifyDelete(user *models.User) {
srv.eventBus.Publish(hub.Message{
Name: config.EventUserDelete,
Fields: map[string]interface{}{config.FieldPayload: user},
})
}