mirror of
https://github.com/zulip/zulip.git
synced 2025-11-16 03:41:58 +00:00
stripe: Fix free trial pay by invoice customer billed twice.
This was a result of us moving `billing_cycle_anchor` ahead in time of the `LicenseLedger` entry the customer paid for. Thus, this confused our logic in thinking that customer hasn't paid for the current billing cycle.
This commit is contained in:
@@ -2301,6 +2301,24 @@ class BillingSession(ABC):
|
||||
# This will create invoice for any additional licenses that user has at the time of
|
||||
# switching from free trial to paid plan since they already paid for the plan's this billing cycle.
|
||||
is_renewal = False
|
||||
|
||||
# Since we need to move the `billing_cycle_anchor` forward below, we also
|
||||
# need to update the `event_time` of the last renewal ledger entry to avoid
|
||||
# our logic from thinking that licenses for the current billing cycle hasn't
|
||||
# been paid for.
|
||||
last_renewal_ledger_entry = (
|
||||
LicenseLedger.objects.filter(
|
||||
plan=plan,
|
||||
is_renewal=True,
|
||||
)
|
||||
.order_by("-id")
|
||||
.first()
|
||||
)
|
||||
assert last_renewal_ledger_entry is not None
|
||||
last_renewal_ledger_entry.event_time = next_billing_cycle.replace(
|
||||
microsecond=0
|
||||
)
|
||||
last_renewal_ledger_entry.save(update_fields=["event_time"])
|
||||
else:
|
||||
# We end the free trial since customer hasn't paid.
|
||||
plan.status = CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
"account_country": "US",
|
||||
"account_name": "NORMALIZED",
|
||||
"account_tax_ids": null,
|
||||
"amount_due": 152000,
|
||||
"amount_due": 984000,
|
||||
"amount_overpaid": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 152000,
|
||||
"amount_paid": 984000,
|
||||
"amount_remaining": 0,
|
||||
"amount_shipping": 0,
|
||||
"application": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"auto_advance": false,
|
||||
"automatic_tax": {
|
||||
"disabled_reason": null,
|
||||
"enabled": false,
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"automatically_finalizes_at": null,
|
||||
"billing_reason": "manual",
|
||||
"collection_method": "charge_automatically",
|
||||
"collection_method": "send_invoice",
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
@@ -43,7 +43,7 @@
|
||||
"default_tax_rates": [],
|
||||
"description": null,
|
||||
"discounts": [],
|
||||
"due_date": null,
|
||||
"due_date": 1000000000,
|
||||
"effective_at": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"footer": null,
|
||||
@@ -59,9 +59,9 @@
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 152000,
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Cloud Standard - renewal",
|
||||
"description": "Zulip Cloud Standard",
|
||||
"discount_amounts": [],
|
||||
"discountable": false,
|
||||
"discounts": [],
|
||||
@@ -83,8 +83,8 @@
|
||||
"type": "invoice_item_details"
|
||||
},
|
||||
"period": {
|
||||
"end": 1393729445,
|
||||
"start": 1362193445
|
||||
"end": 1362193445,
|
||||
"start": 1330657445
|
||||
},
|
||||
"pretax_credit_amounts": [],
|
||||
"pricing": {
|
||||
@@ -95,7 +95,7 @@
|
||||
"type": "price_details",
|
||||
"unit_amount_decimal": "8000"
|
||||
},
|
||||
"quantity": 19,
|
||||
"quantity": 123,
|
||||
"taxes": []
|
||||
}
|
||||
],
|
||||
@@ -105,8 +105,16 @@
|
||||
"url": "/v1/invoices/free_trial_upgrade_by_invoice--Event.list.1.json/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
"plan_tier": "1",
|
||||
"user_id": "10"
|
||||
},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALIZED",
|
||||
"object": "invoice",
|
||||
"on_behalf_of": null,
|
||||
@@ -133,19 +141,19 @@
|
||||
"shipping_details": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Cloud Standard",
|
||||
"status": "open",
|
||||
"status": "paid",
|
||||
"status_transitions": {
|
||||
"finalized_at": 1000000000,
|
||||
"marked_uncollectible_at": null,
|
||||
"paid_at": null,
|
||||
"paid_at": 1000000000,
|
||||
"voided_at": null
|
||||
},
|
||||
"subtotal": 152000,
|
||||
"subtotal_excluding_tax": 152000,
|
||||
"subtotal": 984000,
|
||||
"subtotal_excluding_tax": 984000,
|
||||
"test_clock": null,
|
||||
"total": 152000,
|
||||
"total": 984000,
|
||||
"total_discount_amounts": [],
|
||||
"total_excluding_tax": 152000,
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
@@ -159,7 +167,7 @@
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.1.json",
|
||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
"type": "invoice.finalized"
|
||||
"type": "invoice.paid"
|
||||
}
|
||||
],
|
||||
"has_more": true,
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -156,7 +156,7 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
},
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.2.json",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -156,13 +156,13 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
},
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"livemode": false,
|
||||
"object": "event",
|
||||
"pending_webhooks": 0,
|
||||
"pending_webhooks": 2,
|
||||
"request": {
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||
@@ -276,7 +276,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -325,7 +325,7 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
},
|
||||
"previous_attributes": {
|
||||
"amount_paid": 0,
|
||||
@@ -340,7 +340,7 @@
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"livemode": false,
|
||||
"object": "event",
|
||||
"pending_webhooks": 0,
|
||||
"pending_webhooks": 2,
|
||||
"request": {
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||
@@ -411,7 +411,7 @@
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"livemode": false,
|
||||
"object": "event",
|
||||
"pending_webhooks": 0,
|
||||
"pending_webhooks": 2,
|
||||
"request": {
|
||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -109,5 +109,5 @@
|
||||
"total_excluding_tax": 0,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -150,5 +150,5 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -152,7 +152,7 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"billing_schedule": "1",
|
||||
"current_plan_id": "39",
|
||||
"current_plan_id": "1",
|
||||
"license_management": "manual",
|
||||
"licenses": "123",
|
||||
"on_free_trial": "True",
|
||||
@@ -150,5 +150,5 @@
|
||||
"total_excluding_tax": 984000,
|
||||
"total_pretax_credit_amounts": [],
|
||||
"total_taxes": [],
|
||||
"webhooks_delivered_at": 1000000000
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
|
||||
@@ -1795,6 +1795,15 @@ class StripeTest(StripeTestCase):
|
||||
self.assertEqual(customer_plan.next_invoice_date, free_trial_end_date)
|
||||
|
||||
[last_event] = iter(stripe.Event.list(limit=1))
|
||||
last_renewal_ledger = (
|
||||
LicenseLedger.objects.filter(plan=plan, is_renewal=True).order_by("-id").first()
|
||||
)
|
||||
assert last_renewal_ledger is not None
|
||||
self.assertEqual(
|
||||
last_renewal_ledger.event_time,
|
||||
self.now,
|
||||
)
|
||||
|
||||
# Customer pays the invoice
|
||||
assert invoice.id is not None
|
||||
stripe.Invoice.pay(invoice.id, paid_out_of_band=True)
|
||||
@@ -1806,11 +1815,21 @@ class StripeTest(StripeTestCase):
|
||||
self.assert_in_success_response(["You have no outstanding invoices."], response)
|
||||
|
||||
invoice_plans_as_needed(free_trial_end_date)
|
||||
last_renewal_ledger.refresh_from_db()
|
||||
customer_plan.refresh_from_db()
|
||||
realm.refresh_from_db()
|
||||
plan.refresh_from_db()
|
||||
self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
|
||||
self.assertEqual(customer_plan.next_invoice_date, add_months(free_trial_end_date, 1))
|
||||
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD)
|
||||
self.assertEqual(last_renewal_ledger.event_time, free_trial_end_date)
|
||||
self.assertEqual(customer_plan.billing_cycle_anchor, free_trial_end_date)
|
||||
|
||||
before_ledger_count = LicenseLedger.objects.filter(plan=plan).count()
|
||||
self.billing_session.make_end_of_cycle_updates_if_needed(plan, free_trial_end_date)
|
||||
after_ledger_count = LicenseLedger.objects.filter(plan=plan).count()
|
||||
# No additional ledger entries are created.
|
||||
self.assertEqual(before_ledger_count, after_ledger_count)
|
||||
|
||||
@mock_stripe()
|
||||
def test_free_trial_upgrade_by_invoice_customer_fails_to_pay(self, *mocks: Mock) -> None:
|
||||
|
||||
Reference in New Issue
Block a user