# Phase 07 — Card payment can never complete **Blocker:** blockers.md § "Payments" (the card-payment dead end). **Depends on:** nothing. --- **Root cause.** `MockPaymentProvider.cs:16-21` returns `RedirectUrl: https://mock-psp.local/pay/{reference}` — a host that doesn't exist. `client/.../checkout/page.tsx:137-140` does a **hard browser navigation** (`window.location.assign`) to that URL with no catch, so the user is stranded outside the SPA. The page that used to catch this redirect and bounce back into the app — `client/.../bookings/checkout/gateway/page.tsx` — was **deleted** in commit `64f6aa4` ("refinement phase 4") and never replaced. The exact same pattern still lives today for BNPL: `client/.../bookings/checkout/bnpl/gateway/page.tsx` (env-gated to `NODE_ENV=development` via `notFound()`, pay-success/pay-fail buttons, redirects into `ROUTES.CHECKOUT_BNPL_RETURN`). The target it should redirect to already exists and already works: `client/.../bookings/checkout/return/page.tsx` (`useConfirmGatewayReturn` + bounded backoff poll → success routes to `/checkout/confirmation`). Nothing about the money logic is broken — `ZarinPalPaymentProvider.cs` (the real provider) is fully implemented and simply unreachable pre-e-namad (blockers.md §B.5), which is a legal/business gate, not a code gap. ## The fix (two parts, both needed) 1. Restore `client/.../bookings/checkout/gateway/page.tsx`, mirroring the still-live BNPL harness pattern (env-gate with `notFound()` outside development), targeting `ROUTES.CHECKOUT_RETURN`. 2. Point `MockPaymentProvider.InitPaymentAsync`'s `RedirectUrl` at that harness (a relative or client-origin-absolute URL) instead of the dead `mock-psp.local` host. ## Flag, don't guess (latent, not blocking the mock fix) Once a real gateway is ever switched on, ZarinPal's callback redirects the browser with its **own** query params (`Authority`, `Status`) — nothing under `Controllers/V1` today translates that into what `checkout/return/page.tsx` expects (`request_id`/`transaction_id`/`outcome`). This needs a translating endpoint before the real path can work, separate from the mock-harness fix above.