diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 884e0579ee..78977ca0d7 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -1710,8 +1710,8 @@ class BillingSession(ABC): if request_seat_count == minimum_seat_count and current_seat_count < minimum_seat_count: # Continue to use the minimum licenses for the plan tier. return request_seat_count - # Otherwise, use a current count for billed licenses. - return current_seat_count + # Otherwise, we want to check the current count against the minimum. + return max(current_seat_count, minimum_seat_count) def ensure_current_plan_is_upgradable(self, customer: Customer, new_plan_tier: int) -> None: # Upgrade for customers with an existing plan is only supported for remote realm / server right now. diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Charge.list.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Charge.list.1.json new file mode 100644 index 0000000000..9359655d1b Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Charge.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.create.1.json new file mode 100644 index 0000000000..ec5a4bf2ea Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.modify.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.modify.1.json new file mode 100644 index 0000000000..5b05485fb5 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.modify.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.1.json new file mode 100644 index 0000000000..2562e0b422 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.2.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.2.json new file mode 100644 index 0000000000..2562e0b422 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Customer.retrieve.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.1.json new file mode 100644 index 0000000000..c14d6109e0 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.2.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.2.json new file mode 100644 index 0000000000..7a893d89bd Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.3.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.3.json new file mode 100644 index 0000000000..92539b4345 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.4.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.4.json new file mode 100644 index 0000000000..6d922067af Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Event.list.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.create.1.json new file mode 100644 index 0000000000..957b5e08a3 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.finalize_invoice.1.json new file mode 100644 index 0000000000..b67cbf845e Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.finalize_invoice.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.1.json new file mode 100644 index 0000000000..e39960ab72 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.2.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.2.json new file mode 100644 index 0000000000..774009d5cf Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.list.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.pay.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.pay.1.json new file mode 100644 index 0000000000..b5caddfa80 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--Invoice.pay.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--InvoiceItem.create.1.json new file mode 100644 index 0000000000..61ee109f45 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--InvoiceItem.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--PaymentMethod.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--PaymentMethod.create.1.json new file mode 100644 index 0000000000..fcd9d75d70 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--PaymentMethod.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.create.1.json new file mode 100644 index 0000000000..826083c390 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.list.1.json new file mode 100644 index 0000000000..4b772d80c8 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.retrieve.1.json new file mode 100644 index 0000000000..826083c390 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--SetupIntent.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.create.1.json new file mode 100644 index 0000000000..3248efa9f4 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.list.1.json b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.list.1.json new file mode 100644 index 0000000000..f8607c906e Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier--checkout.Session.list.1.json differ diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 9a7f524772..3dcb1a7ae6 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -2070,6 +2070,57 @@ class StripeTest(StripeTestCase): ) self.assertEqual(self.email_envelope_from(message), settings.NOREPLY_EMAIL_ADDRESS) + @mock_stripe() + def test_upgrade_by_card_with_outdated_seat_count_and_minimum_for_plan_tier( + self, *mocks: Mock + ) -> None: + hamlet = self.example_user("hamlet") + self.login_user(hamlet) + # New seat count is under the minimum for the plan tier + minimum_for_plan_tier = self.seat_count - 1 + new_seat_count = self.seat_count - 2 + initial_upgrade_request = InitialUpgradeRequest( + manual_license_management=False, + tier=CustomerPlan.TIER_CLOUD_STANDARD, + billing_modality="charge_automatically", + ) + billing_session = RealmBillingSession(hamlet) + _, context_when_upgrade_page_is_rendered = billing_session.get_initial_upgrade_context( + initial_upgrade_request + ) + assert context_when_upgrade_page_is_rendered is not None + assert context_when_upgrade_page_is_rendered.get("seat_count") == self.seat_count + # Change the current and minimum license counts in do_upgrade + with ( + patch( + "corporate.lib.stripe.BillingSession.min_licenses_for_plan", + return_value=minimum_for_plan_tier, + ), + patch("corporate.lib.stripe.get_latest_seat_count", return_value=new_seat_count), + patch( + "corporate.lib.stripe.RealmBillingSession.get_initial_upgrade_context", + return_value=(_, context_when_upgrade_page_is_rendered), + ), + ): + self.add_card_and_upgrade(hamlet) + + customer = Customer.objects.first() + assert customer is not None + stripe_customer_id: str = assert_is_not_none(customer.stripe_customer_id) + # Check that the Charge used the minimum seat count + [charge] = iter(stripe.Charge.list(customer=stripe_customer_id)) + self.assertEqual(8000 * minimum_for_plan_tier, charge.amount) + [upgrade_invoice] = iter(stripe.Invoice.list(customer=stripe_customer_id)) + self.assertEqual( + [8000 * minimum_for_plan_tier], + [item.amount for item in upgrade_invoice.lines], + ) + # Check LicenseLedger has the minimum license count + ledger_entry = LicenseLedger.objects.last() + assert ledger_entry is not None + self.assertEqual(ledger_entry.licenses, minimum_for_plan_tier) + self.assertEqual(ledger_entry.licenses_at_next_renewal, minimum_for_plan_tier) + def test_upgrade_with_tampered_seat_count(self) -> None: hamlet = self.example_user("hamlet") self.login_user(hamlet)