mirror of
https://github.com/zulip/zulip.git
synced 2025-11-15 19:31: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
|
# 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.
|
# switching from free trial to paid plan since they already paid for the plan's this billing cycle.
|
||||||
is_renewal = False
|
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:
|
else:
|
||||||
# We end the free trial since customer hasn't paid.
|
# We end the free trial since customer hasn't paid.
|
||||||
plan.status = CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
|
plan.status = CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
"account_country": "US",
|
"account_country": "US",
|
||||||
"account_name": "NORMALIZED",
|
"account_name": "NORMALIZED",
|
||||||
"account_tax_ids": null,
|
"account_tax_ids": null,
|
||||||
"amount_due": 152000,
|
"amount_due": 984000,
|
||||||
"amount_overpaid": 0,
|
"amount_overpaid": 0,
|
||||||
"amount_paid": 0,
|
"amount_paid": 984000,
|
||||||
"amount_remaining": 152000,
|
"amount_remaining": 0,
|
||||||
"amount_shipping": 0,
|
"amount_shipping": 0,
|
||||||
"application": null,
|
"application": null,
|
||||||
"attempt_count": 0,
|
"attempt_count": 0,
|
||||||
"attempted": false,
|
"attempted": false,
|
||||||
"auto_advance": true,
|
"auto_advance": false,
|
||||||
"automatic_tax": {
|
"automatic_tax": {
|
||||||
"disabled_reason": null,
|
"disabled_reason": null,
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"automatically_finalizes_at": null,
|
"automatically_finalizes_at": null,
|
||||||
"billing_reason": "manual",
|
"billing_reason": "manual",
|
||||||
"collection_method": "charge_automatically",
|
"collection_method": "send_invoice",
|
||||||
"created": 1000000000,
|
"created": 1000000000,
|
||||||
"currency": "usd",
|
"currency": "usd",
|
||||||
"custom_fields": null,
|
"custom_fields": null,
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"default_tax_rates": [],
|
"default_tax_rates": [],
|
||||||
"description": null,
|
"description": null,
|
||||||
"discounts": [],
|
"discounts": [],
|
||||||
"due_date": null,
|
"due_date": 1000000000,
|
||||||
"effective_at": 1000000000,
|
"effective_at": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"footer": null,
|
"footer": null,
|
||||||
@@ -59,9 +59,9 @@
|
|||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"amount": 152000,
|
"amount": 984000,
|
||||||
"currency": "usd",
|
"currency": "usd",
|
||||||
"description": "Zulip Cloud Standard - renewal",
|
"description": "Zulip Cloud Standard",
|
||||||
"discount_amounts": [],
|
"discount_amounts": [],
|
||||||
"discountable": false,
|
"discountable": false,
|
||||||
"discounts": [],
|
"discounts": [],
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
"type": "invoice_item_details"
|
"type": "invoice_item_details"
|
||||||
},
|
},
|
||||||
"period": {
|
"period": {
|
||||||
"end": 1393729445,
|
"end": 1362193445,
|
||||||
"start": 1362193445
|
"start": 1330657445
|
||||||
},
|
},
|
||||||
"pretax_credit_amounts": [],
|
"pretax_credit_amounts": [],
|
||||||
"pricing": {
|
"pricing": {
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"type": "price_details",
|
"type": "price_details",
|
||||||
"unit_amount_decimal": "8000"
|
"unit_amount_decimal": "8000"
|
||||||
},
|
},
|
||||||
"quantity": 19,
|
"quantity": 123,
|
||||||
"taxes": []
|
"taxes": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -105,8 +105,16 @@
|
|||||||
"url": "/v1/invoices/free_trial_upgrade_by_invoice--Event.list.1.json/lines"
|
"url": "/v1/invoices/free_trial_upgrade_by_invoice--Event.list.1.json/lines"
|
||||||
},
|
},
|
||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
"next_payment_attempt": 1000000000,
|
"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",
|
"number": "NORMALIZED",
|
||||||
"object": "invoice",
|
"object": "invoice",
|
||||||
"on_behalf_of": null,
|
"on_behalf_of": null,
|
||||||
@@ -133,19 +141,19 @@
|
|||||||
"shipping_details": null,
|
"shipping_details": null,
|
||||||
"starting_balance": 0,
|
"starting_balance": 0,
|
||||||
"statement_descriptor": "Zulip Cloud Standard",
|
"statement_descriptor": "Zulip Cloud Standard",
|
||||||
"status": "open",
|
"status": "paid",
|
||||||
"status_transitions": {
|
"status_transitions": {
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"marked_uncollectible_at": null,
|
"marked_uncollectible_at": null,
|
||||||
"paid_at": null,
|
"paid_at": 1000000000,
|
||||||
"voided_at": null
|
"voided_at": null
|
||||||
},
|
},
|
||||||
"subtotal": 152000,
|
"subtotal": 984000,
|
||||||
"subtotal_excluding_tax": 152000,
|
"subtotal_excluding_tax": 984000,
|
||||||
"test_clock": null,
|
"test_clock": null,
|
||||||
"total": 152000,
|
"total": 984000,
|
||||||
"total_discount_amounts": [],
|
"total_discount_amounts": [],
|
||||||
"total_excluding_tax": 152000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": 1000000000
|
||||||
@@ -159,7 +167,7 @@
|
|||||||
"id": "free_trial_upgrade_by_invoice--Event.list.1.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.1.json",
|
||||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||||
},
|
},
|
||||||
"type": "invoice.finalized"
|
"type": "invoice.paid"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_more": true,
|
"has_more": true,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "free_trial_upgrade_by_invoice--Event.list.2.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.2.json",
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -156,13 +156,13 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"livemode": false,
|
"livemode": false,
|
||||||
"object": "event",
|
"object": "event",
|
||||||
"pending_webhooks": 0,
|
"pending_webhooks": 2,
|
||||||
"request": {
|
"request": {
|
||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -325,7 +325,7 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
},
|
},
|
||||||
"previous_attributes": {
|
"previous_attributes": {
|
||||||
"amount_paid": 0,
|
"amount_paid": 0,
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"livemode": false,
|
"livemode": false,
|
||||||
"object": "event",
|
"object": "event",
|
||||||
"pending_webhooks": 0,
|
"pending_webhooks": 2,
|
||||||
"request": {
|
"request": {
|
||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"livemode": false,
|
"livemode": false,
|
||||||
"object": "event",
|
"object": "event",
|
||||||
"pending_webhooks": 0,
|
"pending_webhooks": 2,
|
||||||
"request": {
|
"request": {
|
||||||
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
"id": "free_trial_upgrade_by_invoice--Event.list.3.json",
|
||||||
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
"idempotency_key": "00000000-0000-0000-0000-000000000000"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -109,5 +109,5 @@
|
|||||||
"total_excluding_tax": 0,
|
"total_excluding_tax": 0,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -150,5 +150,5 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"total_taxes": [],
|
||||||
"webhooks_delivered_at": 1000000000
|
"webhooks_delivered_at": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_more": false,
|
"has_more": false,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
"livemode": false,
|
"livemode": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"billing_schedule": "1",
|
"billing_schedule": "1",
|
||||||
"current_plan_id": "39",
|
"current_plan_id": "1",
|
||||||
"license_management": "manual",
|
"license_management": "manual",
|
||||||
"licenses": "123",
|
"licenses": "123",
|
||||||
"on_free_trial": "True",
|
"on_free_trial": "True",
|
||||||
@@ -150,5 +150,5 @@
|
|||||||
"total_excluding_tax": 984000,
|
"total_excluding_tax": 984000,
|
||||||
"total_pretax_credit_amounts": [],
|
"total_pretax_credit_amounts": [],
|
||||||
"total_taxes": [],
|
"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)
|
self.assertEqual(customer_plan.next_invoice_date, free_trial_end_date)
|
||||||
|
|
||||||
[last_event] = iter(stripe.Event.list(limit=1))
|
[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
|
# Customer pays the invoice
|
||||||
assert invoice.id is not None
|
assert invoice.id is not None
|
||||||
stripe.Invoice.pay(invoice.id, paid_out_of_band=True)
|
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)
|
self.assert_in_success_response(["You have no outstanding invoices."], response)
|
||||||
|
|
||||||
invoice_plans_as_needed(free_trial_end_date)
|
invoice_plans_as_needed(free_trial_end_date)
|
||||||
|
last_renewal_ledger.refresh_from_db()
|
||||||
customer_plan.refresh_from_db()
|
customer_plan.refresh_from_db()
|
||||||
realm.refresh_from_db()
|
realm.refresh_from_db()
|
||||||
|
plan.refresh_from_db()
|
||||||
self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
|
self.assertEqual(customer_plan.status, CustomerPlan.ACTIVE)
|
||||||
self.assertEqual(customer_plan.next_invoice_date, add_months(free_trial_end_date, 1))
|
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(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()
|
@mock_stripe()
|
||||||
def test_free_trial_upgrade_by_invoice_customer_fails_to_pay(self, *mocks: Mock) -> None:
|
def test_free_trial_upgrade_by_invoice_customer_fails_to_pay(self, *mocks: Mock) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user