# 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.
