From a9a1dac45646b45591ea82794c7e4cb7d912cf58 Mon Sep 17 00:00:00 2001 From: Kush Sharma Date: Sat, 24 Aug 2024 20:18:36 +0530 Subject: [PATCH] feat: allow trial on default plan of billing account When the org is created, if a default billing account of customer is registered to billing provider and a default plan is configured with trial days, subscription will start within trial days. By default, after trial expires, it will continue the subscription if payment method is setup. If no payment is setup, it will auto cancel. To cancel the plan after trial even after payment method is setup in a customer account, set `billing.customer.default_plan_cancel_after_trial` as true. Signed-off-by: Kush Sharma --- billing/checkout/service.go | 8 ++++++++ billing/config.go | 9 +++++---- core/event/service.go | 15 +++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/billing/checkout/service.go b/billing/checkout/service.go index ee77a9861..683fb7f9e 100644 --- a/billing/checkout/service.go +++ b/billing/checkout/service.go @@ -956,6 +956,14 @@ func (s *Service) Apply(ctx context.Context, ch Checkout) (*subscription.Subscri if err != nil { return nil, nil, fmt.Errorf("failed to create subscription: %w", err) } + + // if set to cancel after trial, schedule a phase to cancel the subscription + if ch.CancelAfterTrial && stripeSubscription.TrialEnd > 0 { + _, err := s.subscriptionService.Cancel(ctx, subs.ID, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to schedule cancel of subscription after trial: %w", err) + } + } return &subs, nil, nil } else if ch.ProductID != "" { chProduct, err := s.productService.GetByID(ctx, ch.ProductID) diff --git a/billing/config.go b/billing/config.go index 40e8d626e..f7844c48b 100644 --- a/billing/config.go +++ b/billing/config.go @@ -26,10 +26,11 @@ type RefreshInterval struct { } type AccountConfig struct { - AutoCreateWithOrg bool `yaml:"auto_create_with_org" mapstructure:"auto_create_with_org"` - DefaultPlan string `yaml:"default_plan" mapstructure:"default_plan"` - DefaultOffline bool `yaml:"default_offline" mapstructure:"default_offline"` - OnboardCreditsWithOrg int64 `yaml:"onboard_credits_with_org" mapstructure:"onboard_credits_with_org"` + AutoCreateWithOrg bool `yaml:"auto_create_with_org" mapstructure:"auto_create_with_org"` + DefaultPlan string `yaml:"default_plan" mapstructure:"default_plan"` + DefaultOffline bool `yaml:"default_offline" mapstructure:"default_offline"` + OnboardCreditsWithOrg int64 `yaml:"onboard_credits_with_org" mapstructure:"onboard_credits_with_org"` + DefaultPlanCancelAfterTrial bool `yaml:"default_plan_cancel_after_trial" mapstructure:"default_plan_cancel_after_trial"` } type PlanChangeConfig struct { diff --git a/core/event/service.go b/core/event/service.go index 25762f3e2..065d7f1de 100644 --- a/core/event/service.go +++ b/core/event/service.go @@ -135,17 +135,20 @@ func (p *Service) EnsureDefaultPlan(ctx context.Context, orgID string) error { return fmt.Errorf("failed to get default plan: %w", err) } + var totalPrice int64 for _, prod := range defaultPlan.Products { for _, price := range prod.Prices { - if price.Amount > 0 { - return fmt.Errorf("default plan is not free") - } + totalPrice += price.Amount } } + if totalPrice > 0 && defaultPlan.TrialDays == 0 { + return fmt.Errorf("default plan is not free to start") + } + _, _, err = p.checkoutService.Apply(ctx, checkout.Checkout{ - CustomerID: customr.ID, - PlanID: defaultPlan.ID, - SkipTrial: true, + CustomerID: customr.ID, + PlanID: defaultPlan.ID, + CancelAfterTrial: p.billingConf.AccountConfig.DefaultPlanCancelAfterTrial, }) if err != nil { return fmt.Errorf("failed to apply default plan: %w", err)