Found 2026-09-04 on the deploy preview of #1422 (Razorpay Test Mode E2E, flow D).
POST /api/checkout on the WALLET rail correctly confirms synchronously: Payment SUCCEEDED with a WALLET leg for the full amount, BillingAccount.walletBalance debited, Appointment APPROVED, and the response carries paymentIntent.id = org_wallet_<ts>_<rand> with no gateway order. The checkout page then calls razorpay.open() with that id anyway; Razorpay's preferences call answers 400 and the page shows a native alert "Oops! Something went wrong. Payment Failed." over a booking that already succeeded. A member or org admin will believe the booking failed and try again.
Fix: in the shared post-checkout handler of the four checkout pages (app/checkout/plans/{consultation,subscription,webinar,class}/[planId]/page.tsx, or the shared helper in app/checkout/plans/utils.ts), branch on the response before opening any gateway: when the payment is already SUCCEEDED (WALLET / LICENSE / zero-amount / mock) route straight to the success screen, the same way the mock and zero-amount paths are already special-cased. Evidence rows: Payment 7be6621a-…, org cmqb27k4606fvtxyol12naydf.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
Found 2026-09-04 on the deploy preview of #1422 (Razorpay Test Mode E2E, flow D).
POST /api/checkout on the WALLET rail correctly confirms synchronously: Payment SUCCEEDED with a WALLET leg for the full amount, BillingAccount.walletBalance debited, Appointment APPROVED, and the response carries paymentIntent.id =
org_wallet_<ts>_<rand>with no gateway order. The checkout page then callsrazorpay.open()with that id anyway; Razorpay's preferences call answers 400 and the page shows a native alert "Oops! Something went wrong. Payment Failed." over a booking that already succeeded. A member or org admin will believe the booking failed and try again.Fix: in the shared post-checkout handler of the four checkout pages (app/checkout/plans/{consultation,subscription,webinar,class}/[planId]/page.tsx, or the shared helper in app/checkout/plans/utils.ts), branch on the response before opening any gateway: when the payment is already SUCCEEDED (WALLET / LICENSE / zero-amount / mock) route straight to the success screen, the same way the mock and zero-amount paths are already special-cased. Evidence rows: Payment 7be6621a-…, org cmqb27k4606fvtxyol12naydf.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7