diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py
index 8d8a085cc9..ebafe6da48 100644
--- a/corporate/lib/stripe.py
+++ b/corporate/lib/stripe.py
@@ -12,6 +12,7 @@ import stripe
from django.conf import settings
from django.core.signing import Signer
from django.db import transaction
+from django.urls import reverse
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
@@ -26,6 +27,7 @@ from corporate.models import (
get_customer_by_realm,
)
from zerver.lib.logging_util import log_to_file
+from zerver.lib.send_email import FromAddress, send_email_to_billing_admins_and_realm_owners
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
from zerver.models import Realm, RealmAuditLog, UserProfile, get_system_bot
from zproject.config import get_secret
@@ -988,6 +990,46 @@ def void_all_open_invoices(realm: Realm) -> int:
return voided_invoices_count
+def downgrade_small_realms_behind_on_payments_as_needed() -> None:
+ customers = Customer.objects.all()
+ for customer in customers:
+ realm = customer.realm
+
+ # For larger realms, we generally want to talk to the customer
+ # before downgrading; so this logic only applies with 5.
+ if get_latest_seat_count(realm) >= 5:
+ continue
+
+ if get_current_plan_by_customer(customer) is None:
+ continue
+
+ due_invoice_count = 0
+ for invoice in stripe.Invoice.list(customer=customer.stripe_customer_id, limit=2):
+ if invoice.status == "open":
+ due_invoice_count += 1
+
+ # Customers with only 1 overdue invoice are ignored.
+ if due_invoice_count < 2:
+ continue
+
+ # We've now decided to downgrade this customer and void all invoices, and the below will execute this.
+
+ downgrade_now_without_creating_additional_invoices(realm)
+ void_all_open_invoices(realm)
+ context: Dict[str, str] = {
+ "upgrade_url": f"{realm.uri}{reverse('initial_upgrade')}",
+ "realm": realm,
+ }
+ send_email_to_billing_admins_and_realm_owners(
+ "zerver/emails/realm_auto_downgraded",
+ realm,
+ from_name=FromAddress.security_email_from_name(language=realm.default_language),
+ from_address=FromAddress.tokenized_no_reply_address(),
+ language=realm.default_language,
+ context=context,
+ )
+
+
def update_billing_method_of_current_plan(
realm: Realm, charge_automatically: bool, *, acting_user: Optional[UserProfile]
) -> None:
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.1.json
new file mode 100644
index 0000000000..3776562c5a
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.1.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_425511 (None)",
+ "discount": null,
+ "email": "user-0-realm_425511@zulip.com",
+ "id": "cus_NORMALIZED0001",
+ "invoice_prefix": "NORMA01",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "1",
+ "realm_str": "realm_425511"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0001/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0001/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.2.json
new file mode 100644
index 0000000000..770b0eb0a3
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.2.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_701713 (None)",
+ "discount": null,
+ "email": "user-0-realm_701713@zulip.com",
+ "id": "cus_NORMALIZED0002",
+ "invoice_prefix": "NORMA02",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "2",
+ "realm_str": "realm_701713"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0002/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0002/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0002/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.3.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.3.json
new file mode 100644
index 0000000000..9b906b42f9
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.3.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_612438 (None)",
+ "discount": null,
+ "email": "user-0-realm_612438@zulip.com",
+ "id": "cus_NORMALIZED0003",
+ "invoice_prefix": "NORMA03",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "3",
+ "realm_str": "realm_612438"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0003/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0003/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0003/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.4.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.4.json
new file mode 100644
index 0000000000..2947e819fe
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.4.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_158636 (None)",
+ "discount": null,
+ "email": "user-0-realm_158636@zulip.com",
+ "id": "cus_NORMALIZED0004",
+ "invoice_prefix": "NORMA04",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "4",
+ "realm_str": "realm_158636"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0004/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0004/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0004/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.5.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.5.json
new file mode 100644
index 0000000000..3987d8d629
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.5.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_345953 (None)",
+ "discount": null,
+ "email": "user-0-realm_345953@zulip.com",
+ "id": "cus_NORMALIZED0005",
+ "invoice_prefix": "NORMA05",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "5",
+ "realm_str": "realm_345953"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0005/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0005/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0005/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.6.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.6.json
new file mode 100644
index 0000000000..ccdd4d1df0
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Customer.create.6.json
@@ -0,0 +1,54 @@
+{
+ "account_balance": 0,
+ "address": null,
+ "balance": 0,
+ "created": 1000000000,
+ "currency": null,
+ "default_source": null,
+ "delinquent": false,
+ "description": "realm_186548 (None)",
+ "discount": null,
+ "email": "user-0-realm_186548@zulip.com",
+ "id": "cus_NORMALIZED0006",
+ "invoice_prefix": "NORMA06",
+ "invoice_settings": {
+ "custom_fields": null,
+ "default_payment_method": null,
+ "footer": null
+ },
+ "livemode": false,
+ "metadata": {
+ "realm_id": "6",
+ "realm_str": "realm_186548"
+ },
+ "name": null,
+ "next_invoice_sequence": 1,
+ "object": "customer",
+ "phone": null,
+ "preferred_locales": [],
+ "shipping": null,
+ "sources": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0006/sources"
+ },
+ "subscriptions": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0006/subscriptions"
+ },
+ "tax_exempt": "none",
+ "tax_ids": {
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "total_count": 0,
+ "url": "/v1/customers/cus_NORMALIZED0006/tax_ids"
+ },
+ "tax_info": null,
+ "tax_info_verification": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.1.json
new file mode 100644
index 0000000000..90dd65f168
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.1.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0003",
+ "customer_address": null,
+ "customer_email": "user-0-realm_612438@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000001",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000001",
+ "invoice_item": "ii_NORMALIZED00000000000001",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW6D2X8vgpBNGNRpvTb7X",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XEKpg4Z7fzV",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW6D2X8vgpBNGJfmJCwyJ"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0001",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.2.json
new file mode 100644
index 0000000000..6005a53c14
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.2.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000002",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000002",
+ "invoice_item": "ii_NORMALIZED00000000000002",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW8D2X8vgpBNGjQO5ySQC",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XcBc6MoFHgE",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW8D2X8vgpBNGf92BtWOV"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0002",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.3.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.3.json
new file mode 100644
index 0000000000..6ce4cb688d
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.3.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000003",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000003",
+ "invoice_item": "ii_NORMALIZED00000000000003",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWBD2X8vgpBNGk65s4Bby",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XXEj43ttifM",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWBD2X8vgpBNGdBrYTZWS"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0003",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.4.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.4.json
new file mode 100644
index 0000000000..8d29349432
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.4.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000004",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000004",
+ "invoice_item": "ii_NORMALIZED00000000000004",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWED2X8vgpBNGJPqvl8ls",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XOrbeS8UGnX",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWED2X8vgpBNGcPMTOMfD"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0004",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.5.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.5.json
new file mode 100644
index 0000000000..59e25e602f
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.5.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000005",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000005",
+ "invoice_item": "ii_NORMALIZED00000000000005",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWHD2X8vgpBNGwEA9hn4b",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XvpvSalZcLc",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWHD2X8vgpBNGmcQgjQGx"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000005/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0005",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.6.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.6.json
new file mode 100644
index 0000000000..ca1621a0c4
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.6.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0006",
+ "customer_address": null,
+ "customer_email": "user-0-realm_186548@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000006",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000006",
+ "invoice_item": "ii_NORMALIZED00000000000006",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWMD2X8vgpBNGGQAGsuaj",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XRALl1QAQk5",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWMD2X8vgpBNGTMs8cYiR"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000006/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0006",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.7.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.7.json
new file mode 100644
index 0000000000..a3242d50df
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.create.7.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0006",
+ "customer_address": null,
+ "customer_email": "user-0-realm_186548@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": null,
+ "finalized_at": null,
+ "footer": null,
+ "hosted_invoice_url": null,
+ "id": "in_NORMALIZED00000000000007",
+ "invoice_pdf": null,
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000007",
+ "invoice_item": "ii_NORMALIZED00000000000007",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWPD2X8vgpBNGdU5odKe9",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0X7KLLErkxlq",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWPD2X8vgpBNGayzkkXN2"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000007/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0007",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.1.json
new file mode 100644
index 0000000000..c202e76b61
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.1.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0003",
+ "customer_address": null,
+ "customer_email": "user-0-realm_612438@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000001w2De8",
+ "id": "in_NORMALIZED00000000000001",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000001w2De8/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000001",
+ "invoice_item": "ii_NORMALIZED00000000000001",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW6D2X8vgpBNGNRpvTb7X",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XEKpg4Z7fzV",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW6D2X8vgpBNGJfmJCwyJ"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0001",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YW7D2X8vgpBNG5Yb5dYds",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.2.json
new file mode 100644
index 0000000000..4aad5a8ff1
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.2.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000002idVh7",
+ "id": "in_NORMALIZED00000000000002",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000002idVh7/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000002",
+ "invoice_item": "ii_NORMALIZED00000000000002",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW8D2X8vgpBNGjQO5ySQC",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XcBc6MoFHgE",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW8D2X8vgpBNGf92BtWOV"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0002",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWAD2X8vgpBNGXw0Js5PH",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.3.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.3.json
new file mode 100644
index 0000000000..a18f9821c9
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.3.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000003nsJBj",
+ "id": "in_NORMALIZED00000000000003",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000003nsJBj/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000003",
+ "invoice_item": "ii_NORMALIZED00000000000003",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWBD2X8vgpBNGk65s4Bby",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XXEj43ttifM",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWBD2X8vgpBNGdBrYTZWS"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0003",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWDD2X8vgpBNGVKjx2VAT",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.4.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.4.json
new file mode 100644
index 0000000000..881f2cd722
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.4.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX",
+ "id": "in_NORMALIZED00000000000004",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000004",
+ "invoice_item": "ii_NORMALIZED00000000000004",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWED2X8vgpBNGJPqvl8ls",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XOrbeS8UGnX",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWED2X8vgpBNGcPMTOMfD"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0004",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWGD2X8vgpBNGOTyIX1H3",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.5.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.5.json
new file mode 100644
index 0000000000..b7683eba7b
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.5.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY",
+ "id": "in_NORMALIZED00000000000005",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000005",
+ "invoice_item": "ii_NORMALIZED00000000000005",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWHD2X8vgpBNGwEA9hn4b",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XvpvSalZcLc",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWHD2X8vgpBNGmcQgjQGx"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000005/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0005",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWJD2X8vgpBNGkNf5igPN",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.6.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.6.json
new file mode 100644
index 0000000000..8eb21abd54
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.6.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0006",
+ "customer_address": null,
+ "customer_email": "user-0-realm_186548@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000006s5YVx",
+ "id": "in_NORMALIZED00000000000006",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000006s5YVx/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000006",
+ "invoice_item": "ii_NORMALIZED00000000000006",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWMD2X8vgpBNGGQAGsuaj",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XRALl1QAQk5",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWMD2X8vgpBNGTMs8cYiR"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000006/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0006",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWOD2X8vgpBNGoAHwi7t6",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.7.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.7.json
new file mode 100644
index 0000000000..585210a4d9
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.finalize_invoice.7.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0006",
+ "customer_address": null,
+ "customer_email": "user-0-realm_186548@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000007m8tcn",
+ "id": "in_NORMALIZED00000000000007",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000007m8tcn/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000007",
+ "invoice_item": "ii_NORMALIZED00000000000007",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWPD2X8vgpBNGdU5odKe9",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0X7KLLErkxlq",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWPD2X8vgpBNGayzkkXN2"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000007/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0007",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWQD2X8vgpBNGCa6w5Qan",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": null
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.1.json
new file mode 100644
index 0000000000..e39960ab72
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.1.json
@@ -0,0 +1,6 @@
+{
+ "data": [],
+ "has_more": false,
+ "object": "list",
+ "url": "/v1/invoices"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.2.json
new file mode 100644
index 0000000000..72bbc0f46e
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.2.json
@@ -0,0 +1,140 @@
+{
+ "data": [
+ {
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0003",
+ "customer_address": null,
+ "customer_email": "user-0-realm_612438@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000001w2De8",
+ "id": "in_NORMALIZED00000000000001",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000001w2De8/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000001",
+ "invoice_item": "ii_NORMALIZED00000000000001",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW6D2X8vgpBNGNRpvTb7X",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XEKpg4Z7fzV",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW6D2X8vgpBNGJfmJCwyJ"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0001",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YW7D2X8vgpBNG5Yb5dYds",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "url": "/v1/invoices"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.3.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.3.json
new file mode 100644
index 0000000000..ffbf76d4b5
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.3.json
@@ -0,0 +1,273 @@
+{
+ "data": [
+ {
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000003nsJBj",
+ "id": "in_NORMALIZED00000000000003",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000003nsJBj/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000003",
+ "invoice_item": "ii_NORMALIZED00000000000003",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWBD2X8vgpBNGk65s4Bby",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XXEj43ttifM",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWBD2X8vgpBNGdBrYTZWS"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0003",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWDD2X8vgpBNGVKjx2VAT",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+ },
+ {
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": true,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0004",
+ "customer_address": null,
+ "customer_email": "user-0-realm_158636@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000002idVh7",
+ "id": "in_NORMALIZED00000000000002",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000002idVh7/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000002",
+ "invoice_item": "ii_NORMALIZED00000000000002",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW8D2X8vgpBNGjQO5ySQC",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XcBc6MoFHgE",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YW8D2X8vgpBNGf92BtWOV"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0002",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": false,
+ "payment_intent": "pi_1J5YWAD2X8vgpBNGXw0Js5PH",
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "open",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "url": "/v1/invoices"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.4.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.4.json
new file mode 100644
index 0000000000..fca90201bf
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.list.4.json
@@ -0,0 +1,273 @@
+{
+ "data": [
+ {
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": false,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY",
+ "id": "in_NORMALIZED00000000000005",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000005",
+ "invoice_item": "ii_NORMALIZED00000000000005",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWHD2X8vgpBNGwEA9hn4b",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XvpvSalZcLc",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWHD2X8vgpBNGmcQgjQGx"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000005/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0005",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": true,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "paid",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": 1000000000,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+ },
+ {
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": false,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX",
+ "id": "in_NORMALIZED00000000000004",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000004",
+ "invoice_item": "ii_NORMALIZED00000000000004",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWED2X8vgpBNGJPqvl8ls",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XOrbeS8UGnX",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWED2X8vgpBNGcPMTOMfD"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0004",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": true,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "paid",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": 1000000000,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "url": "/v1/invoices"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.1.json
new file mode 100644
index 0000000000..267220fde6
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.1.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": false,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX",
+ "id": "in_NORMALIZED00000000000004",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000004w1nCX/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000004",
+ "invoice_item": "ii_NORMALIZED00000000000004",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWED2X8vgpBNGJPqvl8ls",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XOrbeS8UGnX",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWED2X8vgpBNGcPMTOMfD"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0004",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": true,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "paid",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": 1000000000,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.2.json
new file mode 100644
index 0000000000..b44f8aec57
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--Invoice.pay.2.json
@@ -0,0 +1,133 @@
+{
+ "account_country": "US",
+ "account_name": "NORMALIZED-1",
+ "account_tax_ids": null,
+ "amount_due": 10000,
+ "amount_paid": 0,
+ "amount_remaining": 10000,
+ "application_fee": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": false,
+ "automatic_tax": {
+ "enabled": false,
+ "status": null
+ },
+ "billing": "send_invoice",
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "send_invoice",
+ "created": 1000000000,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NORMALIZED0005",
+ "customer_address": null,
+ "customer_email": "user-0-realm_345953@zulip.com",
+ "customer_name": null,
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "date": 1000000000,
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": 1000000000,
+ "ending_balance": 0,
+ "finalized_at": 1000000000,
+ "footer": null,
+ "hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY",
+ "id": "in_NORMALIZED00000000000005",
+ "invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/invst_NORMALIZED0000000000000005e3XWY/pdf",
+ "last_finalization_error": null,
+ "lines": {
+ "data": [
+ {
+ "amount": 10000,
+ "currency": "usd",
+ "description": "Zulip standard",
+ "discount_amounts": [],
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000005",
+ "invoice_item": "ii_NORMALIZED00000000000005",
+ "livemode": false,
+ "metadata": {},
+ "object": "line_item",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWHD2X8vgpBNGwEA9hn4b",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XvpvSalZcLc",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_amounts": [],
+ "tax_rates": [],
+ "type": "invoiceitem",
+ "unique_id": "il_1J5YWHD2X8vgpBNGmcQgjQGx"
+ }
+ ],
+ "has_more": false,
+ "object": "list",
+ "total_count": 1,
+ "url": "/v1/invoices/in_NORMALIZED00000000000005/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": "NORMALI-0005",
+ "object": "invoice",
+ "on_behalf_of": null,
+ "paid": true,
+ "payment_intent": null,
+ "payment_settings": {
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1000000000,
+ "period_start": 1000000000,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "receipt_number": null,
+ "starting_balance": 0,
+ "statement_descriptor": "Zulip Standard",
+ "status": "paid",
+ "status_transitions": {
+ "finalized_at": 1000000000,
+ "marked_uncollectible_at": null,
+ "paid_at": 1000000000,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 10000,
+ "tax": null,
+ "tax_percent": null,
+ "total": 10000,
+ "total_discount_amounts": [],
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1000000000
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.1.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.1.json
new file mode 100644
index 0000000000..1457f1a436
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.1.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0003",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000001",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW6D2X8vgpBNGNRpvTb7X",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XEKpg4Z7fzV",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.2.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.2.json
new file mode 100644
index 0000000000..e33c0caff0
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.2.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0004",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000002",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YW8D2X8vgpBNGjQO5ySQC",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XcBc6MoFHgE",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.3.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.3.json
new file mode 100644
index 0000000000..a12ccf83b8
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.3.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0004",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000003",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWBD2X8vgpBNGk65s4Bby",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XXEj43ttifM",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.4.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.4.json
new file mode 100644
index 0000000000..9f7dba9443
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.4.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0005",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000004",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWED2X8vgpBNGJPqvl8ls",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XOrbeS8UGnX",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.5.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.5.json
new file mode 100644
index 0000000000..4c6adef06c
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.5.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0005",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000005",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWHD2X8vgpBNGwEA9hn4b",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XvpvSalZcLc",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.6.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.6.json
new file mode 100644
index 0000000000..becb8f0323
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.6.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0006",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000006",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWMD2X8vgpBNGGQAGsuaj",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0XRALl1QAQk5",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.7.json b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.7.json
new file mode 100644
index 0000000000..a666db3a9a
--- /dev/null
+++ b/corporate/tests/stripe_fixtures/downgrade_small_realms_behind_on_payments_as_needed--InvoiceItem.create.7.json
@@ -0,0 +1,44 @@
+{
+ "amount": 10000,
+ "currency": "usd",
+ "customer": "cus_NORMALIZED0006",
+ "date": 1000000000,
+ "description": "Zulip standard",
+ "discountable": false,
+ "discounts": [],
+ "id": "ii_NORMALIZED00000000000007",
+ "invoice": null,
+ "livemode": false,
+ "metadata": {},
+ "object": "invoiceitem",
+ "period": {
+ "end": 1000000000,
+ "start": 1000000000
+ },
+ "plan": null,
+ "price": {
+ "active": false,
+ "billing_scheme": "per_unit",
+ "created": 1000000000,
+ "currency": "usd",
+ "id": "price_1J5YWPD2X8vgpBNGdU5odKe9",
+ "livemode": false,
+ "lookup_key": null,
+ "metadata": {},
+ "nickname": null,
+ "object": "price",
+ "product": "prod_Jj0X7KLLErkxlq",
+ "recurring": null,
+ "tiers_mode": null,
+ "transform_quantity": null,
+ "type": "one_time",
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+ },
+ "proration": false,
+ "quantity": 1,
+ "subscription": null,
+ "tax_rates": [],
+ "unit_amount": 10000,
+ "unit_amount_decimal": "10000"
+}
diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py
index ec0a5251d8..456869ba35 100644
--- a/corporate/tests/test_stripe.py
+++ b/corporate/tests/test_stripe.py
@@ -1,12 +1,13 @@
import json
import operator
import os
+import random
import re
import sys
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from functools import wraps
-from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, TypeVar, cast
+from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, TypeVar, cast
from unittest.mock import Mock, patch
import orjson
@@ -19,6 +20,7 @@ from django.urls.resolvers import get_resolver
from django.utils.timezone import now as timezone_now
from corporate.lib.stripe import (
+ DEFAULT_INVOICE_DAYS_UNTIL_DUE,
MAX_INVOICED_LICENSES,
MIN_INVOICED_LICENSES,
BillingError,
@@ -31,6 +33,7 @@ from corporate.lib.stripe import (
compute_plan_parameters,
customer_has_credit_card_as_default_source,
do_create_stripe_customer,
+ downgrade_small_realms_behind_on_payments_as_needed,
get_discount_for_realm,
get_latest_seat_count,
get_price_per_license,
@@ -273,6 +276,7 @@ MOCKED_STRIPE_FUNCTION_NAMES = [
"Invoice.finalize_invoice",
"Invoice.list",
"Invoice.pay",
+ "Invoice.refresh",
"Invoice.upcoming",
"Invoice.void_invoice",
"InvoiceItem.create",
@@ -2696,6 +2700,135 @@ class StripeTest(StripeTestCase):
for invoice in invoices:
self.assertEqual(invoice.status, "void")
+ @mock_stripe()
+ def test_downgrade_small_realms_behind_on_payments_as_needed(self, *mock: Mock) -> None:
+ def create_realm(
+ users_to_create: int,
+ create_stripe_customer: bool,
+ create_plan: bool,
+ ) -> Tuple[Realm, Optional[Customer], Optional[CustomerPlan]]:
+ realm_string_id = "realm_" + str(random.randrange(1, 1000000))
+ realm = Realm.objects.create(string_id=realm_string_id)
+ users = []
+ for i in range(users_to_create):
+ user = UserProfile.objects.create(
+ delivery_email=f"user-{i}-{realm_string_id}@zulip.com",
+ email=f"user-{i}-{realm_string_id}@zulip.com",
+ realm=realm,
+ )
+ users.append(user)
+
+ customer = None
+ if create_stripe_customer:
+ customer = do_create_stripe_customer(users[0])
+ plan = None
+ if create_plan:
+ plan, _ = self.subscribe_realm_to_monthly_plan_on_manual_license_management(
+ realm, users_to_create, users_to_create
+ )
+ return realm, customer, plan
+
+ def create_invoices(customer: Customer, num_invoices: int) -> List[stripe.Invoice]:
+ invoices = []
+ assert customer.stripe_customer_id is not None
+ for _ in range(num_invoices):
+ stripe.InvoiceItem.create(
+ amount=10000,
+ currency="usd",
+ customer=customer.stripe_customer_id,
+ description="Zulip standard",
+ discountable=False,
+ )
+ invoice = stripe.Invoice.create(
+ auto_advance=True,
+ billing="send_invoice",
+ customer=customer.stripe_customer_id,
+ days_until_due=DEFAULT_INVOICE_DAYS_UNTIL_DUE,
+ statement_descriptor="Zulip Standard",
+ )
+ stripe.Invoice.finalize_invoice(invoice)
+ invoices.append(invoice)
+ return invoices
+
+ realm_1, _, _ = create_realm(
+ users_to_create=1, create_stripe_customer=True, create_plan=False
+ )
+
+ realm_2, _, plan_2 = create_realm(
+ users_to_create=1, create_stripe_customer=True, create_plan=True
+ )
+ assert plan_2
+
+ realm_3, customer_3, plan_3 = create_realm(
+ users_to_create=1, create_stripe_customer=True, create_plan=True
+ )
+ assert customer_3 and plan_3
+ create_invoices(customer_3, num_invoices=1)
+
+ realm_4, customer_4, plan_4 = create_realm(
+ users_to_create=3, create_stripe_customer=True, create_plan=True
+ )
+ assert customer_4 and plan_4
+ create_invoices(customer_4, num_invoices=2)
+
+ realm_5, customer_5, plan_5 = create_realm(
+ users_to_create=1, create_stripe_customer=True, create_plan=True
+ )
+ assert customer_5 and plan_5
+ realm_5_invoices = create_invoices(customer_5, num_invoices=2)
+ for invoice in realm_5_invoices:
+ stripe.Invoice.pay(invoice, paid_out_of_band=True)
+
+ realm_6, customer_6, plan_6 = create_realm(
+ users_to_create=20, create_stripe_customer=True, create_plan=True
+ )
+ assert customer_6 and plan_6
+ create_invoices(customer_6, num_invoices=2)
+
+ with patch("corporate.lib.stripe.void_all_open_invoices") as void_all_open_invoices_mock:
+ downgrade_small_realms_behind_on_payments_as_needed()
+
+ realm_1.refresh_from_db()
+ self.assertEqual(realm_1.plan_type, Realm.SELF_HOSTED)
+
+ realm_2.refresh_from_db()
+ self.assertEqual(realm_2.plan_type, Realm.STANDARD)
+ plan_2.refresh_from_db()
+ self.assertEqual(plan_2.status, CustomerPlan.ACTIVE)
+
+ realm_3.refresh_from_db()
+ self.assertEqual(realm_3.plan_type, Realm.STANDARD)
+ plan_3.refresh_from_db()
+ self.assertEqual(plan_3.status, CustomerPlan.ACTIVE)
+
+ realm_4.refresh_from_db()
+ self.assertEqual(realm_4.plan_type, Realm.LIMITED)
+ plan_4.refresh_from_db()
+ self.assertEqual(plan_4.status, CustomerPlan.ENDED)
+ void_all_open_invoices_mock.assert_called_once_with(realm_4)
+
+ realm_5.refresh_from_db()
+ self.assertEqual(realm_5.plan_type, Realm.STANDARD)
+ plan_5.refresh_from_db()
+ self.assertEqual(plan_5.status, CustomerPlan.ACTIVE)
+
+ realm_6.refresh_from_db()
+ self.assertEqual(realm_6.plan_type, Realm.STANDARD)
+ plan_6.refresh_from_db()
+ self.assertEqual(plan_6.status, CustomerPlan.ACTIVE)
+
+ from django.core.mail import outbox
+
+ self.assert_length(outbox, 1)
+ self.assertIn(
+ f"Your organization, http://{realm_4.string_id}.testserver, has been downgraded",
+ outbox[0].body,
+ )
+ self.assert_length(outbox[0].to, 1)
+ recipient = UserProfile.objects.get(email=outbox[0].to[0])
+ self.assertEqual(recipient.realm, realm_4)
+ self.assertTrue(recipient.is_billing_admin)
+
def test_update_billing_method_of_current_plan(self) -> None:
realm = get_realm("zulip")
customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345")
diff --git a/puppet/zulip/files/cron.d/downgrade-small-realms-behind-on-payments b/puppet/zulip/files/cron.d/downgrade-small-realms-behind-on-payments
new file mode 100644
index 0000000000..e0b3d126f3
--- /dev/null
+++ b/puppet/zulip/files/cron.d/downgrade-small-realms-behind-on-payments
@@ -0,0 +1,5 @@
+SHELL=/bin/bash
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+USER=zulip
+
+0 17 * * * zulip /home/zulip/deployments/current/manage.py downgrade_small_realms_behind_on_payments
diff --git a/puppet/zulip_ops/manifests/prod_app_frontend_once.pp b/puppet/zulip_ops/manifests/prod_app_frontend_once.pp
index 54a2a123fb..79e0c63027 100644
--- a/puppet/zulip_ops/manifests/prod_app_frontend_once.pp
+++ b/puppet/zulip_ops/manifests/prod_app_frontend_once.pp
@@ -17,6 +17,14 @@ class zulip_ops::prod_app_frontend_once {
source => 'puppet:///modules/zulip/cron.d/invoice-plans',
}
+ file { '/etc/cron.d/downgrade-small-realms-behind-on-payments':
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ source => 'puppet:///modules/zulip/cron.d/downgrade-small-realms-behind-on-payments',
+ }
+
file { '/etc/cron.d/check_send_receive_time':
ensure => file,
owner => 'root',
diff --git a/stubs/stripe/__init__.pyi b/stubs/stripe/__init__.pyi
index 5ef2d1b4a0..9561f78562 100644
--- a/stubs/stripe/__init__.pyi
+++ b/stubs/stripe/__init__.pyi
@@ -81,7 +81,7 @@ class Invoice:
...
@staticmethod
- def pay(invoice: Invoice) -> Invoice:
+ def pay(invoice: Invoice, paid_out_of_band: bool=False) -> Invoice:
...
@staticmethod
@@ -91,6 +91,10 @@ class Invoice:
def get(self, key: str) -> Any:
...
+ @staticmethod
+ def refresh(invoice: Invoice) -> Invoice:
+ ...
+
class Subscription:
created: int
status: str
diff --git a/templates/zerver/emails/realm_auto_downgraded.source.html b/templates/zerver/emails/realm_auto_downgraded.source.html
new file mode 100644
index 0000000000..34ca26b7e9
--- /dev/null
+++ b/templates/zerver/emails/realm_auto_downgraded.source.html
@@ -0,0 +1,25 @@
+{% extends "zerver/emails/compiled/email_base_default.html" %}
+
+{% block illustration %}
+
+{% endblock %}
+
+{% block content %}
+ {% trans organization_name_with_link=macros.link_tag(realm.uri, realm.string_id) %}
+ Your organization, {{ organization_name_with_link }}, has been downgraded to the Zulip Cloud
+ Free plan because of unpaid invoices. The unpaid invoices have been voided.
+ {% endtrans %}
+
+
+
+ {% trans upgrade_url=macros.link_tag(upgrade_url) %}
+ To continue on the Zulip Cloud Standard plan, please upgrade again by going to {{ upgrade_url }}.
+ {% endtrans %}
+
+
+
+
+ {% trans support_email=macros.email_tag(support_email) %}
+ If you think this was a mistake or need more details, please reach out to us at {{ support_email }}.
+ {% endtrans %}
+{% endblock %}
diff --git a/templates/zerver/emails/realm_auto_downgraded.subject.txt b/templates/zerver/emails/realm_auto_downgraded.subject.txt
new file mode 100644
index 0000000000..1aaae918c2
--- /dev/null
+++ b/templates/zerver/emails/realm_auto_downgraded.subject.txt
@@ -0,0 +1 @@
+{{ realm.string_id }}: Your organization has been downgraded to Zulip Cloud Free
diff --git a/templates/zerver/emails/realm_auto_downgraded.txt b/templates/zerver/emails/realm_auto_downgraded.txt
new file mode 100644
index 0000000000..2464239565
--- /dev/null
+++ b/templates/zerver/emails/realm_auto_downgraded.txt
@@ -0,0 +1,8 @@
+Your organization, {{ realm.uri }}, has been downgraded to the Zulip Cloud
+Free plan because of unpaid invoices. The unpaid invoices have been voided.
+
+To continue on the Zulip Cloud Standard plan, please upgrade again by going
+to {{ upgrade_url }}.
+
+If you think this was a mistake or need more details, please reach out
+to us at support@zulip.com.
diff --git a/version.py b/version.py
index a4614dea0e..8bde12df69 100644
--- a/version.py
+++ b/version.py
@@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 75
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
-PROVISION_VERSION = "150.5"
+PROVISION_VERSION = "150.6"
diff --git a/zerver/lib/send_email.py b/zerver/lib/send_email.py
index b4638f4c2f..5e7d020eb5 100644
--- a/zerver/lib/send_email.py
+++ b/zerver/lib/send_email.py
@@ -380,6 +380,24 @@ def send_email_to_admins(
)
+def send_email_to_billing_admins_and_realm_owners(
+ template_prefix: str,
+ realm: Realm,
+ from_name: Optional[str] = None,
+ from_address: Optional[str] = None,
+ language: Optional[str] = None,
+ context: Dict[str, Any] = {},
+) -> None:
+ send_email(
+ template_prefix,
+ to_user_ids=[user.id for user in realm.get_human_billing_admin_and_realm_owner_users()],
+ from_name=from_name,
+ from_address=from_address,
+ language=language,
+ context=context,
+ )
+
+
def clear_scheduled_invitation_emails(email: str) -> None:
"""Unlike most scheduled emails, invitation emails don't have an
existing user object to key off of, so we filter by address here."""
diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py
index 026a73c6c2..a046cdf2e1 100644
--- a/zerver/tests/test_management_commands.py
+++ b/zerver/tests/test_management_commands.py
@@ -483,6 +483,19 @@ class TestInvoicePlans(ZulipTestCase):
m.assert_called_once()
+@skipUnless(settings.ZILENCER_ENABLED, "requires zilencer")
+class TestDowngradeSmallRealmsBehindOnPayments(ZulipTestCase):
+ COMMAND_NAME = "downgrade_small_realms_behind_on_payments"
+
+ def test_if_command_calls_downgrade_small_realms_behind_on_payments_as_needed(self) -> None:
+ with patch(
+ "zilencer.management.commands.downgrade_small_realms_behind_on_payments.downgrade_small_realms_behind_on_payments_as_needed"
+ ) as m:
+ call_command(self.COMMAND_NAME)
+
+ m.assert_called_once()
+
+
class TestExport(ZulipTestCase):
COMMAND_NAME = "export"
diff --git a/zilencer/management/commands/downgrade_small_realms_behind_on_payments.py b/zilencer/management/commands/downgrade_small_realms_behind_on_payments.py
new file mode 100644
index 0000000000..5a9abf85d4
--- /dev/null
+++ b/zilencer/management/commands/downgrade_small_realms_behind_on_payments.py
@@ -0,0 +1,11 @@
+from typing import Any
+
+from corporate.lib.stripe import downgrade_small_realms_behind_on_payments_as_needed
+from zerver.lib.management import ZulipBaseCommand
+
+
+class Command(ZulipBaseCommand):
+ help = "Downgrade small realms that are running behind on payments"
+
+ def handle(self, *args: Any, **options: Any) -> None:
+ downgrade_small_realms_behind_on_payments_as_needed()