mirror of https://github.com/zulip/zulip.git
corporate: Fix invoicing of plans on free-trial with changed schedule.
Earlier, the 'next_invoice_date', 'invoiced_through', and 'invoicing_status' fields in 'do_change_schedule_after_free_trial' were not set correctly. It resulted in invoicing the ENDED plan and the ledger we create in this function. Multiple invoices were created depending on the number of times the billing frequency was changed for customers who started free-trial & changed their billing frequency. This commit sets those fields correctly leading to create only one invoice.
This commit is contained in:
parent
3480d204de
commit
a513489f38
|
@ -1903,7 +1903,8 @@ class BillingSession(ABC):
|
|||
raise BillingError("Customer is already on monthly fixed plan.")
|
||||
|
||||
plan.status = CustomerPlan.ENDED
|
||||
plan.save(update_fields=["status"])
|
||||
plan.next_invoice_date = None
|
||||
plan.save(update_fields=["status", "next_invoice_date"])
|
||||
|
||||
discount_for_current_plan = plan.discount
|
||||
_, _, _, price_per_license = compute_plan_parameters(
|
||||
|
@ -1924,11 +1925,9 @@ class BillingSession(ABC):
|
|||
tier=plan.tier,
|
||||
status=CustomerPlan.FREE_TRIAL,
|
||||
next_invoice_date=next_billing_cycle,
|
||||
invoiced_through=None,
|
||||
invoicing_status=CustomerPlan.INVOICING_STATUS_INITIAL_INVOICE_TO_BE_SENT,
|
||||
)
|
||||
|
||||
LicenseLedger.objects.create(
|
||||
ledger_entry = LicenseLedger.objects.create(
|
||||
plan=new_plan,
|
||||
is_renewal=True,
|
||||
event_time=plan.billing_cycle_anchor,
|
||||
|
@ -1936,6 +1935,9 @@ class BillingSession(ABC):
|
|||
licenses_at_next_renewal=licenses_at_next_renewal,
|
||||
)
|
||||
|
||||
new_plan.invoiced_through = ledger_entry
|
||||
new_plan.save(update_fields=["invoiced_through"])
|
||||
|
||||
if schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL:
|
||||
self.write_to_audit_log(
|
||||
event_type=AuditLogEventType.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# Generated by Django 4.2.10 on 2024-03-01 14:05
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.migrations.state import StateApps
|
||||
|
||||
|
||||
def fix_plans_on_free_trial_with_changes_in_schedule(
|
||||
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||
) -> None:
|
||||
CustomerPlan = apps.get_model("corporate", "CustomerPlan")
|
||||
LicenseLedger = apps.get_model("corporate", "LicenseLedger")
|
||||
CustomerPlan.FREE_TRIAL = 3
|
||||
CustomerPlan.ENDED = 11
|
||||
CustomerPlan.INVOICING_STATUS_DONE = 1
|
||||
|
||||
plans_on_free_trial_with_changed_schedule = CustomerPlan.objects.filter(
|
||||
status=CustomerPlan.FREE_TRIAL, invoiced_through=None
|
||||
)
|
||||
for plan in plans_on_free_trial_with_changed_schedule:
|
||||
assert plan.customer is not None
|
||||
assert plan.billing_cycle_anchor is not None
|
||||
|
||||
ledger_entries = LicenseLedger.objects.filter(
|
||||
plan=plan,
|
||||
is_renewal=True,
|
||||
event_time=plan.billing_cycle_anchor,
|
||||
)
|
||||
assert ledger_entries.count() == 1
|
||||
plan.invoiced_through = ledger_entries.first()
|
||||
plan.invoicing_status = CustomerPlan.INVOICING_STATUS_DONE
|
||||
plan.save(update_fields=["invoiced_through", "invoicing_status"])
|
||||
|
||||
CustomerPlan.objects.filter(
|
||||
customer=plan.customer,
|
||||
status=CustomerPlan.ENDED,
|
||||
billing_cycle_anchor=plan.billing_cycle_anchor,
|
||||
).update(next_invoice_date=None)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("corporate", "0040_customerplan_reminder_to_review_plan_email_sent"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
fix_plans_on_free_trial_with_changes_in_schedule,
|
||||
),
|
||||
]
|
|
@ -3023,8 +3023,9 @@ class StripeTest(StripeTestCase):
|
|||
|
||||
plan.refresh_from_db()
|
||||
self.assertEqual(plan.status, CustomerPlan.ENDED)
|
||||
self.assertIsNone(plan.next_invoice_date)
|
||||
|
||||
plan = CustomerPlan.objects.get(
|
||||
new_plan = CustomerPlan.objects.get(
|
||||
customer=customer,
|
||||
automanage_licenses=True,
|
||||
price_per_license=8000,
|
||||
|
@ -3032,19 +3033,19 @@ class StripeTest(StripeTestCase):
|
|||
discount=None,
|
||||
billing_cycle_anchor=self.now,
|
||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||
invoiced_through=None,
|
||||
next_invoice_date=free_trial_end_date,
|
||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
status=CustomerPlan.FREE_TRIAL,
|
||||
charge_automatically=True,
|
||||
)
|
||||
LicenseLedger.objects.get(
|
||||
plan=plan,
|
||||
ledger_entry = LicenseLedger.objects.get(
|
||||
plan=new_plan,
|
||||
is_renewal=True,
|
||||
event_time=self.now,
|
||||
licenses=self.seat_count,
|
||||
licenses_at_next_renewal=self.seat_count,
|
||||
)
|
||||
self.assertEqual(new_plan.invoiced_through, ledger_entry)
|
||||
|
||||
realm_audit_log = RealmAuditLog.objects.filter(
|
||||
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN
|
||||
|
@ -3077,8 +3078,9 @@ class StripeTest(StripeTestCase):
|
|||
self.assert_json_success(result)
|
||||
plan.refresh_from_db()
|
||||
self.assertEqual(plan.status, CustomerPlan.ENDED)
|
||||
self.assertIsNone(plan.next_invoice_date)
|
||||
|
||||
plan = CustomerPlan.objects.get(
|
||||
new_plan = CustomerPlan.objects.get(
|
||||
customer=customer,
|
||||
automanage_licenses=True,
|
||||
price_per_license=800,
|
||||
|
@ -3086,19 +3088,19 @@ class StripeTest(StripeTestCase):
|
|||
discount=None,
|
||||
billing_cycle_anchor=self.now,
|
||||
billing_schedule=CustomerPlan.BILLING_SCHEDULE_MONTHLY,
|
||||
invoiced_through=None,
|
||||
next_invoice_date=free_trial_end_date,
|
||||
tier=CustomerPlan.TIER_CLOUD_STANDARD,
|
||||
status=CustomerPlan.FREE_TRIAL,
|
||||
charge_automatically=True,
|
||||
)
|
||||
LicenseLedger.objects.get(
|
||||
plan=plan,
|
||||
ledger_entry = LicenseLedger.objects.get(
|
||||
plan=new_plan,
|
||||
is_renewal=True,
|
||||
event_time=self.now,
|
||||
licenses=self.seat_count,
|
||||
licenses_at_next_renewal=self.seat_count,
|
||||
)
|
||||
self.assertEqual(new_plan.invoiced_through, ledger_entry)
|
||||
|
||||
realm_audit_log = RealmAuditLog.objects.filter(
|
||||
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN
|
||||
|
|
Loading…
Reference in New Issue