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:
Aman Agrawal 2024-03-05 05:06:57 +00:00 committed by Tim Abbott
parent c84b9cbc97
commit caba57fe1e
2 changed files with 15 additions and 33 deletions

View File

@ -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))

View File

@ -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),
)