Skip to content

fix(billing): sync Stripe invoice email and stop Bento dunning - #3059

Merged
riderx merged 10 commits into
mainfrom
fix/sync-stripe-billing-email
Aug 14, 2026
Merged

fix(billing): sync Stripe invoice email and stop Bento dunning#3059
riderx merged 10 commits into
mainfrom
fix/sync-stripe-billing-email

Conversation

@riderx

@riderx riderx commented Aug 14, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • When a Stripe customer email changes (customer.updated / customer.created), copy it onto orgs.management_email, then sync billing Bento tags
  • Failed payments now send org:failed_payment to billing contacts (management email, billing members, creator, live Stripe customer), not only management_email
  • After Stripe charge.succeeded, send custom Bento event org:charge_succeeded to those same contacts so Dunning can exit

Motivation (AI generated)

Customers can change the invoice email in Stripe, but Capgo kept tagging and emailing the old management_email. Separately, Bento Dunning listened to Stripe's $ChargeSucceeded, which only lands on the Stripe customer subscriber. People paid and kept getting dunning emails because the stop never reached the other billing addresses.

Business Impact (AI generated)

Invoice email changes stay in sync with Bento. Failed-payment sequences start and stop for every billing contact, so paying customers stop receiving dunning mail.

Test Plan (AI generated)

  • Unit tests for email extraction, normalization, same-address no-op, and previous_attributes.email gating
  • Unit tests for org:charge_succeeded event name and charge.succeeded extraction (including missing/expanded customer)
  • Point Bento Dunning exit at org:charge_succeeded (not Stripe $ChargeSucceeded)
  • Enable charge.succeeded on the Stripe webhook endpoint if it is not already there
  • After merge, re-enable customer email updates in the Stripe Customer Portal
  • Change a test customer's Stripe email and confirm orgs.management_email updates
  • Confirm a failed payment emails all billing contacts, then a successful charge stops the sequence for each of them

Generated with AI

Portal email edits never updated orgs.management_email, so cancel and
payment events kept going to the old address.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 15:40 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb8bfe03-5d14-4f6d-a104-92126bc50993

📥 Commits

Reviewing files that changed from the base of the PR and between 92c10ac and e40b38a.

📒 Files selected for processing (6)
  • supabase/functions/_backend/private/set_org_email.ts
  • supabase/functions/_backend/triggers/stripe_event.ts
  • supabase/functions/_backend/utils/stripe_event.ts
  • tests/set-org-email-bento.unit.test.ts
  • tests/stripe-billing-bento-tags.unit.test.ts
  • tests/stripe-customer-email-sync.unit.test.ts

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing fix/sync-stripe-billing-email (e40b38a) with main (ab3d20e)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@riderx
riderx marked this pull request as ready for review August 14, 2026 15:46
Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 15:57 — with GitHub Actions Active

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread supabase/functions/_backend/triggers/stripe_event.ts
Comment thread supabase/functions/_backend/triggers/stripe_event.ts
Comment thread supabase/functions/_backend/triggers/stripe_event.ts
Comment thread supabase/functions/_backend/utils/stripe_event.ts Outdated
Read the live customer email so stale webhooks cannot roll back
management_email, and fail the webhook with 500 so Stripe retries.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 16:02 — with GitHub Actions Active

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread supabase/functions/_backend/triggers/stripe_event.ts Outdated
Comment thread supabase/functions/_backend/triggers/stripe_event.ts Outdated
The Capgo org email form updated Stripe and the DB but left Bento
until a later webhook, so trial/plan tags lagged on the new address.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 16:23 — with GitHub Actions Active
Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 16:26 — with GitHub Actions Active
A retrieve error plus webhook fallback could write a stale invoice
address. Return 500 so Stripe retries instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 16:33 — with GitHub Actions Active
Bento exits dunning on Stripe $ChargeSucceeded, which only hits the
customer email. Send org:charge_succeeded to every org billing contact
so the sequence actually stops after they pay.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx marked this pull request as draft August 14, 2026 20:12
@riderx
riderx deployed to deepsec-pr August 14, 2026 20:12 — with GitHub Actions Active
@riderx riderx changed the title fix(billing): sync Stripe invoice email to Bento fix(billing): sync Stripe invoice email and stop Bento dunning Aug 14, 2026
@riderx
riderx marked this pull request as ready for review August 14, 2026 21:07

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread supabase/functions/_backend/triggers/stripe_event.ts Outdated
Comment thread supabase/functions/_backend/utils/stripe_event.ts Outdated
Comment thread supabase/functions/_backend/utils/stripe_event.ts
Org admins were getting failed-payment events. Missing
charge.customer became "null" and skipped the 400 path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 21:22 — with GitHub Actions Active
@riderx
riderx marked this pull request as draft August 14, 2026 21:23
@riderx
riderx marked this pull request as ready for review August 14, 2026 21:29

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 6 files

Confidence score: 3/5

  • In supabase/functions/_backend/triggers/stripe_event.ts, the customer.subscription.updated/replacement-subscription path can emit org:charge_succeeded without evidence of an actual successful payment, which could prematurely stop Bento dunning for still-unpaid accounts—gate this event on a verified recovery signal (e.g., a confirmed successful charge/invoice payment) before sending.
  • In supabase/functions/_backend/triggers/stripe_event.ts, recovery flows can currently send org:charge_succeeded twice (from both charge.succeeded and createdOrUpdated logic), which risks duplicate customer comms and noisy automation—deduplicate by enforcing a single source of truth or idempotency check for the stop-dunning event.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="supabase/functions/_backend/triggers/stripe_event.ts">

<violation number="1" location="supabase/functions/_backend/triggers/stripe_event.ts:173">
P2: When a failed-payment customer receives an unrelated `customer.subscription.updated` or replacement subscription event, this check emits `org:charge_succeeded` without proving payment recovery, so Bento can stop dunning prematurely. Gate the stop event on an actual successful-charge or otherwise explicit recovery signal rather than only the previous `failed` status.</violation>

<violation number="2" location="supabase/functions/_backend/triggers/stripe_event.ts:1194">
P2: On every failed/past-due recovery, `org:charge_succeeded` is sent to the same billing contacts twice: once from the new `charge.succeeded` webhook handler and again from the `createdOrUpdated` `shouldSendDunningStopEvent` block. Duplicate Bento tracking events and redundant live-Stripe/Bento calls per recovery. Keep one source as the canonical dunning exit (the charge.succeeded handler) unless charge.succeeded may be disabled, in which case gate the createdOrUpdated no-op so it does not re-send when the standalone handler already fired.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread supabase/functions/_backend/triggers/stripe_event.ts Outdated
Comment thread supabase/functions/_backend/triggers/stripe_event.ts Outdated
@riderx
riderx marked this pull request as draft August 14, 2026 21:47
Subscription updates were a second, unproven stop path
and doubled org:charge_succeeded with charge.succeeded.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 21:48 — with GitHub Actions Active
@riderx
riderx marked this pull request as ready for review August 14, 2026 21:55

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread supabase/functions/_backend/triggers/stripe_event.ts
@riderx
riderx marked this pull request as draft August 14, 2026 22:06
Soft-fail tag sync and fail-closed org writes used the
same retrieve path with duplicated catch blocks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@riderx
riderx deployed to deepsec-pr August 14, 2026 22:06 — with GitHub Actions Active
@sonarqubecloud

Copy link
Copy Markdown

@riderx
riderx marked this pull request as ready for review August 14, 2026 22:17

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 6 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@riderx
riderx merged commit 073ff73 into main Aug 14, 2026
110 of 111 checks passed
@riderx
riderx deleted the fix/sync-stripe-billing-email branch August 14, 2026 22:52
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.

2 participants