Skip to content

feat: Implement robust invoice-to-renewal matching#91

Merged
pwnage101 merged 1 commit intomainfrom
pwnage101/ENT-11538-implementation
Mar 17, 2026
Merged

feat: Implement robust invoice-to-renewal matching#91
pwnage101 merged 1 commit intomainfrom
pwnage101/ENT-11538-implementation

Conversation

@pwnage101
Copy link
Copy Markdown
Member

@pwnage101 pwnage101 commented Mar 10, 2026

Add stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal. Add invoice.created handler to link invoices to renewals via subscription ID and effective date. Update invoice.paid handler to look up renewals by stripe_invoice_id. Add backfill data migration for existing records.

ENT-11538

@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 699f523 to c1db6a7 Compare March 10, 2026 23:51
Copy link
Copy Markdown
Member

@iloveagent57 iloveagent57 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good! I like the clarity of the backfill migration.

if trial_summary:
try:
raw_data = trial_summary.stripe_event_data.data
period_end_ts = raw_data['data']['object']['lines']['data'][0]['period']['end']
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪆

@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch 3 times, most recently from c708c90 to 73c2402 Compare March 13, 2026 05:07
@pwnage101 pwnage101 marked this pull request as ready for review March 13, 2026 05:12
@pwnage101 pwnage101 requested review from a team as code owners March 13, 2026 05:12
Copilot AI review requested due to automatic review settings March 13, 2026 05:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements deterministic matching between Stripe invoices and SelfServiceSubscriptionRenewal records by storing invoice identifiers/effective dates, linking on invoice.created, and using that linkage to drive invoice.paid processing (including a backfill migration and expanded tests).

Changes:

  • Add stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal, plus migrations and a backfill data migration (with migration tests).
  • Add an invoice.created handler to link invoices to renewals; update invoice.paid paid-phase flow to look up renewals by stripe_invoice_id and raise on missing link to force Stripe retry.
  • Update provisioning renewal creation to persist effective_date, and update test factories/handler tests to cover the new flow.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
requirements/validation.txt Dependency lock updates; adds django-test-migrations and bumps multiple packages.
requirements/test.txt Dependency lock updates; adds django-test-migrations.
requirements/test.in Adds django-test-migrations to test inputs.
requirements/quality.txt Dependency lock updates; adds django-test-migrations.
requirements/production.txt Dependency lock updates (runtime deps).
requirements/pip.txt Updates pip/setuptools pins.
requirements/doc.txt Dependency lock updates for docs/test tooling.
requirements/django.txt Bumps pinned Django patch version.
requirements/dev.txt Dependency lock updates; adds django-test-migrations and bumps tooling.
requirements/common_constraints.txt Removes the pip<26.0 constraint and its explanatory comment.
requirements/base.txt Dependency lock updates (shared base deps).
enterprise_access/apps/provisioning/tests/test_models.py Updates provisioning test to assert persisted effective_date.
enterprise_access/apps/provisioning/models.py Persists effective_date onto renewal tracking records.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Extends invoice paid tests for stripe_invoice_id and adds comprehensive invoice.created handler coverage.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds django-test-migrations tests for the backfill migration.
enterprise_access/apps/customer_billing/tests/factories.py Expands Stripe invoice factory shape (incl. invoice.created) and updates renewal factory defaults.
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler; changes invoice.paid paid-phase logic to use stripe_invoice_id linkage and raise on missing.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding new renewal fields (incl. historical model).
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration to backfill stripe_invoice_id and effective_date for existing renewals.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py
Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py
Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 73c2402 to fcbfff9 Compare March 13, 2026 06:00
@pwnage101 pwnage101 requested a review from Copilot March 13, 2026 06:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements more deterministic Stripe invoice → renewal matching by persisting stripe_invoice_id and an effective_date on SelfServiceSubscriptionRenewal, adding an invoice.created handler to link invoices to renewals, and a data migration + tests to backfill/verify existing records.

Changes:

  • Add stripe_invoice_id + effective_date fields to renewal tracking and backfill them for existing data.
  • Add invoice.created handler to link invoices to renewals, and update invoice.paid processing to use the linkage.
  • Add/expand tests (including migration tests) and update timestamp-to-datetime utility.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
