diff --git a/templates/zilencer/billing.html b/templates/zilencer/billing.html
index 8c69cbd3e9..2596b7ff10 100644
--- a/templates/zilencer/billing.html
+++ b/templates/zilencer/billing.html
@@ -12,23 +12,27 @@
{{ _("Billing") }}
- Plan
- {{ plan_name }}
-
- You are paying for {{ seat_count }} users.
- Your plan will renew on {{ renewal_date }} for ${{ renewal_amount }}.
- {% if prorated_charges %}
- You have ${{ prorated_charges }} in prorated charges that will be
- added to your next bill.
- {% elif prorated_credits %}
- You have ${{ prorated_credits }} in prorated credits that will be
- automatically applied to your next bill.
+ {% if admin_access %}
+ Plan
+ {{ plan_name }}
+
+ You are paying for {{ seat_count }} users.
+ Your plan will renew on {{ renewal_date }} for ${{ renewal_amount }}.
+ {% if prorated_charges %}
+ You have ${{ prorated_charges }} in prorated charges that will be
+ added to your next bill.
+ {% elif prorated_credits %}
+ You have ${{ prorated_credits }} in prorated credits that will be
+ automatically applied to your next bill.
+ {% endif %}
+
+
+ Payment method: {{ payment_method }}.
+
+ Contact support@zulipchat.com for billing history or to make changes to your subscription.
+ {% else %}
+ You must be an organization administrator or a billing administrator to view this page.
{% endif %}
-
-
- Payment method: {{ payment_method }}.
-
- Contact support@zulipchat.com for billing history or to make changes to your subscription.
{% endblock %}
diff --git a/zilencer/tests/test_stripe.py b/zilencer/tests/test_stripe.py
index d3b896fbac..f2d35ff554 100644
--- a/zilencer/tests/test_stripe.py
+++ b/zilencer/tests/test_stripe.py
@@ -129,6 +129,37 @@ class StripeTest(ZulipTestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual('/billing/', response.url)
+ @mock.patch("zilencer.lib.stripe.STRIPE_PUBLISHABLE_KEY", "stripe_publishable_key")
+ @mock.patch("zilencer.views.STRIPE_PUBLISHABLE_KEY", "stripe_publishable_key")
+ @mock.patch("stripe.Invoice.upcoming", side_effect=mock_upcoming_invoice)
+ @mock.patch("stripe.Customer.retrieve", side_effect=mock_retrieve_customer)
+ @mock.patch("stripe.Customer.create", side_effect=mock_create_customer)
+ @mock.patch("stripe.Subscription.create", side_effect=mock_create_subscription)
+ def test_billing_page_permissions(self, mock_create_subscription: mock.Mock,
+ mock_create_customer: mock.Mock,
+ mock_retrieve_customer: mock.Mock,
+ mock_upcoming_invoice: mock.Mock) -> None:
+ # Check that non-admins can access /upgrade via /billing, when there is no Customer object
+ self.login(self.example_email('hamlet'))
+ response = self.client_get("/billing/")
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual('/upgrade/', response.url)
+ # Check that non-admins can sign up and pay
+ self.client_post("/upgrade/", {'stripeToken': self.token,
+ 'seat_count': self.quantity,
+ 'plan': Plan.CLOUD_ANNUAL})
+ # Check that the non-admin hamlet can still access /billing
+ response = self.client_get("/billing/")
+ self.assert_in_success_response(["Contact support@zulipchat.com for billing"], response)
+ # Check admins can access billing, even though they are not the billing_user
+ self.login(self.example_email('iago'))
+ response = self.client_get("/billing/")
+ self.assert_in_success_response(["Contact support@zulipchat.com for billing"], response)
+ # Check that non-admin, non-billing_user does not have access
+ self.login(self.example_email("cordelia"))
+ response = self.client_get("/billing/")
+ self.assert_in_success_response(["You must be an organization administrator"], response)
+
@mock.patch("zilencer.lib.stripe.STRIPE_PUBLISHABLE_KEY", "stripe_publishable_key")
@mock.patch("zilencer.views.STRIPE_PUBLISHABLE_KEY", "stripe_publishable_key")
@mock.patch("stripe.Customer.create", side_effect=mock_create_customer)
diff --git a/zilencer/views.py b/zilencer/views.py
index fe0cf97039..49c1d52962 100644
--- a/zilencer/views.py
+++ b/zilencer/views.py
@@ -199,10 +199,10 @@ def billing_home(request: HttpRequest) -> HttpResponse:
if customer is None:
return HttpResponseRedirect(reverse('zilencer.views.initial_upgrade'))
- # TODO
- # if not user.is_realm_admin and not user == customer.billing_user:
- # context['error_message'] = _("You must be an administrator to view this page.")
- # return render(request, 'zilencer/billing.html', context=context)
+ if not user.is_realm_admin and not user == customer.billing_user:
+ context = {'admin_access': False} # type: Dict[str, Any]
+ return render(request, 'zilencer/billing.html', context=context)
+ context = {'admin_access': True}
stripe_customer = get_stripe_customer(customer.stripe_customer_id)
@@ -230,7 +230,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
if source is not None:
payment_method = "Card ending in %(last4)s" % {'last4': source.last4}
- context = {
+ context.update({
'plan_name': plan_name,
'seat_count': seat_count,
'renewal_date': renewal_date,
@@ -238,6 +238,6 @@ def billing_home(request: HttpRequest) -> HttpResponse:
'payment_method': payment_method,
'prorated_charges': '{:,.2f}'.format(prorated_charges),
'prorated_credits': '{:,.2f}'.format(prorated_credits),
- } # type: Dict[str, Any]
+ })
return render(request, 'zilencer/billing.html', context=context)