mirror of https://github.com/zulip/zulip.git
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:
parent
f9805c9e1f
commit
374178903e
|
@ -856,7 +856,9 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
|
||||||
zulip_realm = get_realm("zulip")
|
zulip_realm = get_realm("zulip")
|
||||||
|
|
||||||
server_billing_session = RemoteServerBillingSession(self.server)
|
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(
|
server_plan = CustomerPlan.objects.create(
|
||||||
customer=server_customer,
|
customer=server_customer,
|
||||||
billing_cycle_anchor=timezone_now(),
|
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.
|
# Let's create a plan for the realm. This will conflict with the server plan.
|
||||||
remote_realm = RemoteRealm.objects.get(uuid=zulip_realm.uuid)
|
remote_realm = RemoteRealm.objects.get(uuid=zulip_realm.uuid)
|
||||||
realm_billing_session = RemoteRealmBillingSession(remote_realm)
|
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(
|
realm_plan = CustomerPlan.objects.create(
|
||||||
customer=realm_customer,
|
customer=realm_customer,
|
||||||
billing_cycle_anchor=timezone_now(),
|
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}",
|
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
|
# Now we simulate a regular, ACTIVE plan for the server again. Such a plan can be
|
||||||
# the ENDED plan for the realm, we now have a simple case, where the migration
|
# migrated, but we run into the last issue: the realm's customer already has a
|
||||||
# should proceed.
|
# stripe_customer_id. We wouldn't want to overwrite it, so we error out.
|
||||||
server_plan.status = CustomerPlan.ACTIVE
|
server_plan.status = CustomerPlan.ACTIVE
|
||||||
server_plan.save(update_fields=["status"])
|
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(
|
result = self.execute_remote_billing_authentication_flow(
|
||||||
desdemona, return_from_auth_url=False
|
desdemona, return_from_auth_url=False
|
||||||
)
|
)
|
||||||
|
@ -958,6 +986,12 @@ class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
|
||||||
remote_realm.refresh_from_db()
|
remote_realm.refresh_from_db()
|
||||||
self.assertEqual(remote_realm.plan_type, RemoteRealm.PLAN_TYPE_COMMUNITY)
|
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
|
@responses.activate
|
||||||
def test_transfer_business_plan_from_server_to_realm(
|
def test_transfer_business_plan_from_server_to_realm(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -1024,14 +1024,31 @@ def handle_customer_migration_from_server_to_realm(
|
||||||
if (
|
if (
|
||||||
remote_realm_plan is None
|
remote_realm_plan is None
|
||||||
and server_plan.status != CustomerPlan.SWITCH_PLAN_TIER_AT_PLAN_END
|
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
|
# This is a simple case where we don't have to worry about the realm
|
||||||
# having an active plan, or the server having a next plan scheduled that we'd need
|
# 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.
|
# to figure out how to migrate correctly as well.
|
||||||
# Any other case is too complex to handle here, and should be handled manually,
|
# Any other case is too complex to handle here, and should be handled manually,
|
||||||
# especially since that should be extremely rare.
|
# especially since that should be extremely rare.
|
||||||
server_plan.customer = remote_realm_customer
|
server_plan.customer = remote_realm_customer
|
||||||
server_plan.save(update_fields=["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:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Failed to migrate customer from server (id: %s) to realm (id: %s): RemoteRealm customer already exists "
|
"Failed to migrate customer from server (id: %s) to realm (id: %s): RemoteRealm customer already exists "
|
||||||
|
|
Loading…
Reference in New Issue