stripe: Delete fixed price plan offer if customer on comp access plan.

Updates delete_fixed_price_plan to use the same structure as
configure_fixed_price_plan. If there is a customer plan and it's
billable, then we delete the configured CustomerPlan object. If
there is no customer plan or it's a complimentary access plan, we
delete the configured CustomerPlanOffer object.
This commit is contained in:
Lauryn Menard
2025-02-27 13:04:58 +01:00
committed by Tim Abbott
parent 9ad3159e48
commit c4e3ccafa2
2 changed files with 98 additions and 15 deletions

View File

@@ -1561,13 +1561,7 @@ class BillingSession(ABC):
customer = self.get_customer() customer = self.get_customer()
assert customer is not None assert customer is not None
current_plan = get_current_plan_by_customer(customer) current_plan = get_current_plan_by_customer(customer)
if current_plan is None: if current_plan is not None and self.check_plan_tier_is_billable(current_plan.tier):
fixed_price_offer = CustomerPlanOffer.objects.filter(
customer=customer, status=CustomerPlanOffer.CONFIGURED
).first()
assert fixed_price_offer is not None
fixed_price_offer.delete()
return "Fixed-price plan offer deleted"
fixed_price_next_plan = CustomerPlan.objects.filter( fixed_price_next_plan = CustomerPlan.objects.filter(
customer=customer, customer=customer,
status=CustomerPlan.NEVER_STARTED, status=CustomerPlan.NEVER_STARTED,
@@ -1576,6 +1570,12 @@ class BillingSession(ABC):
assert fixed_price_next_plan is not None assert fixed_price_next_plan is not None
fixed_price_next_plan.delete() fixed_price_next_plan.delete()
return "Fixed-price scheduled plan deleted" return "Fixed-price scheduled plan deleted"
fixed_price_offer = CustomerPlanOffer.objects.filter(
customer=customer, status=CustomerPlanOffer.CONFIGURED
).first()
assert fixed_price_offer is not None
fixed_price_offer.delete()
return "Fixed-price plan offer deleted"
def update_customer_sponsorship_status(self, sponsorship_pending: bool) -> str: def update_customer_sponsorship_status(self, sponsorship_pending: bool) -> str:
customer = self.get_customer() customer = self.get_customer()

View File

@@ -7747,8 +7747,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
self.assert_in_response(substring, response) self.assert_in_response(substring, response)
@responses.activate @responses.activate
@mock_stripe() def test_delete_configured_fixed_price_plan_offer_no_active_plan(self) -> None:
def test_delete_configured_fixed_price_plan_offer(self, *mocks: Mock) -> None:
self.login("iago") self.login("iago")
self.add_mock_response() self.add_mock_response()
@@ -7803,6 +7802,90 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
["Configure fixed price plan", "Annual amount in dollars"], result ["Configure fixed price plan", "Annual amount in dollars"], result
) )
@responses.activate
def test_delete_configured_fixed_price_plan_offer_on_complimentary_access_plan(self) -> None:
self.login("iago")
self.add_mock_response()
with time_machine.travel(self.now, tick=False):
send_server_data_to_push_bouncer(consider_usage_statistics=False)
self.assertFalse(CustomerPlanOffer.objects.exists())
annual_fixed_price = 1200
# Configure complimentary access plan
complimentary_access_plan_end = self.next_year.strftime("%Y-%m-%d")
billing_session = RemoteRealmBillingSession(remote_realm=self.remote_realm)
support_request = SupportViewRequest(
support_type=SupportType.configure_complimentary_access_plan,
plan_end_date=complimentary_access_plan_end,
)
with time_machine.travel(self.now, tick=False):
success_message = billing_session.process_support_view_request(support_request)
self.assertEqual(
success_message,
f"Complimentary access plan for Zulip Dev configured to end on {complimentary_access_plan_end}.",
)
# Configure required_plan_tier and fixed_price.
result = self.client_post(
"/activity/remote/support",
{
"remote_realm_id": f"{self.remote_realm.id}",
"required_plan_tier": CustomerPlan.TIER_SELF_HOSTED_BASIC,
},
)
self.assert_in_success_response(
["Required plan tier for Zulip Dev set to Zulip Basic."], result
)
result = self.client_post(
"/activity/remote/support",
{"remote_realm_id": f"{self.remote_realm.id}", "fixed_price": annual_fixed_price},
)
self.assert_in_success_response(
["Customer can now buy a fixed price Zulip Basic plan."], result
)
fixed_price_plan_offer = CustomerPlanOffer.objects.filter(
status=CustomerPlanOffer.CONFIGURED
).first()
assert fixed_price_plan_offer is not None
self.assertEqual(fixed_price_plan_offer.tier, CustomerPlanOffer.TIER_SELF_HOSTED_BASIC)
self.assertEqual(fixed_price_plan_offer.fixed_price, annual_fixed_price * 100)
self.assertEqual(fixed_price_plan_offer.get_plan_status_as_text(), "Configured")
result = self.client_get("/activity/remote/support", {"q": "example.com"})
self.assert_in_success_response(
[
"Next plan information:",
"Zulip Basic",
"Configured",
"Plan has a fixed price.",
"Zulip Basic (complimentary)",
],
result,
)
# Delete configured fixed price plan.
billing_session = RemoteRealmBillingSession(remote_realm=self.remote_realm)
support_request = SupportViewRequest(
support_type=SupportType.delete_fixed_price_next_plan,
)
success_message = billing_session.process_support_view_request(support_request)
self.assertEqual(success_message, "Fixed-price plan offer deleted")
result = self.client_get("/activity/remote/support", {"q": "example.com"})
self.assert_not_in_success_response(["Next plan information:"], result)
self.assert_in_success_response(
[
"Configure fixed price plan",
"Annual amount in dollars",
"Zulip Basic (complimentary)",
],
result,
)
self.assertFalse(CustomerPlanOffer.objects.exists())
@responses.activate @responses.activate
@mock_stripe() @mock_stripe()
def test_upgrade_user_to_fixed_price_plan_pay_by_invoice(self, *mocks: Mock) -> None: def test_upgrade_user_to_fixed_price_plan_pay_by_invoice(self, *mocks: Mock) -> None: