From 6e1bb565193c7dca1252d01b4add84847ba56641 Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Fri, 17 Jan 2025 16:41:59 +0530 Subject: [PATCH] stripe: Check for `min_licenses` when switching plan tier. When we are upgrading a customer to a different plan tier, we should check if switching plans would lead to different licenses due to different minimum license requirement between plans. --- corporate/lib/stripe.py | 12 +++++++++- ...om_standard_to_plus--Invoice.create.1.json | 21 +++++++++-------- ...d_to_plus--Invoice.finalize_invoice.1.json | 21 +++++++++-------- ...from_standard_to_plus--Invoice.list.1.json | 23 +++++++++++-------- ...tandard_to_plus--InvoiceItem.create.1.json | 4 ++-- corporate/tests/test_stripe.py | 13 ++++++----- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 157bfd636e..b2e3df8c19 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -3164,7 +3164,17 @@ class BillingSession(ABC): LicenseLedger.objects.filter(plan=current_plan).order_by("id").last() ) assert current_plan_last_ledger is not None - licenses_for_new_plan = current_plan_last_ledger.licenses_at_next_renewal + + old_plan_licenses_at_next_renewal = current_plan_last_ledger.licenses_at_next_renewal + assert old_plan_licenses_at_next_renewal is not None + licenses_for_new_plan = self.get_billable_licenses_for_customer( + current_plan.customer, + new_plan_tier, + old_plan_licenses_at_next_renewal, + ) + if not new_plan.automanage_licenses: # nocoverage + licenses_for_new_plan = max(old_plan_licenses_at_next_renewal, licenses_for_new_plan) + assert licenses_for_new_plan is not None LicenseLedger.objects.create( plan=new_plan, diff --git a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.create.1.json b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.create.1.json index e5b0e37570..07d6a3da15 100644 --- a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.create.1.json +++ b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.create.1.json @@ -2,9 +2,9 @@ "account_country": "US", "account_name": "Kandra Labs, Inc.", "account_tax_ids": null, - "amount_due": 3600, + "amount_due": 4800, "amount_paid": 0, - "amount_remaining": 3600, + "amount_remaining": 4800, "amount_shipping": 0, "application": null, "application_fee_amount": null, @@ -12,6 +12,7 @@ "attempted": false, "auto_advance": true, "automatic_tax": { + "disabled_reason": null, "enabled": false, "liability": null, "status": null @@ -53,8 +54,8 @@ "lines": { "data": [ { - "amount": 10800, - "amount_excluding_tax": 10800, + "amount": 12000, + "amount_excluding_tax": 12000, "currency": "usd", "description": "Zulip Cloud Plus - renewal", "discount_amounts": [], @@ -71,6 +72,7 @@ "start": 1000000000 }, "plan": null, + "pretax_credit_amounts": [], "price": { "active": false, "billing_scheme": "per_unit", @@ -96,7 +98,7 @@ "proration_details": { "credited_items": null }, - "quantity": 9, + "quantity": 10, "subscription": null, "tax_amounts": [], "tax_rates": [], @@ -153,13 +155,14 @@ "subscription_details": { "metadata": null }, - "subtotal": 10800, - "subtotal_excluding_tax": 10800, + "subtotal": 12000, + "subtotal_excluding_tax": 12000, "tax": null, "test_clock": null, - "total": 10800, + "total": 12000, "total_discount_amounts": [], - "total_excluding_tax": 10800, + "total_excluding_tax": 12000, + "total_pretax_credit_amounts": [], "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null diff --git a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.finalize_invoice.1.json index acf1550900..4b8e861670 100644 --- a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.finalize_invoice.1.json +++ b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.finalize_invoice.1.json @@ -2,9 +2,9 @@ "account_country": "US", "account_name": "Kandra Labs, Inc.", "account_tax_ids": null, - "amount_due": 3600, + "amount_due": 4800, "amount_paid": 0, - "amount_remaining": 3600, + "amount_remaining": 4800, "amount_shipping": 0, "application": null, "application_fee_amount": null, @@ -12,6 +12,7 @@ "attempted": false, "auto_advance": true, "automatic_tax": { + "disabled_reason": null, "enabled": false, "liability": null, "status": null @@ -53,8 +54,8 @@ "lines": { "data": [ { - "amount": 10800, - "amount_excluding_tax": 10800, + "amount": 12000, + "amount_excluding_tax": 12000, "currency": "usd", "description": "Zulip Cloud Plus - renewal", "discount_amounts": [], @@ -71,6 +72,7 @@ "start": 1000000000 }, "plan": null, + "pretax_credit_amounts": [], "price": { "active": false, "billing_scheme": "per_unit", @@ -96,7 +98,7 @@ "proration_details": { "credited_items": null }, - "quantity": 9, + "quantity": 10, "subscription": null, "tax_amounts": [], "tax_rates": [], @@ -153,13 +155,14 @@ "subscription_details": { "metadata": null }, - "subtotal": 10800, - "subtotal_excluding_tax": 10800, + "subtotal": 12000, + "subtotal_excluding_tax": 12000, "tax": null, "test_clock": null, - "total": 10800, + "total": 12000, "total_discount_amounts": [], - "total_excluding_tax": 10800, + "total_excluding_tax": 12000, + "total_pretax_credit_amounts": [], "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null diff --git a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.list.1.json b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.list.1.json index 7dbf594cec..26b6e5eb9e 100644 --- a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.list.1.json +++ b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--Invoice.list.1.json @@ -4,9 +4,9 @@ "account_country": "US", "account_name": "Kandra Labs, Inc.", "account_tax_ids": null, - "amount_due": 3600, + "amount_due": 4800, "amount_paid": 0, - "amount_remaining": 3600, + "amount_remaining": 4800, "amount_shipping": 0, "application": null, "application_fee_amount": null, @@ -14,6 +14,7 @@ "attempted": false, "auto_advance": true, "automatic_tax": { + "disabled_reason": null, "enabled": false, "liability": null, "status": null @@ -55,8 +56,8 @@ "lines": { "data": [ { - "amount": 10800, - "amount_excluding_tax": 10800, + "amount": 12000, + "amount_excluding_tax": 12000, "currency": "usd", "description": "Zulip Cloud Plus - renewal", "discount_amounts": [], @@ -73,6 +74,7 @@ "start": 1000000000 }, "plan": null, + "pretax_credit_amounts": [], "price": { "active": false, "billing_scheme": "per_unit", @@ -98,7 +100,7 @@ "proration_details": { "credited_items": null }, - "quantity": 9, + "quantity": 10, "subscription": null, "tax_amounts": [], "tax_rates": [], @@ -155,16 +157,17 @@ "subscription_details": { "metadata": null }, - "subtotal": 10800, - "subtotal_excluding_tax": 10800, + "subtotal": 12000, + "subtotal_excluding_tax": 12000, "tax": null, "test_clock": null, - "total": 10800, + "total": 12000, "total_discount_amounts": [], - "total_excluding_tax": 10800, + "total_excluding_tax": 12000, + "total_pretax_credit_amounts": [], "total_tax_amounts": [], "transfer_data": null, - "webhooks_delivered_at": 1000000000 + "webhooks_delivered_at": null } ], "has_more": false, diff --git a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--InvoiceItem.create.1.json index 025cc43199..f07aea4029 100644 --- a/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--InvoiceItem.create.1.json +++ b/corporate/tests/stripe_fixtures/change_plan_tier_from_standard_to_plus--InvoiceItem.create.1.json @@ -1,5 +1,5 @@ { - "amount": 10800, + "amount": 12000, "currency": "usd", "customer": "cus_NORMALIZED", "date": 1000000000, @@ -38,7 +38,7 @@ "unit_amount_decimal": "1200" }, "proration": false, - "quantity": 9, + "quantity": 10, "subscription": null, "tax_rates": [], "test_clock": null, diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index e34c0c81dd..d9edb76a78 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -4912,10 +4912,11 @@ class StripeTest(StripeTestCase): # There are 9 licenses and the realm is on the Standard monthly plan. # Therefore, the customer has already paid 800 * 9 = 7200 = $72 for - # the month. Once they upgrade to Plus, the new price for their 9 - # licenses will be 1200 * 9 = 10800 = $108. Since the customer has - # already paid $72 for a month, -7200 = -$72 will be credited to the - # customer's balance. + # the month. Once they upgrade to Plus, they will have to pay for 10 + # licenses as that is the minimum licenses for that plan. + # The new price for their 10 licenses will be 1200 * 10 = 12000 = $120. + # Since the customer has already paid $72 for a month, -7200 = -$72 will + # be credited to the customer's balance. stripe_customer_id = customer.stripe_customer_id assert stripe_customer_id is not None _, cb_txn = iter(stripe.Customer.list_balance_transactions(stripe_customer_id)) @@ -4926,10 +4927,10 @@ class StripeTest(StripeTestCase): ) self.assertEqual(cb_txn.type, "adjustment") - # The customer now only pays the difference 10800 - 7200 = 3600 = $36, + # The customer now only pays the difference 12000 - 7200 = 4800 = $48, # since the unused proration is for the whole month. (invoice,) = iter(stripe.Invoice.list(customer=stripe_customer_id)) - self.assertEqual(invoice.amount_due, 3600) + self.assertEqual(invoice.amount_due, 4800) @mock_stripe() def test_customer_has_credit_card_as_default_payment_method(self, *mocks: Mock) -> None: