Skip to content
Share
Explore

Root Cause Analysis: Why payment_intent.succeeded No Longer Hits NEW_ORDER_URL

Timeline of Changes

1. Original state (pre-Feb 26, 2026)
payment_intent.succeeded had NO projectInvoiceId check
It simply forwarded ALL payment_intent.succeeded events to NEW_ORDER_URL (catalog new order)
This worked because checkout sessions for invoice payments didn’t put projectInvoiceId in PaymentIntent metadata — only in the checkout session metadata
2. ec214f0 — Feb 26, 2026 — “enhance Stripe checkout with autoPay logging and metadata”
This is the breaking commit. It added projectInvoiceId into payment_intent_data.metadata on the checkout session creation:
payment_intent_data: {
setup_future_usage: "off_session",
metadata: {
companyId,
projectInvoiceId, // ← NEW — now PaymentIntent carries this
from: "agencyhandy",
},
},
In the same commit, a new guard was added to payment_intent.succeeded:
if (projectInvoiceId) {
// call handle-single-payment
return;
}
if (isInvoicePayment) {
// ignore subscription renewals
return;
}
// only catalog events reach NEW_ORDER_URL
Intent was correct: for checkout-session-based invoice payments, payment_intent.succeeded should call handle-single-payment as a safety net alongside checkout.session.completed
3. c98217f — Feb 27, 2026 (next day) — “enhance Stripe checkout for subscription invoices”
Realized that both checkout.session.completed AND payment_intent.succeeded were calling handle-single-payment, causing duplicate processing
Fix: collapsed the two separate guards into one blanket skip:
if (projectInvoiceId || isInvoicePayment) {
// skip entirely — "handled by checkout.session.completed / invoice events"
return;
}
This fixed the duplicate problem for checkout-session flows
4. The new direct PaymentIntent flow (current state)
Frontend now confirms PaymentIntent directly (no checkout session)
The PaymentIntent still carries projectInvoiceId in metadata (from the ec214f0 change)
Event sequence: charge.succeededpayment_intent.succeeded
checkout.session.completed never fires (no checkout session was created)
But payment_intent.succeeded sees projectInvoiceId → hits the blanket skip → nothing processes the payment
Result: invoice not updated, project not live, no notifications

Summary for CTO/Team

Date
Commit
Author
What Changed
Side Effect
Feb 26
ec214f0
Hasan
Added projectInvoiceId to PaymentIntent metadata + added payment_intent.succeeded handler for invoice payments
Correct for checkout flows, but now ALL PaymentIntents with projectInvoiceId are intercepted
Feb 27
c98217f
Hasan
Merged the two guards into blanket skip to fix duplicate handle-single-payment calls
Fixed duplicates but now any payment_intent.succeeded with projectInvoiceId is silently dropped
Now
New direct-PaymentIntent flow skips NEW_ORDER_URL because projectInvoiceId is in metadata
Invoice/project/customer not updated
There are no rows in this table

Root Cause (one sentence)

The projectInvoiceId metadata was added to PaymentIntent for checkout-session tracing, and subsequently used as a blanket signal to skip payment_intent.succeeded processing — assuming checkout.session.completed always handles it. The new direct-PaymentIntent flow breaks that assumption because no checkout session exists.
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.