requirements/base.txt Dependency lock updates related to the Stripe/migration work.
requirements/common_constraints.txt Removes the pip<26.0 constraint.
requirements/dev.txt Dependency lock updates; adds test migration tooling dependencies.
requirements/doc.txt Dependency lock updates for docs environment.
requirements/django.txt Bumps Django pin.
requirements/pip.txt Bumps pip/setuptools pins.
requirements/production.txt Dependency lock updates for production environment.
requirements/quality.txt Dependency lock updates for quality tooling.
requirements/test.in Adds django-test-migrations to test inputs.
requirements/test.txt Dependency lock updates for test environment.
requirements/validation.txt Dependency lock updates; adds django-test-migrations and related tooling deps.
enterprise_access/apps/provisioning/models.py Stores renewal effective_date when creating/updating tracking records.
enterprise_access/apps/provisioning/tests/test_models.py Updates provisioning tests to assert effective_date is persisted.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id + effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/utils.py Makes datetime_from_timestamp return UTC-aware datetimes directly.
enterprise_access/apps/customer_billing/tests/test_utils.py Updates timezone assertion for the new UTC behavior.
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler and changes invoice.paid renewal lookup to use stripe_invoice_id.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Expands handler tests for invoice-linking and new invoice-paid behavior.
enterprise_access/apps/customer_billing/tests/factories.py Extends invoice factory payload to support invoice.created/period matching; updates renewal factory.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding the new fields.
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration backfilling invoice/effective-date fields.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds migration tests for the backfill behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/customer_billing/tests/test_utils.py Outdated
Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py Outdated
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from fcbfff9 to 92c721f Compare March 13, 2026 22:15
Copilot AI review requested due to automatic review settings March 16, 2026 19:59
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 308abf5 to 6839ec5 Compare March 16, 2026 20:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements more deterministic Stripe invoice → renewal matching for self-service subscription renewals by introducing stripe_invoice_id + effective_date, linking them during invoice.created, and using the link during invoice.paid, including a backfill migration and expanded test coverage.

Changes:

  • Add stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal and populate effective_date during provisioning.
  • Add invoice.created handler to link invoices to renewals; update invoice.paid to look renewals up by stripe_invoice_id.
  • Add a data backfill migration with dedicated migration tests; update/expand Stripe handler tests and factories.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
requirements/base.txt Dependency lock updates (incl. Django/Stripe/etc.).
requirements/common_constraints.txt Removes the pip<26.0 constraint block.
requirements/dev.txt Dependency lock updates (dev set).
requirements/django.txt Bumps pinned Django version.
requirements/doc.txt Dependency lock updates (docs set).
requirements/pip.txt Updates pinned pip/setuptools versions.
requirements/production.txt Dependency lock updates (prod set).
requirements/quality.txt Dependency lock updates (quality set).
requirements/test.in Adds django-test-migrations to test inputs.
requirements/test.txt Dependency lock updates (test set).
requirements/validation.txt Dependency lock updates (validation set).
enterprise_access/apps/provisioning/models.py Persists effective_date onto renewal tracking record during provisioning.
enterprise_access/apps/provisioning/tests/test_models.py Extends provisioning test to assert effective_date is stored.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id + effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/utils.py Simplifies timestamp→datetime conversion to explicit UTC-aware datetime.
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler + updates invoice.paid renewal lookup/processing flow.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding new renewal fields (incl. historical model).
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration to backfill stripe_invoice_id/effective_date for existing renewals.
enterprise_access/apps/customer_billing/tests/factories.py Extends invoice factory payload to support invoice.created (adds line-item period).
enterprise_access/apps/customer_billing/tests/test_utils.py Updates timezone assertion for UTC tzinfo string.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Adds/updates tests for invoice linking, retry behavior, and stripe_invoice_id usage.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds migration tests for the 0028 backfill using django-test-migrations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py
Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py Outdated
Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py Outdated
Comment thread enterprise_access/apps/customer_billing/tests/test_utils.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 98.18182% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 84.01%. Comparing base (e984275) to head (71b7aa8).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...ess/apps/customer_billing/stripe_event_handlers.py 97.95% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #91      +/-   ##
==========================================
+ Coverage   83.90%   84.01%   +0.11%     
==========================================
  Files         144      144              
  Lines       12202    12226      +24     
  Branches     1169     1170       +1     
==========================================
+ Hits        10238    10272      +34     
+ Misses       1631     1625       -6     
+ Partials      333      329       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 6839ec5 to 6e43826 Compare March 17, 2026 01:12
Copilot AI review requested due to automatic review settings March 17, 2026 05:49
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 6e43826 to 6b80b00 Compare March 17, 2026 05:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements deterministic linking between Stripe invoices and SelfServiceSubscriptionRenewal records to make invoice.paid handling robust (especially for out-of-order webhooks), and adds a data migration + tests to backfill existing renewals.

