Files
baya-monorepo/dev/shared-working-context/reports/frontend-phase-11-report.md
T
2026-07-10 13:55:42 +03:30

8.4 KiB
Raw Blame History

Frontend Phase 11 — BNPL installment checkout (D1D5) — report

Track: frontend · Depends on: f9 (checkout & payment) + the b12 BNPL contract · Date: 2026-07-10 Gate: npm run check green · npm run test:ci green (223 tests, +9) · npm run build green with NEXT_PUBLIC_API_URL set (the build's missing-.env prerender failure is pre-existing/environmental and affects unrelated pages).

What was built

The second exit off C6: instead of paying the full card amount, the family chooses اقساط and is taken through a provider BNPL flow (pick provider → pick plan → credit check → accept schedule → pay down-payment), after which the booking confirms exactly as the card path (the provider paid Balinyaar in full). Repayment is then tracked from the کیف‌پول (Wallet) tab.

services/bnpl (new domain, f0 pattern)

  • types.ts — the b12 enums (BnplStatus/BnplEligibilityStatus/ProviderCode), BnplProvider + BnplPlanOption (per-plan served monthly/down-payment/total IRR strings), BnplEligibilityResult, BnplSchedule + BnplInstallmentRow, BnplOrderStatus, WalletInstallmentPlan (D5 provider-reported), the BnplApi seam, and helpers (isTerminalBnplStatus, isBnplSettlementSuccess, installmentStatusKind). All money is the IRR digit-string type.
  • keys.tsbnplKeys.options/eligibility/schedule/order/walletInstallments.
  • apis/clientApi.ts (real, maps b12 eligibility/initiate/order-by-id 1:1; options/schedule/ wallet/by-request-order are REQ-022/024 gaps on proposed slugs), mockApi.ts (PRIMARY), selecting index.ts (USE_BNPL_MOCK).
  • hooks/useBnplOptions/useCheckEligibility/useBnplSchedule/useIssueBnplToken/ useAcceptBnplSchedule(invalidates booking+checkout+wallet)/useBnplOrder(bounded backoff poll)/ useWalletInstallments. invalidations.ts reuses the f9 invalidateAfterPaymentSuccess + the wallet key.

Screens (customer shell, terracotta financial accent = --bal-secondary)

  • D1 روش پرداخت (checkout/bnpl/page.tsx + MethodStep.tsx) — payable amount + full-card option (returns to the f9 card flow) + provider option cards from useBnplOptions (never hardcoded) + an ownership disclosure at the point of choice. A stateful wizard (StepperHeader) drives D1→D4.
  • D2 انتخاب طرح (PlanStep.tsx) — single-select BnplPlanCard group (term/fee/served monthly/down-payment).
  • D3 اعتبارسنجی (EligibilityStep.tsx) — کد ملی (10-digit client format) + prefilled موبایل (session) + consent-gated submit → useCheckEligibility → approved (credit ceiling) / declined+ceiling-exceeded (declined panel + card fall-back).
  • D4 تایید طرح و قرارداد (ScheduleStep.tsx) — served repayment table (InstallmentScheduleRow) + the ownership-truth note + a contract-acceptance-gated final action → useIssueBnplToken handoff.
  • Provider handoff (bnpl/gateway/page.tsx harness → bnpl/return/page.tsx) — settle (down-payment cleared) → invalidate → the reused f9 confirmation (?method=bnpl adds «پرداخت‌شده با اقساط»).
  • D5 پیگیری اقساط (wallet/page.tsxWalletInstallments.tsx) — provider-reported outstanding balance (terracotta card), next-installment + early-pay provider hand-off (opens the provider URL, not a Balinyaar payment), per-installment due list with status chips, and the provider-owned ownership note.

Shared composites (tested)

  • BnplPlanCard — D2 plan option (terracotta selectable).
  • InstallmentScheduleRow — one repayment row, reused by D4 schedule + D5 wallet due list.

Wiring / changes

  • C6 (checkout/page.tsx): the «پرداخت اقساطی» button is enabled (BNPL_ENABLED = true) and navigates into the wizard with ?request_id=.
  • The f9 confirmation renders the installments line on ?method=bnpl (no parallel confirmation built).
  • New ROUTES.CHECKOUT_BNPL[_GATEWAY|_RETURN], the installments AppIcon, and the bnpl i18n namespace (87 keys, both locales in sync, RTL-first, ICU-number params for Persian digits).

What is now testable, and exactly how

Run npm run dev, sign in as a customer, reach a confirmed booking's checkout (C6) via the f9 flow (the seam mock closes the loop):

  1. C6 → اقساط → D1: payable amount + دیجی‌پی/اسنپ‌پی/اقساط بالین‌یار (terracotta). Pick → «ادامه با …».
  2. D2: plans (۳/۶/۱۲ ماهه or ۴ قسط) with monthly + down-payment; amounts render in Toman via the money util.
  3. D3: enter کد ملی, tick consent (submit stays disabled until consent) → mock approves with a ceiling. Declined path: a national ID ending in 0not_eligible (declined panel + card fall-back); an order above the ceiling → ceiling_exceeded.
  4. D4: پیش‌پرداخت (امروز) + قسط rows with Shamsi due dates; accept the contract (final action disabled until ticked) → handoff harness → the booking confirms, landing on the f9 confirmation marked اقساطی.
  5. کیف‌پول (Wallet) → D5: provider-reported outstanding balance + next due + per-installment due list (پرداخت‌شده / سررسید نزدیک / آینده), the ownership note, and an early-pay hand-off (opens the provider).
  6. Switch to en → all BNPL strings translate, RTL⇄LTR flips; dark mode intact.

What is mocked / waiting on a real service

services/bnpl is mock-primary (USE_BNPL_MOCK = true) — see the BnplApi row in mocks-registry.md. The mock reads the frozen request gross from the f7 store, plays the provider (eligibility verdict, plan math, token/redirect), and on settle reuses the f9 conversion bridge (mockInsertConvertedBooking + mockMarkBookingRequestConverted) — a settled BNPL order is a card payment net-of-fee — then seeds a provider-reported Wallet plan. All money is served IRR digit-strings (the mock computes plan/schedule with integer BigInt math; components only format). Swap is a one-line flip once the upstream is real and the REQs land.

Contracts consumed + gaps filed

Consumed dev/contracts/domains/bnpl.md (b12) for all types/routes. Filed to for-backend.md:

  • REQ-022 — provider/plan options (D1/D2) + repayment schedule (D4); the contract serves neither and explicitly does not model the repayment schedule. Also: add balinyaar to the provider_code enum.
  • REQ-023 — BNPL eligibility should accept the D3 KYC inputs (national ID / mobile / consent).
  • REQ-024 — provider-reported Wallet installment status (D5), a customer-readable bookingId on the settled order, and a by-request order lookup for the return poll (GET checkout_bnpl/{id} is order-id-keyed).

Adversarial review (multi-agent workflow)

Ran a 6-dimension review (money · ownership · contract · reuse/caching · gates/flow · i18n/RTL/theme) with an adversarial verify pass. Of the raised findings, 5 were confirmed and all fixed; the rest were refuted (mock-allowed, non-reachable, or f9-parity conventions):

  1. Return-page window-expired branch had a mislabeled CTA + wrong copy → now reuses the f9 window_expired_* + back_to_request keys (label matches the destination).
  2. Real getBnplOrder keyed by request id vs the contract's order-id path → the settle-on-return now reads the order by its own id (contract-correct); the by-request poll is honestly flagged as a REQ-024 gap.
  3. Return-page order poll fired one needless fetch on immediate success → gated on the late-settle case only.
  4. Dead i18n keys (col_*) deleted; the strongest ownership disclosure (ownership_note) is now rendered at D1.
  5. D1 provider-glyph text was near-illegible in dark mode → switched to the --bal-secondary-dark text token.

Follow-ups for later phases

  • f15 admin BNPL — the admin revert/cancel console (b12 admin_bnpl/*) is out of scope here.
  • BNPL refund/revert — the customer cancellation + BNPL revert ETA surface is f10 (not duplicated here).
  • When REQ-022/023/024 land, flip USE_BNPL_MOCK = false (one line); if f12 nurse-earnings Wallet content lands, it sits beside the self-contained D5 section under /wallet.