remote_billing: Move stripe_customer_id from server to realm if needed.

As explained in the comment, when we're moving the server plan to the
remote realm's Customer object, the realm Customer may not have
stripe_customer_id set and therefore that value needs to get moved from
the server Customer.
This commit is contained in:
Mateusz Mandera 2024-03-29 01:34:00 +01:00 committed by Tim Abbott
parent f9805c9e1f
commit 374178903e
2 changed files with 58 additions and 7 deletions

View File

@ -856,7 +856,9 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
zulip_realm = get_realm("zulip")
server_billing_session = RemoteServerBillingSession(self.server)
server_customer = server_billing_session.update_or_create_customer(stripe_customer_id=None)
server_customer = server_billing_session.update_or_create_customer(
stripe_customer_id="cus_123server"
)
server_plan = CustomerPlan.objects.create(
customer=server_customer,
billing_cycle_anchor=timezone_now(),
@ -880,7 +882,9 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
# Let's create a plan for the realm. This will conflict with the server plan.
remote_realm = RemoteRealm.objects.get(uuid=zulip_realm.uuid)
realm_billing_session = RemoteRealmBillingSession(remote_realm)
realm_customer = realm_billing_session.update_or_create_customer(stripe_customer_id=None)
realm_customer = realm_billing_session.update_or_create_customer(
stripe_customer_id="cus_123realm"
)
realm_plan = CustomerPlan.objects.create(
customer=realm_customer,
billing_cycle_anchor=timezone_now(),
@ -932,12 +936,36 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
f"Couldn't reconcile billing data between server and realm. Please contact {FromAddress.SUPPORT}",
)
# Finally, we simulate a regular, ACTIVE plan for the server again. Combined with
# the ENDED plan for the realm, we now have a simple case, where the migration
# should proceed.
# Now we simulate a regular, ACTIVE plan for the server again. Such a plan can be
# migrated, but we run into the last issue: the realm's customer already has a
# stripe_customer_id. We wouldn't want to overwrite it, so we error out.
server_plan.status = CustomerPlan.ACTIVE
server_plan.save(update_fields=["status"])
# Sanity check the assumption that stripe_customer_id is as expected for realm_customer.
realm_customer.refresh_from_db()
self.assertEqual(realm_customer.stripe_customer_id, "cus_123realm")
with self.assertLogs("zilencer.views", "WARN") as mock_warn:
result = self.execute_remote_billing_authentication_flow(
desdemona, return_from_auth_url=True
)
self.assertEqual(
mock_warn.output,
[
f"WARNING:zilencer.views:Failed to migrate customer from server (id: {remote_realm.server.id}) to realm (id: {remote_realm.id}): "
"RemoteRealm customer already exists and plans can't be migrated automatically."
],
)
self.assert_json_error(
result,
f"Couldn't reconcile billing data between server and realm. Please contact {FromAddress.SUPPORT}",
)
# Finally, set the stripe_customer_id to None for the realm's customer.
# Having an ACTIVE plan for the server and an ENDED plan for the realm, we now have
# a simple case, where the migration should proceed.
realm_customer.stripe_customer_id = None
realm_customer.save(update_fields=["stripe_customer_id"])
result = self.execute_remote_billing_authentication_flow(
desdemona, return_from_auth_url=False
)
@ -958,6 +986,12 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
remote_realm.refresh_from_db()
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_COMMUNITY)
realm_customer.refresh_from_db()
self.assertEqual(realm_customer.stripe_customer_id, "cus_123server")
server_customer.refresh_from_db()
self.assertEqual(server_customer.stripe_customer_id, None)
@responses.activate
def test_transfer_business_plan_from_server_to_realm(
self,

View File

@ -1024,14 +1024,31 @@ def handle_customer_migration_from_server_to_realm(
if (
remote_realm_plan is None
and server_plan.status != CustomerPlan.SWITCH_PLAN_TIER_AT_PLAN_END
and remote_realm_customer.stripe_customer_id is None
):
# This is a simple case where we don't have to worry about the realm already
# having an active plan, or the server having a next plan scheduled that we'd need
# This is a simple case where we don't have to worry about the realm
# having an active plan or an already configured stripe_customer_id,
# or the server having a next plan scheduled that we'd need
# to figure out how to migrate correctly as well.
# Any other case is too complex to handle here, and should be handled manually,
# especially since that should be extremely rare.
server_plan.customer = remote_realm_customer
server_plan.save(update_fields=["customer"])
# The realm's customer does not have .stripe_customer_id set by assumption.
# This situation happens e.g. if the Customer was created by a sponsorship request,
# so we need to move the value over from the server.
# That's because the plan we're transferring might be paid or a free trial and
# therefore need a stripe_customer_id to generate invoices.
# Hypothetically if the server's customer didn't have a stripe_customer_id set,
# that would imply the plan doesn't require it (e.g. this might be a Community plan)
# so we don't have to worry about whether we're copying over a valid value or None here.
stripe_customer_id = server_customer.stripe_customer_id
server_customer.stripe_customer_id = None
server_customer.save(update_fields=["stripe_customer_id"])
remote_realm_customer.stripe_customer_id = stripe_customer_id
remote_realm_customer.save(update_fields=["stripe_customer_id"])
else:
logger.warning(
"Failed to migrate customer from server (id: %s) to realm (id: %s): RemoteRealm customer already exists "