Changes:

  • Add stripe_invoice_id and effective_date to SelfServiceSubscriptionRenewal, populate them during provisioning, and backfill them for existing records.
  • Add an invoice.created Stripe webhook handler that links invoices to renewals (by subscription + effective date), and update invoice.paid handling to lookup renewals by stripe_invoice_id.
  • Update/expand tests (including migration tests via django-test-migrations) and bump assorted dependency pins.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
requirements/validation.txt Updates pinned tooling/runtime deps; adds django-test-migrations and other version bumps.
requirements/test.txt Updates pinned test deps; adds django-test-migrations.
requirements/test.in Adds django-test-migrations to test input requirements.
requirements/quality.txt Updates pinned quality/lint deps; adds django-test-migrations.
requirements/production.txt Updates pinned production deps (including Django, Stripe, Redis, etc.).
requirements/pip.txt Bumps pinned pip/setuptools versions used for tooling environments.
requirements/doc.txt Updates pinned docs deps and related tooling pins.
requirements/django.txt Bumps pinned Django patch version.
requirements/dev.txt Updates pinned dev deps; adds django-test-migrations and related tooling changes.
requirements/common_constraints.txt Removes the prior pip<26 constraint block.
requirements/base.txt Updates base runtime pins (Django patch, Stripe patch, etc.).
enterprise_access/apps/provisioning/tests/test_models.py Extends provisioning test to assert effective_date is set on renewal tracking records.
enterprise_access/apps/provisioning/models.py Writes effective_date onto SelfServiceSubscriptionRenewal during renewal tracking creation/update.
enterprise_access/apps/customer_billing/utils.py Fixes timestamp→datetime conversion to always yield UTC-aware datetimes (stdlib timezone.utc).
enterprise_access/apps/customer_billing/tests/test_utils.py Updates utility tests to assert UTC tzinfo semantics.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Updates constants/function naming; adds extensive coverage for invoice.created linking and new invoice.paid behavior.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds migration tests validating backfill behavior for stripe_invoice_id/effective_date.
enterprise_access/apps/customer_billing/tests/factories.py Updates invoice payload factories for invoice.created and new parent type constant; extends renewal factory defaults.
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler; broadens invoice persistence/validation; switches invoice.paid renewal lookup to stripe_invoice_id.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id + effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding the two new renewal fields (including historical model).
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration backfilling both fields from Stripe event data/summary records.
enterprise_access/apps/customer_billing/constants.py Renames the invoice line-item parent-type constant to SUBSCRIPTION_ITEM_TYPE.
Comments suppressed due to low confidence (1)

requirements/common_constraints.txt:22

  • This constraints file previously pinned pip<26 due to a documented pip-tools incompatibility, but that constraint has been removed while pip is bumped to 26.0.1 in this PR. Please confirm the current pip-tools/tooling stack fully supports pip 26 in CI/build images; otherwise reinstate a constraint (or bump pip-tools accordingly) to avoid breaking make upgrade / dependency compilation jobs.
# elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process.
# elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html
# See https://github.com/openedx/edx-platform/issues/35126 for more info
elasticsearch<7.14.0


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/customer_billing/stripe_event_handlers.py Outdated
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from 6b80b00 to bac1e0d Compare March 17, 2026 05:56
Copilot AI review requested due to automatic review settings March 17, 2026 06:03
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from bac1e0d to b380d32 Compare March 17, 2026 06:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements deterministic Stripe invoice → renewal linkage to make webhook handling resilient to out-of-order delivery (e.g., invoice.paid arriving before related renewal processing).

Changes:

  • Add stripe_invoice_id and effective_date to SelfServiceSubscriptionRenewal, and populate effective_date during provisioning.
  • Add an invoice.created Stripe webhook handler to link invoices to renewals (by subscription + effective date), and update invoice.paid logic to find renewals via stripe_invoice_id.
  • Add a backfill data migration (with migration tests) and refresh Python dependency pins (including adding django-test-migrations).

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated no comments.

