diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 65c17eed6b..4cbb191e3d 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -588,6 +588,8 @@ class UpgradePageParams(TypedDict): seat_count: int billing_base_url: str tier: int + flat_discount: int + flat_discounted_months: int class UpgradePageSessionTypeSpecificContext(TypedDict): @@ -706,9 +708,11 @@ class BillingSession(ABC): def get_data_for_stripe_payment_intent( self, + customer: Customer, price_per_license: int, licenses: int, plan_tier: int, + billing_schedule: int, email: str, ) -> StripePaymentIntentData: if hasattr(self, "support_session") and self.support_session: # nocoverage @@ -721,6 +725,12 @@ class BillingSession(ABC): plan_name = CustomerPlan.name_from_tier(plan_tier) description = f"Upgrade to {plan_name}, ${price_per_license/100} x {licenses}" + if customer.flat_discounted_months > 0: + num_months = 12 if billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL else 1 + flat_discounted_months = min(customer.flat_discounted_months, num_months) + amount -= customer.flat_discount * flat_discounted_months + description += f" - ${customer.flat_discount/100} x {flat_discounted_months}" + return StripePaymentIntentData( amount=amount, description=description, @@ -917,7 +927,12 @@ class BillingSession(ABC): customer = self.get_customer() assert customer is not None and customer.stripe_customer_id is not None payment_intent_data = self.get_data_for_stripe_payment_intent( - price_per_license, licenses, metadata["plan_tier"], self.get_email() + customer, + price_per_license, + licenses, + metadata["plan_tier"], + metadata["billing_schedule"], + self.get_email(), ) # Ensure customers have a default payment method set. stripe_customer = stripe_get_customer(customer.stripe_customer_id) @@ -1021,14 +1036,18 @@ class BillingSession(ABC): plan.save(update_fields=["discount", "price_per_license"]) def attach_discount_to_customer(self, new_discount: Decimal) -> str: + # Remove flat discount if giving customer a percentage discount. customer = self.get_customer() old_discount = None if customer is not None: old_discount = customer.default_discount customer.default_discount = new_discount - customer.save(update_fields=["default_discount"]) + customer.flat_discounted_months = 0 + customer.save(update_fields=["default_discount", "flat_discounted_months"]) else: - customer = self.update_or_create_customer(defaults={"default_discount": new_discount}) + customer = self.update_or_create_customer( + defaults={"default_discount": new_discount, "flat_discounted_months": 0} + ) plan = get_current_plan_by_customer(customer) if plan is not None: self.apply_discount_to_plan(plan, new_discount) @@ -1305,6 +1324,21 @@ class BillingSession(ABC): unit_amount=price_per_license, ) + if customer.flat_discounted_months > 0: + num_months = 12 if billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL else 1 + flat_discounted_months = min(customer.flat_discounted_months, num_months) + discount = customer.flat_discount * flat_discounted_months + customer.flat_discounted_months -= flat_discounted_months + customer.save(update_fields=["flat_discounted_months"]) + + stripe.InvoiceItem.create( + currency="usd", + customer=customer.stripe_customer_id, + description=f"${customer.flat_discount}/month new customer discount", + # Negative value to apply discount. + amount=(-1 * discount), + ) + if charge_automatically: collection_method = "charge_automatically" days_until_due = None @@ -1717,18 +1751,23 @@ class BillingSession(ABC): billing_frequency = CustomerPlan.BILLING_SCHEDULES[plan.billing_schedule] if switch_to_annual_at_end_of_cycle: + num_months_next_cycle = 12 annual_price_per_license = get_price_per_license( plan.tier, CustomerPlan.BILLING_SCHEDULE_ANNUAL, customer.default_discount ) renewal_cents = annual_price_per_license * licenses_at_next_renewal price_per_license = format_money(annual_price_per_license / 12) elif switch_to_monthly_at_end_of_cycle: + num_months_next_cycle = 1 monthly_price_per_license = get_price_per_license( plan.tier, CustomerPlan.BILLING_SCHEDULE_MONTHLY, customer.default_discount ) renewal_cents = monthly_price_per_license * licenses_at_next_renewal price_per_license = format_money(monthly_price_per_license) else: + num_months_next_cycle = ( + 12 if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_ANNUAL else 1 + ) renewal_cents = self.get_customer_plan_renewal_amount(plan, now, last_ledger_entry) if plan.price_per_license is None: @@ -1738,6 +1777,14 @@ class BillingSession(ABC): else: price_per_license = format_money(plan.price_per_license) + # TODO: Do this calculation in `invoice_plan` too. + pre_discount_renewal_cents = renewal_cents + flat_discount, flat_discounted_months = self.get_flat_discount_info(plan.customer) + if flat_discounted_months > 0: + flat_discounted_months = min(flat_discounted_months, num_months_next_cycle) + discount = flat_discount * flat_discounted_months + renewal_cents = renewal_cents - discount + charge_automatically = plan.charge_automatically assert customer.stripe_customer_id is not None # for mypy stripe_customer = stripe_get_customer(customer.stripe_customer_id) @@ -1786,6 +1833,9 @@ class BillingSession(ABC): "legacy_remote_server_next_plan_name": legacy_remote_server_next_plan_name, "using_min_licenses_for_plan": using_min_licenses_for_plan, "min_licenses_for_plan": min_licenses_for_plan, + "pre_discount_renewal_cents": cents_to_dollar_string(pre_discount_renewal_cents), + "flat_discount": format_money(customer.flat_discount), + "discounted_months_left": customer.flat_discounted_months, } return context @@ -1821,12 +1871,30 @@ class BillingSession(ABC): "price_per_license", "discount_percent", "using_min_licenses_for_plan", + "min_licenses_for_plan", + "pre_discount_renewal_cents", ] for key in keys: context[key] = next_plan_context[key] return context + def get_flat_discount_info(self, customer: Optional[Customer] = None) -> Tuple[int, int]: + is_self_hosted_billing = not isinstance(self, RealmBillingSession) + flat_discount = 0 + flat_discounted_months = 0 + if is_self_hosted_billing and (customer is None or customer.flat_discounted_months > 0): + if customer is None: + temp_customer = Customer() + flat_discount = temp_customer.flat_discount + flat_discounted_months = 12 + else: + flat_discount = customer.flat_discount + flat_discounted_months = customer.flat_discounted_months + assert isinstance(flat_discount, int) + assert isinstance(flat_discounted_months, int) + return flat_discount, flat_discounted_months + def get_initial_upgrade_context( self, initial_upgrade_request: InitialUpgradeRequest ) -> Tuple[Optional[str], Optional[UpgradePageContext]]: @@ -1893,6 +1961,7 @@ class BillingSession(ABC): f"{free_trial_end:%B} {free_trial_end.day}, {free_trial_end.year}" ) + flat_discount, flat_discounted_months = self.get_flat_discount_info(customer) context: UpgradePageContext = { "customer_name": customer_specific_context["customer_name"], "default_invoice_days_until_due": DEFAULT_INVOICE_DAYS_UNTIL_DUE, @@ -1917,6 +1986,8 @@ class BillingSession(ABC): "seat_count": seat_count, "billing_base_url": self.billing_base_url, "tier": tier, + "flat_discount": flat_discount, + "flat_discounted_months": flat_discounted_months, }, "using_min_licenses_for_plan": using_min_licenses_for_plan, "min_licenses_for_plan": min_licenses_for_plan, @@ -3245,12 +3316,16 @@ class RemoteRealmBillingSession(BillingSession): remote_realm=self.remote_realm, defaults={"stripe_customer_id": stripe_customer_id}, ) - return customer else: customer, created = Customer.objects.update_or_create( remote_realm=self.remote_realm, defaults=defaults ) - return customer + + if created and not customer.default_discount: + customer.flat_discounted_months = 12 + customer.save(update_fields=["flat_discounted_months"]) + + return customer @override @transaction.atomic @@ -3634,12 +3709,16 @@ class RemoteServerBillingSession(BillingSession): remote_server=self.remote_server, defaults={"stripe_customer_id": stripe_customer_id}, ) - return customer else: customer, created = Customer.objects.update_or_create( remote_server=self.remote_server, defaults=defaults ) - return customer + + if created and not customer.default_discount: + customer.flat_discounted_months = 12 + customer.save(update_fields=["flat_discounted_months"]) + + return customer @override @transaction.atomic diff --git a/corporate/migrations/0031_customer_flat_discount_and_more.py b/corporate/migrations/0031_customer_flat_discount_and_more.py new file mode 100644 index 0000000000..719615a622 --- /dev/null +++ b/corporate/migrations/0031_customer_flat_discount_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.8 on 2023-12-19 12:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("corporate", "0030_alter_zulipsponsorshiprequest_requested_plan"), + ] + + operations = [ + migrations.AddField( + model_name="customer", + name="flat_discount", + field=models.IntegerField(default=2000), + ), + migrations.AddField( + model_name="customer", + name="flat_discounted_months", + field=models.IntegerField(default=0), + ), + ] diff --git a/corporate/models.py b/corporate/models.py index 0d9a57c0dc..b0c1381190 100644 --- a/corporate/models.py +++ b/corporate/models.py @@ -34,6 +34,11 @@ class Customer(models.Model): # they purchased. exempt_from_license_number_check = models.BooleanField(default=False) + # In cents. + flat_discount = models.IntegerField(default=2000) + # Number of months left in the flat discount period. + flat_discounted_months = models.IntegerField(default=0) + class Meta: # Enforce that at least one of these is set. constraints = [ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json index 856442bd13..3d6b9bc66c 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json index 6c64e0b859..828b03f680 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.modify.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.1.json index 50bf46243d..33f1a5cc6c 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.2.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.2.json index 50bf46243d..33f1a5cc6c 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.2.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.2.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.3.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.3.json index 50bf46243d..33f1a5cc6c 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.3.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.3.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.4.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.4.json index 50bf46243d..33f1a5cc6c 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.4.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.4.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.5.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.5.json index 81c544699c..d2e9309f6a 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.5.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Customer.retrieve.5.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.1.json index 2496249011..a78f73dbd7 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json index 0358ea62c2..97a939c624 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.2.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json index cfe538d1fa..22039a294a 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.3.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json index cc6a646b8f..b5c6ad8c81 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.4.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.5.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.5.json index 8c9bb73ac7..3696578a0e 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.5.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Event.list.5.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json index dee6dd2852..5d072c4ee5 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.finalize_invoice.1.json index 2c146c2ff2..6774868ae2 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.finalize_invoice.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--Invoice.finalize_invoice.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json index 76bc062d9c..c351f1fdd5 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.2.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.2.json index ac37773215..f6b553cfa6 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.2.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.2.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.3.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.3.json new file mode 100644 index 0000000000..e23b26f397 Binary files /dev/null and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--InvoiceItem.create.3.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json index 1b346aa07b..797a278c88 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--PaymentIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json index 07472c7cde..cac481c860 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json index 85762d7b11..86512779df 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json index 07472c7cde..cac481c860 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--SetupIntent.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json index 952a0d9ad2..a2510cf57e 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.list.1.json b/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.list.1.json index c27d5b40f9..0fbad52a65 100644 Binary files a/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.list.1.json and b/corporate/tests/stripe_fixtures/non_sponsorship_billing--checkout.Session.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.modify.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.modify.1.json index 8eb8ed7521..937476bea2 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.modify.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.modify.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.1.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.2.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.2.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.2.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.3.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.3.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.3.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.4.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.4.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.4.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.5.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.5.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.5.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.5.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.6.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.6.json index 627cebb879..1b2fa2b4e5 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.6.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--Customer.retrieve.6.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.create.1.json index c00c853dd8..ee193c8508 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.create.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.list.1.json index 07261c6f3a..0e8cdc74dc 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.list.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.retrieve.1.json index c00c853dd8..ee193c8508 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.retrieve.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--SetupIntent.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.create.1.json index f151a7d554..3ade166d1a 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.create.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.list.1.json b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.list.1.json index 8bc8cb038e..b9a56f5470 100644 Binary files a/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.list.1.json and b/corporate/tests/stripe_fixtures/upgrade_legacy_plan--checkout.Session.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.create.1.json new file mode 100644 index 0000000000..a5a64c5447 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.modify.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.modify.1.json new file mode 100644 index 0000000000..768598b513 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.modify.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.1.json new file mode 100644 index 0000000000..2deed286cd Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.2.json new file mode 100644 index 0000000000..2deed286cd Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.3.json new file mode 100644 index 0000000000..2deed286cd Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.4.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.4.json new file mode 100644 index 0000000000..2deed286cd Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.5.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.5.json new file mode 100644 index 0000000000..daa0843dc2 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.5.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.6.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.6.json new file mode 100644 index 0000000000..daa0843dc2 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Customer.retrieve.6.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.1.json new file mode 100644 index 0000000000..50f0bb123a Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.2.json new file mode 100644 index 0000000000..7610e06a81 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.3.json new file mode 100644 index 0000000000..512968a3df Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.4.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.4.json new file mode 100644 index 0000000000..a509e3f719 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.5.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.5.json new file mode 100644 index 0000000000..abd0df7af8 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.5.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.6.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.6.json new file mode 100644 index 0000000000..6d922067af Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Event.list.6.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.create.1.json new file mode 100644 index 0000000000..5d072c4ee5 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.finalize_invoice.1.json new file mode 100644 index 0000000000..84b5302fda Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.finalize_invoice.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.list.1.json new file mode 100644 index 0000000000..e39960ab72 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--Invoice.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.1.json new file mode 100644 index 0000000000..c351f1fdd5 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.2.json new file mode 100644 index 0000000000..f6b553cfa6 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.3.json new file mode 100644 index 0000000000..e23b26f397 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--InvoiceItem.create.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--PaymentIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--PaymentIntent.create.1.json new file mode 100644 index 0000000000..5b6cc46085 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--PaymentIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.create.1.json new file mode 100644 index 0000000000..a67bd95a00 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.list.1.json new file mode 100644 index 0000000000..5933eba6ed Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.retrieve.1.json new file mode 100644 index 0000000000..a67bd95a00 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--SetupIntent.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.create.1.json new file mode 100644 index 0000000000..22f3d590e9 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.list.1.json new file mode 100644 index 0000000000..3f8069fcfb Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_business_plan--checkout.Session.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.create.1.json new file mode 100644 index 0000000000..a5a64c5447 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.modify.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.modify.1.json new file mode 100644 index 0000000000..b2ee3ebb2a Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.modify.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.1.json new file mode 100644 index 0000000000..4396554c44 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.2.json new file mode 100644 index 0000000000..4396554c44 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.3.json new file mode 100644 index 0000000000..4396554c44 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.4.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.4.json new file mode 100644 index 0000000000..4396554c44 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.5.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.5.json new file mode 100644 index 0000000000..b4d2618bef Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.5.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.6.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.6.json new file mode 100644 index 0000000000..b4d2618bef Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Customer.retrieve.6.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.1.json new file mode 100644 index 0000000000..bc0bb766df Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.2.json new file mode 100644 index 0000000000..7e5d292700 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.3.json new file mode 100644 index 0000000000..b83e5557ec Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.4.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.4.json new file mode 100644 index 0000000000..2818c9da6e Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.4.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.5.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.5.json new file mode 100644 index 0000000000..0e2d5d68ad Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.5.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.6.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.6.json new file mode 100644 index 0000000000..6d922067af Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Event.list.6.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.create.1.json new file mode 100644 index 0000000000..2b0ad2bb0e Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.finalize_invoice.1.json new file mode 100644 index 0000000000..288a25a124 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.finalize_invoice.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.list.1.json new file mode 100644 index 0000000000..e39960ab72 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--Invoice.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.1.json new file mode 100644 index 0000000000..041633b56c Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.2.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.2.json new file mode 100644 index 0000000000..82e3bb8ef9 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.2.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.3.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.3.json new file mode 100644 index 0000000000..a29feb75ef Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--InvoiceItem.create.3.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--PaymentIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--PaymentIntent.create.1.json new file mode 100644 index 0000000000..eb09be48ad Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--PaymentIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.create.1.json new file mode 100644 index 0000000000..7854fd6e2d Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.list.1.json new file mode 100644 index 0000000000..42b1947922 Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.list.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.retrieve.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.retrieve.1.json new file mode 100644 index 0000000000..7854fd6e2d Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--SetupIntent.retrieve.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.create.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.create.1.json new file mode 100644 index 0000000000..8960c5ec4b Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.create.1.json differ diff --git a/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.list.1.json b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.list.1.json new file mode 100644 index 0000000000..2e6736d9ce Binary files /dev/null and b/corporate/tests/stripe_fixtures/upgrade_user_to_monthly_basic_plan--checkout.Session.list.1.json differ diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index bc0b0eaf44..310ec9eac4 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -579,14 +579,14 @@ class StripeTestCase(ZulipTestCase): **kwargs: Any, ) -> "TestHttpResponse": if upgrade_page_response is None: + tier = kwargs.get("tier") + upgrade_url = f"{self.billing_session.billing_base_url}/upgrade/" + if tier: + upgrade_url += f"?tier={tier}" if self.billing_session.billing_base_url: - upgrade_page_response = self.client_get( - f"{self.billing_session.billing_base_url}/upgrade/", {}, subdomain="selfhosting" - ) + upgrade_page_response = self.client_get(upgrade_url, {}, subdomain="selfhosting") else: - upgrade_page_response = self.client_get( - f"{self.billing_session.billing_base_url}/upgrade/", {} - ) + upgrade_page_response = self.client_get(upgrade_url, {}) params: Dict[str, Any] = { "schedule": "annual", "signed_seat_count": self.get_signed_seat_count_from_response(upgrade_page_response), @@ -5651,7 +5651,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): @responses.activate @mock_stripe() - def test_non_sponsorship_billing(self, *mocks: Mock) -> None: + def test_upgrade_user_to_business_plan(self, *mocks: Mock) -> None: self.login("hamlet") hamlet = self.example_user("hamlet") @@ -5659,6 +5659,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): realm_user_count = UserProfile.objects.filter( realm=hamlet.realm, is_bot=False, is_active=True ).count() + self.assertEqual(realm_user_count, 11) with time_machine.travel(self.now, tick=False): send_server_data_to_push_bouncer(consider_usage_statistics=False) @@ -5673,7 +5674,24 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): f"{self.billing_session.billing_base_url}/upgrade/", subdomain="selfhosting" ) self.assertEqual(result.status_code, 200) - self.assert_in_success_response(["Add card", "Purchase Zulip Business"], result) + + # Min licenses used since org has less users. + min_licenses = self.billing_session.min_licenses_for_plan( + CustomerPlan.TIER_SELF_HOSTED_BUSINESS + ) + self.assertEqual(min_licenses, 25) + flat_discount, flat_discounted_months = self.billing_session.get_flat_discount_info() + self.assertEqual(flat_discounted_months, 12) + + self.assert_in_success_response( + [ + "Minimum purchase for", + f"{min_licenses} licenses", + "Add card", + "Purchase Zulip Business", + ], + result, + ) self.assertFalse(Customer.objects.exists()) self.assertFalse(CustomerPlan.objects.exists()) @@ -5693,10 +5711,10 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): for substring in [ "Zulip Business", "Number of licenses", - f"{realm_user_count} (managed automatically)", + f"{min_licenses} (managed automatically)", "January 2, 2013", "Your plan will automatically renew on", - f"${80 * realm_user_count:,.2f}", + f"${80 * min_licenses:,.2f}", "Visa ending in 4242", "Update card", ]: @@ -5707,7 +5725,7 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): self.assertEqual(LicenseLedger.objects.count(), 1) with time_machine.travel(self.now + timedelta(days=2), tick=False): - for count in range(4, 14): + for count in range(realm_user_count, min_licenses + 10): do_create_user( f"email {count}", f"password {count}", @@ -5722,11 +5740,140 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase): self.assertEqual( RemoteRealmAuditLog.objects.count(), - audit_log_count + 10, + min_licenses + 10 - realm_user_count + audit_log_count, ) latest_ledger = LicenseLedger.objects.last() assert latest_ledger is not None - self.assertEqual(latest_ledger.licenses, realm_user_count + 10) + self.assertEqual(latest_ledger.licenses, min_licenses + 10) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + + self.assertEqual(latest_ledger.licenses, 35) + for substring in [ + "Zulip Business", + "Number of licenses", + f"{latest_ledger.licenses} (managed automatically)", + "January 2, 2013", + "Your plan will automatically renew on", + f"${80 * latest_ledger.licenses:,.2f}", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) + + @responses.activate + @mock_stripe() + def test_upgrade_user_to_monthly_basic_plan(self, *mocks: Mock) -> None: + self.login("hamlet") + hamlet = self.example_user("hamlet") + + self.add_mock_response() + realm_user_count = UserProfile.objects.filter( + realm=hamlet.realm, is_bot=False, is_active=True + ).count() + self.assertEqual(realm_user_count, 11) + + with time_machine.travel(self.now, tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + result = self.execute_remote_billing_authentication_flow(hamlet) + self.assertEqual(result.status_code, 302) + self.assertEqual(result["Location"], f"{self.billing_session.billing_base_url}/plans/") + + # upgrade to basic plan + with time_machine.travel(self.now, tick=False): + result = self.client_get( + f"{self.billing_session.billing_base_url}/upgrade/?tier={CustomerPlan.TIER_SELF_HOSTED_BASIC}", + subdomain="selfhosting", + ) + self.assertEqual(result.status_code, 200) + + min_licenses = self.billing_session.min_licenses_for_plan( + CustomerPlan.TIER_SELF_HOSTED_BASIC + ) + self.assertEqual(min_licenses, 10) + flat_discount, flat_discounted_months = self.billing_session.get_flat_discount_info() + self.assertEqual(flat_discounted_months, 12) + + self.assert_in_success_response( + [f"{realm_user_count}", "Add card", "Purchase Zulip Basic"], result + ) + + self.assertFalse(Customer.objects.exists()) + self.assertFalse(CustomerPlan.objects.exists()) + self.assertFalse(LicenseLedger.objects.exists()) + + with time_machine.travel(self.now, tick=False): + stripe_customer = self.add_card_and_upgrade( + tier=CustomerPlan.TIER_SELF_HOSTED_BASIC, schedule="monthly" + ) + + customer = Customer.objects.get(stripe_customer_id=stripe_customer.id) + plan = CustomerPlan.objects.get(customer=customer) + LicenseLedger.objects.get(plan=plan) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + for substring in [ + "Zulip Basic", + "Number of licenses", + f"{realm_user_count} (managed automatically)", + "February 2, 2012", + "Your plan will automatically renew on", + f"${3.5 * realm_user_count - flat_discount // 100 * 1:,.2f}", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) + + # Verify that change in user count updates LicenseLedger. + audit_log_count = RemoteRealmAuditLog.objects.count() + self.assertEqual(LicenseLedger.objects.count(), 1) + + with time_machine.travel(self.now + timedelta(days=2), tick=False): + for count in range(realm_user_count, min_licenses + 10): + do_create_user( + f"email {count}", + f"password {count}", + hamlet.realm, + "name", + role=UserProfile.ROLE_MEMBER, + acting_user=None, + ) + + with time_machine.travel(self.now + timedelta(days=3), tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + self.assertEqual( + RemoteRealmAuditLog.objects.count(), + min_licenses + 10 - realm_user_count + audit_log_count, + ) + latest_ledger = LicenseLedger.objects.last() + assert latest_ledger is not None + self.assertEqual(latest_ledger.licenses, min_licenses + 10) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + + self.assertEqual(latest_ledger.licenses, 20) + for substring in [ + "Zulip Basic", + "Number of licenses", + f"{latest_ledger.licenses} (managed automatically)", + "February 2, 2012", + "Your plan will automatically renew on", + f"${3.5 * latest_ledger.licenses - flat_discount // 100 * 1:,.2f}", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) @responses.activate def test_request_sponsorship(self) -> None: @@ -5918,10 +6065,10 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase): for substring in [ "Zulip Business", "Number of licenses", - f"{server_user_count} (managed automatically)", + f"{25} (managed automatically)", "Your plan will automatically renew on", "January 2, 2013", - f"${80 * server_user_count:,.2f}", + f"${80 * 25:,.2f}", "Visa ending in 4242", "Update card", ]: @@ -6151,8 +6298,6 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase): self.assertEqual(new_customer_plan.tier, CustomerPlan.TIER_SELF_HOSTED_BUSINESS) self.assertEqual(new_customer_plan.billing_cycle_anchor, end_date) - server_user_count = UserProfile.objects.filter(is_bot=False, is_active=True).count() - # Visit billing page with time_machine.travel(self.now, tick=False): response = self.client_get(f"{billing_base_url}/billing/", subdomain="selfhosting") @@ -6160,7 +6305,8 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase): "(legacy plan)", f"This is a legacy plan that ends on {end_date.strftime('%B %d, %Y')}", f"Your plan will automatically upgrade to Zulip Business on {end_date.strftime('%B %d, %Y')}", - f"Expected charge: ${80 * server_user_count:,.2f}", + "Expected next charge", + f"${80 * 25 - 20 * 12:,.2f}", "Visa ending in 4242", "Update card", ]: @@ -6189,3 +6335,114 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase): m.output[1], f"INFO:corporate.stripe:Change plan status: Customer.id: {customer.id}, CustomerPlan.id: {customer_plan.id}, status: {CustomerPlan.ACTIVE}", ) + + @responses.activate + @mock_stripe() + def test_upgrade_user_to_monthly_basic_plan(self, *mocks: Mock) -> None: + self.login("hamlet") + hamlet = self.example_user("hamlet") + + self.add_mock_response() + realm_user_count = UserProfile.objects.filter(is_bot=False, is_active=True).count() + self.assertEqual(realm_user_count, 18) + + with time_machine.travel(self.now, tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + result = self.execute_remote_billing_authentication_flow( + hamlet.delivery_email, hamlet.full_name + ) + self.assertEqual(result.status_code, 302) + self.assertEqual(result["Location"], f"{self.billing_session.billing_base_url}/plans/") + + # upgrade to basic plan + with time_machine.travel(self.now, tick=False): + result = self.client_get( + f"{self.billing_session.billing_base_url}/upgrade/?tier={CustomerPlan.TIER_SELF_HOSTED_BASIC}", + subdomain="selfhosting", + ) + self.assertEqual(result.status_code, 200) + + min_licenses = self.billing_session.min_licenses_for_plan( + CustomerPlan.TIER_SELF_HOSTED_BASIC + ) + self.assertEqual(min_licenses, 10) + flat_discount, flat_discounted_months = self.billing_session.get_flat_discount_info() + self.assertEqual(flat_discounted_months, 12) + + self.assert_in_success_response( + [f"{min_licenses}", "Add card", "Purchase Zulip Basic"], result + ) + + self.assertFalse(Customer.objects.exists()) + self.assertFalse(CustomerPlan.objects.exists()) + self.assertFalse(LicenseLedger.objects.exists()) + + with time_machine.travel(self.now, tick=False): + stripe_customer = self.add_card_and_upgrade( + tier=CustomerPlan.TIER_SELF_HOSTED_BASIC, schedule="monthly" + ) + + customer = Customer.objects.get(stripe_customer_id=stripe_customer.id) + plan = CustomerPlan.objects.get(customer=customer) + LicenseLedger.objects.get(plan=plan) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + for substring in [ + "Zulip Basic", + "Number of licenses", + f"{realm_user_count} (managed automatically)", + "February 2, 2012", + "Your plan will automatically renew on", + f"${3.5 * realm_user_count - flat_discount // 100 * 1:,.2f}", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) + + # Verify that change in user count updates LicenseLedger. + audit_log_count = RemoteRealmAuditLog.objects.count() + self.assertEqual(LicenseLedger.objects.count(), 1) + + with time_machine.travel(self.now + timedelta(days=2), tick=False): + for count in range(realm_user_count, min_licenses + 10): + do_create_user( + f"email {count}", + f"password {count}", + hamlet.realm, + "name", + role=UserProfile.ROLE_MEMBER, + acting_user=None, + ) + + with time_machine.travel(self.now + timedelta(days=3), tick=False): + send_server_data_to_push_bouncer(consider_usage_statistics=False) + + self.assertEqual( + RemoteRealmAuditLog.objects.count(), + min_licenses + 10 - realm_user_count + audit_log_count, + ) + latest_ledger = LicenseLedger.objects.last() + assert latest_ledger is not None + self.assertEqual(latest_ledger.licenses, min_licenses + 10) + + with time_machine.travel(self.now + timedelta(days=1), tick=False): + response = self.client_get( + f"{self.billing_session.billing_base_url}/billing/", subdomain="selfhosting" + ) + + self.assertEqual(latest_ledger.licenses, 20) + for substring in [ + "Zulip Basic", + "Number of licenses", + f"{latest_ledger.licenses} (managed automatically)", + "February 2, 2012", + "Your plan will automatically renew on", + f"${3.5 * latest_ledger.licenses - flat_discount // 100 * 1:,.2f}", + "Visa ending in 4242", + "Update card", + ]: + self.assert_in_response(substring, response) diff --git a/templates/corporate/billing.html b/templates/corporate/billing.html index bb6a70af6e..f384af9826 100644 --- a/templates/corporate/billing.html +++ b/templates/corporate/billing.html @@ -237,25 +237,34 @@ Your next invoice is due on {{ renewal_date }}. {% endif %}
- Expected charge: ${{ renewal_amount }} - {% if not fixed_price %} - (${{ price_per_license }} x {{ licenses_at_next_renewal }} {{ 'user' if licenses_at_next_renewal == 1 else 'users' }} x - {% if switch_to_annual_at_end_of_cycle %} - 12 months - {%- elif switch_to_monthly_at_end_of_cycle %} - 1 month - {%- else %} - {{ "1 month" if billing_frequency == "Monthly" else "12 months" }} - {%- endif -%}) - {% if discount_percent %} -
- Includes: {{ discount_percent }}% discount +
+ + {% if not fixed_price %} +
+ {% if using_min_licenses_for_plan %} + Minimum purchase for this plan: {{ min_licenses_for_plan }} licenses +
+ {% endif %} + ${{ pre_discount_renewal_cents }} (${{ price_per_license }} x {{ licenses_at_next_renewal }} {{ 'user' if licenses_at_next_renewal == 1 else 'users' }} x + {% if switch_to_annual_at_end_of_cycle %} + 12 months + {%- elif switch_to_monthly_at_end_of_cycle %} + 1 month + {%- else %} + {{ "1 month" if billing_frequency == "Monthly" else "12 months" }} + {%- endif -%}) + {% if discounted_months_left != 0 %} +
+ Discount: ${{ flat_discount }}/month off ({{ discounted_months_left }} {{ "month" if discounted_months_left == 1 else "months" }} remaining) + {% endif %} + {% if discount_percent %} +
+ Includes: {{ discount_percent }}% discount + {% endif %} +
{% endif %} - {% if using_min_licenses_for_plan %} -
- Minimum purchase for this plan: {{ min_licenses_for_plan }} licenses - {% endif %} - {% endif %} +
+

${{ renewal_amount }}

{% endif %} {% else %} Your plan ends on {{ renewal_date }}, and does not renew. @@ -498,12 +507,12 @@

- Your organization will be not be upgraded to Zulip Business + Your organization will be not be upgraded to {{ legacy_remote_server_next_plan_name }} on {{ remote_server_legacy_plan_end_date }}. If your organization has more than 10 users at that time, you will lose access to the Mobile Push Notification Service. You will also not receive the other benefits - of the Zulip Business plan. Are you sure you want to continue? + of the {{ legacy_remote_server_next_plan_name }} plan. Are you sure you want to continue?