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.
This commit is contained in:
Aman Agrawal 2023-11-02 15:34:37 +00:00 committed by Tim Abbott
parent ff5e1c3aee
commit a3093fad97
5 changed files with 83 additions and 102 deletions

View File

@ -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 <a href="mailto:support@zulip.com">contact Zulip support</a> 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(
['<a href="mailto:support@zulip.com">Contact Zulip support</a> for billing history.'],
self.assert_in_success_response(
[
'Please feel free to <a href="mailto:support@zulip.com">contact Zulip support</a> 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 <b>Zulip Cloud Standard</b>"],
response,
)
self.assert_in_success_response(
['<a href="mailto:support@zulip.com">Contact Zulip support</a> 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 <strong>Zulip Cloud Standard</strong>."], 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"])

View File

@ -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)

View File

@ -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

View File

@ -188,18 +188,8 @@
</p>
</div>
{% elif admin_access and not has_active_plan %}
<!-- For sponsored or sponsorship pending organizations we already redirect to /sponsorship page. -->
<div class="tab-content">
{% if sponsorship_pending %}
<h3>Your organization has requested sponsored or discounted hosting.</h3>
<br />
Please <a href="mailto:support@zulip.com">contact Zulip support</a> if you have any questions or concerns.
{% elif is_sponsored %}
<center>
<p>
<h3>💚 Your organization is fully sponsored and is on the <b>Zulip Cloud Standard</b> plan.</h3>
</p>
</center>
{% else %}
<center>
<p>
<h2>Your organization is on the <b>Zulip Free</b> plan.</h2>
@ -211,16 +201,12 @@
</a>
</p>
</center>
{% endif %}
</div>
{% if not sponsorship_pending %}
{# In the other case, we're displaying a different message about contacting support. #}
<div class="support-link">
<p>
<a href="mailto:support@zulip.com">Contact Zulip support</a> for billing history.
</p>
</div>
{% endif %}
{% else %}
<p>
You must be an organization owner or a billing administrator to view this page.

View File

@ -4,6 +4,30 @@
{% set PAGE_TITLE = "💚 Request sponsorship" %}
{% block portico_content %}
{% if is_sponsored %}
<div class="flex full-page thanks-page">
<div class="center-block new-style">
<h1>Zulip Cloud billing for {{realm_name}}</h1>
<p> Zulip is sponsoring free Zulip Cloud Standard hosting for your organization. 🎉 </p>
</div>
</div>
{% elif is_sponsorship_pending %}
<div class="flex full-page thanks-page">
<div class="center-block new-style">
<h1>Sponsorship request pending for {{realm_name}}</h1>
<p> Your organization has requested sponsorship for a free or discounted Zulip Cloud Standard plan.</p>
<p>
Please feel free to <a href="mailto:support@zulip.com">contact Zulip support</a> with any questions or updates to your request.
</p>
</div>
</div>
{% else %}
<div class="register-account flex full-page sponsorship-page">
<div class="center-block new-style">
<div class="pitch">
@ -55,5 +79,6 @@
</div>
</div>
</div>
{% endif %}
{% endblock %}