Show a summary per file
File Description
requirements/base.txt Updates pinned runtime dependencies (incl. Stripe SDK bump).
requirements/common_constraints.txt Removes the pip<26.0 constraint and associated rationale block.
requirements/dev.txt Updates pinned dev dependencies; adds transitive deps used by tooling.
requirements/doc.txt Updates pinned doc dependencies.
requirements/django.txt Bumps pinned Django version.
requirements/pip.txt Updates pinned pip/setuptools versions used by tooling.
requirements/production.txt Updates pinned production dependencies.
requirements/quality.txt Updates pinned quality/linting dependencies.
requirements/test.in Adds django-test-migrations input dependency for migration tests.
requirements/test.txt Updates pinned test dependencies; includes django-test-migrations.
requirements/validation.txt Updates pinned validation dependencies.
enterprise_access/apps/provisioning/models.py Persists effective_date onto renewal tracking records during provisioning.
enterprise_access/apps/provisioning/tests/test_models.py Extends provisioning tests to assert renewal effective_date is saved.
enterprise_access/apps/customer_billing/constants.py Renames invoice line-item type constant to SUBSCRIPTION_ITEM_TYPE.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/utils.py Simplifies Unix timestamp → UTC datetime conversion (drops pytz usage in this helper).
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler; changes invoice.paid to look up renewals by stripe_invoice_id; broadens invoice-type guard.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding new renewal fields.
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration backfilling stripe_invoice_id / effective_date from Stripe event data.
enterprise_access/apps/customer_billing/tests/factories.py Updates factories to support invoice.created shape and new renewal fields.
enterprise_access/apps/customer_billing/tests/test_utils.py Updates tests for the new UTC conversion behavior.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Adds coverage for invoice.created handler and new invoice/renewal matching behavior.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds migration tests using django-test-migrations.
Comments suppressed due to low confidence (2)

enterprise_access/apps/customer_billing/stripe_event_handlers.py:431

  • persist_stripe_event can return None (e.g., if the event payload is missing subscription details), but the wrapper still calls handler_method(event) unconditionally. Several handlers call StripeEventData.objects.get(event_id=event.id) and will raise if the event wasn't persisted, turning a malformed/unexpected webhook into a 500. Consider short-circuiting when event_record is None (log + return) so handlers aren’t invoked without a corresponding StripeEventData row.
                if event.type in ('invoice.paid', 'invoice.created') and not _valid_invoice_event_type(event):
                    logger.warning(
                        f'[StripeEventHandler] event {event_short_repr} is not a valid invoice type'
                    )
                    return
                event_record = persist_stripe_event(event)
                handler_method(event)
                # Mark event as handled if we persisted it successfully and no exception was raised

enterprise_access/apps/customer_billing/stripe_event_handlers.py:482

  • send_payment_receipt_email.delay(...) is dispatched before _handle_invoice_paid_status_updated(...), but _handle_invoice_paid_status_updated can raise to force Stripe webhook retries when the renewal hasn’t been linked yet. On retries, this will enqueue multiple receipt emails for the same invoice unless downstream systems dedupe. Consider making send_payment_receipt_email idempotent per invoice_id (or moving the dispatch after successful renewal lookup/processing) to avoid duplicate notifications during retry storms.
        if invoice.total > 0:
            # Attempt to send the receipt FIRST before triggering renewal.
            # Renewal might want to force a retry by raising, risking duplicate receipt emails. We'll
            # mitigate this by configuring the Braze campaign to avoid sending more than 1 in a 3
            # day period.
            send_payment_receipt_email.delay(
                invoice_id=invoice.id,
                invoice_data=invoice,
                enterprise_customer_name=checkout_intent.enterprise_name,
                enterprise_slug=checkout_intent.enterprise_slug,
            )
            # only update status for non-trial invoice.paid events
            _handle_invoice_paid_status_updated(event, checkout_intent)
            return

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@iloveagent57 iloveagent57 self-assigned this Mar 17, 2026
return

# Immutability guard: don't overwrite an existing stripe_invoice_id
if renewal.stripe_invoice_id:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: log a warning if this doesn't match invoice['id']

@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from b380d32 to c8628cc Compare March 17, 2026 16:54
Add stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal.
Add invoice.created handler to link invoices to renewals via subscription ID and
effective date. Update invoice.paid handler to look up renewals by stripe_invoice_id,
raising DoesNotExist on miss to force Stripe retry for out-of-order delivery.
Add backfill data migration for existing records.

ENT-11538

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 17, 2026 18:00
@pwnage101 pwnage101 force-pushed the pwnage101/ENT-11538-implementation branch from c8628cc to 71b7aa8 Compare March 17, 2026 18:00
@pwnage101 pwnage101 enabled auto-merge March 17, 2026 18:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements more deterministic matching between Stripe invoices and SelfServiceSubscriptionRenewal records by caching stripe_invoice_id/effective_date, linking renewals on invoice.created, and updating invoice.paid handling to look renewals up by invoice ID (with a backfill migration and added tests).

Changes:

  • Add stripe_invoice_id and effective_date to SelfServiceSubscriptionRenewal + backfill via data migration.
  • Add invoice.created handler to link invoices to renewals (by subscription id + effective date), and update invoice.paid flow to use stripe_invoice_id.
  • Add/adjust unit tests and migration tests; update requirements (incl. django-test-migrations) and bump various pinned dependency versions.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated no comments.

