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.*"
TopicProjectLabel = "project_label.*"
EventUserUpdate = "user.update"
EventUserDelete = "user.delete"
EventHeartbeatCreate = "heartbeat.create"
EventProjectLabelCreate = "project_label.create"
EventProjectLabelDelete = "project_label.delete"

View File

@ -6,6 +6,7 @@ import (
"fmt"
"github.com/emvi/logbuch"
"github.com/go-chi/chi/v5"
"github.com/leandro-lugaresi/hub"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/models"
@ -16,6 +17,7 @@ import (
stripeCheckoutSession "github.com/stripe/stripe-go/v74/checkout/session"
stripeCustomer "github.com/stripe/stripe-go/v74/customer"
stripePrice "github.com/stripe/stripe-go/v74/price"
stripeSubscription "github.com/stripe/stripe-go/v74/subscription"
"github.com/stripe/stripe-go/v74/webhook"
"io/ioutil"
"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'
*/
// TODO: move all logic inside this controller into a separate service
type SubscriptionHandler struct {
config *conf.Config
eventBus *hub.Hub
userSrvc services.IUserService
mailSrvc services.IMailService
keyValueSrvc services.IKeyValueService
@ -46,6 +51,7 @@ func NewSubscriptionHandler(
keyValueService services.IKeyValueService,
) *SubscriptionHandler {
config := conf.Get()
eventBus := conf.EventBus()
if config.Subscriptions.Enabled {
stripe.Key = config.Subscriptions.StripeSecretKey
@ -59,13 +65,32 @@ func NewSubscriptionHandler(
logbuch.Info("enabling subscriptions with stripe payment for %s / month", config.Subscriptions.StandardPrice)
}
return &SubscriptionHandler{
handler := &SubscriptionHandler{
config: config,
userSrvc: userService,
mailSrvc: mailService,
keyValueSrvc: keyValueService,
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
@ -325,6 +350,16 @@ func (h *SubscriptionHandler) parseCheckoutSessionEvent(w http.ResponseWriter, r
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) {
params := &stripe.CustomerSearchParams{
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) {
key := fmt.Sprintf("%s_%s", conf.KeySubscriptionNotificationSent, userId)
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
srv.notifyUpdate(user)
srv.notifyDelete(user)
return srv.repository.Delete(user)
}
@ -232,3 +233,10 @@ func (srv *UserService) notifyUpdate(user *models.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},
})
}