From 684430faa2ef085cdab2ae2a501bac8797e2d8cf Mon Sep 17 00:00:00 2001 From: Mateusz Mandera Date: Mon, 13 Feb 2023 20:40:51 +0100 Subject: [PATCH] billing: Add sponsorship request form to the billing page. Previously this was only available on the upgrade page - meaning an organization that already bought a plan wouldn't be able to request a sponsorship to get a discount or such, even if qualified. --- corporate/tests/test_stripe.py | 42 +++++++++++++++++++++++ corporate/views/billing_page.py | 20 ++++++++++- corporate/views/upgrade.py | 15 +++----- templates/corporate/billing.html | 3 ++ templates/corporate/sponsorship.html | 51 ++++++++++++++++++++++++++++ templates/corporate/upgrade.html | 51 +--------------------------- web/src/billing/billing.js | 1 + web/src/billing/helpers.js | 12 +++++++ web/src/billing/upgrade.js | 11 +----- web/tests/billing.test.js | 6 ++++ 10 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 templates/corporate/sponsorship.html diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index fd7c0fc811..e70c3a6f44 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -21,6 +21,7 @@ from typing import ( Tuple, TypeVar, ) +from unittest import mock from unittest.mock import Mock, patch import orjson @@ -3828,6 +3829,47 @@ 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) + def test_update_billing_method_of_current_plan(self) -> None: realm = get_realm("zulip") customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345") diff --git a/corporate/views/billing_page.py b/corporate/views/billing_page.py index 1efcb72cb2..214928e348 100644 --- a/corporate/views/billing_page.py +++ b/corporate/views/billing_page.py @@ -33,7 +33,7 @@ from zerver.lib.exceptions import JsonableError from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success from zerver.lib.validator import check_bool, check_int, check_int_in -from zerver.models import UserProfile +from zerver.models import Realm, UserProfile billing_logger = logging.getLogger("corporate.stripe") @@ -58,6 +58,23 @@ def payment_method_string(stripe_customer: stripe.Customer) -> str: ) # nocoverage +def add_sponsorship_info_to_context(context: Dict[str, Any], user_profile: UserProfile) -> None: + def key_helper(d: Any) -> int: + return d[1]["display_order"] + + context.update( + realm_org_type=user_profile.realm.org_type, + sorted_org_types=sorted( + ( + [org_type_name, org_type] + for (org_type_name, org_type) in Realm.ORG_TYPES.items() + if not org_type.get("hidden") + ), + key=key_helper, + ), + ) + + @zulip_login_required @has_request_variables def billing_home( @@ -145,6 +162,7 @@ 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 c7dd058166..958930f2da 100644 --- a/corporate/views/upgrade.py +++ b/corporate/views/upgrade.py @@ -36,14 +36,14 @@ from corporate.models import ( get_current_plan_by_customer, get_customer_by_realm, ) -from corporate.views.billing_page import billing_home +from corporate.views.billing_page import add_sponsorship_info_to_context, 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 from zerver.lib.response import json_success from zerver.lib.send_email import FromAddress, send_email from zerver.lib.validator import check_bool, check_int, check_string_in -from zerver.models import Realm, UserProfile, get_org_type_display_name +from zerver.models import UserProfile, get_org_type_display_name billing_logger = logging.getLogger("corporate.stripe") @@ -278,17 +278,10 @@ def initial_upgrade( "percent_off": float(percent_off), "demo_organization_scheduled_deletion_date": user.realm.demo_organization_scheduled_deletion_date, }, - "realm_org_type": user.realm.org_type, - "sorted_org_types": sorted( - ( - [org_type_name, org_type] - for (org_type_name, org_type) in Realm.ORG_TYPES.items() - if not org_type.get("hidden") - ), - key=lambda d: d[1]["display_order"], - ), "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 d005b52f42..2cc37c1d26 100644 --- a/templates/corporate/billing.html +++ b/templates/corporate/billing.html @@ -27,6 +27,7 @@
  • Overview
  • Payment method
  • Settings
  • +
  • 💚 Request sponsorship
  • @@ -170,6 +171,8 @@
    + + {% include "corporate/sponsorship.html" %} -
    -
    -
    -
    - - -
    - - - - -
    -

    - -
    - -
    -
    -
    -
    - -
    -
    -
    - Request received! The page will now reload. -
    -
    + {% include "corporate/sponsorship.html" %}