mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
fix: cancel active subscription upon user account deletion
This commit is contained in:
parent
44c481b9e0
commit
a20456bb8e
@ -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"
|
||||||
|
@ -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: ¶mStatus,
|
||||||
|
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 {
|
||||||
|
@ -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},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user