frontend phase 10
This commit is contained in:
@@ -12,6 +12,45 @@ for awareness.
|
||||
- **Requests filed:** frontend/requests/for-backend.md (yes/no)
|
||||
-->
|
||||
|
||||
## frontend-phase-10-b11 — Cancellation & refund status (customer) — 2026-07-10
|
||||
- **Shipped:** the trust-first **exit** — a **new `services/refunds` domain** (types/constants/keys/
|
||||
invalidations/apis[client+mock]/3 hooks + barrel) and two customer screens: the **cancellation flow**
|
||||
`/bookings/[id]/cancel` (discloses the resolved policy tier + **refund % + fee %** + the Toman
|
||||
refund-vs-fee split + the multi-session refundable/locked breakdown **before** confirm, gated behind an
|
||||
explicit acknowledgement → `useCancelBooking` → refund status) and the read-only **refund status**
|
||||
`/bookings/[id]/refund_status` (3-step **submitted → on-its-way → completed** stepper, the honest BNPL
|
||||
**~7–10-business-day** ETA, `failed`=contact-support with **no retry**, empty state). The customer
|
||||
booking detail now hangs a **Cancel** CTA / **refund section** (`CustomerBookingActions`, reuses the
|
||||
cached booking query). Three shared tested composites: `CancellationPolicyDisclosure`, `RefundStatusCard`,
|
||||
`RefundEtaBanner`. New `refunds` i18n namespace (71 keys, both locales).
|
||||
- **Load-bearing rules honored:** refunds are **admin-approved — the customer never self-issues** (no
|
||||
issue/approve/retry control anywhere; failed = contact-support); the fee/refund % is **disclosed +
|
||||
acknowledged before confirm**; money = IRR digit-strings, **BigInt only** (integer parts-per-10000 —
|
||||
refund+fee and the fee-leg split reconcile to the rial, `PriceBreakdown` dev-guarded); **BNPL surfaced
|
||||
honestly** (the ~7–10-day window + `expected_customer_refund_eta`, money flows *through the provider*,
|
||||
never instant, never Balinyaar→customer) — and **suppressed on a failed refund** (no success-framed
|
||||
"money is on its way"); **per-session** — only un-started sessions refundable, completed-and-verified stay
|
||||
locked; labels are i18n keys off the codes, **never raw enum codes**; polling **only while non-terminal**;
|
||||
cancel invalidates booking detail/lists + **primes** the refund cache.
|
||||
- **Consumes:** dev/contracts/domains/refunds-invoices.md (b11 — `refunds/{id}/status` shape + enums;
|
||||
`refund_status`=requested|approved|processing|succeeded|failed|rejected, `refund_channel`=psp_card|
|
||||
bnpl_revert|manual, `expected_customer_refund_eta` is a date). **Not served by the contract (admin-only):**
|
||||
the customer cancel command, the pre-cancel policy preview + per-session flags, refund-by-booking, and the
|
||||
fee-leg decomposition on the customer status → REQ-019/020/021.
|
||||
- **Mocked client-side:** `services/refunds` via `refundsMockApi` (**USE_REFUNDS_MOCK=true, primary**) — it
|
||||
reads the shared f8 bookings store to resolve the tier by lead time + per-session refundability, flips the
|
||||
booking to `cancelled` (`mockMarkBookingCancelled` stamps the b9 snapshot), and drives card-immediate
|
||||
(`succeeded`) / BNPL-`processing`→`succeeded` refunds with a real ETA + a seeded `failed` refund (5004) and
|
||||
the outside-policy `409`. Added bookings-store seeds 5003 (mid-engagement mixed) + 5004 (cancelled) + two
|
||||
non-seam exports. Real `refundsClientApi` maps `refunds/{id}/status` 1:1 and targets the proposed slugs for
|
||||
the gaps; one flag flip when REQ-019/020/021 land.
|
||||
- **Reviewed:** 4-dimension adversarial review + per-finding verify — 3 confirmed findings, all fixed
|
||||
(failed-state ETA/amount suppression = BNPL-honesty; two missing `no_refund_*` i18n keys; a UTC/local
|
||||
`daysUntil` mismatch that flipped the tier in +offset timezones).
|
||||
- **Gate:** npm run check green · npm run test:ci green (**214 tests, +10**).
|
||||
- **Requests filed:** frontend/requests/for-backend.md — yes (REQ-019 cancel command, REQ-020 policy
|
||||
preview + per-session flags + canonical policy codes, REQ-021 refund-by-booking + customer decomposition).
|
||||
|
||||
## frontend-phase-9-b10 — Checkout, card payment & invoice — 2026-07-10
|
||||
- **Shipped:** the money moment — a **new `services/payment` domain** (types/keys/constants/
|
||||
apis[client+mock]/invalidations/5 hooks + barrel) and the customer checkout flow: **C6 خلاصه و پرداخت**
|
||||
|
||||
@@ -263,3 +263,63 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
|
||||
capture to demo the full flow), but on the real rails every fresh payment would land on that empty
|
||||
state.
|
||||
- **Status:** open
|
||||
|
||||
## REQ-019 — Customer-initiated booking cancellation command — filed by frontend-phase-10-b11 — 2026-07-10
|
||||
- **Need:** A **customer-facing** command to cancel a booking (post-payment) and open its refund, e.g.
|
||||
`POST api/v1/bookings/{bookingId}/cancel` (owner-scoped) with body
|
||||
`{ sessionIds?: long[], reasonCategory: string, reasonNotes?: string }` →
|
||||
the created refund summary (`{ id, bookingId, status, refundChannel, amount, expectedCustomerRefundEta,
|
||||
reference }`, i.e. the `refunds/{id}/status` shape). `sessionIds` omitted = cancel all un-started
|
||||
(remaining) sessions; completed-and-verified sessions stay payout-eligible.
|
||||
- **Why:** b11 shipped refunds **admin-only** (`POST admin_refunds`) — there is **no** customer path to
|
||||
request a cancellation, but f10's whole cancel flow is customer-initiated (the customer *requests*; an
|
||||
admin still *approves/processes* the money). The client mocks this behind the `services/refunds` seam:
|
||||
the mock flips the booking to `cancelled` (stamping the b9 cancellation snapshot), decomposes the refund
|
||||
across the two fee legs, and returns a card-immediate (`succeeded`) or BNPL-`processing` refund. The real
|
||||
`refundsClientApi.cancelBooking` already targets this slug.
|
||||
- **Proposed shape:** `POST api/v1/bookings/{bookingId}/cancel` body as above → `RefundStatusDto`. The
|
||||
server resolves the snapshotted policy, enforces the outside-policy/state rules (`409`), posts the
|
||||
balanced reversal, and (per the admin-only rule) may route the refund through an admin/ticket step — the
|
||||
customer surface just needs to *create* the cancellation request and read the resulting refund.
|
||||
- **Status:** open
|
||||
|
||||
## REQ-020 — Cancellation-policy preview (pre-cancel, per-session) — filed by frontend-phase-10-b11 — 2026-07-10
|
||||
- **Need:** A read that **resolves the applicable cancellation policy by current lead time** *before* the
|
||||
customer confirms, incl. the per-session refundability breakdown. Proposed
|
||||
`GET api/v1/bookings/{bookingId}/cancellation_policy` (owner-scoped) →
|
||||
`{ bookingId, cancellable, cancellationPolicyCode, refundPercentageApplied, feePercentage,
|
||||
refundAmountIrr, feeAmountIrr, refundableAmountIrr, platformFeeRefundedIrr, nursePayoutRefundedIrr,
|
||||
appliesTo, leadTimeLabel, refundChannel, expectedCustomerRefundEta,
|
||||
sessions: [{ bookingSessionId, sessionIndex, scheduledDate, refundable, reasonCode }] }` (IRR fields are
|
||||
digit-strings; `refundAmountIrr + feeAmountIrr = refundableAmountIrr`; the fee-leg split is served,
|
||||
never client-derived).
|
||||
- **Why:** The phase's load-bearing rule is **disclose the fee/refund % before confirm** — an
|
||||
outside-policy fee is never a surprise. b9 snapshots `cancellationPolicyCode`/`cancellationRefundPercentage`/
|
||||
`refundableAmountIrr` on the booking only *after* a cancel; there is no pre-cancel preview that resolves
|
||||
the tier by current lead time and enumerates which sessions are refundable (un-started) vs locked
|
||||
(completed-and-verified). The client mocks the whole preview behind the `services/refunds` seam; the tier
|
||||
**codes** (`free_24h` / `partial_under_24h` / `customer_no_show`) are client-invented placeholders (the
|
||||
product doc pins no wire codes) mapped to i18n keys — please define the canonical `cancellation_policy_code`
|
||||
set so the client maps the real codes.
|
||||
- **Proposed shape:** as above. The `cancellationPolicyCode` set + the per-session `reasonCode` set
|
||||
(`un_started` / the blocking session status) should be documented as stable enum codes → i18n keys.
|
||||
- **Status:** open
|
||||
|
||||
## REQ-021 — Customer refund lookup-by-booking + fee-leg decomposition on the customer status — filed by frontend-phase-10-b11 — 2026-07-10
|
||||
- **Need:** Two additions to the customer refund surface:
|
||||
1. **Reach a refund from its booking.** `GET api/v1/refunds/by_booking/{bookingId}` (owner-scoped) →
|
||||
the `refunds/{id}/status` shape, or `404` when the booking has no refund. Today the only customer read
|
||||
is `GET refunds/{id}/status` keyed by a **refund id** the customer can't obtain (the refund id lives on
|
||||
the admin-only `GET admin_refunds` worklist).
|
||||
2. **Expose the decomposition to the customer.** Add `platformFeeRefundedIrr`, `nursePayoutRefundedIrr`,
|
||||
`refundPercentageApplied`, `cancellationPolicyCode`, `createdAt`, `completedAt` to the customer
|
||||
`refunds/{id}/status` payload (they exist on the admin-only `RefundListItem`).
|
||||
- **Why:** f10's refund-status screen deep-links from a booking and renders (where the design calls for it)
|
||||
a **fee-leg transparency split** (Balinyaar-fee-refunded vs service-cost-refunded). Without (1) the
|
||||
customer can't find their refund; without (2) the split can't render on the real path (the client mock
|
||||
fills all six fields; the real `refundsClientApi` leaves them `null` and the split is hidden).
|
||||
- **Also (minor):** please confirm `provider_commission_reversed_amount` (the BNPL provider's own
|
||||
commission on a revert) is **nullable** on the refund shape and reconciled from the provider response —
|
||||
the b12 `IBnplProvider` mock echoes it as nullable and the client treats any provider-commission figure
|
||||
as opaque/never customer-facing.
|
||||
- **Status:** open
|
||||
|
||||
Reference in New Issue
Block a user