billing: Standardize remote server plan type IDs.

This will likely save us at least one headache.
This commit is contained in:
Tim Abbott 2023-12-13 15:17:55 -08:00
parent 89545891f6
commit 6308e07e53
10 changed files with 99 additions and 39 deletions

View File

@ -75,7 +75,8 @@ def get_plan_type_string(plan_type: int) -> str:
Realm.PLAN_TYPE_STANDARD: "Standard", Realm.PLAN_TYPE_STANDARD: "Standard",
Realm.PLAN_TYPE_STANDARD_FREE: "Standard free", Realm.PLAN_TYPE_STANDARD_FREE: "Standard free",
Realm.PLAN_TYPE_PLUS: "Plus", Realm.PLAN_TYPE_PLUS: "Plus",
RemoteZulipServer.PLAN_TYPE_SELF_HOSTED: "Self-hosted", RemoteZulipServer.PLAN_TYPE_SELF_MANAGED: "Self-managed",
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY: "Self-managed (legacy)",
RemoteZulipServer.PLAN_TYPE_COMMUNITY: "Community", RemoteZulipServer.PLAN_TYPE_COMMUNITY: "Community",
RemoteZulipServer.PLAN_TYPE_BUSINESS: "Business", RemoteZulipServer.PLAN_TYPE_BUSINESS: "Business",
RemoteZulipServer.PLAN_TYPE_ENTERPRISE: "Enterprise", RemoteZulipServer.PLAN_TYPE_ENTERPRISE: "Enterprise",

View File

