corporate: Invoice self-hosted fixed-price plans with stale data.

Because license counts do not change the amount due for plans with
a fixed-price, we invoice them for self-hosted customers even when
the audit log data from the server is stale.
This commit is contained in:
Lauryn Menard 2025-08-19 18:52:36 +02:00 committed by Tim Abbott
parent d8a5105705
commit 208f1b30db
15 changed files with 24 additions and 5 deletions

View File

@ -5531,6 +5531,15 @@ def check_remote_server_audit_log_data(
next_invoice_date = plan.next_invoice_date
last_audit_log_update = remote_server.last_audit_log_update
if last_audit_log_update is None or next_invoice_date > last_audit_log_update:
# We still process fixed-price plans because the current license count
# won't change the amount due or the fact that we should downgrade the
# plan when it is scheduled to end.
if plan.fixed_price is not None:
# TODO: Possibly send an internal billing notice so that billing admin
# have a record that the customer's license ledger entries do not
# reflect actual audit log data from the remote server.
return True
if not plan.stale_audit_log_data_email_sent:
maybe_send_stale_audit_log_data_email(
plan, billing_session, next_invoice_date, last_audit_log_update

View File

@ -10017,16 +10017,26 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase):
]:
self.assert_in_response(substring, response)
# The last audit log update was 25 days before the plan's next
# invoice date, so invoicing the fixed-price plan fails, and an
# internal billing notice is sent.
# Since fixed-price plans do not depend on license counts for the
# amount due, we invoice them even if the audit log data is stale
# from the server.
last_audit_log_update = self.now + timedelta(days=5)
with time_machine.travel(last_audit_log_update, tick=False):
send_server_data_to_push_bouncer(consider_usage_statistics=False)
invoice_plans_as_needed(self.next_month)
current_plan.refresh_from_db()
self.assertEqual(current_plan.next_invoice_date, self.next_month)
self.assertTrue(current_plan.stale_audit_log_data_email_sent)
updated_invoice_date = self.next_month + timedelta(days=29)
self.assertEqual(current_plan.next_invoice_date, updated_invoice_date)
self.assertFalse(current_plan.stale_audit_log_data_email_sent)
# Audit log data is up-to-date when the plan is next invoiced.
with time_machine.travel(updated_invoice_date, tick=False):
send_server_data_to_push_bouncer(consider_usage_statistics=False)
invoice_plans_as_needed(updated_invoice_date)
current_plan.refresh_from_db()
final_invoice_date = updated_invoice_date + timedelta(days=31)
self.assertEqual(current_plan.next_invoice_date, final_invoice_date)
self.assertFalse(current_plan.stale_audit_log_data_email_sent)
@responses.activate
@mock_stripe()