diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 2a0d2ebd86..533906722d 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -1561,21 +1561,21 @@ class BillingSession(ABC): customer = self.get_customer() assert customer is not None current_plan = get_current_plan_by_customer(customer) - if current_plan is None: - fixed_price_offer = CustomerPlanOffer.objects.filter( - customer=customer, status=CustomerPlanOffer.CONFIGURED + if current_plan is not None and self.check_plan_tier_is_billable(current_plan.tier): + fixed_price_next_plan = CustomerPlan.objects.filter( + customer=customer, + status=CustomerPlan.NEVER_STARTED, + fixed_price__isnull=False, ).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( - customer=customer, - status=CustomerPlan.NEVER_STARTED, - fixed_price__isnull=False, + assert fixed_price_next_plan is not None + fixed_price_next_plan.delete() + return "Fixed-price scheduled plan deleted" + fixed_price_offer = CustomerPlanOffer.objects.filter( + customer=customer, status=CustomerPlanOffer.CONFIGURED ).first() - assert fixed_price_next_plan is not None - fixed_price_next_plan.delete() - return "Fixed-price scheduled plan deleted" + 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: customer = self.get_customer() diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 1d75f92999..0406688473 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -7747,8 +7747,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): self.assert_in_response(substring, response) @responses.activate - @mock_stripe() - def test_delete_configured_fixed_price_plan_offer(self, *mocks: Mock) -> None: + def test_delete_configured_fixed_price_plan_offer_no_active_plan(self) -> None: self.login("iago") self.add_mock_response() @@ -7803,6 +7802,90 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): ["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 @mock_stripe() def test_upgrade_user_to_fixed_price_plan_pay_by_invoice(self, *mocks: Mock) -> None: