Skip to content

Commit a9a1dac

Browse files
committed
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 <[email protected]>
1 parent 777a4e9 commit a9a1dac

File tree

3 files changed

+22
-10
lines changed

3 files changed

+22
-10
lines changed

billing/checkout/service.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,14 @@ func (s *Service) Apply(ctx context.Context, ch Checkout) (*subscription.Subscri
956956
if err != nil {
957957
return nil, nil, fmt.Errorf("failed to create subscription: %w", err)
958958
}
959+
960+
// if set to cancel after trial, schedule a phase to cancel the subscription
961+
if ch.CancelAfterTrial && stripeSubscription.TrialEnd > 0 {
962+
_, err := s.subscriptionService.Cancel(ctx, subs.ID, false)
963+
if err != nil {
964+
return nil, nil, fmt.Errorf("failed to schedule cancel of subscription after trial: %w", err)
965+
}
966+
}
959967
return &subs, nil, nil
960968
} else if ch.ProductID != "" {
961969
chProduct, err := s.productService.GetByID(ctx, ch.ProductID)

billing/config.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ type RefreshInterval struct {
2626
}
2727

2828
type AccountConfig struct {
29-
AutoCreateWithOrg bool `yaml:"auto_create_with_org" mapstructure:"auto_create_with_org"`
30-
DefaultPlan string `yaml:"default_plan" mapstructure:"default_plan"`
31-
DefaultOffline bool `yaml:"default_offline" mapstructure:"default_offline"`
32-
OnboardCreditsWithOrg int64 `yaml:"onboard_credits_with_org" mapstructure:"onboard_credits_with_org"`
29+
AutoCreateWithOrg bool `yaml:"auto_create_with_org" mapstructure:"auto_create_with_org"`
30+
DefaultPlan string `yaml:"default_plan" mapstructure:"default_plan"`
31+
DefaultOffline bool `yaml:"default_offline" mapstructure:"default_offline"`
32+
OnboardCreditsWithOrg int64 `yaml:"onboard_credits_with_org" mapstructure:"onboard_credits_with_org"`
33+
DefaultPlanCancelAfterTrial bool `yaml:"default_plan_cancel_after_trial" mapstructure:"default_plan_cancel_after_trial"`
3334
}
3435

3536
type PlanChangeConfig struct {

core/event/service.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,20 @@ func (p *Service) EnsureDefaultPlan(ctx context.Context, orgID string) error {
135135
return fmt.Errorf("failed to get default plan: %w", err)
136136
}
137137

138+
var totalPrice int64
138139
for _, prod := range defaultPlan.Products {
139140
for _, price := range prod.Prices {
140-
if price.Amount > 0 {
141-
return fmt.Errorf("default plan is not free")
142-
}
141+
totalPrice += price.Amount
143142
}
144143
}
144+
if totalPrice > 0 && defaultPlan.TrialDays == 0 {
145+
return fmt.Errorf("default plan is not free to start")
146+
}
147+
145148
_, _, err = p.checkoutService.Apply(ctx, checkout.Checkout{
146-
CustomerID: customr.ID,
147-
PlanID: defaultPlan.ID,
148-
SkipTrial: true,
149+
CustomerID: customr.ID,
150+
PlanID: defaultPlan.ID,
151+
CancelAfterTrial: p.billingConf.AccountConfig.DefaultPlanCancelAfterTrial,
149152
})
150153
if err != nil {
151154
return fmt.Errorf("failed to apply default plan: %w", err)

0 commit comments

Comments
 (0)