Show a summary per file
File Description
requirements/base.txt Dependency pin updates supporting the broader dependency refresh.
requirements/common_constraints.txt Removes the pip<26.0 constraint/commentary.
requirements/dev.txt Dependency pin updates; adds django-test-migrations and other updated pins.
requirements/django.txt Bumps Django patch version pin.
requirements/doc.txt Dependency pin updates for docs environment.
requirements/pip.txt Updates pinned pip/setuptools versions.
requirements/production.txt Dependency pin updates for production environment.
requirements/quality.txt Dependency pin updates for quality tooling environment.
requirements/test.in Adds django-test-migrations input dependency for migration testing.
requirements/test.txt Dependency pin updates for test environment; includes django-test-migrations.
requirements/validation.txt Dependency pin updates for validation environment; includes django-test-migrations.
enterprise_access/apps/customer_billing/constants.py Renames invoice line-item parent type constant to SUBSCRIPTION_ITEM_TYPE.
enterprise_access/apps/customer_billing/models.py Adds stripe_invoice_id and effective_date fields to SelfServiceSubscriptionRenewal.
enterprise_access/apps/customer_billing/migrations/0027_add_renewal_invoice_and_effective_date.py Schema migration adding the new fields (including historical model).
enterprise_access/apps/customer_billing/migrations/0028_backfill_renewal_invoice_and_effective_date.py Data migration backfilling stripe_invoice_id and effective_date for existing renewals.
enterprise_access/apps/customer_billing/stripe_event_handlers.py Adds invoice.created handler; changes invoice event validation; updates invoice.paid renewal lookup to use stripe_invoice_id.
enterprise_access/apps/customer_billing/utils.py Simplifies timestamp→datetime conversion to always produce UTC-aware datetimes.
enterprise_access/apps/customer_billing/tests/factories.py Updates factories for new constant and new renewal fields; supports invoice.created.
enterprise_access/apps/customer_billing/tests/test_migrations.py Adds migration tests for the backfill using django-test-migrations.
enterprise_access/apps/customer_billing/tests/test_stripe_event_handlers.py Updates invoice-type validation tests; adds invoice.created tests; extends invoice.paid coverage for new behavior.
enterprise_access/apps/customer_billing/tests/test_utils.py Updates assertions to reflect UTC-aware datetime behavior without pytz.
enterprise_access/apps/provisioning/models.py Sets effective_date when creating renewal records based on trial plan expiration.
enterprise_access/apps/provisioning/tests/test_models.py Updates provisioning tests to assert effective_date is stored on renewal creation.
Comments suppressed due to low confidence (2)

enterprise_access/apps/customer_billing/stripe_event_handlers.py:434

  • persist_stripe_event() can return None (no related subscription found), but the wrapper still calls the handler unconditionally. Several handlers then call StripeEventData.objects.get(event_id=event.id) in link_event_data_to_checkout_intent, which will raise DoesNotExist if the event wasn't persisted. Consider short-circuiting (returning early) when event_record is None, or adjusting persist_stripe_event/link_event_data_to_checkout_intent so handlers can safely run without a persisted StripeEventData row.
                event_record = persist_stripe_event(event)
                handler_method(event)
                # Mark event as handled if we persisted it successfully and no exception was raised
                if event_record is not None:
                    event_record.refresh_from_db()
                    event_record.mark_as_handled()

enterprise_access/apps/customer_billing/stripe_event_handlers.py:481

  • invoice.paid sends the payment receipt email before _handle_invoice_paid_status_updated(). Since _handle_invoice_paid_status_updated() can raise to force a Stripe retry (e.g., out-of-order delivery), this can enqueue duplicate receipt emails on each retry. Consider only sending the receipt after successful renewal handling, or making send_payment_receipt_email idempotent (e.g., dedupe by invoice_id / StripeEventData.handled_at) so retries don’t spam admins.
        if invoice.total > 0:
            # Attempt to send the receipt FIRST before triggering renewal.
            # Renewal might want to force a retry by raising, risking duplicate receipt emails. We'll
            # mitigate this by configuring the Braze campaign to avoid sending more than 1 in a 3
            # day period.
            send_payment_receipt_email.delay(
                invoice_id=invoice.id,
                invoice_data=invoice,
                enterprise_customer_name=checkout_intent.enterprise_name,
                enterprise_slug=checkout_intent.enterprise_slug,
            )
            # only update status for non-trial invoice.paid events
            _handle_invoice_paid_status_updated(event, checkout_intent)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pwnage101 pwnage101 merged commit 2efc827 into main Mar 17, 2026
7 checks passed
@pwnage101 pwnage101 deleted the pwnage101/ENT-11538-implementation branch March 17, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants