Skip to main content

Statement

Route /statement?chargeId=CHG-... — bonus B-05. Read-only ledger of unpaid charges and mock Pay.

Model

Unpaid = outstanding|contested|accepted (terminal resolved|paid excluded). Group totals per currency (never summed across currencies) — mirrors backend money rule.

class StatementView {
final Map<String /*currency*/, int /*minor*/> totals; // { "GBP": 4200 }
final List<Charge> outstandingCharges;
final List<Charge> acceptedCharges; // each with Pay CTO
final List<Charge> contestedCharges;
}

Behavior

  • Header shows per-currency running total (e.g., £42.00 from minor 4200) with correct localized grouping (en 42.00, cy locale match).
  • Each charge row shows itemName — amountMinor→currency + status pill + deadline hint.
  • Pay on an accepted row calls ChargePorts.pay(chargeId, expectedEpoch, expectedVersion) → optimistic paid → row leaves statement (history-only).
  • Global Pay all CTA (when multiple accepted in same currency) iterates with sequential Idempotency-Keys — not a batch endpoint.

No real settlement — button clearly labelled mock, documented in ADR.

Fees & presentation

  • KxSectionHeader separates totals from groups.
  • KxSkeleton placeholder on initial Drift stream.
  • Reduced motion collapses amount ticker animation.

Tests

test/features/statement/statement_page_test.dart asserts totals per currency, resolved|paid excluded, mock pay journey, and golden responsive states.