diff --git a/zerver/migrations/0187_userprofile_is_billing_admin.py b/zerver/migrations/0187_userprofile_is_billing_admin.py new file mode 100644 index 0000000000..33a4d4aa9a --- /dev/null +++ b/zerver/migrations/0187_userprofile_is_billing_admin.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.14 on 2018-08-22 05:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0186_userprofile_starred_message_counts'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='is_billing_admin', + field=models.BooleanField(db_index=True, default=False), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 9333cc50d2..5d1fb06b03 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -690,6 +690,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): is_active = models.BooleanField(default=True, db_index=True) # type: bool is_realm_admin = models.BooleanField(default=False, db_index=True) # type: bool + is_billing_admin = models.BooleanField(default=False, db_index=True) # type: bool # Guest users are limited users without default access to public streams (etc.) is_guest = models.BooleanField(default=False, db_index=True) # type: bool diff --git a/zilencer/lib/stripe.py b/zilencer/lib/stripe.py index 46dbb88ade..80ccb0b3e2 100644 --- a/zilencer/lib/stripe.py +++ b/zilencer/lib/stripe.py @@ -168,6 +168,8 @@ def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None, realm=realm, stripe_customer_id=stripe_customer.id, billing_user=user) + user.is_billing_admin = True + user.save(update_fields=["is_billing_admin"]) return stripe_customer @catch_stripe_errors @@ -190,7 +192,7 @@ def do_replace_coupon(user: UserProfile, coupon: Coupon) -> stripe.Customer: return stripe_customer.save() @catch_stripe_errors -def do_subscribe_customer_to_plan(stripe_customer: stripe.Customer, stripe_plan_id: str, +def do_subscribe_customer_to_plan(user: UserProfile, stripe_customer: stripe.Customer, stripe_plan_id: str, seat_count: int, tax_percent: float) -> None: if extract_current_subscription(stripe_customer) is not None: # Most likely due to two people in the org going to the billing page, @@ -227,7 +229,7 @@ def do_subscribe_customer_to_plan(stripe_customer: stripe.Customer, stripe_plan_ customer.realm.save(update_fields=['has_seat_based_plan']) RealmAuditLog.objects.create( realm=customer.realm, - acting_user=customer.billing_user, + acting_user=user, event_type=RealmAuditLog.STRIPE_PLAN_CHANGED, event_time=timestamp_to_datetime(stripe_subscription.created), extra_data=ujson.dumps({'plan': stripe_plan_id, 'quantity': seat_count})) @@ -248,6 +250,7 @@ def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stri else: stripe_customer = do_replace_payment_source(user, stripe_token) do_subscribe_customer_to_plan( + user=user, stripe_customer=stripe_customer, stripe_plan_id=plan.stripe_plan_id, seat_count=seat_count, diff --git a/zilencer/tests/test_stripe.py b/zilencer/tests/test_stripe.py index 627578eb3a..7756ad1334 100644 --- a/zilencer/tests/test_stripe.py +++ b/zilencer/tests/test_stripe.py @@ -177,11 +177,11 @@ class StripeTest(ZulipTestCase): # Check that the non-admin hamlet can still access /billing response = self.client_get("/billing/") self.assert_in_success_response(["for billing history or to make changes"], response) - # Check admins can access billing, even though they are not the billing_user + # Check admins can access billing, even though they are not a billing admin self.login(self.example_email('iago')) response = self.client_get("/billing/") self.assert_in_success_response(["for billing history or to make changes"], response) - # Check that non-admin, non-billing_user does not have access + # Check that a non-admin, non-billing admin 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) @@ -310,7 +310,7 @@ class StripeTest(ZulipTestCase): @mock.patch("stripe.Invoice.upcoming", side_effect=mock_upcoming_invoice) def test_billing_home(self, mock_upcoming_invoice: mock.Mock, mock_customer_with_subscription: mock.Mock) -> None: - user = self.example_user("hamlet") + user = self.example_user("iago") self.login(user.email) # No Customer yet; check that we are redirected to /upgrade response = self.client_get("/billing/") @@ -320,6 +320,7 @@ class StripeTest(ZulipTestCase): Customer.objects.create( realm=user.realm, stripe_customer_id=self.stripe_customer_id, billing_user=user, has_billing_relationship=True) + response = self.client_get("/billing/") self.assert_not_in_success_response(['We can also bill by invoice'], response) for substring in ['Your plan will renew on', '$%s.00' % (80 * self.quantity,), @@ -350,7 +351,8 @@ class StripeTest(ZulipTestCase): def test_subscribe_customer_to_second_plan(self) -> None: with self.assertRaisesRegex(BillingError, 'subscribing with existing subscription'): - do_subscribe_customer_to_plan(mock_customer_with_subscription(), + do_subscribe_customer_to_plan(self.example_user("iago"), + mock_customer_with_subscription(), self.stripe_plan_id, self.quantity, 0) def test_sign_string(self) -> None: diff --git a/zilencer/views.py b/zilencer/views.py index 4a4037e7d3..a1d831c303 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -236,7 +236,7 @@ def billing_home(request: HttpRequest) -> HttpResponse: if not customer.has_billing_relationship: return HttpResponseRedirect(reverse('zilencer.views.initial_upgrade')) - if not user.is_realm_admin and not user == customer.billing_user: + if not user.is_realm_admin and not user.is_billing_admin: context = {'admin_access': False} # type: Dict[str, Any] return render(request, 'zilencer/billing.html', context=context) context = {'admin_access': True}