add build development phases
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
# Frontend Phase 9 — Checkout, card payment & invoice
|
||||
|
||||
> **Mission:** turn an accepted booking request into paid, confirmed money on the rails. Build the
|
||||
> **C6 خلاصه و پرداخت** summary screen (the "✓ پرستار تایید کرد" badge, the reconciling
|
||||
> service-cost / commission / tax / total breakdown, and the load-bearing escrow trust notice), then
|
||||
> the **card payment** flow — initiate → mock gateway redirect → return → pending callback →
|
||||
> succeeded → booking flips to **confirmed** — followed by the **confirmation** screen and a
|
||||
> downloadable **invoice** with the VAT-on-commission line. This is the family's first real money
|
||||
> moment in Balinyaar; the breakdown must reconcile to the rial and the escrow copy must build trust,
|
||||
> because the whole anti-disintermediation thesis rests on payment happening *on-platform*.
|
||||
>
|
||||
> **Track:** frontend · **Depends on:** [`frontend-phase-8-b9.md`](./frontend-phase-8-b9.md) (booking
|
||||
> detail / sessions / EVV) + backend phase **b10** (Payments core — the contract you consume) ·
|
||||
> **Unlocks:** [`frontend-phase-10-b11.md`](./frontend-phase-10-b11.md) (refunds & cancellation),
|
||||
> [`frontend-phase-11-b12.md`](./frontend-phase-11-b12.md) (BNPL checkout)
|
||||
> **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional.
|
||||
|
||||
---
|
||||
|
||||
## 1. Context — where this sits
|
||||
|
||||
We are at the payment seam of the customer journey. By the end of **f7-b8** the family can send a
|
||||
booking request and a nurse can accept it; by the end of **f8-b9** there is a booking-detail screen,
|
||||
session list, and EVV. What's missing is the bridge the wireframe calls **C6**: once the nurse has
|
||||
accepted (`accepted_awaiting_payment`), the family sees the price, pays by card, and the
|
||||
`booking_request` converts into a money-bearing `booking` that reaches **confirmed**. Backend **b10**
|
||||
just shipped the money core (ledger, transactions, webhook idempotency, card capture → confirm →
|
||||
convert) behind the `IPaymentProvider`/`IWebhookVerifier` seams; this phase is its frontend
|
||||
counterpart. After this, **f10** can cancel/refund and **f11** can offer BNPL as an alternative to
|
||||
the full-card path you build here.
|
||||
|
||||
**What already exists (do not rebuild) — from prior phases:**
|
||||
|
||||
- **The foundation (f0)** — the three actor shells + route groups, the `services/{domain}` + TanStack
|
||||
Query caching pattern (copy the `auth` service shape), the contracts→types pattern, the **money/format
|
||||
util** in `src/utils/` (`formatIrrToToman`, integer-safe IRR-string parse, Shamsi date display) that
|
||||
**every** price in this phase renders through, the shared composite **price-breakdown** and
|
||||
**status-chip** components, the i18n namespaces (including `payment`), and the RTL baseline. See
|
||||
[`frontend-phase-0.md`](./frontend-phase-0.md).
|
||||
- **Auth & role routing (f1-b2)** — `AuthContext` with roles; the customer app shell + 5-tab bottom nav.
|
||||
- **Booking request flow (f7-b8)** — the request form (C4) and **awaiting-acceptance (C5)** status
|
||||
tracker. C6 is the *next* node after that tracker's "در انتظار تایید پرستار" step resolves to
|
||||
accepted; reuse C5's status-tracker component and the `booking`/`booking_requests` service shapes.
|
||||
- **Booking detail & sessions (f8-b9)** — the `services/booking` domain (detail/list queries, status
|
||||
timeline, the three-amount split already present on the booking payload), the booking status chip,
|
||||
and the booking-detail route this phase links *back to* after confirmation. **Reuse this service**;
|
||||
do not create a parallel booking service.
|
||||
|
||||
> **Out of scope here (DEFERRED):** the **BNPL** method/plan/eligibility/contract screens (D1–D5) — that
|
||||
> is [`frontend-phase-11-b12.md`](./frontend-phase-11-b12.md); this phase builds **only** the full-card
|
||||
> path and must leave a clean "یا پرداخت اقساطی" seam on C6/D1 for f11 to attach to. **Cancellation &
|
||||
> refund** (policy fee disclosure, refund status, BNPL ETA) is
|
||||
> [`frontend-phase-10-b11.md`](./frontend-phase-10-b11.md). The admin-side refund console is **f15**. The
|
||||
> مودیان (e-invoicing) registration state is a backend concern — surface it read-only if the contract
|
||||
> exposes it, never drive it.
|
||||
|
||||
## 2. Required reading (do this first)
|
||||
|
||||
- [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md) and
|
||||
[`../_shared/frontend-conventions-checklist.md`](../_shared/frontend-conventions-checklist.md) — how you
|
||||
work, the gate, the contract/handoff lanes.
|
||||
- [`../../../client/CLAUDE.md`](../../../client/CLAUDE.md) — the engineering contract (RSC/client boundary,
|
||||
`services/{domain}` + Query caching, the toast contract: do **not** toast 401/403/5xx in hooks, only
|
||||
domain 4xx; cookies/constants rules; MUI v9 only). Non-negotiable.
|
||||
- **Invoke the `frontend-designer` skill** — the design/brand contract (palette, the **terracotta**
|
||||
financial accent vs **teal** brand, tokens, typography, the `App*` library, the mobile RTL shell, the
|
||||
hard UI rules). **All visual work on C6, the redirect/pending/confirmation states, and the invoice goes
|
||||
through it.** Do not hand-style money screens off-token.
|
||||
- **The contract you consume:** [`../../contracts/domains/payments.md`](../../contracts/domains/payments.md)
|
||||
(produced by **b10**) — the request/response shapes, routes, status codes, and the **payment status
|
||||
enum** for initiate / verify / confirm / get-transaction. If the **invoice** shape lives in a separate
|
||||
doc, also read `../../contracts/domains/refunds-invoices.md` (from **b11**) — if that file is not yet
|
||||
published, mock the invoice behind the `services/payment` seam and file the gap (see §4 and §8).
|
||||
Always cross-check against the published `../../contracts/openapi/README.md` / `swagger.json` snapshot
|
||||
for exact casing — **derive types from the contract, never guess shapes.**
|
||||
- [`../../contracts/conventions/money-and-types.md`](../../contracts/conventions/money-and-types.md) — **money
|
||||
is IRR rials as a string of digits on the wire**; parse with the f0 integer-safe helper, format to Toman
|
||||
for display, never do float math; `gross_price_irr = balinyaar_commission_irr + nurse_payout_amount`;
|
||||
enums are stable string codes (map to i18n labels, never hardcode a display label off the code).
|
||||
- [`../../contracts/conventions/api-conventions.md`](../../contracts/conventions/api-conventions.md) — the
|
||||
`OperationResult`/`ApiResult` envelope (`clientFetch` already unwraps it), `snake_case` URL segments,
|
||||
status codes (`409` = idempotency/state-machine conflict — handle it as "already paid / in progress",
|
||||
not an error toast), and the **idempotency key** requirement on the money-path POST.
|
||||
- **Product truth (read before designing the breakdown/escrow copy):**
|
||||
- [`../../../product/business/08-payments-and-escrow.md`](../../../product/business/08-payments-and-escrow.md) —
|
||||
merchant-of-record, the three-amount split, **escrow is an internal ledger state, not held cash**,
|
||||
VAT applies to **commission only**. This is *why* the C6 copy reads the way it does.
|
||||
- [`../../../product/payments/index.md`](../../../product/payments/index.md) — the fintech overview;
|
||||
card → PSP → Shaparak → IBAN rails, the تسهیم split, the "PSP received ≠ cash in bank" timing reality
|
||||
that justifies a **pending-callback** UI state rather than assuming instant success.
|
||||
- [`../../../product/wireframes/index.html`](../../../product/wireframes/index.html) — **C6** is the exact
|
||||
screen you implement (the badge, the breakdown rows, the escrow notice, the "ادامه پرداخت ←" CTA);
|
||||
confirm the labels and RTL layout against it before building.
|
||||
- **Code to mirror:** `client/src/services/auth/*` (the `types.ts`/`keys.ts`/`apis/clientApi.ts`/
|
||||
`hooks/use*.ts`/`index.ts` shape every domain copies) and the f8 `services/booking` service; the f0
|
||||
**price-breakdown** and **status-chip** components; the f0 money util in `src/utils/`.
|
||||
|
||||
## 3. Scope — build this
|
||||
|
||||
Everything below lives under `client/` (the customer app shell). Build a `services/payment` domain, the
|
||||
hooks, the C6 + payment-state + confirmation + invoice screens, and the small composites they need —
|
||||
each visual surface produced via the **frontend-designer** skill, RTL-first, both locales.
|
||||
|
||||
### 3.1 `services/payment` domain (the data layer)
|
||||
|
||||
Mirror the `auth`/`booking` service shape exactly. Types come from
|
||||
[`payments.md`](../../contracts/domains/payments.md) — do not invent fields.
|
||||
|
||||
- **`types.ts`** — string-literal unions + DTOs derived from the contract. Expect (confirm exact names
|
||||
against the published swagger):
|
||||
- `PaymentTransactionStatus` = `initiated` | `pending` | `succeeded` | `failed` | `cancelled` (the
|
||||
contract's `payment_transactions.status` enum).
|
||||
- `BookingStatus` reused from `services/booking` — the value that flips `pending_payment` → `confirmed`.
|
||||
- `CheckoutSummary` — the C6 payload: `booking_id`/`booking_request_id`, `nurse` mini-info, the
|
||||
**three amounts** (`gross_price_irr`, `balinyaar_commission_irr`, `nurse_payout_amount` as IRR
|
||||
digit-strings), a derived/served **service cost** and **tax (VAT) line** (`vat_irr` +
|
||||
`vat_rate`), `total_irr` (= `gross_price_irr`), and the escrow flag/copy keys. If the breakdown
|
||||
rows aren't all served, compute display rows **only** from served amounts via the f0 integer-safe
|
||||
helper — never float-derive tax client-side; if a needed line is missing, request it (§8).
|
||||
- `InitiatePaymentRequest` (`booking_id` / `booking_request_id`, `gateway` selector, an
|
||||
**idempotency key**) and `InitiatePaymentResult` (`transaction_id`, `redirect_url`,
|
||||
`gateway_reference_code`).
|
||||
- `VerifyPaymentResult` / `PaymentTransaction` (status, `gateway_reference_code`, amount, the
|
||||
`booking_id` it confirmed).
|
||||
- `Invoice` — `invoice_number`, issued-at, the line items including the **VAT-on-commission** line,
|
||||
`download_url`/`pdf_url` if served (else a client-rendered receipt).
|
||||
- **`keys.ts`** — a key factory: `paymentKeys.all`, `paymentKeys.summary(bookingRequestId)`,
|
||||
`paymentKeys.transaction(transactionId)`, `paymentKeys.invoice(bookingId)`.
|
||||
- **`apis/clientApi.ts`** — a `PaymentClientApi` namespace wrapping `clientFetch` (one call per
|
||||
endpoint). Map to the b10 routes from the contract (illustrative `snake_case` slugs — use the
|
||||
published ones):
|
||||
- `getCheckoutSummary(bookingRequestId)` → `GET .../payment/get_checkout_summary`.
|
||||
- `initiatePayment(req)` → `POST .../payment/initiate_payment` (sends the **idempotency key**).
|
||||
- `verifyPayment({ gateway_reference_code, transaction_id })` → `POST .../payment/verify_payment`
|
||||
(the **server-side re-check** on gateway return — never trust the redirect query alone).
|
||||
- `getTransaction(transactionId)` → `GET .../payment/get_transaction` (the callback-poll target).
|
||||
- `getInvoice(bookingId)` → `GET .../payment/get_invoice` (or the refunds-invoices route).
|
||||
- **Only** add a `serverApi.ts` if an RSC needs to prefetch the summary; otherwise client-only.
|
||||
- **`hooks/` — one hook per file:**
|
||||
- `useCheckoutSummary(bookingRequestId)` — `useQuery`, `staleTime` short (prices can change before
|
||||
pay), keyed `paymentKeys.summary(...)`.
|
||||
- `useInitiatePayment()` — `useMutation`; **generates and reuses one idempotency key** for the
|
||||
attempt (stable across retries — see §5); on success returns the `redirect_url`.
|
||||
- `useVerifyPayment()` — `useMutation`; called on gateway return; on `succeeded`
|
||||
**`invalidateQueries`** for the booking detail/list (so the booking flips to confirmed without a
|
||||
refetch storm) and for the transaction key.
|
||||
- `usePaymentTransaction(transactionId, { enabled })` — `useQuery` for the **pending-callback poll**;
|
||||
use `refetchInterval` with **backoff** and **stop on terminal status** (`succeeded`/`failed`/
|
||||
`cancelled`) — see §5. Do **not** aggressive-poll.
|
||||
- `useInvoice(bookingId)` — `useQuery`, longer `staleTime` (an issued invoice is immutable).
|
||||
- `index.ts` re-exports the **hooks only** (per `client/CLAUDE.md` — no `types`/`keys`/`apis` in the
|
||||
barrel).
|
||||
|
||||
### 3.2 Screens & routes (under the customer shell, inside `[locale]/(private-routes)`)
|
||||
|
||||
Decide route segments that read cleanly under the existing role-scoped customer group (e.g. a
|
||||
`checkout` segment keyed by the booking-request id). No layout above `[locale]`; respect the RSC/client
|
||||
boundary.
|
||||
|
||||
- **C6 · خلاصه و پرداخت (Summary & pay)** — the checkout screen. Composes:
|
||||
- the **"✓ پرستار تایید کرد"** acceptance badge (reuse the f0 **status-chip**, success token),
|
||||
- the nurse mini-summary (name, service, schedule) pulled from the booking,
|
||||
- the **price breakdown** via the f0 **price-breakdown** composite — rows: **هزینه خدمت** (service
|
||||
cost, e.g. "۸ ساعت"), **کارمزد بالینیار** (platform commission), **مالیات (VAT)** (tax), and the
|
||||
bold **مبلغ کل** (total). Every amount rendered through the f0 money util; the visible rows must
|
||||
**reconcile to the total** (§5).
|
||||
- the **escrow notice** — a distinct trust callout (reuse `AppAlert`/an info surface, teal/info
|
||||
token, **not** an error color): the load-bearing copy
|
||||
**«مبلغ بهصورت امانی نزد بالینیار میماند و پس از پایان ویزیت آزاد میشود»** (i18n key in both
|
||||
locales). This copy is product-mandated trust UX — keep it verbatim in `fa`, with a faithful `en`
|
||||
translation.
|
||||
- the primary CTA **«ادامه پرداخت ←»** (drives `useInitiatePayment` → redirect), plus a disabled-state
|
||||
**«یا پرداخت اقساطی»** seam stub that f11 wires to D1 (render it as a clearly-deferred secondary,
|
||||
not a dead button — gate behind a `bnplEnabled` flag defaulting off).
|
||||
- **Card payment states** — a single payment-state surface (page or modal) driving the wireframe's
|
||||
documented checkout states **initiating → redirect-to-gateway → pending-callback → succeeded→confirmed
|
||||
→ failed/retry**:
|
||||
- **initiating** — CTA shows a spinner while `useInitiatePayment` runs.
|
||||
- **redirect** — on `redirect_url`, navigate to the **mock gateway** (the b10 mock returns a redirect
|
||||
URL; in dev this is a local mock-gateway page that immediately returns success — build a tiny
|
||||
**mock-gateway return page** under the checkout segment so the round-trip is real without a PSP).
|
||||
- **return / pending-callback** — on return, read `gateway_reference_code`/`transaction_id` from the
|
||||
query, fire `useVerifyPayment`, and if status is `pending` show a **"در حال تایید پرداخت…"** state
|
||||
backed by `usePaymentTransaction` polling with backoff until terminal.
|
||||
- **succeeded → confirmed** — on `succeeded`, invalidate booking queries and route to the confirmation
|
||||
screen.
|
||||
- **failed / retry** — on `failed`/`cancelled`, show a retry affordance (re-initiate generates a
|
||||
**new** idempotency key for the new attempt) and a "بازگشت به خلاصه" link.
|
||||
- **Confirmation screen** — success state: "پرداخت با موفقیت انجام شد", the booking now **confirmed**,
|
||||
a summary card, a **«مشاهده رزرو»** link back to the f8 booking-detail route, and a **«دانلود فاکتور»**
|
||||
link to the invoice.
|
||||
- **Invoice view / download** — renders `useInvoice(bookingId)`: header (`invoice_number`, issued
|
||||
Shamsi date), line items, and the **VAT line on the commission** explicitly shown (per product rule:
|
||||
VAT is on Balinyaar's commission, not the nurse's earnings). If the contract serves a `pdf_url`/
|
||||
`download_url`, the download button hits it; otherwise render a clean printable receipt
|
||||
(`window.print()` styled view) — no float math, every figure via the money util. Surface the
|
||||
مودیان state read-only (e.g. «در انتظار ثبت») **only if** the contract exposes it.
|
||||
|
||||
### 3.3 Shared composites (build/extend at the shared level)
|
||||
|
||||
- **`PriceBreakdown`** — if f0 stubbed it, finish it here as the shared composite (`src/components/…`)
|
||||
with a co-located `*.test.tsx`: props are typed display rows + a total; it formats nothing itself
|
||||
beyond receiving already-formatted strings or raw IRR + the money util — keep money formatting in one
|
||||
place. A test asserts the rows render and the total equals the sum of the served amounts.
|
||||
- **`EscrowNotice`** — a small shared callout composite wrapping `AppAlert` with the mandated copy key,
|
||||
so f10/f11 reuse the identical trust message. Co-located test asserts it renders the key.
|
||||
- **`PaymentStatusBadge`** — map `PaymentTransactionStatus` → an i18n label + the f0 status-chip
|
||||
variant. Co-located test for each status.
|
||||
- Keep page-only composition (the confirmation layout, the mock-gateway return page) in the page.
|
||||
|
||||
### 3.4 i18n
|
||||
|
||||
Add all C6 / payment-state / confirmation / invoice strings to the **`payment`** namespace in **both**
|
||||
`messages/en.json` and `messages/fa.json`, in sync, RTL-first. Status/enum codes map to label keys —
|
||||
never hardcode a label off a code. The escrow copy and the breakdown row labels (هزینه خدمت / کارمزد
|
||||
بالینیار / مالیات / مبلغ کل) are keys.
|
||||
|
||||
## 4. Mocks & seams in this phase
|
||||
|
||||
- **No new cross-cutting seam is owned here.** The PSP/gateway, تسهیم split, and webhook verification
|
||||
are **backend** seams (`IPaymentProvider`, `ISettlementSplitProvider`, `IWebhookVerifier`) introduced
|
||||
in **b10** — the frontend never talks to a PSP directly; it consumes the b10 contract. Reuse the
|
||||
`services/{domain}` mock-`clientApi` seam pattern from **f0**.
|
||||
- **The frontend-side mock (only if a shape is missing):** if `payments.md` (or `refunds-invoices.md`
|
||||
for the **invoice**) is not yet published when you build, implement a **mock `clientApi`** behind the
|
||||
`services/payment` seam — real-shaped responses (a `redirect_url` pointing at your local mock-gateway
|
||||
return page, a transaction that goes `initiated → pending → succeeded`, an invoice with a VAT line) —
|
||||
swap to the real `clientFetch` calls once the contract lands. **Record it** in your frontend report and
|
||||
the mock registry, and **append the missing shape** to
|
||||
`dev/shared-working-context/frontend/requests/for-backend.md` (operating-rules §6).
|
||||
- **The dev mock-gateway return page** is a *test harness*, not a product feature: it exists so the
|
||||
redirect→return round-trip is exercisable without a PSP. Keep it behind the checkout segment and note
|
||||
it as test-only in the report.
|
||||
|
||||
## 5. Critical rules you must not get wrong
|
||||
|
||||
- **Money is IRR `BIGINT`, no floats.** Every amount on the wire is an **IRR digit-string**; parse it
|
||||
with the f0 **integer-safe** helper and format to **Toman** for display through the f0 money util — do
|
||||
**no float math** anywhere on the money path, in the DB, in the API, or in the client.
|
||||
- **The breakdown must reconcile.** `gross = commission + payout`, and the **displayed rows must sum to
|
||||
the displayed total** (service cost + platform commission + tax = total) using integer-safe addition —
|
||||
never render a breakdown that doesn't add up, and never compute tax with a float rate client-side
|
||||
(show the served `vat_irr`; if absent, request it, don't derive it loosely).
|
||||
- **VAT is on the commission, not the nurse's earnings.** The invoice's VAT line is computed on
|
||||
Balinyaar's commission (the taxable supply) — label and place it accordingly; never imply the nurse is
|
||||
taxed.
|
||||
- **The escrow copy is load-bearing trust UX.** Render the mandated notice verbatim («مبلغ بهصورت امانی
|
||||
نزد بالینیار میماند و پس از پایان ویزیت آزاد میشود») as an info/trust callout, never an error tone,
|
||||
on C6 — it is *why* the family pays on-platform.
|
||||
- **Webhook/callback idempotency is the backend's guarantee — don't fight it.** Send **one stable
|
||||
idempotency key** per payment attempt and **reuse it across retries of the same attempt** (a new
|
||||
attempt gets a new key). A `409` on initiate/verify means "already in progress / already captured" —
|
||||
treat it as a benign state convergence (re-fetch the transaction and continue to confirmation), **not**
|
||||
an error toast. The server enforces one succeeded transaction per booking; the UI must never try to
|
||||
double-capture.
|
||||
- **Do not poll aggressively.** The pending-callback state uses `usePaymentTransaction` with
|
||||
`refetchInterval` **backoff** (e.g. start ~2s, grow, cap; bounded total attempts) and **stops on any
|
||||
terminal status**; never a tight loop. "PSP received ≠ cash in bank," so a pending state is normal —
|
||||
reflect it calmly, don't hammer the endpoint.
|
||||
- **Confirmation flips the booking, by cache invalidation.** On `succeeded`, `invalidateQueries` the
|
||||
`services/booking` detail/list keys so the booking shows **confirmed** — do not refetch everything and
|
||||
do not duplicate booking state in `services/payment`.
|
||||
- **Caching & re-renders.** Deliberate `queryKey`/`staleTime`; the summary is short-lived, an issued
|
||||
invoice is immutable (long `staleTime`). Use `select`/stable refs so the polling state doesn't re-render
|
||||
the whole breakdown.
|
||||
- **RTL + both locales + tokens.** `fa` default & RTL; every string in both locale files; colours from
|
||||
`tokens.css` (terracotta for financial accents, info/teal for the escrow callout — never hardcoded);
|
||||
MUI v9 API only; MUI primitives stay MUI, composites live shared.
|
||||
- **Boundary & fetch discipline.** Fetch only via `clientFetch` through `services/payment`; no raw
|
||||
`fetch()`; no toast for 401/403/5xx in hooks (only domain 4xx like a gateway-declined message).
|
||||
|
||||
## 6. Definition of Done
|
||||
|
||||
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
|
||||
|
||||
- [ ] **C6** renders the acceptance badge, the reconciling **service cost / commission / tax / total**
|
||||
breakdown (all via the f0 money util), and the verbatim **escrow notice**; the «ادامه پرداخت ←»
|
||||
CTA initiates payment.
|
||||
- [ ] The **card flow** drives all five documented states (initiating → redirect → pending-callback →
|
||||
succeeded→confirmed → failed/retry) end-to-end against the mock gateway; the **idempotency key** is
|
||||
stable per attempt; `409` converges instead of erroring.
|
||||
- [ ] On success the booking flips to **confirmed** via `invalidateQueries` (verified in React Query
|
||||
Devtools — no over-fetch), and the **confirmation** screen links back to booking detail and to the
|
||||
invoice.
|
||||
- [ ] The **invoice** view renders the line items with the **VAT-on-commission** line and downloads
|
||||
(served `pdf_url`) or prints a clean receipt; every figure via the money util, no float math.
|
||||
- [ ] The pending-callback poll uses **backoff** and **stops on terminal status** — no aggressive loop.
|
||||
- [ ] `PriceBreakdown`, `EscrowNotice`, `PaymentStatusBadge` are shared and each has a co-located
|
||||
`*.test.tsx`; the breakdown test asserts rows sum to total.
|
||||
- [ ] `payment` strings in **both** `en.json`/`fa.json`, in sync; RTL verified; colours from tokens.
|
||||
- [ ] `npm run check` green; `npm run test:ci` green (shared components touched); `client/CLAUDE.md`
|
||||
*Project Structure* updated for the new `services/payment` domain, checkout route segment, and new
|
||||
shared components.
|
||||
- [ ] Types derive from the published contract; any gap is appended to
|
||||
`dev/shared-working-context/frontend/requests/for-backend.md` and mocked behind the
|
||||
`services/payment` seam meanwhile (recorded in the report + mock registry).
|
||||
|
||||
## 7. How to test (what a human can verify after this phase)
|
||||
|
||||
Prereq: a booking request that the nurse has **accepted** (`accepted_awaiting_payment`) — create one via
|
||||
the f7 request flow + nurse accept, or seed it. Run `npm run dev`.
|
||||
|
||||
1. **Open C6** from the accepted request (the C5 tracker's "پرداخت و تایید نهایی" step) → the screen
|
||||
shows the **«✓ پرستار تایید کرد»** badge, the **breakdown** (هزینه خدمت / کارمزد بالینیار / مالیات /
|
||||
مبلغ کل) where the rows **sum to the total**, and the **escrow notice** in an info tone. Switch locale
|
||||
→ `dir` flips, all strings translate, amounts still format as Toman.
|
||||
2. **Pay (mock redirect)** → tap «ادامه پرداخت ←»: CTA spins (initiating) → you are redirected to the
|
||||
**mock gateway** → it returns to the checkout → **verify** runs; if pending you briefly see "در حال
|
||||
تایید پرداخت…" (poll with backoff) → it resolves **succeeded** → the **confirmation** screen appears.
|
||||
3. **Booking flips to confirmed** → follow «مشاهده رزرو» to the f8 booking detail: status is now
|
||||
**confirmed** (no full refetch — confirm via React Query Devtools that only the booking keys were
|
||||
invalidated).
|
||||
4. **Download the invoice** → from confirmation tap «دانلود فاکتور»: the invoice shows the
|
||||
`invoice_number`, line items, and the **VAT line on the commission**; download (or print) works; every
|
||||
figure matches C6 to the rial.
|
||||
5. **Idempotency / retry** → re-trigger pay on the same attempt (double-tap / refresh on return): no
|
||||
double-capture — a `409`/already-succeeded converges to the confirmation, not an error toast. A
|
||||
**new** attempt after a simulated failure issues a new idempotency key.
|
||||
6. **Gate** → `npm run check` and `npm run test:ci` pass; the `PriceBreakdown` test proves rows sum to
|
||||
total; the `PaymentStatusBadge` test covers each status.
|
||||
|
||||
## 8. Hand off & document (close the phase)
|
||||
|
||||
- **Docs:** update `client/CLAUDE.md` *Project Structure* for the new `services/payment` domain, the
|
||||
checkout route segment, and the new shared components (`PriceBreakdown` if promoted, `EscrowNotice`,
|
||||
`PaymentStatusBadge`). Note the `payment` i18n namespace usage. Don't reintroduce removed scaffolding.
|
||||
- **Contract consumed:** [`../../contracts/domains/payments.md`](../../contracts/domains/payments.md)
|
||||
(b10) for checkout-summary / initiate / verify / get-transaction, and the invoice part of
|
||||
`../../contracts/domains/refunds-invoices.md` (b11) **if available**. Types come from the published
|
||||
swagger — don't guess. **Append any gap** (missing breakdown line, missing `vat_irr`/`vat_rate`,
|
||||
missing `redirect_url`, missing invoice shape, مودیان state field) to
|
||||
`dev/shared-working-context/frontend/requests/for-backend.md`.
|
||||
- **Handoff & report:** append to `dev/shared-working-context/frontend/STATUS.md`; write
|
||||
`dev/shared-working-context/reports/frontend-phase-9-report.md` (what was built, **what is now testable
|
||||
and exactly how** — the steps in §7, what is mocked client-side (the gateway return harness / any
|
||||
unmet shape) and how it swaps to real, contracts consumed, follow-ups for f10/f11). Update
|
||||
`dev/shared-working-context/reports/mocks-registry.md` for the `services/payment` mock `clientApi` and
|
||||
the dev mock-gateway page (seam, what's faked, config, how to make it real once the contract lands).
|
||||
- **Memory:** save a `project` memory note for the checkout state machine (initiating → redirect →
|
||||
pending-callback → succeeded → failed/retry), the **idempotency-key-per-attempt** rule, the
|
||||
**backoff poll, no aggressive loop** decision, and the **escrow-copy-is-verbatim-trust-UX** constraint,
|
||||
with a one-line `MEMORY.md` pointer.
|
||||
Reference in New Issue
Block a user