stripe: Add a CustomerPlan for self hosted sponsored customers.

This commit is contained in:
Aman Agrawal 2023-12-18 20:09:33 +00:00 committed by Tim Abbott
parent d4a852e97c
commit b2faa5c5bb
3 changed files with 78 additions and 0 deletions

View File

@ -2677,6 +2677,59 @@ class BillingSession(ABC):
self.do_change_plan_type(tier=CustomerPlan.TIER_SELF_HOSTED_LEGACY, is_sponsored=False)
def add_customer_to_community_plan(self) -> None:
# There is no CustomerPlan for organizations on Zulip Cloud and
# they enjoy the same benefits as the Standard plan.
# For self-hosted organizations, sponsored organizations have
# a Community CustomerPlan and they have different benefits compared
# to customers on Business plan.
assert not isinstance(self, RealmBillingSession)
customer = self.update_or_create_customer()
plan = get_current_plan_by_customer(customer)
# Only plan that can be active is legacy plan. Which is already
# ended by the support path from which is this function is called.
assert plan is None
now = timezone_now()
community_plan_params = {
"billing_cycle_anchor": now,
"status": CustomerPlan.ACTIVE,
"tier": CustomerPlan.TIER_SELF_HOSTED_COMMUNITY,
# The primary mechanism for preventing charges under this
# plan is setting a null `next_invoice_date`, but setting
# a 0 price is useful defense in depth here.
"next_invoice_date": None,
"price_per_license": 0,
"billing_schedule": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
"automanage_licenses": True,
}
community_plan = CustomerPlan.objects.create(
customer=customer,
**community_plan_params,
)
try:
billed_licenses = self.get_billable_licenses_for_customer(customer, community_plan.tier)
except MissingDataError:
billed_licenses = 0
# Create a ledger entry for the community plan for tracking purposes.
# Also, since it is an active plan we need to it have at least one license ledger entry.
ledger_entry = LicenseLedger.objects.create(
plan=community_plan,
is_renewal=True,
event_time=now,
licenses=billed_licenses,
licenses_at_next_renewal=billed_licenses,
)
community_plan.invoiced_through = ledger_entry
community_plan.save(update_fields=["invoiced_through"])
self.write_to_audit_log(
event_type=AuditLogEventType.CUSTOMER_PLAN_CREATED,
event_time=now,
extra_data=community_plan_params,
)
def get_last_ledger_for_automanaged_plan_if_exists(
self,
) -> Optional[LicenseLedger]: # nocoverage
@ -2860,6 +2913,7 @@ class RealmBillingSession(BillingSession):
# This function needs to translate between the different
# formats of CustomerPlan.tier and Realm.plan_type.
if is_sponsored:
# Cloud sponsored customers don't have an active CustomerPlan.
plan_type = Realm.PLAN_TYPE_STANDARD_FREE
elif tier == CustomerPlan.TIER_CLOUD_STANDARD:
plan_type = Realm.PLAN_TYPE_STANDARD
@ -3199,11 +3253,13 @@ class RemoteRealmBillingSession(BillingSession):
return customer
@override
@transaction.atomic
def do_change_plan_type(
self, *, tier: Optional[int], is_sponsored: bool = False
) -> None: # nocoverage
if is_sponsored:
plan_type = RemoteRealm.PLAN_TYPE_COMMUNITY
self.add_customer_to_community_plan()
elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
plan_type = RemoteRealm.PLAN_TYPE_BUSINESS
elif tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY:
@ -3581,6 +3637,7 @@ class RemoteServerBillingSession(BillingSession):
return customer
@override
@transaction.atomic
def do_change_plan_type(
self, *, tier: Optional[int], is_sponsored: bool = False
) -> None: # nocoverage
@ -3588,6 +3645,7 @@ class RemoteServerBillingSession(BillingSession):
# formats of CustomerPlan.tier and RealmZulipServer.plan_type.
if is_sponsored:
plan_type = RemoteZulipServer.PLAN_TYPE_COMMUNITY
self.add_customer_to_community_plan()
elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
plan_type = RemoteZulipServer.PLAN_TYPE_BUSINESS
elif tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY:

View File

@ -5797,6 +5797,14 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
billing_session.approve_sponsorship()
remote_realm.refresh_from_db()
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_COMMUNITY)
# Assert such a plan exists
CustomerPlan.objects.get(
customer=customer,
tier=CustomerPlan.TIER_SELF_HOSTED_COMMUNITY,
status=CustomerPlan.ACTIVE,
next_invoice_date=None,
price_per_license=0,
)
# Check email sent.
expected_message = (
@ -5920,6 +5928,14 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase):
billing_session.approve_sponsorship()
self.remote_server.refresh_from_db()
self.assertEqual(self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_COMMUNITY)
# Assert such a plan exists
CustomerPlan.objects.get(
customer=customer,
tier=CustomerPlan.TIER_SELF_HOSTED_COMMUNITY,
status=CustomerPlan.ACTIVE,
next_invoice_date=None,
price_per_license=0,
)
# Check email sent.
expected_message = (

View File

@ -7,6 +7,9 @@
{% endif %}
<b>Plan name</b>: {{ plan_data.current_plan.name }}<br />
<b>Status</b>: {{ plan_data.current_plan.get_plan_status_as_text() }}<br />
{% if plan_data.current_plan.tier == plan_data.current_plan.TIER_SELF_HOSTED_COMMUNITY %}
<!-- Any data below doesn't makes sense for sponsored organizations. -->
{% else %}
{% if plan_data.is_legacy_plan %}
<b>End date</b>: {{ plan_data.current_plan.end_date.strftime('%d %B %Y') }}<br />
{% else %}
@ -19,3 +22,4 @@
{% endif %}
<b>Next invoice date</b>: {{ plan_data.current_plan.next_invoice_date.strftime('%d %B %Y') }}<br />
{% endif %}
{% endif %}