mirror of https://github.com/zulip/zulip.git
stripe: Fix legacy plans not being invoiced.
Earlier, in 'migrate_customer_to_legacy_plan`, we set 'next_invoice_date' to None for legacy plans. This will result in legacy plans not getting invoiced. We need to invoice legacy plans on their end date to either downgrade them or switch to a new plan. We set next_invoice_date for legacy plans to end_date.
This commit is contained in:
parent
e4258b56d5
commit
026eb37c28
|
@ -854,7 +854,6 @@ class BillingSession(ABC):
|
||||||
customer=customer,
|
customer=customer,
|
||||||
tier=CustomerPlan.TIER_SELF_HOSTED_LEGACY,
|
tier=CustomerPlan.TIER_SELF_HOSTED_LEGACY,
|
||||||
status=status,
|
status=status,
|
||||||
next_invoice_date=None,
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
def get_formatted_remote_server_legacy_plan_end_date(
|
def get_formatted_remote_server_legacy_plan_end_date(
|
||||||
|
@ -2418,7 +2417,10 @@ class BillingSession(ABC):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Plan with invoicing_status==STARTED needs manual resolution."
|
"Plan with invoicing_status==STARTED needs manual resolution."
|
||||||
)
|
)
|
||||||
if not plan.customer.stripe_customer_id:
|
if (
|
||||||
|
plan.tier != CustomerPlan.TIER_SELF_HOSTED_LEGACY
|
||||||
|
and not plan.customer.stripe_customer_id
|
||||||
|
):
|
||||||
raise BillingError(
|
raise BillingError(
|
||||||
f"Customer has a paid plan without a Stripe customer ID: {plan.customer!s}"
|
f"Customer has a paid plan without a Stripe customer ID: {plan.customer!s}"
|
||||||
)
|
)
|
||||||
|
@ -2483,6 +2485,7 @@ class BillingSession(ABC):
|
||||||
plan.invoiced_through = ledger_entry
|
plan.invoiced_through = ledger_entry
|
||||||
plan.invoicing_status = CustomerPlan.INVOICING_STATUS_STARTED
|
plan.invoicing_status = CustomerPlan.INVOICING_STATUS_STARTED
|
||||||
plan.save(update_fields=["invoicing_status", "invoiced_through"])
|
plan.save(update_fields=["invoicing_status", "invoiced_through"])
|
||||||
|
assert plan.customer.stripe_customer_id is not None
|
||||||
stripe.InvoiceItem.create(
|
stripe.InvoiceItem.create(
|
||||||
currency="usd",
|
currency="usd",
|
||||||
customer=plan.customer.stripe_customer_id,
|
customer=plan.customer.stripe_customer_id,
|
||||||
|
@ -2918,10 +2921,10 @@ class BillingSession(ABC):
|
||||||
"tier": CustomerPlan.TIER_SELF_HOSTED_LEGACY,
|
"tier": CustomerPlan.TIER_SELF_HOSTED_LEGACY,
|
||||||
# End when the new plan starts.
|
# End when the new plan starts.
|
||||||
"end_date": end_date,
|
"end_date": end_date,
|
||||||
|
"next_invoice_date": end_date,
|
||||||
# The primary mechanism for preventing charges under this
|
# The primary mechanism for preventing charges under this
|
||||||
# plan is setting a null `next_invoice_date`, but setting
|
# plan is setting 'invoiced_through' to last ledger_entry below,
|
||||||
# a 0 price is useful defense in depth here.
|
# but setting a 0 price is useful defense in depth here.
|
||||||
"next_invoice_date": None,
|
|
||||||
"price_per_license": 0,
|
"price_per_license": 0,
|
||||||
"billing_schedule": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
"billing_schedule": CustomerPlan.BILLING_SCHEDULE_ANNUAL,
|
||||||
"automanage_licenses": True,
|
"automanage_licenses": True,
|
||||||
|
@ -4433,7 +4436,7 @@ def get_plan_renewal_or_end_date(plan: CustomerPlan, event_time: datetime) -> da
|
||||||
|
|
||||||
|
|
||||||
def invoice_plans_as_needed(event_time: Optional[datetime] = None) -> None:
|
def invoice_plans_as_needed(event_time: Optional[datetime] = None) -> None:
|
||||||
if event_time is None: # nocoverage
|
if event_time is None:
|
||||||
event_time = timezone_now()
|
event_time = timezone_now()
|
||||||
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
|
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
|
||||||
remote_server: Optional[RemoteZulipServer] = None
|
remote_server: Optional[RemoteZulipServer] = None
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-25 05:49
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
from django.db.models import F
|
||||||
|
|
||||||
|
|
||||||
|
def update_legacy_plan_next_invoice_date(
|
||||||
|
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||||
|
) -> None:
|
||||||
|
CustomerPlan = apps.get_model("corporate", "CustomerPlan")
|
||||||
|
CustomerPlan.TIER_SELF_HOSTED_LEGACY = 101
|
||||||
|
|
||||||
|
# For legacy plans, set next_invoice_date = end_date.
|
||||||
|
CustomerPlan.objects.filter(tier=CustomerPlan.TIER_SELF_HOSTED_LEGACY).update(
|
||||||
|
next_invoice_date=F("end_date")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("corporate", "0034_customer_discount_required_tier"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_legacy_plan_next_invoice_date),
|
||||||
|
]
|
|
@ -7359,10 +7359,21 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase):
|
||||||
assert customer is not None
|
assert customer is not None
|
||||||
plan = get_current_plan_by_customer(customer)
|
plan = get_current_plan_by_customer(customer)
|
||||||
assert plan is not None
|
assert plan is not None
|
||||||
|
self.assertEqual(plan.end_date, plan_end_date)
|
||||||
|
self.assertEqual(plan.next_invoice_date, plan_end_date)
|
||||||
|
self.assertEqual(plan.status, CustomerPlan.ACTIVE)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY
|
self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY
|
||||||
)
|
)
|
||||||
self.billing_session.make_end_of_cycle_updates_if_needed(plan, plan_end_date)
|
|
||||||
|
with mock.patch("stripe.Invoice.create") as invoice_create, time_machine.travel(
|
||||||
|
plan_end_date, tick=False
|
||||||
|
):
|
||||||
|
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
||||||
|
invoice_plans_as_needed()
|
||||||
|
# The legacy plan is downgraded, no invoice created.
|
||||||
|
invoice_create.assert_not_called()
|
||||||
|
|
||||||
plan.refresh_from_db()
|
plan.refresh_from_db()
|
||||||
self.remote_server.refresh_from_db()
|
self.remote_server.refresh_from_db()
|
||||||
self.assertEqual(self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
|
self.assertEqual(self.remote_server.plan_type, RemoteZulipServer.PLAN_TYPE_SELF_MANAGED)
|
||||||
|
|
Loading…
Reference in New Issue