mirror of https://github.com/zulip/zulip.git
billing: Enforce min license for plan on upgrade and billing page.
This commit is contained in:
parent
67d4e8456d
commit
dca7e654ca
|
@ -184,8 +184,9 @@ def validate_licenses(
|
||||||
licenses: Optional[int],
|
licenses: Optional[int],
|
||||||
seat_count: int,
|
seat_count: int,
|
||||||
exempt_from_license_number_check: bool,
|
exempt_from_license_number_check: bool,
|
||||||
|
min_licenses_for_plan: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
min_licenses = seat_count
|
min_licenses = max(seat_count, min_licenses_for_plan)
|
||||||
max_licenses = None
|
max_licenses = None
|
||||||
if not charge_automatically:
|
if not charge_automatically:
|
||||||
min_licenses = max(seat_count, MIN_INVOICED_LICENSES)
|
min_licenses = max(seat_count, MIN_INVOICED_LICENSES)
|
||||||
|
@ -214,6 +215,7 @@ def check_upgrade_parameters(
|
||||||
licenses: Optional[int],
|
licenses: Optional[int],
|
||||||
seat_count: int,
|
seat_count: int,
|
||||||
exempt_from_license_number_check: bool,
|
exempt_from_license_number_check: bool,
|
||||||
|
min_licenses_for_plan: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if billing_modality not in VALID_BILLING_MODALITY_VALUES: # nocoverage
|
if billing_modality not in VALID_BILLING_MODALITY_VALUES: # nocoverage
|
||||||
raise BillingError("unknown billing_modality", "")
|
raise BillingError("unknown billing_modality", "")
|
||||||
|
@ -226,6 +228,7 @@ def check_upgrade_parameters(
|
||||||
licenses,
|
licenses,
|
||||||
seat_count,
|
seat_count,
|
||||||
exempt_from_license_number_check,
|
exempt_from_license_number_check,
|
||||||
|
min_licenses_for_plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -633,6 +636,7 @@ class UpgradePageContext(TypedDict):
|
||||||
free_trial_end_date: Optional[str]
|
free_trial_end_date: Optional[str]
|
||||||
is_demo_organization: bool
|
is_demo_organization: bool
|
||||||
manual_license_management: bool
|
manual_license_management: bool
|
||||||
|
using_min_licenses_for_plan: bool
|
||||||
page_params: UpgradePageParams
|
page_params: UpgradePageParams
|
||||||
payment_method: Optional[str]
|
payment_method: Optional[str]
|
||||||
plan: str
|
plan: str
|
||||||
|
@ -1287,6 +1291,7 @@ class BillingSession(ABC):
|
||||||
licenses,
|
licenses,
|
||||||
seat_count,
|
seat_count,
|
||||||
exempt_from_license_number_check,
|
exempt_from_license_number_check,
|
||||||
|
self.min_licenses_for_plan(upgrade_request.tier),
|
||||||
)
|
)
|
||||||
assert licenses is not None and license_management is not None
|
assert licenses is not None and license_management is not None
|
||||||
automanage_licenses = license_management == "automatic"
|
automanage_licenses = license_management == "automatic"
|
||||||
|
@ -1612,7 +1617,12 @@ class BillingSession(ABC):
|
||||||
licenses = last_ledger_entry.licenses
|
licenses = last_ledger_entry.licenses
|
||||||
licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
|
licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
|
||||||
assert licenses_at_next_renewal is not None
|
assert licenses_at_next_renewal is not None
|
||||||
|
min_licenses_for_plan = self.min_licenses_for_plan(plan.tier)
|
||||||
seat_count = self.current_count_for_billed_licenses()
|
seat_count = self.current_count_for_billed_licenses()
|
||||||
|
using_min_licenses_for_plan = (
|
||||||
|
min_licenses_for_plan == licenses_at_next_renewal
|
||||||
|
and licenses_at_next_renewal > seat_count
|
||||||
|
)
|
||||||
|
|
||||||
# Should do this in JavaScript, using the user's time zone
|
# Should do this in JavaScript, using the user's time zone
|
||||||
if plan.is_free_trial() or downgrade_at_end_of_free_trial:
|
if plan.is_free_trial() or downgrade_at_end_of_free_trial:
|
||||||
|
@ -1693,6 +1703,7 @@ class BillingSession(ABC):
|
||||||
"is_server_on_legacy_plan": remote_server_legacy_plan_end_date is not None,
|
"is_server_on_legacy_plan": remote_server_legacy_plan_end_date is not None,
|
||||||
"remote_server_legacy_plan_end_date": remote_server_legacy_plan_end_date,
|
"remote_server_legacy_plan_end_date": remote_server_legacy_plan_end_date,
|
||||||
"legacy_remote_server_new_plan_name": legacy_remote_server_new_plan_name,
|
"legacy_remote_server_new_plan_name": legacy_remote_server_new_plan_name,
|
||||||
|
"using_min_licenses_for_plan": using_min_licenses_for_plan,
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -1727,6 +1738,7 @@ class BillingSession(ABC):
|
||||||
"fixed_price",
|
"fixed_price",
|
||||||
"price_per_license",
|
"price_per_license",
|
||||||
"discount_percent",
|
"discount_percent",
|
||||||
|
"using_min_licenses_for_plan",
|
||||||
]
|
]
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
|
@ -1767,10 +1779,14 @@ class BillingSession(ABC):
|
||||||
# Show "Update card" button if user has already added a card.
|
# Show "Update card" button if user has already added a card.
|
||||||
current_payment_method = None if "ending in" not in payment_method else payment_method
|
current_payment_method = None if "ending in" not in payment_method else payment_method
|
||||||
|
|
||||||
customer_specific_context = self.get_upgrade_page_session_type_specific_context()
|
|
||||||
seat_count = self.current_count_for_billed_licenses()
|
|
||||||
signed_seat_count, salt = sign_string(str(seat_count))
|
|
||||||
tier = initial_upgrade_request.tier
|
tier = initial_upgrade_request.tier
|
||||||
|
customer_specific_context = self.get_upgrade_page_session_type_specific_context()
|
||||||
|
min_licenses_for_plan = self.min_licenses_for_plan(tier)
|
||||||
|
seat_count = self.current_count_for_billed_licenses()
|
||||||
|
using_min_licenses_for_plan = min_licenses_for_plan > seat_count
|
||||||
|
if using_min_licenses_for_plan:
|
||||||
|
seat_count = min_licenses_for_plan
|
||||||
|
signed_seat_count, salt = sign_string(str(seat_count))
|
||||||
|
|
||||||
free_trial_days = None
|
free_trial_days = None
|
||||||
free_trial_end_date = None
|
free_trial_end_date = None
|
||||||
|
@ -1809,6 +1825,7 @@ class BillingSession(ABC):
|
||||||
"seat_count": seat_count,
|
"seat_count": seat_count,
|
||||||
"billing_base_url": self.billing_base_url,
|
"billing_base_url": self.billing_base_url,
|
||||||
},
|
},
|
||||||
|
"using_min_licenses_for_plan": using_min_licenses_for_plan,
|
||||||
"payment_method": current_payment_method,
|
"payment_method": current_payment_method,
|
||||||
"plan": CustomerPlan.name_from_tier(tier),
|
"plan": CustomerPlan.name_from_tier(tier),
|
||||||
"salt": salt,
|
"salt": salt,
|
||||||
|
@ -1819,6 +1836,11 @@ class BillingSession(ABC):
|
||||||
|
|
||||||
return None, context
|
return None, context
|
||||||
|
|
||||||
|
def min_licenses_for_plan(self, tier: int) -> int:
|
||||||
|
if tier == CustomerPlan.TIER_SELF_HOSTED_BUSINESS:
|
||||||
|
return 10
|
||||||
|
return 1
|
||||||
|
|
||||||
def downgrade_at_the_end_of_billing_cycle(self, plan: Optional[CustomerPlan] = None) -> None:
|
def downgrade_at_the_end_of_billing_cycle(self, plan: Optional[CustomerPlan] = None) -> None:
|
||||||
if plan is None: # nocoverage
|
if plan is None: # nocoverage
|
||||||
# TODO: Add test coverage. Right now, this logic is used
|
# TODO: Add test coverage. Right now, this logic is used
|
||||||
|
@ -1955,6 +1977,7 @@ class BillingSession(ABC):
|
||||||
licenses,
|
licenses,
|
||||||
self.current_count_for_billed_licenses(),
|
self.current_count_for_billed_licenses(),
|
||||||
plan.customer.exempt_from_license_number_check,
|
plan.customer.exempt_from_license_number_check,
|
||||||
|
self.min_licenses_for_plan(plan.tier),
|
||||||
)
|
)
|
||||||
self.update_license_ledger_for_manual_plan(plan, timezone_now(), licenses=licenses)
|
self.update_license_ledger_for_manual_plan(plan, timezone_now(), licenses=licenses)
|
||||||
return
|
return
|
||||||
|
@ -1987,6 +2010,7 @@ class BillingSession(ABC):
|
||||||
licenses_at_next_renewal,
|
licenses_at_next_renewal,
|
||||||
self.current_count_for_billed_licenses(),
|
self.current_count_for_billed_licenses(),
|
||||||
plan.customer.exempt_from_license_number_check,
|
plan.customer.exempt_from_license_number_check,
|
||||||
|
self.min_licenses_for_plan(plan.tier),
|
||||||
)
|
)
|
||||||
self.update_license_ledger_for_manual_plan(
|
self.update_license_ledger_for_manual_plan(
|
||||||
plan, timezone_now(), licenses_at_next_renewal=licenses_at_next_renewal
|
plan, timezone_now(), licenses_at_next_renewal=licenses_at_next_renewal
|
||||||
|
|
|
@ -193,11 +193,14 @@
|
||||||
(${{ price_per_license }} x {{ licenses_at_next_renewal }} {{ 'user' if licenses_at_next_renewal == 1 else 'users' }} x
|
(${{ price_per_license }} x {{ licenses_at_next_renewal }} {{ 'user' if licenses_at_next_renewal == 1 else 'users' }} x
|
||||||
{% if switch_to_annual_at_end_of_cycle %}
|
{% if switch_to_annual_at_end_of_cycle %}
|
||||||
12 months
|
12 months
|
||||||
{% elif switch_to_monthly_at_end_of_cycle %}
|
{%- elif switch_to_monthly_at_end_of_cycle %}
|
||||||
1 month
|
1 month
|
||||||
{% else %}
|
{%- else %}
|
||||||
{{ "1 month" if billing_frequency == "Monthly" else "12 months" }}
|
{{ "1 month" if billing_frequency == "Monthly" else "12 months" }}
|
||||||
{% endif %})
|
{%- endif -%})
|
||||||
|
{% if using_min_licenses_for_plan %}
|
||||||
|
(<i>minimum purchase</i>)
|
||||||
|
{% endif %}
|
||||||
{% if discount_percent %}
|
{% if discount_percent %}
|
||||||
<br />
|
<br />
|
||||||
<i class="billing-page-discount">Includes {{ discount_percent }}% discount.</i>
|
<i class="billing-page-discount">Includes {{ discount_percent }}% discount.</i>
|
||||||
|
|
|
@ -125,8 +125,12 @@
|
||||||
{{ 'user' if seat_count == 1 else 'users' }}
|
{{ 'user' if seat_count == 1 else 'users' }}
|
||||||
</span> x
|
</span> x
|
||||||
<span class="due-today-duration"></span>
|
<span class="due-today-duration"></span>
|
||||||
|
{% if not manual_license_management and using_min_licenses_for_plan %}
|
||||||
|
<i>(minimum purchase)</i>
|
||||||
|
{% endif %}
|
||||||
{% if discount_percent %}
|
{% if discount_percent %}
|
||||||
<i class="billing-page-discount">(includes {{ discount_percent }}% discount)</i>
|
<br/>
|
||||||
|
<i class="billing-page-discount">Includes {{ discount_percent }}% discount.</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h1>$<span class="due-today-price"></span></h1>
|
<h1>$<span class="due-today-price"></span></h1>
|
||||||
{% if free_trial_days %}
|
{% if free_trial_days %}
|
||||||
|
|
Loading…
Reference in New Issue