mirror of https://github.com/zulip/zulip.git
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:
parent
ff5e1c3aee
commit
a3093fad97
|
@ -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"])
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 %}
|
||||
|
|
Loading…
Reference in New Issue