feat: Implement robust invoice-to-renewal matching#91
Conversation
699f523 to
c1db6a7
Compare
iloveagent57
left a comment
There was a problem hiding this comment.
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'] |
c708c90 to
73c2402
Compare
There was a problem hiding this comment.
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_idandeffective_datefields toSelfServiceSubscriptionRenewal, plus migrations and a backfill data migration (with migration tests). - Add an
invoice.createdhandler to link invoices to renewals; updateinvoice.paidpaid-phase flow to look up renewals bystripe_invoice_idand 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.
73c2402 to
fcbfff9
Compare
There was a problem hiding this comment.
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_datefields to renewal tracking and backfill them for existing data. - Add
invoice.createdhandler to link invoices to renewals, and updateinvoice.paidprocessing 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.
fcbfff9 to
92c721f
Compare
308abf5 to
6839ec5
Compare
There was a problem hiding this comment.
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_idandeffective_datefields toSelfServiceSubscriptionRenewaland populateeffective_dateduring provisioning. - Add
invoice.createdhandler to link invoices to renewals; updateinvoice.paidto look renewals up bystripe_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.
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
6839ec5 to
6e43826
Compare
6e43826 to
6b80b00
Compare
There was a problem hiding this comment.
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_idandeffective_datetoSelfServiceSubscriptionRenewal, populate them during provisioning, and backfill them for existing records. - Add an
invoice.createdStripe webhook handler that links invoices to renewals (by subscription + effective date), and updateinvoice.paidhandling to lookup renewals bystripe_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<26due 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 breakingmake 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.
6b80b00 to
bac1e0d
Compare
bac1e0d to
b380d32
Compare
There was a problem hiding this comment.
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_idandeffective_datetoSelfServiceSubscriptionRenewal, and populateeffective_dateduring provisioning. - Add an
invoice.createdStripe webhook handler to link invoices to renewals (by subscription + effective date), and updateinvoice.paidlogic to find renewals viastripe_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_eventcan returnNone(e.g., if the event payload is missing subscription details), but the wrapper still callshandler_method(event)unconditionally. Several handlers callStripeEventData.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 whenevent_record is None(log + return) so handlers aren’t invoked without a correspondingStripeEventDatarow.
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_updatedcan 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 makingsend_payment_receipt_emailidempotent perinvoice_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.
| return | ||
|
|
||
| # Immutability guard: don't overwrite an existing stripe_invoice_id | ||
| if renewal.stripe_invoice_id: |
There was a problem hiding this comment.
nit: log a warning if this doesn't match invoice['id']
b380d32 to
c8628cc
Compare
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>
c8628cc to
71b7aa8
Compare
There was a problem hiding this comment.
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_idandeffective_datetoSelfServiceSubscriptionRenewal+ backfill via data migration. - Add
invoice.createdhandler to link invoices to renewals (by subscription id + effective date), and updateinvoice.paidflow to usestripe_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 returnNone(no related subscription found), but the wrapper still calls the handler unconditionally. Several handlers then callStripeEventData.objects.get(event_id=event.id)inlink_event_data_to_checkout_intent, which will raiseDoesNotExistif the event wasn't persisted. Consider short-circuiting (returning early) whenevent_record is None, or adjustingpersist_stripe_event/link_event_data_to_checkout_intentso 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.paidsends 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 makingsend_payment_receipt_emailidempotent (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.
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