You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dozens of DodoPayments subscription payments are stuck in status "Requires customer action" (3DS/SCA not completed). The same customers stack 4-5 duplicate attempts, and the overlay freezes on "Processing… / Payment Link Expires in 00:00." This is multi-factor: a mix of Dodo-overlay reality our integration got wrong and our own return/webhook handling being blind to the state.
How a 3DS payment dies (mechanism)
Card needs 3DS. Dodo's overlay runs the challenge via Hyperswitch inside the iframe; banks that block iframe rendering force a full-page redirect to the bank (the overlay iframe sandbox has no allow-top-navigation, so redirects bubble out via postMessage).
After the bank, Dodo returns the user to return_url = bare origin → the welcome marketing page (regression feat: serve welcome at root and dashboard at /dashboard #4307), which never reconciles the return → the user gets no success/failure signal.
So the user retries → a brand-new payment (no idempotency key; dedup guard can't see pre-webhook attempts) → the stacked duplicates on the dashboard.
Our webhook drops requires_customer_action/processing (records them as status:"processed" but persists no pending state) → we're blind: no pending-payment signal, so no duplicate-prevention, no reconciliation, no resume path → they rot at "requires_customer_action" forever. (Dedup is by webhookId, so later distinct transitions still arrive — the harm is the lost pending signal, not blocked transitions.)
What is NOT the cause (don't chase)
The backend checkout payload is correct: on-session (no confirm/payment_method_id, so Dodo does present the 3DS UI), correct product_cart/metadata, and there is no expiry param to set on checkoutSessions.create. The "00:00" timer is Dodo's showTimer on the session's server-side expires_at (24h default, since we don't set confirm=true) — a symptom, not the root cause.
Daily reconciliation cron: poll Dodo for payments stuck in requires_customer_action past a threshold → email a fresh checkout link / cancel. (convex/crons.ts has no payment reconciliation today.)
Four adversarial agents (backend checkout-creation params, frontend overlay/3DS, webhook status handling, Dodo docs research) + direct reads of SDK 1.8.0 (dodopayments-checkout/dist/index.esm.js) and an independent Codex review converged on the same multi-factor picture above.
Incident
Dozens of DodoPayments subscription payments are stuck in status "Requires customer action" (3DS/SCA not completed). The same customers stack 4-5 duplicate attempts, and the overlay freezes on "Processing… / Payment Link Expires in 00:00." This is multi-factor: a mix of Dodo-overlay reality our integration got wrong and our own return/webhook handling being blind to the state.
How a 3DS payment dies (mechanism)
allow-top-navigation, so redirects bubble out via postMessage).return_url= bare origin → the welcome marketing page (regression feat: serve welcome at root and dashboard at /dashboard #4307), which never reconciles the return → the user gets no success/failure signal.requires_customer_action/processing(records them asstatus:"processed"but persists no pending state) → we're blind: no pending-payment signal, so no duplicate-prevention, no reconciliation, no resume path → they rot at "requires_customer_action" forever. (Dedup is bywebhookId, so later distinct transitions still arrive — the harm is the lost pending signal, not blocked transitions.)What is NOT the cause (don't chase)
The backend checkout payload is correct: on-session (no
confirm/payment_method_id, so Dodo does present the 3DS UI), correctproduct_cart/metadata, and there is no expiry param to set oncheckoutSessions.create. The "00:00" timer is Dodo'sshowTimeron the session's server-sideexpires_at(24h default, since we don't setconfirm=true) — a symptom, not the root cause.Child issues
P0 — stops the bleeding:
confirmreturns 200 then no terminal status ever arrives — upstream of the redirect/return/webhook factors below. Fix =displayType:'redirect'.checkout.link_expiredhandler → frozen "Processing 00:00" overlayrequires_customer_action/processing→ loses pending-payment signalP1 — correctness + duplicates:
redirect_requestedtreated as success before 3DS authenticationrequires_customer_actiontimeoutP2 — follow-ups (flagged, not yet split out):
requires_customer_actionpast a threshold → email a fresh checkout link / cancel. (convex/crons.tshas no payment reconciliation today.)Strategic: evaluate→ promoted to P0 fix(checkout): P0 — overlay iframe hangs Dodo 3DS/fraud stack at 'Processing' (move to redirect mode) #4449 (HAR confirmed it's the primary user-facing hang, not a "later" item).displayType:'redirect'How this was diagnosed
Four adversarial agents (backend checkout-creation params, frontend overlay/3DS, webhook status handling, Dodo docs research) + direct reads of SDK 1.8.0 (
dodopayments-checkout/dist/index.esm.js) and an independent Codex review converged on the same multi-factor picture above.