Story Index: customer-portal (CP) — V0 Envelope¶
Module: customer-portal
Total stories: 8 (V0 envelope only — v0.0.0 → v0.1.0)
Decomposed from: docs/architecture/customer-portal-arch.md (§0 V0 deltas + §11 Vertical Slice Decomposition) — approved by Emmanuel BLONVIA 05/23/2026
PRD reference: docs/prd/customer-portal-prd.md (V1 contract + FSD) + docs/prd/prd-v0.md (V0 envelope)
Design reference: docs/design/customer-portal-design.md (DESIGNER, 28/05/2026) — canonical screens CP-S1…CP-S11, §8 micro-copy, §9 components, §10 compliance UI. Stories were reconciled against it on the date below.
Wireframes: docs/wireframes/customer-portal/CP-001/ exists (CP-S1 prototype.tsx + README.md). CP-002/CP-003 prototypes pending per design §12.
Date: 29/05/2026
Stories are authored in simple English for the Claude DEVELOPER personality; they will be translated to French for human developers via the DeepL
/translatepipeline later.
⚠ Notes flagged for the human PO¶
- Design doc reconciled (was previously absent).
docs/design/customer-portal-design.mdlanded on main after the first decomposition pass; the stories were re-diffed against it and patched (BrokerHeader/StickyPayBar/NetworkBanner/StepIndicator component names + file paths, exact §8 French micro-copy, the CP-S2 droit-de-rétractation block placement, the CP-S3 initiation loading treatment, the CP-S4 receipt banner structure, the CP-S5/S6 pending + mapped-reason copy incl. theINDISPONIBLEtypo fix and theRETOUR_INCOHÉRENTretry-suppression rule). CP-001 now points to its existing prototype. - ⚠ Design-vs-V0-scope divergence — kept V0 scope, flagged here (human PO decision). The design doc (§2 / §11) assigns the full CP-S7 "Lien remplacé", CP-S8 "Déjà réglé" and CP-S9 "Mentions légales" page to V0.0.0–V0.0.2, and is even internally inconsistent (§2 says CP-S7 = V0.0.2, §11 says CP-S7 = V0.0.0). The locked PO scope keeps those full screens in V1 / CP-006 (CP-10 / CP-12 / CP-13). Per the directive not to re-debate V0 scope, the scope is unchanged: in V0 only the mentions-légales footer link + content-version read ship; the full CP-S7/S8/S9 screens remain V1. When a link is revoked/expired mid-V0, CP defers to AUTH (hard reload → AUTH-owned screen) rather than rendering a CP-owned screen. Re-confirm or adjust at the V1 PO pass.
- V0 envelope only. This pass decomposes CP-S0 → CP-005 (v0.0.0 → v0.1.0). The V1 hardening slice CP-006 (CP-S7 "lien remplacé", CP-S9 mentions-légales page, CP-S8/CP-13 "déjà réglé" receipt re-download, full anti-replay, AUTH DOB/RCCM challenge, fine-grained selection) is out of the V0 envelope — listed under "Hors Scope V0". Re-decompose it in the V1 PO pass.
- CP-002 split. The architecture flags CP-002 (7 ACs) as a split candidate. It is split into CP-002a (summary+consent), CP-002b (initiation+return), CP-002c (receipt) so each story holds 3–4 ACs.
V0 envelope → stories¶
| Sub-version | Design screens | CP scope (per arch §0 + §11) | Stories |
|---|---|---|---|
[V0.0.0] |
CP-S1 | Dev/test infra (reuse TENANT-S0 stack + WireMock PAY). Landing signed-link + contract list (read-only), session cookie, all-or-none selection, server-side draft, offline banner. No payment. Frozen CP schema baseline. Publishes customer.session_opened. |
CP-S0, CP-001 |
[V0.0.1] |
CP-S2, CP-S3, CP-S4 | Live payment via PAY: summary + audited consent + rétractation; initiation + PSP redirect + return/status resolution + PaymentConfirmed listener; receipt screen + reçu download + NOTIF receipt push. | CP-002a, CP-002b, CP-002c |
[V0.0.2] |
CP-S5, CP-S6 | Pending "vérification en cours" screen + TanStack Query polling; failed screen + mapped French reasons + manual retry; per-contract toggle (replaces all-or-none); phone-last-4 capture at payment. | CP-003 |
[V0.0.3] |
— | Lazy-load payment/receipt bundle + CI bundle-budget gate (≤ 180 KB gzipped landing path). | CP-004 |
[V0.1.0] |
CP-S10 (AUTH-owned skin) | Prod hardening: 72 h link TTL enforced + attempt counter + reverse-proxy rate limiting. | CP-005 |
Dependency Graph¶
CP-S0 [V0.0.0] Dev + test infra (reuse TENANT-S0 + WireMock)
│
CP-001 [V0.0.0] Landing + contract list + session cookie + all-or-none + frozen schema baseline
│
CP-002a [V0.0.1] Summary + consent + rétractation (no PAY call yet)
│
CP-002b [V0.0.1] Payment initiation + PSP redirect + return/status + PaymentConfirmed listener
│
CP-002c [V0.0.1] Receipt screen + reçu download + NOTIF receipt push
├───────────────┬───────────────┐
▼ ▼ ▼
CP-003 CP-004 CP-005
[V0.0.2] [V0.0.3] [V0.1.0]
Pending+poll Lazy-load + Link TTL +
+retry+toggle budget gate attempt counter
+phone-last4 + rate limiting
(CP-005 depends on CP-002b; CP-004 depends on CP-003. CP-003/004/005 all build on the CP-002 chain and can be sequenced or parallelised after CP-002c.)
Story List¶
| # | Story ID | Title | Side | Sub-ver | Design screen | Depends On | Concurrent With | Complexity | Status |
|---|---|---|---|---|---|---|---|---|---|
| 0 | CP-S0 | Dev environment + test infra | BACKEND | V0.0.0 | — | TENANT-S0 | — | S | TODO |
| 1 | CP-001 | Landing + contract list + session cookie + all-or-none (no payment) | CUSTOMER+BE | V0.0.0 | CP-S1 | CP-S0 | — | M | TODO |
| 2 | CP-002a | Pre-payment summary + consent capture | CUSTOMER+BE | V0.0.1 | CP-S2 | CP-001 | — | M | TODO |
| 3 | CP-002b | Payment initiation + PSP redirect + return/status | CUSTOMER+BE | V0.0.1 | CP-S3 | CP-002a | — | M | TODO |
| 4 | CP-002c | Receipt screen + reçu download + NOTIF push | CUSTOMER+BE | V0.0.1 | CP-S4 | CP-002b | — | S | TODO |
| 5 | CP-003 | Pending + polling + retry + per-contract toggle + phone-last-4 | CUSTOMER+BE | V0.0.2 | CP-S5, CP-S6 | CP-002c | CP-004, CP-005 | M | TODO |
| 6 | CP-004 | Lazy-load bundle + CI budget gate | CUSTOMER+BE | V0.0.3 | — | CP-003 | CP-005 | S | TODO |
| 7 | CP-005 | Link TTL + attempt counter + rate limiting | CUSTOMER+BE | V0.1.0 | CP-S10 | CP-002b | CP-003, CP-004 | M | TODO |
Concurrency Tiers¶
| Tier | Stories | Can start after |
|---|---|---|
| T0 | CP-S0 | TENANT-S0 (serial; merge first) |
| T1 | CP-001 | CP-S0 |
| T2 | CP-002a | CP-001 |
| T3 | CP-002b | CP-002a |
| T4 | CP-002c | CP-002b |
| T5 | CP-003, CP-005 | CP-002c (CP-005 needs CP-002b) |
| T6 | CP-004 | CP-003 |
PRD Acceptance Criteria Coverage (V0 envelope)¶
| PRD User Story | ACs in V0 | Assigned to | Coverage |
|---|---|---|---|
| CP-01 (landing + list) | list render, broker identity, greeting, list-updated notice | CP-001 | ✅ |
| CP-02 (expand row details) | expand, missing-detail copy | CP-001 | ✅ |
| CP-03 (selection) | all-or-none + total (CP-001); per-contract toggle (CP-003) | CP-001 + CP-003 | ✅ |
| CP-04 (summary + consent) | summary, eligibility re-check, rétractation, consent capture | CP-002a | ✅ |
| CP-05 (initiate payment) | initiate+303, CP-S3 loading, init-fail surface | CP-002b | ✅ |
| CP-06 (return) | status routing, resumable in-flight | CP-002b | ✅ |
| CP-07 (reçu) | receipt screen, download, NOTIF push | CP-002c | ✅ |
| CP-08 (pending) | pending screen, poll cadence, resume, expired-pending | CP-003 | ✅ |
| CP-09 (failed) | mapped reasons, retry preserves selection | CP-003 (+ init-fail surface in CP-002b) | ✅ |
| CP-11 (connection drops) | offline banner + selection safety (CP-001); 72 h TTL leg (CP-005) | CP-001 + CP-005 | ✅ |
| TOTAL (V0) | all V0-envelope ACs | All assigned ✅ | 100% of V0 envelope |
Design screen coverage (V0)¶
| Screen | Title | In V0? | Assigned to |
|---|---|---|---|
| CP-S1 | Contract list | ✅ | CP-001 |
| CP-S2 | Récapitulatif + consentement | ✅ | CP-002a |
| CP-S3 | Initiation paiement (redirect loading) | ✅ | CP-002b |
| CP-S4 | Reçu | ✅ | CP-002c |
| CP-S5 | Vérification en cours (pending) | ✅ | CP-003 |
| CP-S6 | Échec du paiement | ✅ | CP-003 |
| CP-S7 | Lien remplacé | ❌ V1 (CP-006) | — (AUTH defers in V0) |
| CP-S8 | Déjà réglé | ❌ V1 (CP-006) | — |
| CP-S9 | Mentions légales (full page) | ❌ V1 (CP-006) | footer link only in V0 |
| CP-S10 | Lien expiré (AUTH-owned skin) | ✅ | CP-005 |
| CP-S11 | Tenant suspendu (AUTH 503) | ❌ V1 | AUTH-owned |
CP-S7/S8/S9 are placed in V1 per the locked scope (see Note 2), diverging from design §2/§11 which list them in V0. Flagged for the human PO.
Hors Scope V0 (re-decomposed in the V1 PO pass)¶
These PRD/arch/design items are not in the V0 envelope and are listed in the "Out of Scope" of the relevant stories:
- CP-006 (V1) — full hardening slice: CP-S7 "lien remplacé" (REVOKED reason=REISSUED), CP-S9 / CP-12 Mentions-légales page render (GET /mentions-legales full page + customer_mentions_legales_viewed), CP-S8 / CP-13 "déjà réglé" receipt re-download (GET /{token}/recus + cp_readonly_sessions cookie, post-ALL_PAID auto-revoke), full anti-replay, AUTH DOB/RCCM challenge integration, fine-grained selection. (The footer mentions-légales link + content-version read exist from V0; only the full page render is V1.)
- CP-14 (operator timeline) — file-detail timeline of CP audit events belongs to the OP module (OP queries AUDIT; no CP API surface). Covered in operator-console decomposition.
- Refund / dispute form (CP-OQ-06) — V1: operational only (call the agency).
- V2/V3: per-contract partial-amount payment, installments, claims/attestation/doc upload, broker quittance download, multi-language, multi-currency (XAF), in-portal chat, WebSocket/SSE push, embedded PSP checkout, customer self-service re-issuance, AUTH OTP step-up.
Implementation Order (recommended)¶
- CP-S0 — dev/test infra; merge before any CP story.
- CP-001 — landing + contract list + frozen schema baseline (first demo-able v0.0.0 milestone).
- CP-002a → CP-002b → CP-002c — the v0.0.1 payment chain (summary/consent → initiation/return → receipt).
- CP-003 + CP-005 (after CP-002c; CP-005 needs CP-002b) — pending/polling/toggle + TTL/rate-limit hardening.
- CP-004 — lazy-load + bundle-budget gate (after CP-003; closes v0.0.3).
Bloquants to clear before dev-start¶
- [LEGAL-REVIEW] BLOQUANT — magic-link as opposable consent =
INCERTAIN - avocat requis(audit red flag #5). Percompliance-discipline.md, anINCERTAINitem blocks production implementation and the dependent stories do not enter the shippable backlog until a qualified lawyer clears the civil qualification of the magic link as opposable consent in Ivorian law. Every CP story (CP-001 onward) carries this gate. RelatedINCERTAIN: CP-OQ-02 (probative value of un-pre-checked checkbox consent), CP-OQ-05 (anonymous mandataire receipt acceptability), CP-OQ-01 (CIMA Reg 01-24 article-anchored mandatory fields). These may force field changes in the per-contract panel. - AUDIT catalog: the AUDIT
event_typecatalog (AUDIT §B.1) must admit every CP code before any audited CP story merges —customer_session_opened,customer_contract_list_viewed,customer_contract_details_missing,customer_selection_changed,customer_summary_viewed,customer_consent_captured,customer_mentions_legales_viewed,customer_payment_initiated,customer_payment_initiation_failed,customer_payment_return_mismatch,customer_payment_status_polled,customer_payment_confirmed,customer_payment_failed,customer_payment_expired_pending,CP_RECU_DOWNLOADED,customer_offline_banner_shown,customer_link_replaced_landing,customer_already_paid_landing,customer_support_contact_clicked. (AUDIT API rejects unknown event types — CLAUDE.md rule #3.) - AUTH signed-link service (
SignedLinkService.verifyAndOpen,customerSessionAuthFilter) available for CP-001. - TENANT
TenantConfigCache(broker identity surface) available for CP-001. - ING
ContractRepository.findEligibleForFileavailable for CP-001. - PAY sandbox (
initiate,getStatus,getReceiptUrls) + events (PaymentConfirmed/PaymentFailed/PaymentExpiredPending) for CP-002b/c. - NOTIF
cp_payment_receipttemplate (NOTIF-US-11) registered for CP-002c.
Compliance — synthesis¶
| Status | Items | Policy |
|---|---|---|
VÉRIFIÉ |
Single-use magic link admitted (CIMA 001-2024 art. 26 + Loi CI 2013-546 arts. 5+8); double-clic (art. 20); droit de rétractation 14 j affiché avant paiement (CIMA art. 24); mentions précontractuelles (CIMA arts. 22-23 + Loi 2013-546 art. 19); tenant isolation; audit trail; XOF minor units | Implementable |
PRÉLIMINAIRE ([LEGAL-REVIEW]) |
CP-TV-01 (CIMA mandatory-info working set), CP-TV-02 (checkbox consent as digital proof) | Implementable with warning; surfaced at point of use |
INCERTAIN (BLOQUANT) |
Magic-link as opposable consent (red flag #5); CP-OQ-01 article-anchored fields; CP-OQ-02 probative value; CP-OQ-05 mandataire receipt | Blocks production ship. Build may proceed behind the gate; does NOT enter shippable backlog until legal clears |
Each story carries the explicit list of compliance rules it touches, with article/assumption reference and status.
Decomposition Quality Checks¶
- [x] Every story has 2–4 ACs (CP-S0 3 technical ACs; CP-004 2 ACs; CP-001/002b/003 4 ACs; CP-002a/002c/005 3 ACs)
- [x] Vertical slice with 5+ ACs split (CP-002 7 ACs → CP-002a/b/c)
- [x] Every in-scope V0 PRD AC assigned to ≥ 1 story
- [x] Every in-scope V0 design screen assigned (CP-S1→001, CP-S2→002a, CP-S3→002b, CP-S4→002c, CP-S5/S6→003, CP-S10→005); CP-S7/S8/S9/S11 marked V1
- [x] Self-containment: story + standards + design = full implementation context
- [x] No circular dependencies (DAG verified)
- [x] Story IDs sequential, zero-padded where applicable, uppercase prefix (CP-002 split a/b/c per established AUDIT-001a/b/c precedent)
- [x] French copy for every UI state on every UI-bearing story, reconciled against design §8
- [x] Compliance rules carry status +
[LEGAL-REVIEW]where needed;INCERTAINbloquant flagged at module + story level; no INCERTAIN marker altered in this review - [x] Out-of-scope explicit per story; V1 items collected in "Hors Scope V0"
- [x] V0 envelope coverage: 100%
- [x] [CUSTOMER stories] explicit connectivity behavior section in each
- [x] Design doc reconciled (stale "no design doc" flag removed); design-vs-V0-scope divergence (CP-S7/S8/S9) flagged for the human PO rather than silently resolved
Decomposition completed 28/05/2026; reconciled against customer-portal-design.md on 29/05/2026. V0 envelope only. Stories in simple English for Claude DEVELOPER; translate via /translate for human devs.
Merge onto main after founder review, then hand to developers.