support: Show date for start of next billing cycle for current plan.

Instead of showing the next invoice date for the plan, show the
date for the next billing cycle start (e.g. the next plan renewal
charge date), except for plans currently on a free trial.

For plans on a free trial, the next plan renewal date will be when
the free trial ends, which is stored as the next invoice date on
the plan.
This commit is contained in:
Lauryn Menard 2024-01-19 19:40:37 +01:00 committed by Tim Abbott
parent 60225591dc
commit fca9ff1ae7
3 changed files with 24 additions and 5 deletions

View File

@ -8,7 +8,12 @@ import time_machine
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from typing_extensions import override from typing_extensions import override
from corporate.lib.stripe import RealmBillingSession, RemoteRealmBillingSession, add_months from corporate.lib.stripe import (
RealmBillingSession,
RemoteRealmBillingSession,
add_months,
start_of_next_billing_cycle,
)
from corporate.models import ( from corporate.models import (
Customer, Customer,
CustomerPlan, CustomerPlan,
@ -599,7 +604,7 @@ class TestSupportEndpoint(ZulipTestCase):
"<b>Licenses</b>: 2/10 (Manual)", "<b>Licenses</b>: 2/10 (Manual)",
"<b>Price per license</b>: $80.00", "<b>Price per license</b>: $80.00",
"<b>Annual recurring revenue</b>: $800.00", "<b>Annual recurring revenue</b>: $800.00",
"<b>Next invoice date</b>: 02 January 2017", "<b>Start of next billing cycle</b>:",
'<option value="send_invoice" selected>', '<option value="send_invoice" selected>',
'<option value="charge_automatically" >', '<option value="charge_automatically" >',
], ],
@ -934,6 +939,8 @@ class TestSupportEndpoint(ZulipTestCase):
assert plan is not None assert plan is not None
self.assertEqual(customer.default_discount, Decimal(25)) self.assertEqual(customer.default_discount, Decimal(25))
self.assertEqual(plan.discount, Decimal(25)) self.assertEqual(plan.discount, Decimal(25))
start_next_billing_cycle = start_of_next_billing_cycle(plan, timezone_now())
biling_cycle_string = start_next_billing_cycle.strftime("%d %B %Y")
result = self.client_get("/activity/support", {"q": "lear"}) result = self.client_get("/activity/support", {"q": "lear"})
self.assert_in_success_response( self.assert_in_success_response(
@ -945,7 +952,7 @@ class TestSupportEndpoint(ZulipTestCase):
"<b>Licenses</b>: 2/10 (Manual)", "<b>Licenses</b>: 2/10 (Manual)",
"<b>Price per license</b>: $6.00", "<b>Price per license</b>: $6.00",
"<b>Annual recurring revenue</b>: $720.00", "<b>Annual recurring revenue</b>: $720.00",
"<b>Next invoice date</b>: 02 February 2016", f"<b>Start of next billing cycle</b>: {biling_cycle_string}",
], ],
result, result,
) )

View File

@ -13,6 +13,7 @@ from corporate.lib.stripe import (
BillingSession, BillingSession,
RemoteRealmBillingSession, RemoteRealmBillingSession,
RemoteServerBillingSession, RemoteServerBillingSession,
start_of_next_billing_cycle,
) )
from corporate.models import ( from corporate.models import (
Customer, Customer,
@ -60,6 +61,7 @@ class PlanData:
next_plan: Optional["CustomerPlan"] = None next_plan: Optional["CustomerPlan"] = None
licenses: Optional[int] = None licenses: Optional[int] = None
licenses_used: Optional[int] = None licenses_used: Optional[int] = None
next_billing_cycle_start: Optional[datetime] = None
is_legacy_plan: bool = False is_legacy_plan: bool = False
has_fixed_price: bool = False has_fixed_price: bool = False
warning: Optional[str] = None warning: Optional[str] = None
@ -167,7 +169,6 @@ def get_current_plan_data_for_support_view(billing_session: BillingSession) -> P
plan_data.warning = ( plan_data.warning = (
"Recent audit log data missing: No information for used licenses" "Recent audit log data missing: No information for used licenses"
) )
assert plan_data.current_plan is not None # for mypy assert plan_data.current_plan is not None # for mypy
plan_data.next_plan = billing_session.get_next_plan(plan_data.current_plan) plan_data.next_plan = billing_session.get_next_plan(plan_data.current_plan)
@ -186,6 +187,17 @@ def get_current_plan_data_for_support_view(billing_session: BillingSession) -> P
else: else:
plan_data.estimated_next_plan_revenue = 0 # nocoverage plan_data.estimated_next_plan_revenue = 0 # nocoverage
if plan_data.current_plan.status in (
CustomerPlan.FREE_TRIAL,
CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL,
): # nocoverage
assert plan_data.current_plan.next_invoice_date is not None
plan_data.next_billing_cycle_start = plan_data.current_plan.next_invoice_date
else:
plan_data.next_billing_cycle_start = start_of_next_billing_cycle(
plan_data.current_plan, timezone_now()
)
plan_data.is_legacy_plan = ( plan_data.is_legacy_plan = (
plan_data.current_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY plan_data.current_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY
) )

View File

@ -24,6 +24,6 @@
<b>Plan has a fixed price.</b> <b>Plan has a fixed price.</b>
{% endif %} {% endif %}
<b>Annual recurring revenue</b>: ${{ dollar_amount(plan_data.annual_recurring_revenue) }}<br /> <b>Annual recurring revenue</b>: ${{ dollar_amount(plan_data.annual_recurring_revenue) }}<br />
<b>Next invoice date</b>: {{ plan_data.current_plan.next_invoice_date.strftime('%d %B %Y') }}<br /> <b>Start of next billing cycle</b>: {{ plan_data.next_billing_cycle_start.strftime('%d %B %Y') }}<br />
{% endif %} {% endif %}
{% endif %} {% endif %}