332 lines
26 KiB
Markdown
332 lines
26 KiB
Markdown
# Frontend Phase 11 — BNPL checkout (installments)
|
||
|
||
> **Mission:** give the family an alternative to full-card payment at checkout — pay a booking **in
|
||
> installments** through a provider (دیجیپی / اسنپپی / اقساط بالینیار). Build the five BNPL screens
|
||
> from the wireframe (D1 method → D2 plan → D3 eligibility → D4 contract/schedule → D5 wallet status),
|
||
> wired to the b12 BNPL endpoints, styled in the **financial terracotta** language the design system
|
||
> reserves for money/installments. The load-bearing product truth you must encode in the UI: **the
|
||
> installment repayment is owned by the provider, not Balinyaar** — the provider pays Balinyaar the full
|
||
> amount up-front and bears 100% of customer-default risk, so Balinyaar *shows* the installment status
|
||
> it is told about; it does **not** run the schedule. D5 is provider-reported status, never a
|
||
> Balinyaar-managed ledger.
|
||
>
|
||
> **Track:** frontend · **Depends on:** [`frontend-phase-9-b10.md`](./frontend-phase-9-b10.md) (checkout
|
||
> & payment) + the **b12 BNPL contract** ([`dev/contracts/domains/bnpl.md`](../../contracts/domains/bnpl.md)) ·
|
||
> **Unlocks:** nothing downstream depends on it (BNPL is an alternate checkout branch)
|
||
> **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 **alternate-checkout branch** of the customer money path. By f9 a family can already see the
|
||
price breakdown (C6 خلاصه و پرداخت) and pay a confirmed booking by card. This phase adds the second exit
|
||
off C6: instead of paying the full amount on a card, the family chooses **اقساط (installments)** and is
|
||
taken through a provider's BNPL flow — pick a provider, pick a plan, pass a credit check, accept a
|
||
repayment schedule, pay the down-payment — after which the **booking confirms exactly as the card path
|
||
confirms it** (the provider has paid Balinyaar in full). The family then tracks repayment from the
|
||
**کیفپول (Wallet)** tab.
|
||
|
||
The single rule that shapes every screen: in Balinyaar's books a BNPL order is **identical to a card
|
||
payment that lands net-of-fee in one inbound settlement** ([product/business/09](../../../product/business/09-installments-bnpl.md)).
|
||
The customer's 4-installment (or 3/6/12-month) repayment is **decoupled** from Balinyaar's escrow/EVV/payout
|
||
cycle. So the Wallet screen (D5) renders a **provider-reported** balance and due-date list — not a ledger
|
||
Balinyaar owns or can settle. The copy must make that ownership clear without scaring the user.
|
||
|
||
**What already exists (do not rebuild) — from prior phases:**
|
||
- **Foundations** ([`frontend-phase-0.md`](./frontend-phase-0.md)): the three actor shells, the
|
||
**customer 5-tab bottom nav** (خانه · رزروها · بیماران · **کیفپول/Wallet** · پروفایل), the
|
||
`services/{domain}` + TanStack Query pattern (a `keys.ts` factory, `apis/clientApi.ts`,
|
||
one-hook-per-file, mutation-invalidates-cache), the **money/format util** (`formatIrrToToman`,
|
||
integer-safe IRR parse, Shamsi date display) in `src/utils/`, the shared composites (stepper/progress
|
||
header, status chip, price-breakdown), the i18n namespaces — including a reserved **`bnpl`** namespace
|
||
— and the RTL baseline. Reuse all of it; do not re-derive the data pattern.
|
||
- **Checkout & payment** ([`frontend-phase-9-b10.md`](./frontend-phase-9-b10.md)): the C6 summary-&-pay
|
||
screen with the commission/tax breakdown and the **escrow notice**, the card-payment redirect flow, the
|
||
confirmation screen, and the booking-confirmed state. This phase **adds a branch** to C6's payment-method
|
||
step and **reuses C6's confirmation/booking-confirmed UI** once the down-payment clears — it does not
|
||
build a second confirmation.
|
||
- **Refund & cancellation** ([`frontend-phase-10-b11.md`](./frontend-phase-10-b11.md)): the cancellation
|
||
flow and the customer refund-status view, including the **BNPL revert ETA** copy ("~7–10 business days,
|
||
provider-owned"). When a BNPL booking is cancelled, the refund status surface from f10 is what the user
|
||
sees — **do not** build a BNPL-specific refund screen here; that path belongs to f10. This phase owns only
|
||
the *forward* checkout (D1–D4) and the *status* view (D5).
|
||
|
||
> **Branch note:** BNPL is gated to bookings above a configurable threshold (e.g. total/duration) — a
|
||
> **config flag**, not a feature, per [scope notes](../README.md#scope-notes--deferrals). If the contract
|
||
> exposes an `bnplEligible` flag on the booking/checkout summary, hide the "اقساط" option when it's false;
|
||
> otherwise show it always and let D3 eligibility be the gate. Do not invent a client-side threshold rule.
|
||
|
||
## 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).
|
||
- [`client/CLAUDE.md`](../../../client/CLAUDE.md) — the engineering contract (RSC/client boundary, layouts,
|
||
i18n, theme, cookies, the fetch services, anti-patterns). You add a `services/bnpl` domain and screens
|
||
under the customer shell; nothing above `[locale]`.
|
||
- **Invoke the `frontend-designer` skill before any visual work.** It is the design/brand contract. For
|
||
this phase it carries the rule that matters most: **money/installment surfaces use the terracotta
|
||
financial accent** (`--bal-terracotta` `#d98c6a`, the wireframe's "installments/financial" legend
|
||
colour) — D1–D5 are terracotta-accented, distinct from the teal of the core booking flow. Ask it for the
|
||
installment-plan card, the eligibility-result panel, the repayment-schedule table, and the
|
||
outstanding-balance Wallet card.
|
||
- **Product truth:**
|
||
- [`product/business/09-installments-bnpl.md`](../../../product/business/09-installments-bnpl.md) — the
|
||
full-upfront / provider-bears-risk / decoupled-repayment model. This is *why* D5 is provider-reported.
|
||
- [`product/payments/bnpl-landscape.md`](../../../product/payments/bnpl-landscape.md) — the provider
|
||
comparison: SnappPay (4 interest-free), Digipay (3/6/12 + 4-installment), Torob Pay (25% down, 6.6%),
|
||
Balinyaar in-house plan. Use these for the **provider/plan copy and fee/down-payment shapes** D1/D2 show.
|
||
Note: settlement timing is **not instant** and commission is **per-contract** — never hardcode a fee in
|
||
the client; render whatever the contract returns.
|
||
- **Wireframe:** [`product/wireframes/index.html`](../../../product/wireframes/index.html), **Section D
|
||
(D1–D5)** — the exact screens, RTL Persian, terracotta financial accent. D1 روش پرداخت, D2 انتخاب طرح
|
||
اقساط, D3 اعتبارسنجی, D4 تایید طرح و قرارداد, D5 پیگیری اقساط (in Wallet). D5 carries the bottom tab nav
|
||
(Wallet active); D1–D4 are mid-flow (no tab bar).
|
||
- **Contract to consume:** [`dev/contracts/domains/bnpl.md`](../../contracts/domains/bnpl.md) (from b12) —
|
||
the request/response shapes, routes, the BNPL `status` enum codes, and the eligibility result shape. Plus
|
||
[`../../contracts/conventions/money-and-types.md`](../../contracts/conventions/money-and-types.md) (IRR-as-string,
|
||
Toman display, enums-as-codes, UTC + Shamsi) and [`api-conventions.md`](../../contracts/conventions/api-conventions.md)
|
||
(the envelope). **Types come from the contract — do not guess shapes.**
|
||
- The existing `src/services/payment/*` from f9 (the checkout service) and `src/services/auth/*` — the
|
||
exact service pattern (`types.ts` / `keys.ts` / `apis/clientApi.ts` / `hooks/use*.ts` / `index.ts`) your
|
||
new `services/bnpl` copies; and the C6 screen you branch from.
|
||
|
||
## 3. Scope — build this
|
||
|
||
A new **`services/bnpl`** domain + the five wireframe screens, all under the **customer** shell, all
|
||
terracotta-financial. Build the forward checkout (D1–D4) and the Wallet status view (D5).
|
||
|
||
### 3.1 `services/bnpl` (the data layer — copy the f0 pattern)
|
||
|
||
Under `src/services/bnpl/`:
|
||
- **`types.ts`** — derived from [`bnpl.md`](../../contracts/domains/bnpl.md). At minimum:
|
||
`BnplProvider` (`providerCode` e.g. `digipay` / `snapppay` / `balinyaar`, display name, supported plans),
|
||
`BnplPlanOption` (`termMonths` 3/6/12 or `installmentCount` 4, `feePercent`, `downPaymentPercent`,
|
||
`monthlyAmountIrr`, `totalIrr` — all IRR amounts as **string**), `BnplEligibilityResult`
|
||
(`eligibilityStatus`: `eligible` / `not_eligible` / `ceiling_exceeded`, `creditCeilingIrr`),
|
||
`BnplSchedule` (`downPaymentIrr`, `dueDate` + `amountIrr` per installment), `BnplTransaction`
|
||
(`status` state-machine code: `eligible` / `token_issued` / `verified` / `settled` / `reverted` /
|
||
`cancelled` / `failed`, `installmentCount`, `outstandingBalanceIrr`, `installments[]` with per-row
|
||
`status` and `dueDate`). **All money is the IRR-string type from the money-and-types contract.**
|
||
- **`keys.ts`** — a query-key factory: `bnplKeys.providers(bookingId)`,
|
||
`bnplKeys.eligibility(bookingId)`, `bnplKeys.transaction(bookingId)` / `bnplKeys.walletStatus()`.
|
||
- **`apis/clientApi.ts`** wrapping `clientFetch` — `getBnplOptions(bookingId)`,
|
||
`checkEligibility({ bookingId, nationalId, mobile, consent })`, `issueToken({ bookingId, providerCode,
|
||
planSelection })`, `acceptSchedule({ bookingId, ... })` / pay-down-payment, `getBnplTransaction(bookingId)`,
|
||
and `getWalletInstallments()`. Map these to the **b12 routes** from the contract
|
||
(`POST /checkout/bnpl/eligibility`, `POST /checkout/bnpl/token`, the provider-handoff/verify return, and
|
||
the read endpoints). If a route or shape is missing from the contract, see §4.
|
||
- **`hooks/`** — one hook per file: `useBnplOptions` (query), `useCheckEligibility` (mutation),
|
||
`useIssueBnplToken` (mutation), `useAcceptBnplSchedule` (mutation, **invalidates the booking + checkout
|
||
query** so the confirmed booking is not refetched stale), `useBnplTransaction` (query),
|
||
`useWalletInstallments` (query). Deliberate `staleTime` (eligibility/options are short-lived;
|
||
wallet-status is moderate). Don't toast 401/403/5xx — only domain 4xx (ineligible, ceiling-exceeded,
|
||
token-expired) get a message.
|
||
- **`index.ts`** barrel.
|
||
|
||
### 3.2 D1 · روش پرداخت (Payment method) — the branch off C6
|
||
|
||
The payment-method chooser the family reaches from **C6 (Summary & pay)**. Shows the **payable amount**
|
||
(reuse the f0 price-breakdown / money util — Toman display), then the method options:
|
||
- **پرداخت کامل با کارت (full card)** — selecting it returns to / continues the **f9 card flow** (do not
|
||
rebuild it).
|
||
- **Installment providers** (terracotta-accented option cards, each with provider branding/label and a
|
||
one-line plan summary): **دیجیپی** (3–12 installments), **اسنپپی** (۴ قسط بدون بهره / 4 interest-free),
|
||
**اقساط بالینیار** (in-house plan). Render the provider list **from `useBnplOptions`** — do not hardcode
|
||
the provider set or fees; the contract is the source. Primary action: "ادامه با {provider}".
|
||
|
||
States: options-loading (skeleton), loaded, empty/none-eligible (only card shown), error (retry / fall back
|
||
to card). If the booking is not BNPL-eligible (§1 branch note), the installment options are hidden and only
|
||
card shows.
|
||
|
||
### 3.3 D2 · انتخاب طرح اقساط (Choose plan)
|
||
|
||
For the chosen provider, the plan selector. Shows the **total amount** and the plan options the contract
|
||
returned — e.g. **۳ ماهه (بدون کارمزد)**, **۶ ماهه (کارمزد ۴٪)**, **۱۲ ماهه (کارمزد ۹٪)** — each rendering
|
||
its **monthly amount** and (where present) **down-payment %** (پیشپرداخت ۲۰٪). A single-select plan card
|
||
group (terracotta), plus a down-payment indicator. **Every amount comes through the money util from the
|
||
contract's IRR strings** — the client computes nothing about money beyond formatting; if the contract gives
|
||
per-plan `monthlyAmountIrr` use it, otherwise show only what the contract provides. Primary action: "ادامه".
|
||
States: loading, loaded, none (no plans for this provider → back to D1).
|
||
|
||
### 3.4 D3 · اعتبارسنجی (Credit eligibility)
|
||
|
||
The provider credit check. Fields: **کد ملی (national ID)**, **موبایل (mobile, prefilled from the session)**,
|
||
and a **consent checkbox** — "با استعلام اعتبارسنجی … موافقم" — which **gates** the submit (no consent →
|
||
disabled). On submit, call `useCheckEligibility`. Result panel:
|
||
- **approved** → "اعتبار شما تایید شد" + the returned **credit ceiling** (سقف اعتبار, money util). Action:
|
||
"تایید و ادامه" → D4.
|
||
- **not_eligible / ceiling_exceeded** → a clear declined panel with a **"پرداخت با کارت" fall-back** to the
|
||
f9 card flow. (Ceiling-exceeded copy: the booking total exceeds the available credit.)
|
||
- **error/timeout** → retry or fall back to card.
|
||
|
||
National-ID input validation is client-side format only (10 digits) — the real check is the provider's;
|
||
surface its result, don't pre-judge. Reuse the f0 phone-field for the mobile display.
|
||
|
||
### 3.5 D4 · تایید طرح و قرارداد (Schedule & contract)
|
||
|
||
The repayment schedule + contract acceptance. Renders the **repayment table** from the contract's schedule:
|
||
a **پیشپرداخت (down-payment) — today** row, then **قسط ۱…N** rows each with a **Shamsi due date** and an
|
||
**amount** (money util). A **terms/contract acceptance checkbox** that **gates** the final action. Primary
|
||
action: **"تایید نهایی و پرداخت پیشپرداخت"** → `useIssueBnplToken` / `useAcceptBnplSchedule`, which performs
|
||
the **provider handoff** (redirect or in-app token state — follow whatever the contract specifies, mirroring
|
||
f9's card redirect handling) and pays the down-payment. On success: **the booking confirms** — route to the
|
||
**f9 confirmation / booking-confirmed UI** (reused, not rebuilt), now reflecting "paid via installments".
|
||
States: schedule-loading, handoff/redirect in-progress (spinner + "در حال انتقال به {provider}"), success
|
||
(→ confirmation), declined/expired-token (→ retry or card).
|
||
|
||
The contract-acceptance copy must reflect the ownership truth: **the installment agreement is between the
|
||
customer and the provider** (provider-financed, provider bears risk); Balinyaar is the merchant being paid
|
||
in full. Get this exact copy from the frontend-designer skill / product docs, in both locales.
|
||
|
||
### 3.6 D5 · پیگیری اقساط (Installment status — in Wallet)
|
||
|
||
The Wallet-tab view of an active installment plan (bottom tab nav, **Wallet active**). It reads
|
||
`useWalletInstallments` and renders a **provider-reported** status:
|
||
- **Outstanding-balance card** (مانده بدهی, money util) — terracotta.
|
||
- **Next-installment** date + an **"پرداخت زودهنگام" (early pay)** affordance — but **early-pay is a
|
||
provider action, not a Balinyaar transaction**: link out / hand off to the provider; do **not** build a
|
||
Balinyaar payment for it.
|
||
- **Due-date list** — one row per installment with a **status chip** (reuse the f0 status chip):
|
||
پرداختشده (paid) / سررسید نزدیک (due soon) / آینده (future), each with a Shamsi due date and amount.
|
||
- A short **"وضعیت اقساط نزد {provider} ثبت میشود" / provider-owned** note so the user understands
|
||
Balinyaar is displaying, not managing, this schedule.
|
||
|
||
States: no-active-plan (empty Wallet installments section), loading, error (provider status unavailable →
|
||
"وضعیت اقساط در دسترس نیست"). If the f12 nurse-earnings Wallet content also lands here later, keep D5 a
|
||
self-contained section under the Wallet route.
|
||
|
||
### 3.7 i18n & tokens
|
||
Every user-visible string is a key in the **`bnpl`** namespace (seeded in f0) in **both** `messages/en.json`
|
||
and `messages/fa.json`, in sync, **`fa` default & RTL-first**. Provider names render from the contract but
|
||
their surrounding copy is i18n. Colours from `tokens.css` (the terracotta financial accent via the
|
||
designer's tokens) — never hardcoded hex in `sx`.
|
||
|
||
### Out of scope (DEFERRED — do not build here)
|
||
- **BNPL refund / revert UI** — the cancellation + refund-status surface (incl. the BNPL "~7–10 business
|
||
days, provider-owned" ETA) is **f10** ([`frontend-phase-10-b11.md`](./frontend-phase-10-b11.md)). Don't
|
||
duplicate it.
|
||
- **Admin BNPL revert/cancel console** — admin-side BNPL ops live in **f15**
|
||
([`frontend-phase-15-b15.md`](./frontend-phase-15-b15.md)).
|
||
- **Multiple-provider routing / tranched settlement** — b12 ships one provider mock; treat the provider
|
||
list as data, but don't build provider-comparison or multi-provider reconciliation UI.
|
||
- **Customer per-installment webhook / default handling** — there is none on Balinyaar's side; the provider
|
||
owns it (D5 is read-only status).
|
||
|
||
## 4. Mocks & seams in this phase
|
||
|
||
No new client seam family is *introduced* here — you **reuse the `services/{domain}` seam pattern from
|
||
[`frontend-phase-0.md`](./frontend-phase-0.md)**. The BNPL provider integration itself is mocked **on the
|
||
backend** behind `IBnplProvider` (b12) — the frontend just consumes the b12 endpoints.
|
||
|
||
- **If the b12 contract is published and live:** wire `services/bnpl/apis/clientApi.ts` to the real routes;
|
||
no client mock needed.
|
||
- **If b12 is not yet merged when you run:** build a **mock `clientApi`** behind the same `services/bnpl`
|
||
seam (per operating-rules §6) that returns deterministic shapes — a provider list, an always-`eligible`
|
||
eligibility result with a sample ceiling, a sample 6-month plan + schedule, and a sample D5 status with a
|
||
paid/due/future mix — so D1–D5 are fully exercisable. Record the mock in your report and as a row in
|
||
[`reports/mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md); it is swapped for
|
||
the real `clientApi` (one file) once b12 lands.
|
||
- **Contract gaps:** any shape the contract doesn't provide that D1–D5 need (e.g. per-plan `monthlyAmountIrr`,
|
||
the schedule rows, the D5 provider-reported `installments[]`, a `bnplEligible` flag on the booking) →
|
||
**append a request to**
|
||
[`dev/shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md)
|
||
and mock behind the seam meanwhile. Never edit backend files.
|
||
|
||
## 5. Critical rules you must not get wrong
|
||
|
||
- **The installment repayment is OWNED BY THE PROVIDER. Balinyaar shows status; it does not run the
|
||
schedule.** D5 renders **provider-reported** balance/due-dates/status — it is **not** a Balinyaar-managed
|
||
ledger and Balinyaar never settles a customer installment. "Early pay" hands off to the provider.
|
||
- **The provider pays Balinyaar the full amount up-front and bears 100% of customer-default risk** — the
|
||
contract-acceptance copy (D4) and the D5 ownership note must reflect that the installment agreement is
|
||
**customer ↔ provider**, interest-free-to-customer, provider-financed. Do not imply Balinyaar lends.
|
||
- **Money correctness (verbatim, the payments-track invariants that bind the client):** all internal money
|
||
is **IRR `BIGINT`, no floats anywhere** — the client receives IRR as **strings** and **must use the money
|
||
util** (`formatIrrToToman` / integer-safe parse) for every amount; never `Number()`-coerce an IRR string
|
||
or do arithmetic on money in the client. The split is **gross = commission + payout**; the **ledger is
|
||
append-only and balanced**; settlement is reconciled via **webhook idempotency**; payout is **one per
|
||
booking** and **dispute-window-gated**. The client never computes commission, fee, ceiling, or schedule
|
||
amounts — it **renders whatever the contract returns** (commission and BNPL fee are per-contract config,
|
||
never hardcoded in the UI).
|
||
- **A confirmed BNPL booking is, to Balinyaar, a card payment that landed net-of-fee** — after the
|
||
down-payment clears, **reuse the f9 booking-confirmed/confirmation UI**; do not build a parallel
|
||
confirmation, and do not show the customer a Balinyaar-side installment ledger.
|
||
- **Eligibility & consent are gates.** D3 submit is disabled without the consent checkbox; D4 final action is
|
||
disabled without contract acceptance. Decline / ceiling-exceeded must offer the **card fall-back** (f9),
|
||
never a dead end.
|
||
- **RSC/client boundary** — payment interactions are client components; don't pull `next-intl/server` or
|
||
server cookies into them. **Caching:** set `queryKey`/`staleTime` deliberately; the
|
||
accept-schedule mutation **invalidates the booking/checkout queries** so the confirmed booking isn't
|
||
refetched stale and the wallet status reflects the new plan.
|
||
- **RTL-first, `fa` default, both locales in sync; terracotta financial accent from tokens** (not teal, not
|
||
hardcoded hex); MUI **v9** primitives reused (no re-implemented Button/Card); shareable composites at the
|
||
shared level. Keep `npm run check` green throughout.
|
||
|
||
## 6. Definition of Done
|
||
|
||
The shared [definition-of-done.md](../_shared/definition-of-done.md), plus:
|
||
- [ ] `services/bnpl` exists following the f0 pattern (types from the b12 contract, `keys.ts`, `clientApi`,
|
||
one-hook-per-file, deliberate caching + mutation invalidation).
|
||
- [ ] **D1–D5** are built under the customer shell, terracotta-financial (frontend-designer-driven), with
|
||
all states (loading / loaded / empty / declined / error) handled; D5 lives under the **Wallet** tab.
|
||
- [ ] The **branch off C6** works: choosing اقساط leads into D1–D4 and, on a cleared down-payment, **routes
|
||
to the reused f9 confirmation with the booking confirmed**; the card fall-back is reachable from
|
||
decline/ceiling-exceeded/error.
|
||
- [ ] D5 is presented as **provider-reported status** with the ownership note; early-pay hands off to the
|
||
provider; no Balinyaar-side installment ledger is implied anywhere.
|
||
- [ ] Every money value renders through the money util; nothing about money is computed client-side; no
|
||
hardcoded fee/ceiling/provider set.
|
||
- [ ] `bnpl` strings in **both** `en.json`/`fa.json`, in sync, RTL-correct; colours from tokens.
|
||
- [ ] `npm run check` green; `npm run test:ci` green for any shared composite you add/touch (e.g. a
|
||
reusable installment-schedule row/card or plan-option card gets a co-located `*.test.tsx`).
|
||
- [ ] `client/CLAUDE.md` *Project Structure* updated if you add a route group/folder for the BNPL screens
|
||
or the `services/bnpl` domain.
|
||
|
||
## 7. How to test (what a human can verify after this phase)
|
||
|
||
Run `npm run dev`, sign in as a customer, reach a **confirmed** booking's checkout (C6) — using the f9
|
||
flow (or the seam mock if b12 isn't merged):
|
||
1. On **C6**, choose **اقساط** → **D1** shows the payable amount + provider options (دیجیپی / اسنپپی /
|
||
اقساط بالینیار) loaded from the contract/mock, terracotta-accented. Pick a provider → "ادامه با …".
|
||
2. **D2** shows the plan options (e.g. ۳/۶/۱۲ ماهه) with monthly amount + down-payment for the chosen
|
||
provider; select a plan → "ادامه". Verify amounts render in Toman via the money util.
|
||
3. **D3** — enter کد ملی, confirm the prefilled mobile, **tick consent** (submit stays disabled until you
|
||
do), submit → **mock approves** with a credit ceiling → "تایید و ادامه". Then verify the **declined**
|
||
path (mock a `not_eligible`/`ceiling_exceeded`) shows the panel **and the card fall-back**.
|
||
4. **D4** — the repayment table shows پیشپرداخت (today) + قسط rows with **Shamsi due dates** and amounts;
|
||
**accept the contract** (final action disabled until ticked) → "تایید نهایی و پرداخت پیشپرداخت" →
|
||
provider handoff → **the booking confirms** and you land on the **f9 confirmation** marked as paid via
|
||
installments.
|
||
5. Open the **کیفپول (Wallet)** tab → **D5** shows the **provider-reported** outstanding balance, next due
|
||
date, the per-installment due list with status chips (پرداختشده / سررسید نزدیک / آینده), the ownership
|
||
note, and an early-pay hand-off (not a Balinyaar payment).
|
||
6. Switch locale to `en` → all BNPL strings translate, layout flips correctly (RTL ⇄ LTR); dark mode intact.
|
||
7. `npm run check` and (if a shared composite changed) `npm run test:ci` pass.
|
||
|
||
This becomes the "what can be tested" section of your report.
|
||
|
||
## 8. Hand off & document (close the phase)
|
||
|
||
- **Docs:** update `client/CLAUDE.md` *Project Structure* for the BNPL route(s) and the `services/bnpl`
|
||
domain; if you establish a reusable installment-schedule/plan-option composite, note it. If you discover a
|
||
BNPL business-rule gap while building (e.g. the eligibility/ceiling copy, the threshold flag), record the
|
||
decision in [`product/business/09-installments-bnpl.md`](../../../product/business/09-installments-bnpl.md)
|
||
— don't invent rules; flag uncertain ones in your report.
|
||
- **Contract:** **consume** [`dev/contracts/domains/bnpl.md`](../../contracts/domains/bnpl.md) (b12) for all
|
||
types/routes — do **not** guess shapes. Any gap (per-plan monthly amount, schedule rows, D5
|
||
`installments[]`, a `bnplEligible` flag) goes to
|
||
[`dev/shared-working-context/frontend/requests/for-backend.md`](../../shared-working-context/frontend/requests/for-backend.md).
|
||
- **Handoff & report:** append your phase summary to
|
||
[`shared-working-context/frontend/STATUS.md`](../../shared-working-context/frontend/STATUS.md); write
|
||
`reports/frontend-phase-11-report.md` (the D1–D5 screens + `services/bnpl` built; **what is now testable
|
||
and exactly how** — the C6→D1→…→D4→confirm→D5 walkthrough; what is mocked behind the seam and how it
|
||
swaps to the real b12 `clientApi`; the contract consumed + any gaps filed; follow-ups for f15 admin BNPL).
|
||
Add/refresh the BNPL row in
|
||
[`reports/mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) if you mocked the
|
||
client seam.
|
||
- **Memory:** save a `project` memory note for the non-obvious decision that **D5 is provider-reported,
|
||
not a Balinyaar-managed ledger**, and that BNPL confirmation **reuses the f9 confirmation** — with a
|
||
`MEMORY.md` pointer, so a future agent doesn't build a Balinyaar installment ledger or a duplicate
|
||
confirmation screen.
|