mirror of https://github.com/zulip/zulip.git
stripe: Bill manually managed licenses monthly for additional licenses.
Regardless of plan renewal schedule, we try to invoice all plans monthly with some exceptions like free trial and fixed price plans, which help us charge users for additional licenses used during the previous month.
This commit is contained in:
parent
c84b9cbc97
commit
caba57fe1e
|
@ -313,12 +313,7 @@ def next_invoice_date(plan: CustomerPlan) -> Optional[datetime]:
|
|||
if plan.status == CustomerPlan.ENDED:
|
||||
return None
|
||||
assert plan.next_invoice_date is not None # for mypy
|
||||
months_per_period = {
|
||||
CustomerPlan.BILLING_SCHEDULE_ANNUAL: 12,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY: 1,
|
||||
}[plan.billing_schedule]
|
||||
if plan.automanage_licenses:
|
||||
months_per_period = 1
|
||||
months_per_period = 1
|
||||
periods = 1
|
||||
dt = plan.billing_cycle_anchor
|
||||
while dt <= plan.next_invoice_date:
|
||||
|
@ -1657,7 +1652,6 @@ class BillingSession(ABC):
|
|||
price_per_license,
|
||||
) = compute_plan_parameters(
|
||||
plan_tier,
|
||||
automanage_licenses,
|
||||
billing_schedule,
|
||||
discount_for_plan,
|
||||
free_trial,
|
||||
|
@ -1923,7 +1917,6 @@ class BillingSession(ABC):
|
|||
discount_for_current_plan = plan.discount
|
||||
_, _, _, price_per_license = compute_plan_parameters(
|
||||
tier=plan.tier,
|
||||
automanage_licenses=plan.automanage_licenses,
|
||||
billing_schedule=schedule,
|
||||
discount=discount_for_current_plan,
|
||||
)
|
||||
|
@ -2069,7 +2062,6 @@ class BillingSession(ABC):
|
|||
discount_for_current_plan = plan.discount
|
||||
_, _, _, price_per_license = compute_plan_parameters(
|
||||
tier=plan.tier,
|
||||
automanage_licenses=plan.automanage_licenses,
|
||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
discount=discount_for_current_plan,
|
||||
)
|
||||
|
@ -2118,7 +2110,6 @@ class BillingSession(ABC):
|
|||
discount_for_current_plan = plan.discount
|
||||
_, _, _, price_per_license = compute_plan_parameters(
|
||||
tier=plan.tier,
|
||||
automanage_licenses=plan.automanage_licenses,
|
||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
discount=discount_for_current_plan,
|
||||
)
|
||||
|
@ -2469,7 +2460,6 @@ class BillingSession(ABC):
|
|||
if free_trial_days is not None:
|
||||
_, _, free_trial_end, _ = compute_plan_parameters(
|
||||
tier,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
None,
|
||||
True,
|
||||
|
@ -4716,7 +4706,6 @@ def get_price_per_license(
|
|||
|
||||
def compute_plan_parameters(
|
||||
tier: int,
|
||||
automanage_licenses: bool,
|
||||
billing_schedule: int,
|
||||
discount: Optional[Decimal],
|
||||
free_trial: bool = False,
|
||||
|
@ -4739,9 +4728,9 @@ def compute_plan_parameters(
|
|||
|
||||
price_per_license = get_price_per_license(tier, billing_schedule, discount)
|
||||
|
||||
next_invoice_date = period_end
|
||||
if automanage_licenses:
|
||||
next_invoice_date = add_months(billing_cycle_anchor, 1)
|
||||
# `next_invoice_date` is the date when we check if there are any invoices that need to be generated.
|
||||
# It is always the next month regardless of the billing schedule / billing modality.
|
||||
next_invoice_date = add_months(billing_cycle_anchor, 1)
|
||||
if free_trial:
|
||||
period_end = billing_cycle_anchor + timedelta(
|
||||
days=assert_is_not_none(get_free_trial_days(is_self_hosted_billing, tier))
|
||||
|
|
|
@ -1039,7 +1039,7 @@ class StripeTest(StripeTestCase):
|
|||
billing_cycle_anchor=self.now,
|
||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
invoiced_through=LicenseLedger.objects.first(),
|
||||
next_invoice_date=self.next_year,
|
||||
next_invoice_date=self.next_month,
|
||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
status=CustomerPlan.ACTIVE,
|
||||
)
|
||||
|
@ -1438,7 +1438,7 @@ class StripeTest(StripeTestCase):
|
|||
customer_plan.refresh_from_db()
|
||||
realm.refresh_from_db()
|
||||
self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
|
||||
self.assertEqual(customer_plan.next_invoice_date, add_months(free_trial_end_date, 12))
|
||||
self.assertEqual(customer_plan.next_invoice_date, add_months(free_trial_end_date, 1))
|
||||
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD)
|
||||
[invoice] = iter(stripe.Invoice.list(customer=stripe_customer.id))
|
||||
invoice_params = {
|
||||
|
@ -2719,7 +2719,7 @@ class StripeTest(StripeTestCase):
|
|||
|
||||
annual_plan.refresh_from_db()
|
||||
self.assertEqual(annual_plan.invoiced_through, annual_ledger_entries[0])
|
||||
self.assertEqual(annual_plan.next_invoice_date, add_months(self.next_month, 12))
|
||||
self.assertEqual(annual_plan.next_invoice_date, add_months(self.next_month, 1))
|
||||
self.assertEqual(annual_plan.invoicing_status, CustomerPlan.INVOICING_STATUS_DONE)
|
||||
|
||||
assert customer.stripe_customer_id
|
||||
|
@ -2743,7 +2743,8 @@ class StripeTest(StripeTestCase):
|
|||
|
||||
with patch("corporate.lib.stripe.BillingSession.invoice_plan") as m:
|
||||
invoice_plans_as_needed(add_months(self.now, 2))
|
||||
m.assert_not_called()
|
||||
# Even annual plans get invoiced monthly for additional licenses.
|
||||
m.assert_called_once()
|
||||
|
||||
invoice_plans_as_needed(add_months(self.now, 13))
|
||||
|
||||
|
@ -4542,46 +4543,42 @@ class BillingHelpersTest(ZulipTestCase):
|
|||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
True,
|
||||
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
None,
|
||||
),
|
||||
(anchor, month_later, year_later, 8000),
|
||||
),
|
||||
(
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||
(anchor, month_later, year_later, 1200),
|
||||
),
|
||||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
True,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
None,
|
||||
),
|
||||
(anchor, month_later, month_later, 800),
|
||||
),
|
||||
(
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.BILLING_SCHEDULE_MONTHLY, 85),
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_MONTHLY, 85),
|
||||
(anchor, month_later, month_later, 120),
|
||||
),
|
||||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
None,
|
||||
),
|
||||
(anchor, year_later, year_later, 8000),
|
||||
(anchor, month_later, year_later, 8000),
|
||||
),
|
||||
(
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||
(anchor, year_later, year_later, 1200),
|
||||
(CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||
(anchor, month_later, year_later, 1200),
|
||||
),
|
||||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
None,
|
||||
),
|
||||
|
@ -4590,7 +4587,6 @@ class BillingHelpersTest(ZulipTestCase):
|
|||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
85,
|
||||
),
|
||||
|
@ -4600,7 +4596,6 @@ class BillingHelpersTest(ZulipTestCase):
|
|||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
87.25,
|
||||
),
|
||||
|
@ -4610,7 +4605,6 @@ class BillingHelpersTest(ZulipTestCase):
|
|||
(
|
||||
(
|
||||
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
False,
|
||||
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
87.15,
|
||||
),
|
||||
|
@ -4618,10 +4612,9 @@ class BillingHelpersTest(ZulipTestCase):
|
|||
),
|
||||
]
|
||||
with time_machine.travel(anchor, tick=False):
|
||||
for (tier, automanage_licenses, billing_schedule, discount), output in test_cases:
|
||||
for (tier, billing_schedule, discount), output in test_cases:
|
||||
output_ = compute_plan_parameters(
|
||||
tier,
|
||||
automanage_licenses,
|
||||
billing_schedule,
|
||||
None if discount is None else Decimal(discount),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue