billing: Update next_renewal_date to take an event_time.

Also changes a < into a <= in add_plan_renewal_to_license_ledger_if_needed.
This commit is contained in:
Rishi Gupta 2019-01-26 11:45:26 -08:00
parent 7c11fe819a
commit 83a7595feb
3 changed files with 20 additions and 18 deletions

View File

@ -79,14 +79,14 @@ def next_month(billing_cycle_anchor: datetime, dt: datetime) -> datetime:
'billing_cycle_anchor: %s, dt: %s' % (billing_cycle_anchor, dt))
# TODO take downgrade into account
def next_renewal_date(plan: CustomerPlan) -> datetime:
def next_renewal_date(plan: CustomerPlan, event_time: datetime) -> datetime:
months_per_period = {
CustomerPlan.ANNUAL: 12,
CustomerPlan.MONTHLY: 1,
}[plan.billing_schedule]
periods = 1
dt = plan.billing_cycle_anchor
while dt <= plan.billed_through:
while dt <= event_time:
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
periods += 1
return dt
@ -191,14 +191,14 @@ def do_replace_payment_source(user: UserProfile, stripe_token: str) -> stripe.Cu
# TODO handle downgrade
def add_plan_renewal_to_license_ledger_if_needed(plan: CustomerPlan, event_time: datetime) -> LicenseLedger:
last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by('-id').first()
plan_renewal_date = next_renewal_date(plan)
if plan_renewal_date < event_time:
if not LicenseLedger.objects.filter(
plan=plan, event_time=plan_renewal_date, is_renewal=True).exists():
return LicenseLedger.objects.create(
plan=plan, is_renewal=True, event_time=plan_renewal_date,
licenses=last_ledger_entry.licenses_at_next_renewal,
licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
last_renewal = LicenseLedger.objects.filter(plan=plan, is_renewal=True) \
.order_by('-id').first().event_time
plan_renewal_date = next_renewal_date(plan, last_renewal)
if plan_renewal_date <= event_time:
return LicenseLedger.objects.create(
plan=plan, is_renewal=True, event_time=plan_renewal_date,
licenses=last_ledger_entry.licenses_at_next_renewal,
licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
return last_ledger_entry
# Returns Customer instead of stripe_customer so that we don't make a Stripe

View File

@ -403,7 +403,8 @@ class StripeTest(ZulipTestCase):
self.assertEqual('/billing/', response.url)
# Check /billing has the correct information
response = self.client_get("/billing/")
with patch('corporate.views.timezone_now', return_value=self.now):
response = self.client_get("/billing/")
self.assert_not_in_success_response(['Pay annually'], response)
for substring in [
'Zulip Standard', str(self.seat_count),
@ -485,7 +486,8 @@ class StripeTest(ZulipTestCase):
self.assertEqual('/billing/', response.url)
# Check /billing has the correct information
response = self.client_get("/billing/")
with patch('corporate.views.timezone_now', return_value=self.now):
response = self.client_get("/billing/")
self.assert_not_in_success_response(['Pay annually', 'Update card'], response)
for substring in [
'Zulip Standard', str(123),
@ -950,12 +952,11 @@ class LicenseLedgerTest(ZulipTestCase):
self.assertEqual(LicenseLedger.objects.count(), 1)
plan = CustomerPlan.objects.get()
# Plan hasn't renewed yet
add_plan_renewal_to_license_ledger_if_needed(plan, self.next_year)
add_plan_renewal_to_license_ledger_if_needed(plan, self.next_year - timedelta(days=1))
self.assertEqual(LicenseLedger.objects.count(), 1)
# Plan needs to renew
# TODO: do_deactivate_user for a user, so that licenses_at_next_renewal != licenses
ledger_entry = add_plan_renewal_to_license_ledger_if_needed(
plan, self.next_year + timedelta(seconds=1))
ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, self.next_year)
self.assertEqual(LicenseLedger.objects.count(), 2)
ledger_params = {
'plan': plan, 'is_renewal': True, 'event_time': self.next_year,
@ -963,7 +964,7 @@ class LicenseLedgerTest(ZulipTestCase):
for key, value in ledger_params.items():
self.assertEqual(getattr(ledger_entry, key), value)
# Plan needs to renew, but we already added the plan_renewal ledger entry
add_plan_renewal_to_license_ledger_if_needed(plan, self.next_year + timedelta(seconds=1))
add_plan_renewal_to_license_ledger_if_needed(plan, self.next_year + timedelta(days=1))
self.assertEqual(LicenseLedger.objects.count(), 2)
def test_update_license_ledger_if_needed(self) -> None:

View File

@ -168,11 +168,12 @@ def billing_home(request: HttpRequest) -> HttpResponse:
CustomerPlan.STANDARD: 'Zulip Standard',
CustomerPlan.PLUS: 'Zulip Plus',
}[plan.tier]
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, timezone_now())
now = timezone_now()
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, now)
# TODO: this is not really correct; need to give the situation as of the "fillstate"
licenses = last_ledger_entry.licenses
# Should do this in javascript, using the user's timezone
renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(dt=next_renewal_date(plan))
renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(dt=next_renewal_date(plan, now))
renewal_cents = renewal_amount(plan)
# TODO: this is the case where the plan doesn't automatically renew
if renewal_cents is None: # nocoverage