Skip to content

Commit

Permalink
feat: allow trial on default plan of billing account
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
kushsharma committed Aug 28, 2024
1 parent 777a4e9 commit a9a1dac
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 10 deletions.
8 changes: 8 additions & 0 deletions billing/checkout/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 5 additions & 4 deletions billing/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 9 additions & 6 deletions core/event/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit a9a1dac

Please sign in to comment.