From 9a7afa3e7ac4ebccff575bc9fd7e5c683e184840 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 17:20:29 +0100 Subject: [PATCH 01/20] moved update plan to subscrition update event --- internal/rest/controllers/webhooks/stripe.go | 26 +++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 7e70725..7b77743 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -51,8 +51,8 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.Error(session) c.Log.Error(session.ID) c.Log.Error(event.Data.Object["customer"].(string)) - lineItems := h.BillingService.GetLineItems(session.ID).List() - c.Log.Error(lineItems) + // lineItems := h.BillingService.GetLineItems(session.ID).List() + // c.Log.Error(lineItems) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) if err != nil { @@ -68,16 +68,30 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { // TODO if not same plan remove old plan - if customerTeam.PaymentPlan == "TEST" { //lineItems.Price.Product.Name { - return c.NoContent(http.StatusOK) - } + // if customerTeam.PaymentPlan == "TEST" { //lineItems.Price.Product.Name { + // return c.NoContent(http.StatusOK) + // } customerID := event.Data.Object["customer"].(string) if customerTeam.StripeCustomerID == nil { customerTeam.StripeCustomerID = &customerID } - h.TeamService.UpdateTeam(c.Request().Context(), customerTeam) + err = h.TeamService.UpdateTeam(c.Request().Context(), customerTeam) + if err != nil { + c.Log.WithError(err).Debug("Error updating team") + return c.NoContent(http.StatusInternalServerError) + } + case "customer.subscription.updated": + var subscription stripe.Subscription + err := json.Unmarshal(event.Data.Raw, &subscription) + if err != nil { + c.Log.WithError(err).Debug("Error parsing webhook JSON") + return echo.ErrBadRequest + } + c.Log.Info(*subscription.Items.Data[0]) + // c.Log.Info(subscription.Plan) + c.Log.Info("customer.subscription.updated") default: c.Log.WithField("event", event.Type).Debug("Unhandled event type") From f216b31c8ada1d8cf308c199a7a24663f72c786d Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 17:29:10 +0100 Subject: [PATCH 02/20] debug logs --- internal/rest/controllers/webhooks/stripe.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 7b77743..90fd66a 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -60,6 +60,8 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { return c.NoContent(http.StatusInternalServerError) } + c.Log.Error(c.Request().Context()) + c.Log.Error(uint(teamID)) customerTeam, err := h.TeamService.GetByID(c.Request().Context(), uint(teamID)) if err != nil { c.Log.WithError(err).Debug("Error getting team by id", teamID) From 0be2abdbca0236394d7483e16b43aaa398893a09 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 17:32:22 +0100 Subject: [PATCH 03/20] fixed cancel and success url checkout --- internal/billing/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index 89dc609..9ac9d4f 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -55,8 +55,8 @@ func (s *ServiceImpl) PostConfig() StripeConfig { func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey string) (*stripe.CheckoutSession, error) { params := &stripe.CheckoutSessionParams{ - SuccessURL: stripe.String(s.Config.Domain + "/team/plan"), - CancelURL: stripe.String(s.Config.Domain + "/canceled.html"), + SuccessURL: stripe.String(s.Config.Domain + "/team/subscription"), + CancelURL: stripe.String(s.Config.Domain + "/team/subscription"), Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)), ClientReferenceID: stripe.String(strconv.FormatUint(uint64(team.ID), 10)), From eb8dc207247b474d1322b04d4c3aa9108c42eee7 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 17:43:58 +0100 Subject: [PATCH 04/20] instantiate teamservice for wehook --- internal/rest/controllers/webhooks/routes.go | 1 + internal/rest/controllers/webhooks/stripe.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/rest/controllers/webhooks/routes.go b/internal/rest/controllers/webhooks/routes.go index e56f156..ade1f45 100644 --- a/internal/rest/controllers/webhooks/routes.go +++ b/internal/rest/controllers/webhooks/routes.go @@ -24,6 +24,7 @@ func Register( ) { h := &Handlers{ BillingService: billingService, + TeamService: teamService, } root := e.Group( diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 90fd66a..3c34c44 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -51,8 +51,8 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.Error(session) c.Log.Error(session.ID) c.Log.Error(event.Data.Object["customer"].(string)) - // lineItems := h.BillingService.GetLineItems(session.ID).List() - // c.Log.Error(lineItems) + lineItems := h.BillingService.GetLineItems(session.ID).List() + c.Log.Error(lineItems) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) if err != nil { From 449bf8e62d5932733807dd381239e2af0d6f5850 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 17:55:41 +0100 Subject: [PATCH 05/20] get current lineitem --- internal/rest/controllers/webhooks/stripe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 3c34c44..efb3f55 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -51,7 +51,7 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.Error(session) c.Log.Error(session.ID) c.Log.Error(event.Data.Object["customer"].(string)) - lineItems := h.BillingService.GetLineItems(session.ID).List() + lineItems := h.BillingService.GetLineItems(session.ID).Current() c.Log.Error(lineItems) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) From 579e291d3bed687d8901eb33a23f21a55f3d77b8 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 18:09:32 +0100 Subject: [PATCH 06/20] added delete customer handling --- internal/rest/controllers/webhooks/stripe.go | 27 ++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index efb3f55..e294257 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -51,8 +51,8 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.Error(session) c.Log.Error(session.ID) c.Log.Error(event.Data.Object["customer"].(string)) - lineItems := h.BillingService.GetLineItems(session.ID).Current() - c.Log.Error(lineItems) + // lineItems := h.BillingService.GetLineItems(session.ID).Current() + // c.Log.Error(lineItems) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) if err != nil { @@ -95,6 +95,29 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { // c.Log.Info(subscription.Plan) c.Log.Info("customer.subscription.updated") + case "customer.deleted": + var subscription stripe.Customer + err := json.Unmarshal(event.Data.Raw, &subscription) + if err != nil { + c.Log.WithError(err).Debug("Error parsing webhook JSON") + return echo.ErrBadRequest + } + + team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.ID) + if err != nil { + c.Log.WithError(err).Debug("Error getting team by stripe id") + return c.NoContent(http.StatusInternalServerError) + } + + team.PaymentPlan = "FREE" + team.StripeCustomerID = nil + + err = h.TeamService.UpdateTeam(c.Request().Context(), team) + if err != nil { + c.Log.WithError(err).Debug("Error updating team") + return c.NoContent(http.StatusInternalServerError) + } + default: c.Log.WithField("event", event.Type).Debug("Unhandled event type") // unhandled event type From fa4f37c7e40f549ecc9d2b79a5eac1e925de5f0d Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 18:30:09 +0100 Subject: [PATCH 07/20] getline item --- internal/rest/controllers/webhooks/stripe.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index e294257..3baf664 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -51,8 +51,9 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.Error(session) c.Log.Error(session.ID) c.Log.Error(event.Data.Object["customer"].(string)) - // lineItems := h.BillingService.GetLineItems(session.ID).Current() - // c.Log.Error(lineItems) + lineItems := h.BillingService.GetLineItems(session.ID).LineItem() + c.Log.Error(lineItems) + c.Log.Error(*lineItems) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) if err != nil { @@ -91,7 +92,7 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.WithError(err).Debug("Error parsing webhook JSON") return echo.ErrBadRequest } - c.Log.Info(*subscription.Items.Data[0]) + c.Log.Info(subscription.Items.Data[0].Price.LookupKey) // c.Log.Info(subscription.Plan) c.Log.Info("customer.subscription.updated") From 0b87169a63f27367fb364b29ce052d5838d471c0 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 18:47:15 +0100 Subject: [PATCH 08/20] lookup subscription --- internal/billing/service.go | 8 +++ internal/rest/controllers/webhooks/stripe.go | 56 ++++++++++---------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index 9ac9d4f..dde20c4 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -9,6 +9,7 @@ import ( "github.com/stripe/stripe-go/v76" portalsession "github.com/stripe/stripe-go/v76/billingportal/session" "github.com/stripe/stripe-go/v76/checkout/session" + "github.com/stripe/stripe-go/v76/subscription" "github.com/stripe/stripe-go/v76/webhook" ) @@ -24,6 +25,7 @@ type Service interface { CreateCheckoutSession(team *entities.Team, priceLookupKey string) (*stripe.CheckoutSession, error) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) GetLineItems(sessionID string) *session.LineItemIter + GetSubscribtion(subID string) (*stripe.Subscription, error) CreateCustomerPortal(sessionID string) (*stripe.BillingPortalSession, error) ConstructEvent(payload []byte, header string) (stripe.Event, error) } @@ -87,6 +89,12 @@ func (s *ServiceImpl) GetLineItems(sessionID string) *session.LineItemIter { return session.ListLineItems(params) } +func (s *ServiceImpl) GetSubscribtion(subID string) (*stripe.Subscription, error) { + params := &stripe.SubscriptionParams{} + return subscription.Get(subID, params) + +} + func (s *ServiceImpl) CreateCustomerPortal(sessionID string) (*stripe.BillingPortalSession, error) { // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 3baf664..41e03ea 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -37,23 +37,13 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { return echo.ErrBadRequest } - params := &stripe.CheckoutSessionParams{} - params.AddExpand("line_items") - - // Retrieve the session. If you require line items in the response, you may include them by expanding line_items. - // sessionWithLineItems, err := h.BillingService.GetCheckoutSession(session.ID) - // if err != nil { - // c.Log.WithError(err).Debug("Error getting checkout session") - // return echo.ErrBadRequest - // } - - c.Log.Error(session.ClientReferenceID) - c.Log.Error(session) - c.Log.Error(session.ID) - c.Log.Error(event.Data.Object["customer"].(string)) - lineItems := h.BillingService.GetLineItems(session.ID).LineItem() - c.Log.Error(lineItems) - c.Log.Error(*lineItems) + c.Log.Error(event.Data.Object["subscription"].(string)) + subscription, err := h.BillingService.GetSubscribtion(event.Data.Object["subscription"].(string)) + if err != nil { + c.Log.WithError(err).Debug("Error getting subscription") + } + c.Log.Error(subscription.Items.Data[0]) + c.Log.Error(subscription.Items.Data[0].Price.LookupKey) teamID, err := strconv.ParseUint(session.ClientReferenceID, 10, 32) if err != nil { @@ -69,17 +59,13 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { return c.NoContent(http.StatusInternalServerError) } - // TODO if not same plan remove old plan - - // if customerTeam.PaymentPlan == "TEST" { //lineItems.Price.Product.Name { - // return c.NoContent(http.StatusOK) - // } - customerID := event.Data.Object["customer"].(string) if customerTeam.StripeCustomerID == nil { customerTeam.StripeCustomerID = &customerID } + customerTeam.PaymentPlan = subscription.Items.Data[0].Price.LookupKey + err = h.TeamService.UpdateTeam(c.Request().Context(), customerTeam) if err != nil { c.Log.WithError(err).Debug("Error updating team") @@ -92,19 +78,31 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { c.Log.WithError(err).Debug("Error parsing webhook JSON") return echo.ErrBadRequest } - c.Log.Info(subscription.Items.Data[0].Price.LookupKey) - // c.Log.Info(subscription.Plan) - c.Log.Info("customer.subscription.updated") + + if subscription.Status == "canceled" { + team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.Customer.ID) + if err != nil { + c.Log.WithError(err).Debug("Error getting team by stripe id") + return c.NoContent(http.StatusInternalServerError) + } + + team.PaymentPlan = "FREE" + err = h.TeamService.UpdateTeam(c.Request().Context(), team) + if err != nil { + c.Log.WithError(err).Debug("Error updating team") + return c.NoContent(http.StatusInternalServerError) + } + } case "customer.deleted": - var subscription stripe.Customer - err := json.Unmarshal(event.Data.Raw, &subscription) + var customer stripe.Customer + err := json.Unmarshal(event.Data.Raw, &customer) if err != nil { c.Log.WithError(err).Debug("Error parsing webhook JSON") return echo.ErrBadRequest } - team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.ID) + team, err := h.TeamService.GetByStripeID(c.Request().Context(), customer.ID) if err != nil { c.Log.WithError(err).Debug("Error getting team by stripe id") return c.NoContent(http.StatusInternalServerError) From f77193fbc3c8aa21f28fb3aaf56f006bc3c7a62a Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 20:03:04 +0100 Subject: [PATCH 09/20] handling update subscription --- internal/billing/service.go | 30 +++++++++++++++++++++- internal/rest/controllers/teams/billing.go | 14 ++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index dde20c4..581f324 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -23,8 +23,10 @@ type Config struct { type Service interface { PostConfig() StripeConfig CreateCheckoutSession(team *entities.Team, priceLookupKey string) (*stripe.CheckoutSession, error) + UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) GetLineItems(sessionID string) *session.LineItemIter + GetCustomerSubscribtion(customerID string) *stripe.Subscription GetSubscribtion(subID string) (*stripe.Subscription, error) CreateCustomerPortal(sessionID string) (*stripe.BillingPortalSession, error) ConstructEvent(payload []byte, header string) (stripe.Event, error) @@ -64,13 +66,13 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey LineItems: []*stripe.CheckoutSessionLineItemParams{ { + Price: stripe.String(priceLookupKey), Quantity: stripe.Int64(1), }, }, } - // Set Customer on session if already a customer if team.StripeCustomerID != nil { params.Customer = stripe.String(*team.StripeCustomerID) } @@ -78,6 +80,27 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey return session.New(params) } +func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) { + // Set Customer on session if already a customer + + teamSubscription := s.GetCustomerSubscribtion(*team.StripeCustomerID) + + params := &stripe.SubscriptionParams{ + Items: []*stripe.SubscriptionItemsParams{ + { + ID: stripe.String(teamSubscription.Items.Data[0].ID), + Price: stripe.String(priceLookupKey), + }, + }, + } + result, err := subscription.Update(teamSubscription.ID, params) + if err != nil { + return nil, errors.Wrap(err, "failed to update subscription") + } + + return result, nil +} + func (s *ServiceImpl) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) { return session.Get(sessionID, &stripe.CheckoutSessionParams{}) } @@ -89,6 +112,11 @@ func (s *ServiceImpl) GetLineItems(sessionID string) *session.LineItemIter { return session.ListLineItems(params) } +func (s *ServiceImpl) GetCustomerSubscribtion(customerID string) *stripe.Subscription { + params := &stripe.SubscriptionListParams{Customer: stripe.String(customerID)} + return subscription.List(params).Subscription() +} + func (s *ServiceImpl) GetSubscribtion(subID string) (*stripe.Subscription, error) { params := &stripe.SubscriptionParams{} return subscription.Get(subID, params) diff --git a/internal/rest/controllers/teams/billing.go b/internal/rest/controllers/teams/billing.go index cc40508..ea9c1a0 100644 --- a/internal/rest/controllers/teams/billing.go +++ b/internal/rest/controllers/teams/billing.go @@ -34,6 +34,20 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { return echo.ErrInternalServerError } + teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) + if teamSubscription != nil { + // if req.PriceLookupKey == "" { + // TODO CANCEL SUBSCRIPTION + // } + _, err := h.BillingService.UpdateSubscribtion(team, req.PriceLookupKey) + if err != nil { + c.Log.WithError(err).Debug("update subscription") + + return echo.ErrInternalServerError + } + return c.JSON(http.StatusOK, "") + } + s, err := h.BillingService.CreateCheckoutSession(team, req.PriceLookupKey) if err != nil { c.Log.WithError(err).Debug("create stripe checkout session") From 415b08dea6a6d2bdba8c712169ce01090db4a73f Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 20:09:54 +0100 Subject: [PATCH 10/20] fixed null error --- internal/billing/service.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index 581f324..df01343 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -26,7 +26,7 @@ type Service interface { UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) GetLineItems(sessionID string) *session.LineItemIter - GetCustomerSubscribtion(customerID string) *stripe.Subscription + GetCustomerSubscribtion(customerID string) *subscription.Iter GetSubscribtion(subID string) (*stripe.Subscription, error) CreateCustomerPortal(sessionID string) (*stripe.BillingPortalSession, error) ConstructEvent(payload []byte, header string) (stripe.Event, error) @@ -83,7 +83,7 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) { // Set Customer on session if already a customer - teamSubscription := s.GetCustomerSubscribtion(*team.StripeCustomerID) + teamSubscription := s.GetCustomerSubscribtion(*team.StripeCustomerID).Subscription() params := &stripe.SubscriptionParams{ Items: []*stripe.SubscriptionItemsParams{ @@ -112,9 +112,9 @@ func (s *ServiceImpl) GetLineItems(sessionID string) *session.LineItemIter { return session.ListLineItems(params) } -func (s *ServiceImpl) GetCustomerSubscribtion(customerID string) *stripe.Subscription { +func (s *ServiceImpl) GetCustomerSubscribtion(customerID string) *subscription.Iter { params := &stripe.SubscriptionListParams{Customer: stripe.String(customerID)} - return subscription.List(params).Subscription() + return subscription.List(params) } func (s *ServiceImpl) GetSubscribtion(subID string) (*stripe.Subscription, error) { From 1aac98c3330946ba47bc44573e234f79662c187c Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 20:16:12 +0100 Subject: [PATCH 11/20] testing iter function stripe --- internal/billing/service.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index df01343..a5a0776 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -83,7 +83,9 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) { // Set Customer on session if already a customer - teamSubscription := s.GetCustomerSubscribtion(*team.StripeCustomerID).Subscription() + sub := s.GetCustomerSubscribtion(*team.StripeCustomerID) + sub.Next() + teamSubscription := sub.Subscription() params := &stripe.SubscriptionParams{ Items: []*stripe.SubscriptionItemsParams{ From d3e61a0af63ed0687707c2f63b297d76b75405e2 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 20:22:54 +0100 Subject: [PATCH 12/20] corrected webhook update --- internal/rest/controllers/webhooks/stripe.go | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 41e03ea..7428c08 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -79,19 +79,21 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { return echo.ErrBadRequest } - if subscription.Status == "canceled" { - team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.Customer.ID) - if err != nil { - c.Log.WithError(err).Debug("Error getting team by stripe id") - return c.NoContent(http.StatusInternalServerError) - } + team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.Customer.ID) + if err != nil { + c.Log.WithError(err).Debug("Error getting team by stripe id") + return c.NoContent(http.StatusInternalServerError) + } + team.PaymentPlan = subscription.Items.Data[0].Price.LookupKey + if subscription.Status == "canceled" { team.PaymentPlan = "FREE" - err = h.TeamService.UpdateTeam(c.Request().Context(), team) - if err != nil { - c.Log.WithError(err).Debug("Error updating team") - return c.NoContent(http.StatusInternalServerError) - } + } + + err = h.TeamService.UpdateTeam(c.Request().Context(), team) + if err != nil { + c.Log.WithError(err).Debug("Error updating team") + return c.NoContent(http.StatusInternalServerError) } case "customer.deleted": From 83823e78f8dbb4d94bda9cb2141e04414340bd51 Mon Sep 17 00:00:00 2001 From: teisnp Date: Sun, 4 Feb 2024 20:28:53 +0100 Subject: [PATCH 13/20] cancel subscribtion --- internal/billing/service.go | 24 ++++++++++++++++++++++ internal/rest/controllers/teams/billing.go | 12 ++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index a5a0776..31d6480 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -24,6 +24,7 @@ type Service interface { PostConfig() StripeConfig CreateCheckoutSession(team *entities.Team, priceLookupKey string) (*stripe.CheckoutSession, error) UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) + CancelSubscribtion(team *entities.Team) (*stripe.Subscription, error) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) GetLineItems(sessionID string) *session.LineItemIter GetCustomerSubscribtion(customerID string) *subscription.Iter @@ -103,6 +104,29 @@ func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey str return result, nil } +func (s *ServiceImpl) CancelSubscribtion(team *entities.Team) (*stripe.Subscription, error) { + // Set Customer on session if already a customer + + sub := s.GetCustomerSubscribtion(*team.StripeCustomerID) + sub.Next() + teamSubscription := sub.Subscription() + + params := &stripe.SubscriptionParams{ + Items: []*stripe.SubscriptionItemsParams{ + { + ID: stripe.String(teamSubscription.Items.Data[0].ID), + Deleted: stripe.Bool(true), + }, + }, + } + result, err := subscription.Update(teamSubscription.ID, params) + if err != nil { + return nil, errors.Wrap(err, "failed to update subscription") + } + + return result, nil +} + func (s *ServiceImpl) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) { return session.Get(sessionID, &stripe.CheckoutSessionParams{}) } diff --git a/internal/rest/controllers/teams/billing.go b/internal/rest/controllers/teams/billing.go index ea9c1a0..8cc0b6f 100644 --- a/internal/rest/controllers/teams/billing.go +++ b/internal/rest/controllers/teams/billing.go @@ -36,9 +36,15 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) if teamSubscription != nil { - // if req.PriceLookupKey == "" { - // TODO CANCEL SUBSCRIPTION - // } + if req.PriceLookupKey == "" { + _, err := h.BillingService.CancelSubscribtion(team) + if err != nil { + c.Log.WithError(err).Debug("cancel subscription") + + return echo.ErrInternalServerError + } + return c.JSON(http.StatusOK, "") + } _, err := h.BillingService.UpdateSubscribtion(team, req.PriceLookupKey) if err != nil { c.Log.WithError(err).Debug("update subscription") From aa606929e10a1d94b6a09edc805be001e931b45c Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 5 Feb 2024 20:00:16 +0100 Subject: [PATCH 14/20] use lookupkey to get price IDS stripe --- internal/billing/service.go | 34 ++++++++++++++++++++-- internal/rest/controllers/teams/billing.go | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index 31d6480..cd8ccab 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -9,6 +9,7 @@ import ( "github.com/stripe/stripe-go/v76" portalsession "github.com/stripe/stripe-go/v76/billingportal/session" "github.com/stripe/stripe-go/v76/checkout/session" + "github.com/stripe/stripe-go/v76/price" "github.com/stripe/stripe-go/v76/subscription" "github.com/stripe/stripe-go/v76/webhook" ) @@ -27,6 +28,7 @@ type Service interface { CancelSubscribtion(team *entities.Team) (*stripe.Subscription, error) GetCheckoutSession(sessionID string) (*stripe.CheckoutSession, error) GetLineItems(sessionID string) *session.LineItemIter + GetPrice(priceLookupKey string) (*stripe.Price, error) GetCustomerSubscribtion(customerID string) *subscription.Iter GetSubscribtion(subID string) (*stripe.Subscription, error) CreateCustomerPortal(sessionID string) (*stripe.BillingPortalSession, error) @@ -59,6 +61,11 @@ func (s *ServiceImpl) PostConfig() StripeConfig { } func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey string) (*stripe.CheckoutSession, error) { + priceID, err := s.GetPrice(priceLookupKey) + if err != nil { + return nil, errors.Wrap(err, "failed to get price") + } + params := &stripe.CheckoutSessionParams{ SuccessURL: stripe.String(s.Config.Domain + "/team/subscription"), CancelURL: stripe.String(s.Config.Domain + "/team/subscription"), @@ -68,7 +75,7 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey LineItems: []*stripe.CheckoutSessionLineItemParams{ { - Price: stripe.String(priceLookupKey), + Price: stripe.String(priceID.ID), Quantity: stripe.Int64(1), }, }, @@ -84,6 +91,10 @@ func (s *ServiceImpl) CreateCheckoutSession(team *entities.Team, priceLookupKey func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey string) (*stripe.Subscription, error) { // Set Customer on session if already a customer + priceID, err := s.GetPrice(priceLookupKey) + if err != nil { + return nil, errors.Wrap(err, "failed to get price") + } sub := s.GetCustomerSubscribtion(*team.StripeCustomerID) sub.Next() teamSubscription := sub.Subscription() @@ -92,7 +103,7 @@ func (s *ServiceImpl) UpdateSubscribtion(team *entities.Team, priceLookupKey str Items: []*stripe.SubscriptionItemsParams{ { ID: stripe.String(teamSubscription.Items.Data[0].ID), - Price: stripe.String(priceLookupKey), + Price: stripe.String(priceID.ID), }, }, } @@ -138,6 +149,25 @@ func (s *ServiceImpl) GetLineItems(sessionID string) *session.LineItemIter { return session.ListLineItems(params) } +func (s *ServiceImpl) GetPrice(priceLookupKey string) (*stripe.Price, error) { + params := &stripe.PriceListParams{ + LookupKeys: stripe.StringSlice([]string{ + priceLookupKey, + }), + } + i := price.List(params) + + var price *stripe.Price + for i.Next() { + p := i.Price() + price = p + } + if price == nil { + return nil, errors.New("Price not found for lookup key" + priceLookupKey) + } + return price, nil +} + func (s *ServiceImpl) GetCustomerSubscribtion(customerID string) *subscription.Iter { params := &stripe.SubscriptionListParams{Customer: stripe.String(customerID)} return subscription.List(params) diff --git a/internal/rest/controllers/teams/billing.go b/internal/rest/controllers/teams/billing.go index 8cc0b6f..5f300b9 100644 --- a/internal/rest/controllers/teams/billing.go +++ b/internal/rest/controllers/teams/billing.go @@ -36,7 +36,7 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) if teamSubscription != nil { - if req.PriceLookupKey == "" { + if req.PriceLookupKey == "FREE" { _, err := h.BillingService.CancelSubscribtion(team) if err != nil { c.Log.WithError(err).Debug("cancel subscription") From 3d9c729dab3badeea66d86fb9c5d1feb9b701c49 Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 18:47:43 +0100 Subject: [PATCH 15/20] tidy --- go.mod | 3 +-- go.sum | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d16c616..22eb657 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 - github.com/stripe/stripe-go v70.15.0+incompatible + github.com/stripe/stripe-go/v76 v76.14.0 github.com/tj/assert v0.0.3 github.com/vmihailenco/msgpack v4.0.4+incompatible github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 @@ -111,7 +111,6 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stripe/stripe-go/v76 v76.14.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect diff --git a/go.sum b/go.sum index e3ba19b..42ee500 100644 --- a/go.sum +++ b/go.sum @@ -483,12 +483,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stripe/stripe-go v70.15.0+incompatible h1:hNML7M1zx8RgtepEMlxyu/FpVPrP7KZm1gPFQquJQvM= -github.com/stripe/stripe-go v70.15.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= -github.com/stripe/stripe-go/v76 v76.2.0 h1:5zhef624MgfewEJ3YmZUfat1SGw+mtkR5HXtMW1J5dg= -github.com/stripe/stripe-go/v76 v76.2.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= -github.com/stripe/stripe-go/v76 v76.13.0 h1:j1tkBBA2v67yKHg9hj/0c24af8hze1vVVErJC9naT9Q= -github.com/stripe/stripe-go/v76 v76.13.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= github.com/stripe/stripe-go/v76 v76.14.0 h1:G5v9/PzFzlfgivZApCBpzAiFbrfPMMnI7ym/wU1W9cY= github.com/stripe/stripe-go/v76 v76.14.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= From e3c819a6fe28994a79a581ae0c84be288a9c1f09 Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 18:47:57 +0100 Subject: [PATCH 16/20] little debug prints --- internal/billing/service.go | 2 ++ internal/rest/controllers/teams/billing.go | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index cd8ccab..dd6b600 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -1,6 +1,7 @@ package billing import ( + "fmt" "os" "strconv" @@ -119,6 +120,7 @@ func (s *ServiceImpl) CancelSubscribtion(team *entities.Team) (*stripe.Subscript // Set Customer on session if already a customer sub := s.GetCustomerSubscribtion(*team.StripeCustomerID) + fmt.Println(sub.SubscriptionList()) sub.Next() teamSubscription := sub.Subscription() diff --git a/internal/rest/controllers/teams/billing.go b/internal/rest/controllers/teams/billing.go index 5f300b9..7d0fb26 100644 --- a/internal/rest/controllers/teams/billing.go +++ b/internal/rest/controllers/teams/billing.go @@ -1,6 +1,7 @@ package teams import ( + "fmt" "net/http" "github.com/labstack/echo/v4" @@ -34,8 +35,10 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { return echo.ErrInternalServerError } - teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) - if teamSubscription != nil { + fmt.Println(team) + + // teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) + if team.StripeCustomerID != nil { if req.PriceLookupKey == "FREE" { _, err := h.BillingService.CancelSubscribtion(team) if err != nil { From 9302712c3aeb223fd9ad6c63e573bd11ae3392ec Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 19:02:40 +0100 Subject: [PATCH 17/20] fixed cancel subscribtions --- internal/billing/service.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/internal/billing/service.go b/internal/billing/service.go index dd6b600..c2a2b1b 100644 --- a/internal/billing/service.go +++ b/internal/billing/service.go @@ -1,7 +1,6 @@ package billing import ( - "fmt" "os" "strconv" @@ -120,19 +119,12 @@ func (s *ServiceImpl) CancelSubscribtion(team *entities.Team) (*stripe.Subscript // Set Customer on session if already a customer sub := s.GetCustomerSubscribtion(*team.StripeCustomerID) - fmt.Println(sub.SubscriptionList()) sub.Next() teamSubscription := sub.Subscription() - params := &stripe.SubscriptionParams{ - Items: []*stripe.SubscriptionItemsParams{ - { - ID: stripe.String(teamSubscription.Items.Data[0].ID), - Deleted: stripe.Bool(true), - }, - }, - } - result, err := subscription.Update(teamSubscription.ID, params) + params := &stripe.SubscriptionCancelParams{} + result, err := subscription.Cancel(teamSubscription.ID, params) + if err != nil { return nil, errors.Wrap(err, "failed to update subscription") } From e4641a35365146100e1672ed03fa1cd0e9bedbc3 Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 19:12:08 +0100 Subject: [PATCH 18/20] handle subscription delete --- internal/rest/controllers/webhooks/stripe.go | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/internal/rest/controllers/webhooks/stripe.go b/internal/rest/controllers/webhooks/stripe.go index 7428c08..8541d89 100644 --- a/internal/rest/controllers/webhooks/stripe.go +++ b/internal/rest/controllers/webhooks/stripe.go @@ -96,6 +96,31 @@ func (h *Handlers) handleWebhook(c hs.StripeContext) error { return c.NoContent(http.StatusInternalServerError) } + case "customer.subscription.deleted": + var subscription stripe.Subscription + err := json.Unmarshal(event.Data.Raw, &subscription) + if err != nil { + c.Log.WithError(err).Debug("Error parsing webhook JSON") + return echo.ErrBadRequest + } + + team, err := h.TeamService.GetByStripeID(c.Request().Context(), subscription.Customer.ID) + if err != nil { + c.Log.WithError(err).Debug("Error getting team by stripe id") + return c.NoContent(http.StatusInternalServerError) + } + team.PaymentPlan = subscription.Items.Data[0].Price.LookupKey + + if subscription.Status == "canceled" { + team.PaymentPlan = "FREE" + } + + err = h.TeamService.UpdateTeam(c.Request().Context(), team) + if err != nil { + c.Log.WithError(err).Debug("Error updating team") + return c.NoContent(http.StatusInternalServerError) + } + case "customer.deleted": var customer stripe.Customer err := json.Unmarshal(event.Data.Raw, &customer) From 6882ebd4936ce2c797ed899d755b65c420f4d9cf Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 19:32:06 +0100 Subject: [PATCH 19/20] create sessions update handling stripe --- internal/rest/controllers/teams/billing.go | 46 ++++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/internal/rest/controllers/teams/billing.go b/internal/rest/controllers/teams/billing.go index 7d0fb26..6d6dba3 100644 --- a/internal/rest/controllers/teams/billing.go +++ b/internal/rest/controllers/teams/billing.go @@ -1,7 +1,6 @@ package teams import ( - "fmt" "net/http" "github.com/labstack/echo/v4" @@ -25,8 +24,6 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { return echo.ErrBadRequest } - c.Log.Error(req.PriceLookupKey) - c.Log.Info(req.TeamID) team, err := h.TeamService.GetByID(c.Request().Context(), req.TeamID) if err != nil { @@ -35,36 +32,51 @@ func (h *Handlers) PostCreateCheckoutSession(c hs.AuthenticatedContext) error { return echo.ErrInternalServerError } - fmt.Println(team) + if team.PaymentPlan == req.PriceLookupKey { + return c.JSON(http.StatusOK, "") + } - // teamSubscription := h.BillingService.GetCustomerSubscribtion(*team.StripeCustomerID) - if team.StripeCustomerID != nil { + if team.StripeCustomerID == nil { if req.PriceLookupKey == "FREE" { - _, err := h.BillingService.CancelSubscribtion(team) - if err != nil { - c.Log.WithError(err).Debug("cancel subscription") - - return echo.ErrInternalServerError - } return c.JSON(http.StatusOK, "") } - _, err := h.BillingService.UpdateSubscribtion(team, req.PriceLookupKey) + s, err := h.BillingService.CreateCheckoutSession(team, req.PriceLookupKey) if err != nil { - c.Log.WithError(err).Debug("update subscription") + c.Log.WithError(err).Debug("create stripe checkout session") + + return echo.ErrInternalServerError + } + return c.JSON(http.StatusOK, s.URL) + } + + if req.PriceLookupKey == "FREE" { + _, err := h.BillingService.CancelSubscribtion(team) + if err != nil { + c.Log.WithError(err).Debug("cancel subscription") return echo.ErrInternalServerError } return c.JSON(http.StatusOK, "") } - s, err := h.BillingService.CreateCheckoutSession(team, req.PriceLookupKey) + if team.PaymentPlan == "FREE" { + s, err := h.BillingService.CreateCheckoutSession(team, req.PriceLookupKey) + if err != nil { + c.Log.WithError(err).Debug("create stripe checkout session") + + return echo.ErrInternalServerError + } + return c.JSON(http.StatusOK, s.URL) + } + + _, err = h.BillingService.UpdateSubscribtion(team, req.PriceLookupKey) if err != nil { - c.Log.WithError(err).Debug("create stripe checkout session") + c.Log.WithError(err).Debug("update subscription") return echo.ErrInternalServerError } + return c.JSON(http.StatusOK, "") - return c.JSON(http.StatusOK, s.URL) } type GetCheckoutSession struct { From 7c9a9f1a63980867c33c656bbc6536821a0c1b7c Mon Sep 17 00:00:00 2001 From: teisnp Date: Mon, 12 Feb 2024 21:05:39 +0100 Subject: [PATCH 20/20] added secret file loading if exists --- cmd/root.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 3692997..e68bb80 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -74,6 +74,11 @@ func initConfig() { logrus.Info("Using config file: ", viper.ConfigFileUsed()) } + viper.SetConfigName("secrets") + if err := viper.MergeInConfig(); err == nil { + logrus.Info("Using secret file: ", viper.ConfigFileUsed()) + } + viper.AutomaticEnv() }