diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 79bfa86344..8649b088de 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -71,6 +71,26 @@ def unsign_string(signed_string: str, salt: str) -> str: return signer.unsign(signed_string) +def validate_licenses(charge_automatically: bool, licenses: Optional[int], seat_count: int) -> None: + min_licenses = seat_count + max_licenses = None + if not charge_automatically: + min_licenses = max(seat_count, MIN_INVOICED_LICENSES) + max_licenses = MAX_INVOICED_LICENSES + + if licenses is None or licenses < min_licenses: + raise BillingError( + "not enough licenses", _("You must invoice for at least {} users.").format(min_licenses) + ) + + if max_licenses is not None and licenses > max_licenses: + message = _( + "Invoices with more than {} licenses can't be processed from this page. To complete " + "the upgrade, please contact {}." + ).format(max_licenses, settings.ZULIP_ADMINISTRATOR) + raise BillingError("too many licenses", message) + + # Be extremely careful changing this function. Historical billing periods # are not stored anywhere, and are just computed on the fly using this # function. Any change you make here should return the same value (or be diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index dc009c0778..bfeb724508 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -1363,7 +1363,7 @@ class StripeTest(StripeTestCase): # Invoice with licenses < MIN_INVOICED_LICENSES check_min_licenses_error(True, MIN_INVOICED_LICENSES - 1, MIN_INVOICED_LICENSES) # Invoice with licenses < seat count - with patch("corporate.views.MIN_INVOICED_LICENSES", 3): + with patch("corporate.lib.stripe.MIN_INVOICED_LICENSES", 3): check_min_licenses_error(True, 4, self.seat_count) # Invoice with not setting licenses check_min_licenses_error(True, None, MIN_INVOICED_LICENSES) diff --git a/corporate/views.py b/corporate/views.py index 4661a1e0a3..ce104b42be 100644 --- a/corporate/views.py +++ b/corporate/views.py @@ -14,7 +14,6 @@ from django.utils.translation import gettext as _ from corporate.lib.stripe import ( DEFAULT_INVOICE_DAYS_UNTIL_DUE, - MAX_INVOICED_LICENSES, MIN_INVOICED_LICENSES, STRIPE_PUBLISHABLE_KEY, BillingError, @@ -31,6 +30,7 @@ from corporate.lib.stripe import ( stripe_get_customer, unsign_string, update_sponsorship_status, + validate_licenses, ) from corporate.models import ( CustomerPlan, @@ -78,27 +78,13 @@ def check_upgrade_parameters( if license_management not in VALID_LICENSE_MANAGEMENT_VALUES: # nocoverage raise BillingError("unknown license_management") + charge_automatically = False if billing_modality == "charge_automatically": + charge_automatically = True if not has_stripe_token: raise BillingError("autopay with no card") - min_licenses = seat_count - max_licenses = None - if billing_modality == "send_invoice": - min_licenses = max(seat_count, MIN_INVOICED_LICENSES) - max_licenses = MAX_INVOICED_LICENSES - - if licenses is None or licenses < min_licenses: - raise BillingError( - "not enough licenses", _("You must invoice for at least {} users.").format(min_licenses) - ) - - if max_licenses is not None and licenses > max_licenses: - message = _( - "Invoices with more than {} licenses can't be processed from this page. To complete " - "the upgrade, please contact {}." - ).format(max_licenses, settings.ZULIP_ADMINISTRATOR) - raise BillingError("too many licenses", message) + validate_licenses(charge_automatically, licenses, seat_count) # Should only be called if the customer is being charged automatically