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
After a 3DS challenge that escapes the overlay as a full-page redirect, Dodo returns the customer to our return_url — but return_url is the bare origin, which vercel.json rewrites to the welcome marketing page, where no checkout-return reconciliation runs. The customer sees no success/failure, assumes it failed, and retries → duplicate stuck payments.
Evidence
src/services/checkout.ts:806 → returnUrl: window.location.origin (always scheme+host, no path).
vercel.json:12 → / on the apex/www/variant hosts rewrites to /pro/welcome.html.
handleCheckoutReturn() is called in exactly one place — src/app/panel-layout.ts (the dashboard). The welcome/root bundle never reconciles a Dodo return (?subscription_id/?status/payment_id).
Regression origin:feat: serve welcome at root and dashboard at /dashboard #4307 (2026-06-16) "serve welcome at root and dashboard at /dashboard" moved the dashboard off /. Before that, /was the dashboard, so return_url = origin reconciled correctly. This has been broken for ~10 days.
Why it hits 3DS hardest
Non-3DS happy paths usually resolve in-overlay via checkout.status=succeeded (handled on the dashboard before any navigation). 3DS is the path that actually exercises the full-page redirect to the bank and back — so the broken return URL lands those users on the marketing page.
Fix direction (small)
Send a return URL that hits a route which runs handleCheckoutReturn, e.g. https://worldmonitor.app/dashboard?wm_checkout=return (preserving Dodo's appended subscription_id/status/payment_id params), instead of the bare origin. Verify the dashboard route reconciles it.
Test first (per bug-fix protocol)
Add a failing test asserting the checkout returnUrl resolves to a path that handleCheckoutReturn services (not a path the SPA rewrites to /pro/welcome.html).
Part of the DodoPayments 3DS stuck-payments incident.
Summary
After a 3DS challenge that escapes the overlay as a full-page redirect, Dodo returns the customer to our
return_url— butreturn_urlis the bare origin, whichvercel.jsonrewrites to the welcome marketing page, where no checkout-return reconciliation runs. The customer sees no success/failure, assumes it failed, and retries → duplicate stuck payments.Evidence
src/services/checkout.ts:806→returnUrl: window.location.origin(always scheme+host, no path).vercel.json:12→/on the apex/www/variant hosts rewrites to/pro/welcome.html.handleCheckoutReturn()is called in exactly one place —src/app/panel-layout.ts(the dashboard). The welcome/root bundle never reconciles a Dodo return (?subscription_id/?status/payment_id)./. Before that,/was the dashboard, soreturn_url = originreconciled correctly. This has been broken for ~10 days.Why it hits 3DS hardest
Non-3DS happy paths usually resolve in-overlay via
checkout.status=succeeded(handled on the dashboard before any navigation). 3DS is the path that actually exercises the full-page redirect to the bank and back — so the broken return URL lands those users on the marketing page.Fix direction (small)
Send a return URL that hits a route which runs
handleCheckoutReturn, e.g.https://worldmonitor.app/dashboard?wm_checkout=return(preserving Dodo's appendedsubscription_id/status/payment_idparams), instead of the bare origin. Verify the dashboard route reconciles it.Test first (per bug-fix protocol)
Add a failing test asserting the checkout
returnUrlresolves to a path thathandleCheckoutReturnservices (not a path the SPA rewrites to/pro/welcome.html).Part of the DodoPayments 3DS stuck-payments incident.