From a3093fad9713af75e4077e9bac930898046a1ca1 Mon Sep 17 00:00:00 2001
From: Aman Agrawal
Date: Thu, 2 Nov 2023 15:34:37 +0000
Subject: [PATCH] billing: Show sponsorship status on /sponsorship page.
We redirect users from billing and upgrade page to sponsorship page if the
org has requested for sponsorship or is already sponsored.
---
corporate/tests/test_stripe.py | 97 +++++++++-------------------
corporate/views/billing_page.py | 28 +++++---
corporate/views/upgrade.py | 19 +++---
templates/corporate/billing.html | 16 +----
templates/corporate/sponsorship.html | 25 +++++++
5 files changed, 83 insertions(+), 102 deletions(-)
diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py
index b2db3224e6..2d5b6c6ff7 100644
--- a/corporate/tests/test_stripe.py
+++ b/corporate/tests/test_stripe.py
@@ -22,7 +22,6 @@ from typing import (
Tuple,
TypeVar,
)
-from unittest import mock
from unittest.mock import Mock, patch
import orjson
@@ -438,6 +437,11 @@ class StripeTestCase(ZulipTestCase):
self.next_month = datetime(2012, 2, 2, 3, 4, 5, tzinfo=timezone.utc)
self.next_year = datetime(2013, 1, 2, 3, 4, 5, tzinfo=timezone.utc)
+ # Make hamlet billing admin for testing.
+ hamlet = self.example_user("hamlet")
+ hamlet.is_billing_admin = True
+ hamlet.save(update_fields=["is_billing_admin"])
+
def get_signed_seat_count_from_response(self, response: "TestHttpResponse") -> Optional[str]:
match = re.search(r"name=\"signed_seat_count\" value=\"(.+)\"", response.content.decode())
return match.group(1) if match else None
@@ -2288,22 +2292,23 @@ class StripeTest(StripeTestCase):
response = self.client_get("/upgrade/")
self.assertEqual(response.status_code, 302)
- self.assertEqual(response["Location"], "/billing/")
+ self.assertEqual(response["Location"], "/sponsorship/")
response = self.client_get("/billing/")
- self.assert_in_success_response(
- ["Your organization has requested sponsored or discounted hosting."], response
- )
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response["Location"], "/sponsorship/")
+
+ response = self.client_get("/sponsorship/")
self.assert_in_success_response(
[
- 'Please contact Zulip support if you have any questions or concerns.'
+ "Your organization has requested sponsorship for a free or discounted Zulip Cloud Standard plan."
],
response,
)
- # Ensure that the other "contact support" footer is not displayed, since that would be
- # duplicate.
- self.assert_not_in_success_response(
- ['Contact Zulip support for billing history.'],
+ self.assert_in_success_response(
+ [
+ 'Please feel free to contact Zulip support with any questions or updates to your request.'
+ ],
response,
)
@@ -2317,13 +2322,9 @@ class StripeTest(StripeTestCase):
user.realm.plan_type = Realm.PLAN_TYPE_STANDARD_FREE
user.realm.save()
self.login_user(self.example_user("hamlet"))
- response = self.client_get("/billing/")
+ response = self.client_get("/sponsorship/")
self.assert_in_success_response(
- ["Your organization is fully sponsored and is on the Zulip Cloud Standard"],
- response,
- )
- self.assert_in_success_response(
- ['Contact Zulip support for billing history.'],
+ ["Zulip is sponsoring free Zulip Cloud Standard hosting for your organization."],
response,
)
@@ -2331,13 +2332,22 @@ class StripeTest(StripeTestCase):
user = self.example_user("iago")
self.login_user(user)
response = self.client_get("/billing/")
- self.assertEqual(response.status_code, 302)
- self.assertEqual("/upgrade/", response["Location"])
+ not_admin_message = (
+ "You must be an organization owner or a billing administrator to view this page."
+ )
+ self.assert_in_success_response([not_admin_message], response)
user.realm.plan_type = Realm.PLAN_TYPE_STANDARD_FREE
user.realm.save()
response = self.client_get("/billing/")
- self.assertEqual(response.status_code, 200)
+ self.assert_in_success_response([not_admin_message], response)
+
+ # Billing page redirects to sponsorship page for standard free admins.
+ user = self.example_user("hamlet")
+ self.login_user(user)
+ response = self.client_get("/billing/")
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual("/sponsorship/", response["Location"])
user.realm.plan_type = Realm.PLAN_TYPE_LIMITED
user.realm.save()
@@ -2347,7 +2357,7 @@ class StripeTest(StripeTestCase):
self.assertEqual("/upgrade/", response["Location"])
def test_upgrade_page_for_demo_organizations(self) -> None:
- user = self.example_user("iago")
+ user = self.example_user("hamlet")
user.realm.demo_organization_scheduled_deletion_date = timezone_now() + timedelta(days=30)
user.realm.save()
self.login_user(user)
@@ -2366,7 +2376,7 @@ class StripeTest(StripeTestCase):
user.realm.save()
response = self.client_get("/upgrade/")
self.assertEqual(response.status_code, 302)
- self.assertEqual(response["Location"], "/billing/")
+ self.assertEqual(response["Location"], "/sponsorship/")
user.realm.plan_type = Realm.PLAN_TYPE_LIMITED
user.realm.save()
@@ -3808,47 +3818,6 @@ class StripeTest(StripeTestCase):
(invoice,) = stripe.Invoice.list(customer=stripe_customer_id)
self.assertEqual(invoice.amount_due, 7200)
- def test_request_sponsorship_available_on_upgrade_and_billing_pages(self) -> None:
- """
- Verifies that the Request sponsorship form is available on both the upgrade
- page and the billing page for already subscribed customers.
- """
- realm = get_realm("zulip")
- self.login("desdemona")
- result = self.client_get("/upgrade/")
- self.assert_in_success_response(["Request sponsorship"], result)
-
- customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345")
- plan = CustomerPlan.objects.create(
- customer=customer,
- status=CustomerPlan.ACTIVE,
- billing_cycle_anchor=timezone_now(),
- billing_schedule=CustomerPlan.ANNUAL,
- tier=CustomerPlan.STANDARD,
- price_per_license=1000,
- )
- LicenseLedger.objects.create(
- plan=plan,
- is_renewal=True,
- event_time=timezone_now(),
- licenses=9,
- licenses_at_next_renewal=9,
- )
-
- mock_stripe_customer = mock.MagicMock()
- mock_stripe_customer.email = "desdemona@zulip.com"
-
- with mock.patch(
- "corporate.views.billing_page.stripe_get_customer", return_value=mock_stripe_customer
- ):
- result = self.client_get("/billing/")
- # Sanity assert to make sure we're testing the subscribed billing page.
- self.assert_in_success_response(
- ["Your current plan is Zulip Cloud Standard."], result
- )
-
- self.assert_in_success_response(["Request sponsorship"], result)
-
@mock_stripe()
def test_customer_has_credit_card_as_default_payment_method(self, *mocks: Mock) -> None:
iago = self.example_user("iago")
@@ -4046,10 +4015,6 @@ class RequiresBillingAccessTest(StripeTestCase):
@override
def setUp(self, *mocks: Mock) -> None:
super().setUp()
- hamlet = self.example_user("hamlet")
- hamlet.is_billing_admin = True
- hamlet.save(update_fields=["is_billing_admin"])
-
desdemona = self.example_user("desdemona")
desdemona.role = UserProfile.ROLE_REALM_OWNER
desdemona.save(update_fields=["role"])
diff --git a/corporate/views/billing_page.py b/corporate/views/billing_page.py
index 1c1c71f165..2c3b604d97 100644
--- a/corporate/views/billing_page.py
+++ b/corporate/views/billing_page.py
@@ -81,6 +81,14 @@ def sponsorship_request(request: HttpRequest) -> HttpResponse:
user = request.user
assert user.is_authenticated
context: Dict[str, Any] = {}
+
+ customer = get_customer_by_realm(user.realm)
+ if customer is not None and customer.sponsorship_pending:
+ context["is_sponsorship_pending"] = True
+
+ if user.realm.plan_type == user.realm.PLAN_TYPE_STANDARD_FREE:
+ context["is_sponsored"] = True
+
add_sponsorship_info_to_context(context, user)
return render(request, "corporate/sponsorship.html", context=context)
@@ -93,33 +101,34 @@ def billing_home(
user = request.user
assert user.is_authenticated
- customer = get_customer_by_realm(user.realm)
context: Dict[str, Any] = {
"admin_access": user.has_billing_access,
"has_active_plan": False,
}
+ if not user.has_billing_access:
+ return render(request, "corporate/billing.html", context=context)
+
if user.realm.plan_type == user.realm.PLAN_TYPE_STANDARD_FREE:
context["is_sponsored"] = True
- return render(request, "corporate/billing.html", context=context)
+ return HttpResponseRedirect(reverse("sponsorship_request"))
+
+ customer = get_customer_by_realm(user.realm)
+ if (
+ customer is not None and customer.sponsorship_pending
+ ) or user.realm.plan_type == user.realm.PLAN_TYPE_STANDARD_FREE:
+ return HttpResponseRedirect(reverse("sponsorship_request"))
if customer is None:
from corporate.views.upgrade import initial_upgrade
return HttpResponseRedirect(reverse(initial_upgrade))
- if customer.sponsorship_pending:
- context["sponsorship_pending"] = True
- return render(request, "corporate/billing.html", context=context)
-
if not CustomerPlan.objects.filter(customer=customer).exists():
from corporate.views.upgrade import initial_upgrade
return HttpResponseRedirect(reverse(initial_upgrade))
- if not user.has_billing_access:
- return render(request, "corporate/billing.html", context=context)
-
plan = get_current_plan_by_customer(customer)
if plan is not None:
now = timezone_now()
@@ -172,7 +181,6 @@ def billing_home(
CustomerPlan=CustomerPlan,
onboarding=onboarding,
)
- add_sponsorship_info_to_context(context, user)
return render(request, "corporate/billing.html", context=context)
diff --git a/corporate/views/upgrade.py b/corporate/views/upgrade.py
index 2315773672..bce4185146 100644
--- a/corporate/views/upgrade.py
+++ b/corporate/views/upgrade.py
@@ -20,7 +20,6 @@ from corporate.lib.stripe import (
get_latest_seat_count,
get_price_per_license,
is_free_trial_offer_enabled,
- is_sponsored_realm,
process_initial_upgrade,
sign_string,
unsign_string,
@@ -34,7 +33,7 @@ from corporate.models import (
get_current_plan_by_customer,
get_customer_by_realm,
)
-from corporate.views.billing_page import add_sponsorship_info_to_context, billing_home
+from corporate.views.billing_page import billing_home
from zerver.actions.users import do_make_user_billing_admin
from zerver.decorator import require_organization_member, zulip_login_required
from zerver.lib.request import REQ, has_request_variables
@@ -231,19 +230,18 @@ def initial_upgrade(
if not settings.BILLING_ENABLED or user.is_guest:
return render(request, "404.html", status=404)
- billing_page_url = reverse(billing_home)
-
customer = get_customer_by_realm(user.realm)
- if customer is not None and (
- get_current_plan_by_customer(customer) is not None or customer.sponsorship_pending
- ):
+ if (
+ customer is not None and customer.sponsorship_pending
+ ) or user.realm.plan_type == user.realm.PLAN_TYPE_STANDARD_FREE:
+ return HttpResponseRedirect(reverse("sponsorship_request"))
+
+ billing_page_url = reverse(billing_home)
+ if customer is not None and (get_current_plan_by_customer(customer) is not None or onboarding):
if onboarding:
billing_page_url = f"{billing_page_url}?onboarding=true"
return HttpResponseRedirect(billing_page_url)
- if is_sponsored_realm(user.realm):
- return HttpResponseRedirect(billing_page_url)
-
percent_off = Decimal(0)
if customer is not None and customer.default_discount is not None:
percent_off = customer.default_discount
@@ -275,7 +273,6 @@ def initial_upgrade(
},
"is_demo_organization": user.realm.demo_organization_scheduled_deletion_date is not None,
}
- add_sponsorship_info_to_context(context, user)
response = render(request, "corporate/upgrade.html", context=context)
return response
diff --git a/templates/corporate/billing.html b/templates/corporate/billing.html
index 7fbd93266b..ac29e09db5 100644
--- a/templates/corporate/billing.html
+++ b/templates/corporate/billing.html
@@ -188,18 +188,8 @@
{% elif admin_access and not has_active_plan %}
+
- {% if sponsorship_pending %}
-
Your organization has requested sponsored or discounted hosting.
-
- Please
contact Zulip support if you have any questions or concerns.
- {% elif is_sponsored %}
-
-
-
💚 Your organization is fully sponsored and is on the Zulip Cloud Standard plan.
-
-
- {% else %}
Your organization is on the Zulip Free plan.
@@ -211,16 +201,12 @@
- {% endif %}
- {% if not sponsorship_pending %}
- {# In the other case, we're displaying a different message about contacting support. #}
- {% endif %}
{% else %}
You must be an organization owner or a billing administrator to view this page.
diff --git a/templates/corporate/sponsorship.html b/templates/corporate/sponsorship.html
index f931668aa1..8829bc9f82 100644
--- a/templates/corporate/sponsorship.html
+++ b/templates/corporate/sponsorship.html
@@ -4,6 +4,30 @@
{% set PAGE_TITLE = "💚 Request sponsorship" %}
{% block portico_content %}
+
+{% if is_sponsored %}
+
+
+
+
Zulip Cloud billing for {{realm_name}}
+
Zulip is sponsoring free Zulip Cloud Standard hosting for your organization. 🎉
+
+
+
+{% elif is_sponsorship_pending %}
+
+
+
+
Sponsorship request pending for {{realm_name}}
+
Your organization has requested sponsorship for a free or discounted Zulip Cloud Standard plan.
+
+ Please feel free to contact Zulip support with any questions or updates to your request.
+
+
+
+
+{% else %}
+
+{% endif %}
{% endblock %}