@ -3177,10 +3177,6 @@ class RemoteRealmBillingSession(BillingSession):
plan_type = RemoteRealm.PLAN_TYPE_COMMUNITY plan_type = RemoteRealm.PLAN_TYPE_COMMUNITY
elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS: elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
plan_type = RemoteRealm.PLAN_TYPE_BUSINESS plan_type = RemoteRealm.PLAN_TYPE_BUSINESS
elif (
tier == CustomerPlan.TIER_SELF_HOSTED_PLUS
): # nocoverage # Plus plan doesn't use this code path yet.
plan_type = RemoteRealm.PLAN_TYPE_ENTERPRISE
else: else:
raise AssertionError("Unexpected tier") raise AssertionError("Unexpected tier")
@ -3259,7 +3255,7 @@ class RemoteRealmBillingSession(BillingSession):
def process_downgrade(self, plan: CustomerPlan) -> None: # nocoverage def process_downgrade(self, plan: CustomerPlan) -> None: # nocoverage
with transaction.atomic(): with transaction.atomic():
old_plan_type = self.remote_realm.plan_type old_plan_type = self.remote_realm.plan_type
new_plan_type = RemoteRealm.PLAN_TYPE_SELF_HOSTED new_plan_type = RemoteRealm.PLAN_TYPE_SELF_MANAGED
self.remote_realm.plan_type = new_plan_type self.remote_realm.plan_type = new_plan_type
self.remote_realm.save(update_fields=["plan_type"]) self.remote_realm.save(update_fields=["plan_type"])
self.write_to_audit_log( self.write_to_audit_log(
@ -3565,18 +3561,12 @@ class RemoteServerBillingSession(BillingSession):
def do_change_plan_type( def do_change_plan_type(
self, *, tier: Optional[int], is_sponsored: bool = False self, *, tier: Optional[int], is_sponsored: bool = False
) -> None: # nocoverage ) -> None: # nocoverage
# TODO: Create actual plan types.
# This function needs to translate between the different # This function needs to translate between the different
# formats of CustomerPlan.tier and RealmZulipServer.plan_type. # formats of CustomerPlan.tier and RealmZulipServer.plan_type.
if is_sponsored: if is_sponsored:
plan_type = RemoteZulipServer.PLAN_TYPE_COMMUNITY plan_type = RemoteZulipServer.PLAN_TYPE_COMMUNITY
elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS: elif tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
plan_type = RemoteZulipServer.PLAN_TYPE_BUSINESS plan_type = RemoteZulipServer.PLAN_TYPE_BUSINESS
elif (
tier == CustomerPlan.TIER_SELF_HOSTED_PLUS
): # nocoverage # Plus plan doesn't use this code path yet.
plan_type = RemoteZulipServer.PLAN_TYPE_ENTERPRISE
else: else:
raise AssertionError("Unexpected tier") raise AssertionError("Unexpected tier")
@ -3628,7 +3618,7 @@ class RemoteServerBillingSession(BillingSession):
def process_downgrade(self, plan: CustomerPlan) -> None: # nocoverage def process_downgrade(self, plan: CustomerPlan) -> None: # nocoverage
with transaction.atomic(): with transaction.atomic():
old_plan_type = self.remote_server.plan_type old_plan_type = self.remote_server.plan_type
new_plan_type = RemoteZulipServer.PLAN_TYPE_SELF_HOSTED new_plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED
self.remote_server.plan_type = new_plan_type self.remote_server.plan_type = new_plan_type
self.remote_server.save(update_fields=["plan_type"]) self.remote_server.save(update_fields=["plan_type"])
self.write_to_audit_log( self.write_to_audit_log(

View File

@ -4578,7 +4578,7 @@ class BillingHelpersTest(ZulipTestCase):
hostname="demo.example.com", hostname="demo.example.com",
contact_email="email@example.com", contact_email="email@example.com",
) )
self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_HOSTED) self.assertEqual(remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
do_change_remote_server_plan_type(remote_server, RemoteZulipServer.PLAN_TYPE_BUSINESS) do_change_remote_server_plan_type(remote_server, RemoteZulipServer.PLAN_TYPE_BUSINESS)
@ -4588,7 +4588,7 @@ class BillingHelpersTest(ZulipTestCase):
).last() ).last()
assert remote_realm_audit_log is not None assert remote_realm_audit_log is not None
expected_extra_data = { expected_extra_data = {
"old_value": RemoteZulipServer.PLAN_TYPE_SELF_HOSTED, "old_value": RemoteZulipServer.PLAN_TYPE_SELF_MANAGED,
"new_value": RemoteZulipServer.PLAN_TYPE_BUSINESS, "new_value": RemoteZulipServer.PLAN_TYPE_BUSINESS,
} }
self.assertEqual(remote_realm_audit_log.extra_data, expected_extra_data) self.assertEqual(remote_realm_audit_log.extra_data, expected_extra_data)

View File

@ -127,7 +127,11 @@ def remote_realm_billing_page(
or get_current_plan_by_customer(customer) is None or get_current_plan_by_customer(customer) is None
or ( or (
billing_session.get_legacy_remote_server_next_plan_name(customer) is None billing_session.get_legacy_remote_server_next_plan_name(customer) is None
and billing_session.remote_realm.plan_type == RemoteRealm.PLAN_TYPE_SELF_HOSTED and billing_session.remote_realm.plan_type
in [
RemoteRealm.PLAN_TYPE_SELF_MANAGED,
RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY,
]
) )
): ):
return HttpResponseRedirect(reverse("remote_realm_plans_page", args=(realm_uuid,))) return HttpResponseRedirect(reverse("remote_realm_plans_page", args=(realm_uuid,)))
@ -186,7 +190,11 @@ def remote_server_billing_page(
or get_current_plan_by_customer(customer) is None or get_current_plan_by_customer(customer) is None
or ( or (
billing_session.get_legacy_remote_server_next_plan_name(customer) is None billing_session.get_legacy_remote_server_next_plan_name(customer) is None
and billing_session.remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED and billing_session.remote_server.plan_type
in [
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED,
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY,
]
) )
): ):
return HttpResponseRedirect( return HttpResponseRedirect(

View File

@ -268,7 +268,10 @@ def remote_realm_billing_finalize_login(
return HttpResponseRedirect( return HttpResponseRedirect(
reverse(f"remote_realm_{next_page}_page", args=(remote_realm_uuid,)) reverse(f"remote_realm_{next_page}_page", args=(remote_realm_uuid,))
) )
elif remote_realm.plan_type == RemoteRealm.PLAN_TYPE_SELF_HOSTED: elif remote_realm.plan_type in [
RemoteRealm.PLAN_TYPE_SELF_MANAGED,
RemoteRealm.PLAN_TYPE_SELF_MANAGED_LEGACY,
]:
# If they have a scheduled upgrade, redirect to billing page. # If they have a scheduled upgrade, redirect to billing page.
billing_session = RemoteRealmBillingSession(remote_realm) billing_session = RemoteRealmBillingSession(remote_realm)
customer = billing_session.get_customer() customer = billing_session.get_customer()
@ -657,7 +660,10 @@ def remote_billing_legacy_server_from_login_confirmation_link(
return HttpResponseRedirect( return HttpResponseRedirect(
reverse(f"remote_server_{next_page}_page", args=(remote_server_uuid,)) reverse(f"remote_server_{next_page}_page", args=(remote_server_uuid,))
) )
elif remote_server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED: elif remote_server.plan_type in [
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED,
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY,
]:
# If they have a scheduled upgrade, redirect to billing page. # If they have a scheduled upgrade, redirect to billing page.
billing_session = RemoteServerBillingSession(remote_server) billing_session = RemoteServerBillingSession(remote_server)
customer = billing_session.get_customer() customer = billing_session.get_customer()

View File

@ -1171,7 +1171,7 @@ class AnalyticsBouncerTest(BouncerTestCase):
"realm_date_created": realm.date_created, "realm_date_created": realm.date_created,
"registration_deactivated": False, "registration_deactivated": False,
"realm_deactivated": False, "realm_deactivated": False,
"plan_type": RemoteRealm.PLAN_TYPE_SELF_HOSTED, "plan_type": RemoteRealm.PLAN_TYPE_SELF_MANAGED,
"is_system_bot_realm": realm.string_id == "zulipinternal", "is_system_bot_realm": realm.string_id == "zulipinternal",
} }
for realm in Realm.objects.order_by("id") for realm in Realm.objects.order_by("id")
@ -1613,7 +1613,7 @@ class AnalyticsBouncerTest(BouncerTestCase):
realm_date_created=realm.date_created, realm_date_created=realm.date_created,
registration_deactivated=False, registration_deactivated=False,
realm_deactivated=False, realm_deactivated=False,
plan_type=RemoteRealm.PLAN_TYPE_SELF_HOSTED, plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED,
) )
with transaction.atomic(), self.assertLogs("zulip.analytics", level="WARNING") as m: with transaction.atomic(), self.assertLogs("zulip.analytics", level="WARNING") as m:
@ -1903,7 +1903,7 @@ class AnalyticsBouncerTest(BouncerTestCase):
"realm_date_created": realm.date_created, "realm_date_created": realm.date_created,
"registration_deactivated": False, "registration_deactivated": False,
"realm_deactivated": False, "realm_deactivated": False,
"plan_type": RemoteRealm.PLAN_TYPE_SELF_HOSTED, "plan_type": RemoteRealm.PLAN_TYPE_SELF_MANAGED,
} }
for realm in Realm.objects.order_by("id") for realm in Realm.objects.order_by("id")
], ],

View File

@ -394,11 +394,11 @@ def populate_remote_server(customer_profile: CustomerProfile) -> Dict[str, str]:
): ):
plan_type = RemoteZulipServer.PLAN_TYPE_COMMUNITY plan_type = RemoteZulipServer.PLAN_TYPE_COMMUNITY
elif customer_profile.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY: elif customer_profile.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY:
plan_type = RemoteZulipServer.PLAN_TYPE_SELF_HOSTED plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY
elif customer_profile.tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS: elif customer_profile.tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
plan_type = RemoteZulipServer.PLAN_TYPE_BUSINESS plan_type = RemoteZulipServer.PLAN_TYPE_BUSINESS
elif customer_profile.tier is CustomerPlan.TIER_SELF_HOSTED_BASE: elif customer_profile.tier is CustomerPlan.TIER_SELF_HOSTED_BASE:
plan_type = RemoteZulipServer.PLAN_TYPE_SELF_HOSTED plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED
else: else:
raise AssertionError("Unexpected tier!") raise AssertionError("Unexpected tier!")

View File

@ -0,0 +1,39 @@
# Generated by Django 4.2.8 on 2023-12-13 23:20
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
PLAN_TYPE_SELF_HOSTED = 1
PLAN_TYPE_SELF_MANAGED = 100
def renumber_plan_types(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None:
RemoteZulipServer = apps.get_model("zilencer", "RemoteZulipServer")
RemoteRealm = apps.get_model("zilencer", "RemoteRealm")
RemoteRealm.objects.filter(plan_type=PLAN_TYPE_SELF_HOSTED).update(
plan_type=PLAN_TYPE_SELF_MANAGED
)
RemoteZulipServer.objects.filter(plan_type=PLAN_TYPE_SELF_HOSTED).update(
plan_type=PLAN_TYPE_SELF_MANAGED
)
class Migration(migrations.Migration):
dependencies = [
("zilencer", "0051_remoterealm_is_system_bot_realm"),
]
operations = [
migrations.AlterField(
model_name="remoterealm",
name="plan_type",
field=models.PositiveSmallIntegerField(db_index=True, default=PLAN_TYPE_SELF_MANAGED),
),
migrations.AlterField(
model_name="remotezulipserver",
name="plan_type",
field=models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_MANAGED),
),
migrations.RunPython(renumber_plan_types, reverse_code=migrations.RunPython.noop),
]

View File

@ -54,13 +54,22 @@ class RemoteZulipServer(models.Model):
deactivated = models.BooleanField(default=False) deactivated = models.BooleanField(default=False)
# Plan types for self-hosted customers # Plan types for self-hosted customers
#
# We reserve PLAN_TYPE_SELF_HOSTED=Realm.PLAN_TYPE_SELF_HOSTED for
# self-hosted installations that aren't using the notifications
# service.
#
# The other values align with, e.g., CustomerPlan.TIER_SELF_HOSTED_BASE
PLAN_TYPE_SELF_HOSTED = 1 PLAN_TYPE_SELF_HOSTED = 1
PLAN_TYPE_COMMUNITY = 100 PLAN_TYPE_SELF_MANAGED = 100
PLAN_TYPE_BUSINESS = 101 PLAN_TYPE_SELF_MANAGED_LEGACY = 101
PLAN_TYPE_ENTERPRISE = 102 PLAN_TYPE_COMMUNITY = 102
PLAN_TYPE_BUSINESS = 103
PLAN_TYPE_PLUS = 104
PLAN_TYPE_ENTERPRISE = 105
# The current billing plan for the remote server, similar to Realm.plan_type. # The current billing plan for the remote server, similar to Realm.plan_type.
plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_HOSTED) plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_MANAGED)
# This is not synced with the remote server, but only filled for sponsorship requests. # This is not synced with the remote server, but only filled for sponsorship requests.
org_type = models.PositiveSmallIntegerField( org_type = models.PositiveSmallIntegerField(
@ -143,13 +152,20 @@ class RemoteRealm(models.Model):
realm_date_created = models.DateTimeField() realm_date_created = models.DateTimeField()
# Plan types for self-hosted customers # Plan types for self-hosted customers
#
# We reserve PLAN_TYPE_SELF_HOSTED=Realm.PLAN_TYPE_SELF_HOSTED for
# self-hosted installations that aren't using the notifications
# service.
#
# The other values align with, e.g., CustomerPlan.TIER_SELF_HOSTED_BASE
PLAN_TYPE_SELF_HOSTED = 1 PLAN_TYPE_SELF_HOSTED = 1
PLAN_TYPE_COMMUNITY = 100 PLAN_TYPE_SELF_MANAGED = 100
PLAN_TYPE_BUSINESS = 101 PLAN_TYPE_SELF_MANAGED_LEGACY = 101
PLAN_TYPE_ENTERPRISE = 102 PLAN_TYPE_COMMUNITY = 102
PLAN_TYPE_BUSINESS = 103
# The current billing plan for the remote server, similar to Realm.plan_type. PLAN_TYPE_PLUS = 104
plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_HOSTED, db_index=True) PLAN_TYPE_ENTERPRISE = 105
plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_MANAGED, db_index=True)
@override @override
def __str__(self) -> str: def __str__(self) -> str:

View File

@ -768,10 +768,10 @@ def handle_customer_migration_from_server_to_realms(
and server_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY and server_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY
and server_plan.status == CustomerPlan.ACTIVE and server_plan.status == CustomerPlan.ACTIVE
): ):
assert server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_HOSTED assert server.plan_type == RemoteZulipServer.PLAN_TYPE_SELF_MANAGED
assert server_plan.end_date is not None assert server_plan.end_date is not None
remote_realms = RemoteRealm.objects.filter( remote_realms = RemoteRealm.objects.filter(
uuid__in=realm_uuids, server=server, plan_type=RemoteRealm.PLAN_TYPE_SELF_HOSTED uuid__in=realm_uuids, server=server, plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED
) )
# Verify that all the realms are on self hosted plan. # Verify that all the realms are on self hosted plan.
@ -799,7 +799,7 @@ def handle_customer_migration_from_server_to_realms(
# We only do this migration if there is only one realm besides the system bot realm. # We only do this migration if there is only one realm besides the system bot realm.
elif len(realm_uuids) == 1: elif len(realm_uuids) == 1:
remote_realm = RemoteRealm.objects.get( remote_realm = RemoteRealm.objects.get(
uuid=realm_uuids[0], plan_type=RemoteRealm.PLAN_TYPE_SELF_HOSTED uuid=realm_uuids[0], plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED
) )
# Migrate customer from server to remote realm if there is only one realm. # Migrate customer from server to remote realm if there is only one realm.
server_customer.remote_realm = remote_realm server_customer.remote_realm = remote_realm
@ -809,7 +809,7 @@ def handle_customer_migration_from_server_to_realms(
# Might be better to call do_change_plan_type here. # Might be better to call do_change_plan_type here.
remote_realm.plan_type = server.plan_type remote_realm.plan_type = server.plan_type
remote_realm.save(update_fields=["plan_type"]) remote_realm.save(update_fields=["plan_type"])
server.plan_type = RemoteZulipServer.PLAN_TYPE_SELF_HOSTED server.plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED
server.save(update_fields=["plan_type"]) server.save(update_fields=["plan_type"])
remote_realm_audit_logs.append( remote_realm_audit_logs.append(
RemoteRealmAuditLog( RemoteRealmAuditLog(
@ -819,7 +819,7 @@ def handle_customer_migration_from_server_to_realms(
event_time=event_time, event_time=event_time,
extra_data={ extra_data={
"attr_name": "plan_type", "attr_name": "plan_type",
"old_value": RemoteRealm.PLAN_TYPE_SELF_HOSTED, "old_value": RemoteRealm.PLAN_TYPE_SELF_MANAGED,
"new_value": remote_realm.plan_type, "new_value": remote_realm.plan_type,
}, },
) )