mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
billing: Add BILLING_SCHEDULE_ prefix to values.
This commit is contained in:
@@ -292,7 +292,7 @@ class TestSupportEndpoint(ZulipTestCase):
|
|||||||
plan = CustomerPlan.objects.create(
|
plan = CustomerPlan.objects.create(
|
||||||
customer=customer,
|
customer=customer,
|
||||||
billing_cycle_anchor=now,
|
billing_cycle_anchor=now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
price_per_license=8000,
|
price_per_license=8000,
|
||||||
next_invoice_date=add_months(now, 12),
|
next_invoice_date=add_months(now, 12),
|
||||||
|
@@ -32,7 +32,7 @@ def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverag
|
|||||||
# TODO: figure out what to do for plans that don't automatically
|
# TODO: figure out what to do for plans that don't automatically
|
||||||
# renew, but which probably will renew
|
# renew, but which probably will renew
|
||||||
renewal_cents = renewal_amount(plan, timezone_now())
|
renewal_cents = renewal_amount(plan, timezone_now())
|
||||||
if plan.billing_schedule == CustomerPlan.MONTHLY:
|
if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
|
||||||
renewal_cents *= 12
|
renewal_cents *= 12
|
||||||
# TODO: Decimal stuff
|
# TODO: Decimal stuff
|
||||||
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
|
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
|
||||||
|
@@ -265,8 +265,8 @@ def next_month(billing_cycle_anchor: datetime, dt: datetime) -> datetime:
|
|||||||
|
|
||||||
def start_of_next_billing_cycle(plan: CustomerPlan, event_time: datetime) -> datetime:
|
def start_of_next_billing_cycle(plan: CustomerPlan, event_time: datetime) -> datetime:
|
||||||
months_per_period = {
|
months_per_period = {
|
||||||
CustomerPlan.ANNUAL: 12,
|
CustomerPlan.BILLING_SCHEDULE_ANNUAL: 12,
|
||||||
CustomerPlan.MONTHLY: 1,
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY: 1,
|
||||||
}[plan.billing_schedule]
|
}[plan.billing_schedule]
|
||||||
periods = 1
|
periods = 1
|
||||||
dt = plan.billing_cycle_anchor
|
dt = plan.billing_cycle_anchor
|
||||||
@@ -281,8 +281,8 @@ def next_invoice_date(plan: CustomerPlan) -> Optional[datetime]:
|
|||||||
return None
|
return None
|
||||||
assert plan.next_invoice_date is not None # for mypy
|
assert plan.next_invoice_date is not None # for mypy
|
||||||
months_per_period = {
|
months_per_period = {
|
||||||
CustomerPlan.ANNUAL: 12,
|
CustomerPlan.BILLING_SCHEDULE_ANNUAL: 12,
|
||||||
CustomerPlan.MONTHLY: 1,
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY: 1,
|
||||||
}[plan.billing_schedule]
|
}[plan.billing_schedule]
|
||||||
if plan.automanage_licenses:
|
if plan.automanage_licenses:
|
||||||
months_per_period = 1
|
months_per_period = 1
|
||||||
@@ -1060,9 +1060,10 @@ class BillingSession(ABC):
|
|||||||
automanage_licenses = license_management == "automatic"
|
automanage_licenses = license_management == "automatic"
|
||||||
charge_automatically = billing_modality == "charge_automatically"
|
charge_automatically = billing_modality == "charge_automatically"
|
||||||
|
|
||||||
billing_schedule = {"annual": CustomerPlan.ANNUAL, "monthly": CustomerPlan.MONTHLY}[
|
billing_schedule = {
|
||||||
schedule
|
"annual": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
]
|
"monthly": CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
}[schedule]
|
||||||
data: Dict[str, Any] = {}
|
data: Dict[str, Any] = {}
|
||||||
free_trial = is_free_trial_offer_enabled()
|
free_trial = is_free_trial_offer_enabled()
|
||||||
# Directly upgrade free trial orgs or invoice payment orgs to standard plan.
|
# Directly upgrade free trial orgs or invoice payment orgs to standard plan.
|
||||||
@@ -1090,7 +1091,10 @@ class BillingSession(ABC):
|
|||||||
|
|
||||||
def do_change_schedule_after_free_trial(self, plan: CustomerPlan, schedule: int) -> None:
|
def do_change_schedule_after_free_trial(self, plan: CustomerPlan, schedule: int) -> None:
|
||||||
# Change the billing frequency of the plan after the free trial ends.
|
# Change the billing frequency of the plan after the free trial ends.
|
||||||
assert schedule in (CustomerPlan.MONTHLY, CustomerPlan.ANNUAL)
|
assert schedule in (
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
|
)
|
||||||
last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by("-id").first()
|
last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by("-id").first()
|
||||||
assert last_ledger_entry is not None
|
assert last_ledger_entry is not None
|
||||||
licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
|
licenses_at_next_renewal = last_ledger_entry.licenses_at_next_renewal
|
||||||
@@ -1135,7 +1139,7 @@ class BillingSession(ABC):
|
|||||||
licenses_at_next_renewal=licenses_at_next_renewal,
|
licenses_at_next_renewal=licenses_at_next_renewal,
|
||||||
)
|
)
|
||||||
|
|
||||||
if schedule == CustomerPlan.ANNUAL:
|
if schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
|
||||||
self.write_to_audit_log(
|
self.write_to_audit_log(
|
||||||
event_type=AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
|
event_type=AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
|
||||||
event_time=timezone_now(),
|
event_time=timezone_now(),
|
||||||
@@ -1217,13 +1221,13 @@ class BillingSession(ABC):
|
|||||||
_, _, _, price_per_license = compute_plan_parameters(
|
_, _, _, price_per_license = compute_plan_parameters(
|
||||||
tier=plan.tier,
|
tier=plan.tier,
|
||||||
automanage_licenses=plan.automanage_licenses,
|
automanage_licenses=plan.automanage_licenses,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
discount=plan.discount,
|
discount=plan.discount,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_plan = CustomerPlan.objects.create(
|
new_plan = CustomerPlan.objects.create(
|
||||||
customer=plan.customer,
|
customer=plan.customer,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
automanage_licenses=plan.automanage_licenses,
|
automanage_licenses=plan.automanage_licenses,
|
||||||
charge_automatically=plan.charge_automatically,
|
charge_automatically=plan.charge_automatically,
|
||||||
price_per_license=price_per_license,
|
price_per_license=price_per_license,
|
||||||
@@ -1265,13 +1269,13 @@ class BillingSession(ABC):
|
|||||||
_, _, _, price_per_license = compute_plan_parameters(
|
_, _, _, price_per_license = compute_plan_parameters(
|
||||||
tier=plan.tier,
|
tier=plan.tier,
|
||||||
automanage_licenses=plan.automanage_licenses,
|
automanage_licenses=plan.automanage_licenses,
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
discount=plan.discount,
|
discount=plan.discount,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_plan = CustomerPlan.objects.create(
|
new_plan = CustomerPlan.objects.create(
|
||||||
customer=plan.customer,
|
customer=plan.customer,
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
automanage_licenses=plan.automanage_licenses,
|
automanage_licenses=plan.automanage_licenses,
|
||||||
charge_automatically=plan.charge_automatically,
|
charge_automatically=plan.charge_automatically,
|
||||||
price_per_license=price_per_license,
|
price_per_license=price_per_license,
|
||||||
@@ -1351,13 +1355,13 @@ class BillingSession(ABC):
|
|||||||
|
|
||||||
if switch_to_annual_at_end_of_cycle:
|
if switch_to_annual_at_end_of_cycle:
|
||||||
annual_price_per_license = get_price_per_license(
|
annual_price_per_license = get_price_per_license(
|
||||||
plan.tier, CustomerPlan.ANNUAL, customer.default_discount
|
plan.tier, CustomerPlan.BILLING_SCHEDULE_ANNUAL, customer.default_discount
|
||||||
)
|
)
|
||||||
renewal_cents = annual_price_per_license * licenses_at_next_renewal
|
renewal_cents = annual_price_per_license * licenses_at_next_renewal
|
||||||
price_per_license = format_money(annual_price_per_license / 12)
|
price_per_license = format_money(annual_price_per_license / 12)
|
||||||
elif switch_to_monthly_at_end_of_cycle:
|
elif switch_to_monthly_at_end_of_cycle:
|
||||||
monthly_price_per_license = get_price_per_license(
|
monthly_price_per_license = get_price_per_license(
|
||||||
plan.tier, CustomerPlan.MONTHLY, customer.default_discount
|
plan.tier, CustomerPlan.BILLING_SCHEDULE_MONTHLY, customer.default_discount
|
||||||
)
|
)
|
||||||
renewal_cents = monthly_price_per_license * licenses_at_next_renewal
|
renewal_cents = monthly_price_per_license * licenses_at_next_renewal
|
||||||
price_per_license = format_money(monthly_price_per_license)
|
price_per_license = format_money(monthly_price_per_license)
|
||||||
@@ -1448,7 +1452,7 @@ class BillingSession(ABC):
|
|||||||
free_trial_end_date = None
|
free_trial_end_date = None
|
||||||
if free_trial_days is not None:
|
if free_trial_days is not None:
|
||||||
_, _, free_trial_end, _ = compute_plan_parameters(
|
_, _, free_trial_end, _ = compute_plan_parameters(
|
||||||
tier, False, CustomerPlan.ANNUAL, None, True
|
tier, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, None, True
|
||||||
)
|
)
|
||||||
free_trial_end_date = f"{free_trial_end:%B} {free_trial_end.day}, {free_trial_end.year}"
|
free_trial_end_date = f"{free_trial_end:%B} {free_trial_end.day}, {free_trial_end.year}"
|
||||||
|
|
||||||
@@ -1464,11 +1468,15 @@ class BillingSession(ABC):
|
|||||||
"manual_license_management": initial_upgrade_request.manual_license_management,
|
"manual_license_management": initial_upgrade_request.manual_license_management,
|
||||||
"min_invoiced_licenses": max(seat_count, MIN_INVOICED_LICENSES),
|
"min_invoiced_licenses": max(seat_count, MIN_INVOICED_LICENSES),
|
||||||
"page_params": {
|
"page_params": {
|
||||||
"annual_price": get_price_per_license(tier, CustomerPlan.ANNUAL, percent_off),
|
"annual_price": get_price_per_license(
|
||||||
|
tier, CustomerPlan.BILLING_SCHEDULE_ANNUAL, percent_off
|
||||||
|
),
|
||||||
"demo_organization_scheduled_deletion_date": customer_specific_context[
|
"demo_organization_scheduled_deletion_date": customer_specific_context[
|
||||||
"demo_organization_scheduled_deletion_date"
|
"demo_organization_scheduled_deletion_date"
|
||||||
],
|
],
|
||||||
"monthly_price": get_price_per_license(tier, CustomerPlan.MONTHLY, percent_off),
|
"monthly_price": get_price_per_license(
|
||||||
|
tier, CustomerPlan.BILLING_SCHEDULE_MONTHLY, percent_off
|
||||||
|
),
|
||||||
"seat_count": seat_count,
|
"seat_count": seat_count,
|
||||||
},
|
},
|
||||||
"payment_method": current_payment_method,
|
"payment_method": current_payment_method,
|
||||||
@@ -1537,7 +1545,7 @@ class BillingSession(ABC):
|
|||||||
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
||||||
self.downgrade_at_the_end_of_billing_cycle(plan=plan)
|
self.downgrade_at_the_end_of_billing_cycle(plan=plan)
|
||||||
elif status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE:
|
elif status == CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE:
|
||||||
assert plan.billing_schedule == CustomerPlan.MONTHLY
|
assert plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
||||||
# Customer needs to switch to an active plan first to avoid unexpected behavior.
|
# Customer needs to switch to an active plan first to avoid unexpected behavior.
|
||||||
assert plan.status != CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
assert plan.status != CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
||||||
@@ -1546,7 +1554,7 @@ class BillingSession(ABC):
|
|||||||
assert plan.fixed_price is None
|
assert plan.fixed_price is None
|
||||||
do_change_plan_status(plan, status)
|
do_change_plan_status(plan, status)
|
||||||
elif status == CustomerPlan.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE:
|
elif status == CustomerPlan.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE:
|
||||||
assert plan.billing_schedule == CustomerPlan.ANNUAL
|
assert plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD
|
||||||
# Customer needs to switch to an active plan first to avoid unexpected behavior.
|
# Customer needs to switch to an active plan first to avoid unexpected behavior.
|
||||||
assert plan.status != CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
assert plan.status != CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE
|
||||||
@@ -2658,16 +2666,16 @@ def get_price_per_license(
|
|||||||
price_per_license: Optional[int] = None
|
price_per_license: Optional[int] = None
|
||||||
|
|
||||||
if tier == CustomerPlan.TIER_CLOUD_STANDARD:
|
if tier == CustomerPlan.TIER_CLOUD_STANDARD:
|
||||||
if billing_schedule == CustomerPlan.ANNUAL:
|
if billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
|
||||||
price_per_license = 8000
|
price_per_license = 8000
|
||||||
elif billing_schedule == CustomerPlan.MONTHLY:
|
elif billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
|
||||||
price_per_license = 800
|
price_per_license = 800
|
||||||
else: # nocoverage
|
else: # nocoverage
|
||||||
raise InvalidBillingScheduleError(billing_schedule)
|
raise InvalidBillingScheduleError(billing_schedule)
|
||||||
elif tier == CustomerPlan.TIER_CLOUD_PLUS:
|
elif tier == CustomerPlan.TIER_CLOUD_PLUS:
|
||||||
if billing_schedule == CustomerPlan.ANNUAL:
|
if billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
|
||||||
price_per_license = 16000
|
price_per_license = 16000
|
||||||
elif billing_schedule == CustomerPlan.MONTHLY:
|
elif billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
|
||||||
price_per_license = 1600
|
price_per_license = 1600
|
||||||
else: # nocoverage
|
else: # nocoverage
|
||||||
raise InvalidBillingScheduleError(billing_schedule)
|
raise InvalidBillingScheduleError(billing_schedule)
|
||||||
@@ -2690,9 +2698,9 @@ def compute_plan_parameters(
|
|||||||
# so standardize on 1 second resolution.
|
# so standardize on 1 second resolution.
|
||||||
# TODO talk about leap seconds?
|
# TODO talk about leap seconds?
|
||||||
billing_cycle_anchor = timezone_now().replace(microsecond=0)
|
billing_cycle_anchor = timezone_now().replace(microsecond=0)
|
||||||
if billing_schedule == CustomerPlan.ANNUAL:
|
if billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
|
||||||
period_end = add_months(billing_cycle_anchor, 12)
|
period_end = add_months(billing_cycle_anchor, 12)
|
||||||
elif billing_schedule == CustomerPlan.MONTHLY:
|
elif billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
|
||||||
period_end = add_months(billing_cycle_anchor, 1)
|
period_end = add_months(billing_cycle_anchor, 1)
|
||||||
else: # nocoverage
|
else: # nocoverage
|
||||||
raise InvalidBillingScheduleError(billing_schedule)
|
raise InvalidBillingScheduleError(billing_schedule)
|
||||||
|
@@ -223,11 +223,11 @@ class CustomerPlan(models.Model):
|
|||||||
# billing_cycle_anchor.
|
# billing_cycle_anchor.
|
||||||
billing_cycle_anchor = models.DateTimeField()
|
billing_cycle_anchor = models.DateTimeField()
|
||||||
|
|
||||||
ANNUAL = 1
|
BILLING_SCHEDULE_ANNUAL = 1
|
||||||
MONTHLY = 2
|
BILLING_SCHEDULE_MONTHLY = 2
|
||||||
BILLING_SCHEDULES = {
|
BILLING_SCHEDULES = {
|
||||||
ANNUAL: "Annual",
|
BILLING_SCHEDULE_ANNUAL: "Annual",
|
||||||
MONTHLY: "Monthly",
|
BILLING_SCHEDULE_MONTHLY: "Monthly",
|
||||||
}
|
}
|
||||||
billing_schedule = models.SmallIntegerField()
|
billing_schedule = models.SmallIntegerField()
|
||||||
|
|
||||||
|
@@ -821,7 +821,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
invoiced_through=LicenseLedger.objects.first(),
|
invoiced_through=LicenseLedger.objects.first(),
|
||||||
next_invoice_date=self.next_month,
|
next_invoice_date=self.next_month,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -970,7 +970,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
invoiced_through=LicenseLedger.objects.first(),
|
invoiced_through=LicenseLedger.objects.first(),
|
||||||
next_invoice_date=self.next_year,
|
next_invoice_date=self.next_year,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -1080,7 +1080,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
invoiced_through=LicenseLedger.objects.first(),
|
invoiced_through=LicenseLedger.objects.first(),
|
||||||
next_invoice_date=free_trial_end_date,
|
next_invoice_date=free_trial_end_date,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -1290,7 +1290,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
invoiced_through=LicenseLedger.objects.first(),
|
invoiced_through=LicenseLedger.objects.first(),
|
||||||
next_invoice_date=free_trial_end_date,
|
next_invoice_date=free_trial_end_date,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -1496,10 +1496,12 @@ class StripeTest(StripeTestCase):
|
|||||||
def test_upgrade_race_condition_during_invoice_upgrade(self) -> None:
|
def test_upgrade_race_condition_during_invoice_upgrade(self) -> None:
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
self.login_user(hamlet)
|
self.login_user(hamlet)
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False)
|
||||||
with self.assertLogs("corporate.stripe", "WARNING") as m:
|
with self.assertLogs("corporate.stripe", "WARNING") as m:
|
||||||
with self.assertRaises(BillingError) as context:
|
with self.assertRaises(BillingError) as context:
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"subscribing with existing subscription", context.exception.error_description
|
"subscribing with existing subscription", context.exception.error_description
|
||||||
)
|
)
|
||||||
@@ -1971,7 +1973,7 @@ class StripeTest(StripeTestCase):
|
|||||||
CustomerPlan.objects.create(
|
CustomerPlan.objects.create(
|
||||||
customer=customer,
|
customer=customer,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
response = self.client_get("/upgrade/")
|
response = self.client_get("/upgrade/")
|
||||||
@@ -2204,7 +2206,9 @@ class StripeTest(StripeTestCase):
|
|||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
self.login_user(user)
|
self.login_user(user)
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
plan = get_current_plan_by_realm(user.realm)
|
plan = get_current_plan_by_realm(user.realm)
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.licenses(), self.seat_count)
|
self.assertEqual(plan.licenses(), self.seat_count)
|
||||||
@@ -2321,7 +2325,7 @@ class StripeTest(StripeTestCase):
|
|||||||
monthly_plan = get_current_plan_by_realm(user.realm)
|
monthly_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert monthly_plan is not None
|
assert monthly_plan is not None
|
||||||
self.assertEqual(monthly_plan.automanage_licenses, True)
|
self.assertEqual(monthly_plan.automanage_licenses, True)
|
||||||
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.MONTHLY)
|
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_MONTHLY)
|
||||||
|
|
||||||
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
||||||
new_plan = get_current_plan_by_realm(user.realm)
|
new_plan = get_current_plan_by_realm(user.realm)
|
||||||
@@ -2367,7 +2371,7 @@ class StripeTest(StripeTestCase):
|
|||||||
annual_plan = get_current_plan_by_realm(user.realm)
|
annual_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert annual_plan is not None
|
assert annual_plan is not None
|
||||||
self.assertEqual(annual_plan.status, CustomerPlan.ACTIVE)
|
self.assertEqual(annual_plan.status, CustomerPlan.ACTIVE)
|
||||||
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.ANNUAL)
|
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_ANNUAL)
|
||||||
self.assertEqual(annual_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
self.assertEqual(annual_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
||||||
self.assertEqual(annual_plan.billing_cycle_anchor, self.next_month)
|
self.assertEqual(annual_plan.billing_cycle_anchor, self.next_month)
|
||||||
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
||||||
@@ -2512,7 +2516,7 @@ class StripeTest(StripeTestCase):
|
|||||||
monthly_plan = get_current_plan_by_realm(user.realm)
|
monthly_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert monthly_plan is not None
|
assert monthly_plan is not None
|
||||||
self.assertEqual(monthly_plan.automanage_licenses, False)
|
self.assertEqual(monthly_plan.automanage_licenses, False)
|
||||||
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.MONTHLY)
|
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_MONTHLY)
|
||||||
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
||||||
new_plan = get_current_plan_by_realm(user.realm)
|
new_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert new_plan is not None
|
assert new_plan is not None
|
||||||
@@ -2547,7 +2551,7 @@ class StripeTest(StripeTestCase):
|
|||||||
annual_plan = get_current_plan_by_realm(user.realm)
|
annual_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert annual_plan is not None
|
assert annual_plan is not None
|
||||||
self.assertEqual(annual_plan.status, CustomerPlan.ACTIVE)
|
self.assertEqual(annual_plan.status, CustomerPlan.ACTIVE)
|
||||||
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.ANNUAL)
|
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_ANNUAL)
|
||||||
self.assertEqual(annual_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
self.assertEqual(annual_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
||||||
self.assertEqual(annual_plan.billing_cycle_anchor, self.next_month)
|
self.assertEqual(annual_plan.billing_cycle_anchor, self.next_month)
|
||||||
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
||||||
@@ -2624,7 +2628,7 @@ class StripeTest(StripeTestCase):
|
|||||||
annual_plan = get_current_plan_by_realm(user.realm)
|
annual_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert annual_plan is not None
|
assert annual_plan is not None
|
||||||
self.assertEqual(annual_plan.automanage_licenses, True)
|
self.assertEqual(annual_plan.automanage_licenses, True)
|
||||||
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.ANNUAL)
|
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_ANNUAL)
|
||||||
|
|
||||||
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
||||||
new_plan = get_current_plan_by_realm(user.realm)
|
new_plan = get_current_plan_by_realm(user.realm)
|
||||||
@@ -2669,7 +2673,7 @@ class StripeTest(StripeTestCase):
|
|||||||
annual_plan.refresh_from_db()
|
annual_plan.refresh_from_db()
|
||||||
self.assertEqual(annual_plan.status, CustomerPlan.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE)
|
self.assertEqual(annual_plan.status, CustomerPlan.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE)
|
||||||
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
self.assertEqual(annual_plan.next_invoice_date, self.next_month)
|
||||||
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.ANNUAL)
|
self.assertEqual(annual_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_ANNUAL)
|
||||||
self.assertEqual(LicenseLedger.objects.filter(plan=annual_plan).count(), 3)
|
self.assertEqual(LicenseLedger.objects.filter(plan=annual_plan).count(), 3)
|
||||||
|
|
||||||
invoice_plans_as_needed(self.next_month + timedelta(days=1))
|
invoice_plans_as_needed(self.next_month + timedelta(days=1))
|
||||||
@@ -2729,7 +2733,7 @@ class StripeTest(StripeTestCase):
|
|||||||
monthly_plan = get_current_plan_by_realm(user.realm)
|
monthly_plan = get_current_plan_by_realm(user.realm)
|
||||||
assert monthly_plan is not None
|
assert monthly_plan is not None
|
||||||
self.assertEqual(monthly_plan.status, CustomerPlan.ACTIVE)
|
self.assertEqual(monthly_plan.status, CustomerPlan.ACTIVE)
|
||||||
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.MONTHLY)
|
self.assertEqual(monthly_plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_MONTHLY)
|
||||||
self.assertEqual(monthly_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
self.assertEqual(monthly_plan.invoicing_status, CustomerPlan.INITIAL_INVOICE_TO_BE_SENT)
|
||||||
self.assertEqual(monthly_plan.billing_cycle_anchor, self.next_year)
|
self.assertEqual(monthly_plan.billing_cycle_anchor, self.next_year)
|
||||||
self.assertEqual(monthly_plan.next_invoice_date, self.next_year)
|
self.assertEqual(monthly_plan.next_invoice_date, self.next_year)
|
||||||
@@ -2795,7 +2799,9 @@ class StripeTest(StripeTestCase):
|
|||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
self.login_user(user)
|
self.login_user(user)
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
with self.assertLogs("corporate.stripe", "INFO") as m:
|
with self.assertLogs("corporate.stripe", "INFO") as m:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
response = self.client_patch(
|
response = self.client_patch(
|
||||||
@@ -2832,7 +2838,9 @@ class StripeTest(StripeTestCase):
|
|||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
self.login_user(user)
|
self.login_user(user)
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
with self.assertLogs("corporate.stripe", "INFO") as m:
|
with self.assertLogs("corporate.stripe", "INFO") as m:
|
||||||
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
stripe_customer_id = Customer.objects.get(realm=user.realm).id
|
||||||
new_plan = get_current_plan_by_realm(user.realm)
|
new_plan = get_current_plan_by_realm(user.realm)
|
||||||
@@ -2874,7 +2882,7 @@ class StripeTest(StripeTestCase):
|
|||||||
"/json/billing/plan",
|
"/json/billing/plan",
|
||||||
{
|
{
|
||||||
"status": CustomerPlan.FREE_TRIAL,
|
"status": CustomerPlan.FREE_TRIAL,
|
||||||
"schedule": CustomerPlan.ANNUAL,
|
"schedule": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
@@ -2889,7 +2897,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
invoiced_through=None,
|
invoiced_through=None,
|
||||||
next_invoice_date=free_trial_end_date,
|
next_invoice_date=free_trial_end_date,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -2929,7 +2937,7 @@ class StripeTest(StripeTestCase):
|
|||||||
"/json/billing/plan",
|
"/json/billing/plan",
|
||||||
{
|
{
|
||||||
"status": CustomerPlan.FREE_TRIAL,
|
"status": CustomerPlan.FREE_TRIAL,
|
||||||
"schedule": CustomerPlan.MONTHLY,
|
"schedule": CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
@@ -2943,7 +2951,7 @@ class StripeTest(StripeTestCase):
|
|||||||
fixed_price=None,
|
fixed_price=None,
|
||||||
discount=None,
|
discount=None,
|
||||||
billing_cycle_anchor=self.now,
|
billing_cycle_anchor=self.now,
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
invoiced_through=None,
|
invoiced_through=None,
|
||||||
next_invoice_date=free_trial_end_date,
|
next_invoice_date=free_trial_end_date,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
@@ -2969,7 +2977,9 @@ class StripeTest(StripeTestCase):
|
|||||||
free_trial_end_date = self.now + timedelta(days=60)
|
free_trial_end_date = self.now + timedelta(days=60)
|
||||||
with self.settings(FREE_TRIAL_DAYS=60):
|
with self.settings(FREE_TRIAL_DAYS=60):
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, False, True)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, False, True
|
||||||
|
)
|
||||||
|
|
||||||
plan = CustomerPlan.objects.get()
|
plan = CustomerPlan.objects.get()
|
||||||
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
||||||
@@ -3018,7 +3028,9 @@ class StripeTest(StripeTestCase):
|
|||||||
free_trial_end_date = self.now + timedelta(days=60)
|
free_trial_end_date = self.now + timedelta(days=60)
|
||||||
with self.settings(FREE_TRIAL_DAYS=60):
|
with self.settings(FREE_TRIAL_DAYS=60):
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, False, True)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, False, True
|
||||||
|
)
|
||||||
plan = get_current_plan_by_realm(user.realm)
|
plan = get_current_plan_by_realm(user.realm)
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
||||||
@@ -3126,7 +3138,9 @@ class StripeTest(StripeTestCase):
|
|||||||
free_trial_end_date = self.now + timedelta(days=60)
|
free_trial_end_date = self.now + timedelta(days=60)
|
||||||
with self.settings(FREE_TRIAL_DAYS=60):
|
with self.settings(FREE_TRIAL_DAYS=60):
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, False, True)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, False, True
|
||||||
|
)
|
||||||
plan = get_current_plan_by_realm(user.realm)
|
plan = get_current_plan_by_realm(user.realm)
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
self.assertEqual(plan.next_invoice_date, free_trial_end_date)
|
||||||
@@ -3178,7 +3192,9 @@ class StripeTest(StripeTestCase):
|
|||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.login_user(user)
|
self.login_user(user)
|
||||||
with self.assertLogs("corporate.stripe", "INFO") as m:
|
with self.assertLogs("corporate.stripe", "INFO") as m:
|
||||||
@@ -3196,7 +3212,9 @@ class StripeTest(StripeTestCase):
|
|||||||
"corporate.stripe", "WARNING"
|
"corporate.stripe", "WARNING"
|
||||||
) as m:
|
) as m:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
m.output[0],
|
m.output[0],
|
||||||
"WARNING:corporate.stripe:Upgrade of <Realm: zulip 2> (with stripe_customer_id: cus_123) failed because of existing active plan.",
|
"WARNING:corporate.stripe:Upgrade of <Realm: zulip 2> (with stripe_customer_id: cus_123) failed because of existing active plan.",
|
||||||
@@ -3212,7 +3230,9 @@ class StripeTest(StripeTestCase):
|
|||||||
self.assertEqual("/plans/", response["Location"])
|
self.assertEqual("/plans/", response["Location"])
|
||||||
|
|
||||||
with time_machine.travel(self.next_year, tick=False):
|
with time_machine.travel(self.next_year, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(Customer.objects.count(), 1)
|
self.assertEqual(Customer.objects.count(), 1)
|
||||||
self.assertEqual(CustomerPlan.objects.count(), 2)
|
self.assertEqual(CustomerPlan.objects.count(), 2)
|
||||||
@@ -3369,7 +3389,7 @@ class StripeTest(StripeTestCase):
|
|||||||
customer.save()
|
customer.save()
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(100, False, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(100, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False)
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
@@ -3403,7 +3423,9 @@ class StripeTest(StripeTestCase):
|
|||||||
reduced_seat_count = get_latest_seat_count(user.realm) - 2
|
reduced_seat_count = get_latest_seat_count(user.realm) - 2
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(reduced_seat_count, False, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
reduced_seat_count, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
latest_license_ledger = LicenseLedger.objects.last()
|
latest_license_ledger = LicenseLedger.objects.last()
|
||||||
assert latest_license_ledger is not None
|
assert latest_license_ledger is not None
|
||||||
@@ -3415,7 +3437,9 @@ class StripeTest(StripeTestCase):
|
|||||||
self.login_user(user)
|
self.login_user(user)
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
result = self.client_patch("/json/billing/plan", {"licenses": 100})
|
result = self.client_patch("/json/billing/plan", {"licenses": 100})
|
||||||
@@ -3427,7 +3451,9 @@ class StripeTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_update_plan_with_invalid_status(self) -> None:
|
def test_update_plan_with_invalid_status(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
self.login_user(self.example_user("hamlet"))
|
self.login_user(self.example_user("hamlet"))
|
||||||
|
|
||||||
response = self.client_patch(
|
response = self.client_patch(
|
||||||
@@ -3438,7 +3464,9 @@ class StripeTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_update_plan_without_any_params(self) -> None:
|
def test_update_plan_without_any_params(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.login_user(self.example_user("hamlet"))
|
self.login_user(self.example_user("hamlet"))
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
@@ -3447,7 +3475,9 @@ class StripeTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_update_plan_that_which_is_due_for_expiry(self) -> None:
|
def test_update_plan_that_which_is_due_for_expiry(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.login_user(self.example_user("hamlet"))
|
self.login_user(self.example_user("hamlet"))
|
||||||
with self.assertLogs("corporate.stripe", "INFO") as m:
|
with self.assertLogs("corporate.stripe", "INFO") as m:
|
||||||
@@ -3469,7 +3499,9 @@ class StripeTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_update_plan_that_which_is_due_for_replacement(self) -> None:
|
def test_update_plan_that_which_is_due_for_replacement(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.MONTHLY, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_MONTHLY, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.login_user(self.example_user("hamlet"))
|
self.login_user(self.example_user("hamlet"))
|
||||||
with self.assertLogs("corporate.stripe", "INFO") as m:
|
with self.assertLogs("corporate.stripe", "INFO") as m:
|
||||||
@@ -3494,7 +3526,9 @@ class StripeTest(StripeTestCase):
|
|||||||
def test_deactivate_realm(self, mock_: Mock) -> None:
|
def test_deactivate_realm(self, mock_: Mock) -> None:
|
||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
plan = CustomerPlan.objects.get()
|
plan = CustomerPlan.objects.get()
|
||||||
self.assertEqual(plan.next_invoice_date, self.next_month)
|
self.assertEqual(plan.next_invoice_date, self.next_month)
|
||||||
@@ -3540,7 +3574,9 @@ class StripeTest(StripeTestCase):
|
|||||||
user = self.example_user("hamlet")
|
user = self.example_user("hamlet")
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
do_deactivate_realm(get_realm("zulip"), acting_user=None)
|
do_deactivate_realm(get_realm("zulip"), acting_user=None)
|
||||||
self.assertTrue(get_realm("zulip").deactivated)
|
self.assertTrue(get_realm("zulip").deactivated)
|
||||||
@@ -3552,7 +3588,9 @@ class StripeTest(StripeTestCase):
|
|||||||
self.assertEqual("/plans/", response["Location"])
|
self.assertEqual("/plans/", response["Location"])
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(Customer.objects.count(), 1)
|
self.assertEqual(Customer.objects.count(), 1)
|
||||||
|
|
||||||
@@ -3812,7 +3850,7 @@ class StripeTest(StripeTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
plan, ledger = self.subscribe_realm_to_manual_license_management_plan(
|
plan, ledger = self.subscribe_realm_to_manual_license_management_plan(
|
||||||
realm, 9, 9, CustomerPlan.MONTHLY
|
realm, 9, 9, CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
)
|
)
|
||||||
# Test upgrading to Plus when realm has no stripe_customer_id
|
# Test upgrading to Plus when realm has no stripe_customer_id
|
||||||
with self.assertRaises(BillingError) as billing_context:
|
with self.assertRaises(BillingError) as billing_context:
|
||||||
@@ -3829,7 +3867,7 @@ class StripeTest(StripeTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
automanage_licenses=True,
|
automanage_licenses=True,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
ledger = LicenseLedger.objects.create(
|
ledger = LicenseLedger.objects.create(
|
||||||
@@ -3843,7 +3881,7 @@ class StripeTest(StripeTestCase):
|
|||||||
realm.save(update_fields=["plan_type"])
|
realm.save(update_fields=["plan_type"])
|
||||||
plan.invoiced_through = ledger
|
plan.invoiced_through = ledger
|
||||||
plan.price_per_license = get_price_per_license(
|
plan.price_per_license = get_price_per_license(
|
||||||
CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.MONTHLY
|
CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
)
|
)
|
||||||
plan.save(update_fields=["invoiced_through", "price_per_license"])
|
plan.save(update_fields=["invoiced_through", "price_per_license"])
|
||||||
|
|
||||||
@@ -4223,45 +4261,80 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
test_cases = [
|
test_cases = [
|
||||||
# test all possibilities, since there aren't that many
|
# test all possibilities, since there aren't that many
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.ANNUAL, None),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
True,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
|
None,
|
||||||
|
),
|
||||||
(anchor, month_later, year_later, 8000),
|
(anchor, month_later, year_later, 8000),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.ANNUAL, 85),
|
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||||
(anchor, month_later, year_later, 1200),
|
(anchor, month_later, year_later, 1200),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.MONTHLY, None),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
True,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
None,
|
||||||
|
),
|
||||||
(anchor, month_later, month_later, 800),
|
(anchor, month_later, month_later, 800),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.MONTHLY, 85),
|
(CustomerPlan.TIER_CLOUD_STANDARD, True, CustomerPlan.BILLING_SCHEDULE_MONTHLY, 85),
|
||||||
(anchor, month_later, month_later, 120),
|
(anchor, month_later, month_later, 120),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.ANNUAL, None),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
False,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
|
None,
|
||||||
|
),
|
||||||
(anchor, year_later, year_later, 8000),
|
(anchor, year_later, year_later, 8000),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.ANNUAL, 85),
|
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, 85),
|
||||||
(anchor, year_later, year_later, 1200),
|
(anchor, year_later, year_later, 1200),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.MONTHLY, None),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
False,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
None,
|
||||||
|
),
|
||||||
(anchor, month_later, month_later, 800),
|
(anchor, month_later, month_later, 800),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.MONTHLY, 85),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
False,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
85,
|
||||||
|
),
|
||||||
(anchor, month_later, month_later, 120),
|
(anchor, month_later, month_later, 120),
|
||||||
),
|
),
|
||||||
# test exact math of Decimals; 800 * (1 - 87.25) = 101.9999999..
|
# test exact math of Decimals; 800 * (1 - 87.25) = 101.9999999..
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.MONTHLY, 87.25),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
False,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
87.25,
|
||||||
|
),
|
||||||
(anchor, month_later, month_later, 102),
|
(anchor, month_later, month_later, 102),
|
||||||
),
|
),
|
||||||
# test dropping of fractional cents; without the int it's 102.8
|
# test dropping of fractional cents; without the int it's 102.8
|
||||||
(
|
(
|
||||||
(CustomerPlan.TIER_CLOUD_STANDARD, False, CustomerPlan.MONTHLY, 87.15),
|
(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
False,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
87.15,
|
||||||
|
),
|
||||||
(anchor, month_later, month_later, 102),
|
(anchor, month_later, month_later, 102),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -4277,27 +4350,43 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
|
|
||||||
def test_get_price_per_license(self) -> None:
|
def test_get_price_per_license(self) -> None:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.ANNUAL), 8000
|
get_price_per_license(
|
||||||
)
|
CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
self.assertEqual(
|
),
|
||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.MONTHLY), 800
|
8000,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
get_price_per_license(
|
get_price_per_license(
|
||||||
CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.MONTHLY, discount=Decimal(50)
|
CustomerPlan.TIER_CLOUD_STANDARD, CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
|
),
|
||||||
|
800,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
get_price_per_license(
|
||||||
|
CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
discount=Decimal(50),
|
||||||
),
|
),
|
||||||
400,
|
400,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_PLUS, CustomerPlan.ANNUAL), 16000
|
get_price_per_license(
|
||||||
)
|
CustomerPlan.TIER_CLOUD_PLUS, CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
self.assertEqual(
|
),
|
||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_PLUS, CustomerPlan.MONTHLY), 1600
|
16000,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
get_price_per_license(
|
get_price_per_license(
|
||||||
CustomerPlan.TIER_CLOUD_PLUS, CustomerPlan.MONTHLY, discount=Decimal(50)
|
CustomerPlan.TIER_CLOUD_PLUS, CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
|
),
|
||||||
|
1600,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
get_price_per_license(
|
||||||
|
CustomerPlan.TIER_CLOUD_PLUS,
|
||||||
|
CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
|
discount=Decimal(50),
|
||||||
),
|
),
|
||||||
800,
|
800,
|
||||||
)
|
)
|
||||||
@@ -4306,7 +4395,9 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_STANDARD, 1000)
|
get_price_per_license(CustomerPlan.TIER_CLOUD_STANDARD, 1000)
|
||||||
|
|
||||||
with self.assertRaisesRegex(InvalidTierError, "Unknown tier: 4"):
|
with self.assertRaisesRegex(InvalidTierError, "Unknown tier: 4"):
|
||||||
get_price_per_license(CustomerPlan.TIER_CLOUD_ENTERPRISE, CustomerPlan.ANNUAL)
|
get_price_per_license(
|
||||||
|
CustomerPlan.TIER_CLOUD_ENTERPRISE, CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_plan_renewal_or_end_date(self) -> None:
|
def test_get_plan_renewal_or_end_date(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
@@ -4316,7 +4407,7 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
status=CustomerPlan.ACTIVE,
|
status=CustomerPlan.ACTIVE,
|
||||||
billing_cycle_anchor=billing_cycle_anchor,
|
billing_cycle_anchor=billing_cycle_anchor,
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
renewal_date = get_plan_renewal_or_end_date(plan, billing_cycle_anchor)
|
renewal_date = get_plan_renewal_or_end_date(plan, billing_cycle_anchor)
|
||||||
@@ -4385,7 +4476,7 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
status=CustomerPlan.ACTIVE,
|
status=CustomerPlan.ACTIVE,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
self.assertEqual(get_current_plan_by_customer(customer), plan)
|
self.assertEqual(get_current_plan_by_customer(customer), plan)
|
||||||
@@ -4414,7 +4505,7 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
status=CustomerPlan.ACTIVE,
|
status=CustomerPlan.ACTIVE,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
self.assertEqual(get_current_plan_by_realm(realm), plan)
|
self.assertEqual(get_current_plan_by_realm(realm), plan)
|
||||||
@@ -4428,7 +4519,7 @@ class BillingHelpersTest(ZulipTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
status=CustomerPlan.ACTIVE,
|
status=CustomerPlan.ACTIVE,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
self.assertFalse(is_realm_on_free_trial(realm))
|
self.assertFalse(is_realm_on_free_trial(realm))
|
||||||
@@ -4534,7 +4625,9 @@ class AnalyticsHelpersTest(ZulipTestCase):
|
|||||||
class LicenseLedgerTest(StripeTestCase):
|
class LicenseLedgerTest(StripeTestCase):
|
||||||
def test_add_plan_renewal_if_needed(self) -> None:
|
def test_add_plan_renewal_if_needed(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
self.assertEqual(LicenseLedger.objects.count(), 1)
|
self.assertEqual(LicenseLedger.objects.count(), 1)
|
||||||
plan = CustomerPlan.objects.get()
|
plan = CustomerPlan.objects.get()
|
||||||
# Plan hasn't renewed yet
|
# Plan hasn't renewed yet
|
||||||
@@ -4572,7 +4665,9 @@ class LicenseLedgerTest(StripeTestCase):
|
|||||||
update_license_ledger_if_needed(realm, self.now)
|
update_license_ledger_if_needed(realm, self.now)
|
||||||
self.assertFalse(LicenseLedger.objects.exists())
|
self.assertFalse(LicenseLedger.objects.exists())
|
||||||
# Test plan not automanaged
|
# Test plan not automanaged
|
||||||
self.local_upgrade(self.seat_count + 1, False, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count + 1, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
plan = CustomerPlan.objects.get()
|
plan = CustomerPlan.objects.get()
|
||||||
self.assertEqual(LicenseLedger.objects.count(), 1)
|
self.assertEqual(LicenseLedger.objects.count(), 1)
|
||||||
self.assertEqual(plan.licenses(), self.seat_count + 1)
|
self.assertEqual(plan.licenses(), self.seat_count + 1)
|
||||||
@@ -4594,7 +4689,9 @@ class LicenseLedgerTest(StripeTestCase):
|
|||||||
def test_update_license_ledger_for_automanaged_plan(self) -> None:
|
def test_update_license_ledger_for_automanaged_plan(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
plan = CustomerPlan.objects.first()
|
plan = CustomerPlan.objects.first()
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.licenses(), self.seat_count)
|
self.assertEqual(plan.licenses(), self.seat_count)
|
||||||
@@ -4643,7 +4740,9 @@ class LicenseLedgerTest(StripeTestCase):
|
|||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
|
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count + 1, False, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count + 1, False, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
|
|
||||||
plan = get_current_plan_by_realm(realm)
|
plan = get_current_plan_by_realm(realm)
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
@@ -4700,7 +4799,7 @@ class LicenseLedgerTest(StripeTestCase):
|
|||||||
update_license_ledger_for_manual_plan(plan, self.now)
|
update_license_ledger_for_manual_plan(plan, self.now)
|
||||||
|
|
||||||
def test_user_changes(self) -> None:
|
def test_user_changes(self) -> None:
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False)
|
||||||
user = do_create_user("email", "password", get_realm("zulip"), "name", acting_user=None)
|
user = do_create_user("email", "password", get_realm("zulip"), "name", acting_user=None)
|
||||||
do_deactivate_user(user, acting_user=None)
|
do_deactivate_user(user, acting_user=None)
|
||||||
do_reactivate_user(user, acting_user=None)
|
do_reactivate_user(user, acting_user=None)
|
||||||
@@ -4725,7 +4824,7 @@ class LicenseLedgerTest(StripeTestCase):
|
|||||||
|
|
||||||
class InvoiceTest(StripeTestCase):
|
class InvoiceTest(StripeTestCase):
|
||||||
def test_invoicing_status_is_started(self) -> None:
|
def test_invoicing_status_is_started(self) -> None:
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False)
|
||||||
plan = CustomerPlan.objects.first()
|
plan = CustomerPlan.objects.first()
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
plan.invoicing_status = CustomerPlan.STARTED
|
plan.invoicing_status = CustomerPlan.STARTED
|
||||||
@@ -4734,7 +4833,9 @@ class InvoiceTest(StripeTestCase):
|
|||||||
invoice_plan(assert_is_not_none(CustomerPlan.objects.first()), self.now)
|
invoice_plan(assert_is_not_none(CustomerPlan.objects.first()), self.now)
|
||||||
|
|
||||||
def test_invoice_plan_without_stripe_customer(self) -> None:
|
def test_invoice_plan_without_stripe_customer(self) -> None:
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, False, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, False, False
|
||||||
|
)
|
||||||
plan = get_current_plan_by_realm(get_realm("zulip"))
|
plan = get_current_plan_by_realm(get_realm("zulip"))
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
plan.customer.stripe_customer_id = None
|
plan.customer.stripe_customer_id = None
|
||||||
@@ -4845,7 +4946,9 @@ class InvoiceTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_no_invoice_needed(self) -> None:
|
def test_no_invoice_needed(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
plan = CustomerPlan.objects.first()
|
plan = CustomerPlan.objects.first()
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.next_invoice_date, self.next_month)
|
self.assertEqual(plan.next_invoice_date, self.next_month)
|
||||||
@@ -4858,7 +4961,9 @@ class InvoiceTest(StripeTestCase):
|
|||||||
|
|
||||||
def test_invoice_plans_as_needed(self) -> None:
|
def test_invoice_plans_as_needed(self) -> None:
|
||||||
with time_machine.travel(self.now, tick=False):
|
with time_machine.travel(self.now, tick=False):
|
||||||
self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, True, False)
|
self.local_upgrade(
|
||||||
|
self.seat_count, True, CustomerPlan.BILLING_SCHEDULE_ANNUAL, True, False
|
||||||
|
)
|
||||||
plan = CustomerPlan.objects.first()
|
plan = CustomerPlan.objects.first()
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
self.assertEqual(plan.next_invoice_date, self.next_month)
|
self.assertEqual(plan.next_invoice_date, self.next_month)
|
||||||
@@ -4877,12 +4982,12 @@ class TestTestClasses(ZulipTestCase):
|
|||||||
def test_subscribe_realm_to_manual_license_management_plan(self) -> None:
|
def test_subscribe_realm_to_manual_license_management_plan(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
plan, ledger = self.subscribe_realm_to_manual_license_management_plan(
|
plan, ledger = self.subscribe_realm_to_manual_license_management_plan(
|
||||||
realm, 50, 60, CustomerPlan.ANNUAL
|
realm, 50, 60, CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
)
|
)
|
||||||
|
|
||||||
plan.refresh_from_db()
|
plan.refresh_from_db()
|
||||||
self.assertEqual(plan.automanage_licenses, False)
|
self.assertEqual(plan.automanage_licenses, False)
|
||||||
self.assertEqual(plan.billing_schedule, CustomerPlan.ANNUAL)
|
self.assertEqual(plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_ANNUAL)
|
||||||
self.assertEqual(plan.tier, CustomerPlan.TIER_CLOUD_STANDARD)
|
self.assertEqual(plan.tier, CustomerPlan.TIER_CLOUD_STANDARD)
|
||||||
self.assertEqual(plan.licenses(), 50)
|
self.assertEqual(plan.licenses(), 50)
|
||||||
self.assertEqual(plan.licenses_at_next_renewal(), 60)
|
self.assertEqual(plan.licenses_at_next_renewal(), 60)
|
||||||
@@ -4903,7 +5008,7 @@ class TestTestClasses(ZulipTestCase):
|
|||||||
|
|
||||||
plan.refresh_from_db()
|
plan.refresh_from_db()
|
||||||
self.assertEqual(plan.automanage_licenses, False)
|
self.assertEqual(plan.automanage_licenses, False)
|
||||||
self.assertEqual(plan.billing_schedule, CustomerPlan.MONTHLY)
|
self.assertEqual(plan.billing_schedule, CustomerPlan.BILLING_SCHEDULE_MONTHLY)
|
||||||
self.assertEqual(plan.tier, CustomerPlan.TIER_CLOUD_STANDARD)
|
self.assertEqual(plan.tier, CustomerPlan.TIER_CLOUD_STANDARD)
|
||||||
self.assertEqual(plan.licenses(), 20)
|
self.assertEqual(plan.licenses(), 20)
|
||||||
self.assertEqual(plan.licenses_at_next_renewal(), 30)
|
self.assertEqual(plan.licenses_at_next_renewal(), 30)
|
||||||
@@ -5121,7 +5226,7 @@ class TestSupportBillingHelpers(StripeTestCase):
|
|||||||
customer=customer,
|
customer=customer,
|
||||||
status=CustomerPlan.ACTIVE,
|
status=CustomerPlan.ACTIVE,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
)
|
)
|
||||||
self.assertEqual(plan.charge_automatically, False)
|
self.assertEqual(plan.charge_automatically, False)
|
||||||
|
@@ -113,7 +113,7 @@
|
|||||||
<h3>📅 Current plan</h3>
|
<h3>📅 Current plan</h3>
|
||||||
<b>Name</b>: {{ plan_data[realm.id].current_plan.name }}<br />
|
<b>Name</b>: {{ plan_data[realm.id].current_plan.name }}<br />
|
||||||
<b>Status</b>: {{plan_data[realm.id].current_plan.get_plan_status_as_text()}}<br />
|
<b>Status</b>: {{plan_data[realm.id].current_plan.get_plan_status_as_text()}}<br />
|
||||||
<b>Billing schedule</b>: {% if plan_data[realm.id].current_plan.billing_schedule == plan_data[realm.id].current_plan.ANNUAL %}Annual{% else %}Monthly{% endif %}<br />
|
<b>Billing schedule</b>: {% if plan_data[realm.id].current_plan.billing_schedule == plan_data[realm.id].current_plan.BILLING_SCHEDULE_ANNUAL %}Annual{% else %}Monthly{% endif %}<br />
|
||||||
<b>Licenses</b>: {{ plan_data[realm.id].licenses_used }}/{{ plan_data[realm.id].licenses }} ({% if plan_data[realm.id].current_plan.automanage_licenses %}Automatic{% else %}Manual{% endif %})<br />
|
<b>Licenses</b>: {{ plan_data[realm.id].licenses_used }}/{{ plan_data[realm.id].licenses }} ({% if plan_data[realm.id].current_plan.automanage_licenses %}Automatic{% else %}Manual{% endif %})<br />
|
||||||
{% if plan_data[realm.id].current_plan.price_per_license %}
|
{% if plan_data[realm.id].current_plan.price_per_license %}
|
||||||
<b>Price per license</b>: ${{ plan_data[realm.id].current_plan.price_per_license/100 }}<br />
|
<b>Price per license</b>: ${{ plan_data[realm.id].current_plan.price_per_license/100 }}<br />
|
||||||
|
@@ -9,8 +9,8 @@ const billing_frequency_schema = z.enum(["Monthly", "Annual"]);
|
|||||||
|
|
||||||
// Matches the CustomerPlan model in the backend.
|
// Matches the CustomerPlan model in the backend.
|
||||||
enum BillingFrequency {
|
enum BillingFrequency {
|
||||||
ANNUAL = 1,
|
BILLING_SCHEDULE_ANNUAL = 1,
|
||||||
MONTHLY = 2,
|
BILLING_SCHEDULE_MONTHLY = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CustomerPlanStatus {
|
enum CustomerPlanStatus {
|
||||||
@@ -302,12 +302,12 @@ export function initialize(): void {
|
|||||||
let new_status = free_trial
|
let new_status = free_trial
|
||||||
? CustomerPlanStatus.FREE_TRIAL
|
? CustomerPlanStatus.FREE_TRIAL
|
||||||
: CustomerPlanStatus.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE;
|
: CustomerPlanStatus.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE;
|
||||||
let new_schedule = BillingFrequency.ANNUAL;
|
let new_schedule = BillingFrequency.BILLING_SCHEDULE_ANNUAL;
|
||||||
if (billing_frequency_selected === "Monthly") {
|
if (billing_frequency_selected === "Monthly") {
|
||||||
new_status = free_trial
|
new_status = free_trial
|
||||||
? CustomerPlanStatus.FREE_TRIAL
|
? CustomerPlanStatus.FREE_TRIAL
|
||||||
: CustomerPlanStatus.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE;
|
: CustomerPlanStatus.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE;
|
||||||
new_schedule = BillingFrequency.MONTHLY;
|
new_schedule = BillingFrequency.BILLING_SCHEDULE_MONTHLY;
|
||||||
}
|
}
|
||||||
$("#org-billing-frequency-confirm-button").attr("data-status", new_status);
|
$("#org-billing-frequency-confirm-button").attr("data-status", new_status);
|
||||||
if (free_trial) {
|
if (free_trial) {
|
||||||
|
@@ -1813,7 +1813,7 @@ Output:
|
|||||||
self, realm: Realm, licenses: int, licenses_at_next_renewal: int
|
self, realm: Realm, licenses: int, licenses_at_next_renewal: int
|
||||||
) -> Tuple[CustomerPlan, LicenseLedger]:
|
) -> Tuple[CustomerPlan, LicenseLedger]:
|
||||||
return self.subscribe_realm_to_manual_license_management_plan(
|
return self.subscribe_realm_to_manual_license_management_plan(
|
||||||
realm, licenses, licenses_at_next_renewal, CustomerPlan.MONTHLY
|
realm, licenses, licenses_at_next_renewal, CustomerPlan.BILLING_SCHEDULE_MONTHLY
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_user_notifications_data_object(
|
def create_user_notifications_data_object(
|
||||||
|
@@ -602,7 +602,7 @@ class PlansPageTest(ZulipTestCase):
|
|||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
status=CustomerPlan.FREE_TRIAL,
|
status=CustomerPlan.FREE_TRIAL,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
)
|
)
|
||||||
result = self.client_get("/plans/", subdomain="zulip")
|
result = self.client_get("/plans/", subdomain="zulip")
|
||||||
self.assert_in_success_response(["Current plan (free trial)"], result)
|
self.assert_in_success_response(["Current plan (free trial)"], result)
|
||||||
|
@@ -852,7 +852,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
CustomerPlan.objects.create(
|
CustomerPlan.objects.create(
|
||||||
customer=customer,
|
customer=customer,
|
||||||
billing_cycle_anchor=timezone_now(),
|
billing_cycle_anchor=timezone_now(),
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
next_invoice_date=timezone_now(),
|
next_invoice_date=timezone_now(),
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
status=CustomerPlan.ENDED,
|
status=CustomerPlan.ENDED,
|
||||||
|
@@ -21,7 +21,7 @@ from zproject.config import get_secret
|
|||||||
@dataclass
|
@dataclass
|
||||||
class CustomerProfile:
|
class CustomerProfile:
|
||||||
unique_id: str
|
unique_id: str
|
||||||
billing_schedule: int = CustomerPlan.ANNUAL
|
billing_schedule: int = CustomerPlan.BILLING_SCHEDULE_ANNUAL
|
||||||
tier: Optional[int] = None
|
tier: Optional[int] = None
|
||||||
automanage_licenses: bool = False
|
automanage_licenses: bool = False
|
||||||
status: int = CustomerPlan.ACTIVE
|
status: int = CustomerPlan.ACTIVE
|
||||||
@@ -43,66 +43,66 @@ class Command(BaseCommand):
|
|||||||
CustomerProfile(unique_id="sponsorship-pending", sponsorship_pending=True),
|
CustomerProfile(unique_id="sponsorship-pending", sponsorship_pending=True),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="annual-free",
|
unique_id="annual-free",
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="annual-standard",
|
unique_id="annual-standard",
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="annual-plus",
|
unique_id="annual-plus",
|
||||||
billing_schedule=CustomerPlan.ANNUAL,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
tier=CustomerPlan.TIER_CLOUD_PLUS,
|
tier=CustomerPlan.TIER_CLOUD_PLUS,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="monthly-free",
|
unique_id="monthly-free",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="monthly-standard",
|
unique_id="monthly-standard",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="monthly-plus",
|
unique_id="monthly-plus",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_PLUS,
|
tier=CustomerPlan.TIER_CLOUD_PLUS,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="downgrade-end-of-cycle",
|
unique_id="downgrade-end-of-cycle",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
status=CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
|
status=CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="standard-automanage-licenses",
|
unique_id="standard-automanage-licenses",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
automanage_licenses=True,
|
automanage_licenses=True,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="standard-automatic-card",
|
unique_id="standard-automatic-card",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
card="pm_card_visa",
|
card="pm_card_visa",
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="standard-invoice-payment",
|
unique_id="standard-invoice-payment",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
charge_automatically=False,
|
charge_automatically=False,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="standard-switch-to-annual-eoc",
|
unique_id="standard-switch-to-annual-eoc",
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
status=CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE,
|
status=CustomerPlan.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
unique_id="sponsored",
|
unique_id="sponsored",
|
||||||
is_sponsored=True,
|
is_sponsored=True,
|
||||||
billing_schedule=CustomerPlan.MONTHLY,
|
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||||
),
|
),
|
||||||
CustomerProfile(
|
CustomerProfile(
|
||||||
@@ -202,7 +202,7 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
|
|
||||||
months = 12
|
months = 12
|
||||||
if customer_profile.billing_schedule == CustomerPlan.MONTHLY:
|
if customer_profile.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
|
||||||
months = 1
|
months = 1
|
||||||
next_invoice_date = add_months(timezone_now(), months)
|
next_invoice_date = add_months(timezone_now(), months)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user