03 — User Journey (Payments)
- 1. Record a counter payment (Accountant / Receptionist)
- 2. Refund a payment (Accountant)
- 3. Reconcile a gateway outcome
- 4. Search an audit trail (Org Admin)
- Common journey attributes
End-to-end payment journeys. All flows assume Bearer JWT; every step uses the shared envelope contract (00-shared/07_API_Conventions.md).
1. Record a counter payment (Accountant / Receptionist)
sequenceDiagram
participant U as Accountant
participant A as App
participant API as /payments
U->>A: Open Payments > New payment
A->>API: POST /payments {amount, gateway:cash, invoiceId, payerName, payerEmail}
API-->>A: 201 {payment, receipt}
A-->>U: Success screen: payment detail + receipt card (print/share)
Note over A,U: invoice paidAmount/status updated server-side (PAID|PARTIAL)
- Entry: Fees → invoice detail → "Record payment"; or Payments FAB → New payment.
- Intent: credit an invoice instantly.
- Decision points: gateway (cash/cheque/bank_transfer), prefill amount from invoice, attach payer identity.
- System response: synchronous; skeleton → success view. Receipt number shown.
- Failures: 400 validation (amount Min 0), 409 duplicate reference, network → retry prompt. Never optimistic — payment must be confirmed by the server.
- Exit: "Done" → back to invoice (refreshed) or payments list.
- Abandonment: form keeps entered values on back; not submitted until confirm.
- Offline: blocked — must be online; guidance toast.
- Multi-device: invoice status diverges if another clerk is mid-payment; pull-to-refresh.
2. Refund a payment (Accountant)
sequenceDiagram
U->>A: Payments list > tap completed payment > Refund
A->>A: Confirm sheet (reason required, shows refundable amount)
A->>API: POST /payments/refund {paymentId, amount?}
alt full refund
API-->>A: status=refunded
else partial
API-->>A: status=partially_refunded
end
A-->>U: Success snackbar + updated payment
- **Decision: full vs partial (amount optional = full).
- Failures:
- 409 "Only completed payments can be refunded." (status banner)
- 409 "Refund amount exceeds payment amount." (inline amount error)
- 404 payment missing.
- Recovery: retry with corrected amount.
3. Reconcile a gateway outcome
flowchart LR
G[Gateway async event] --> P[Payment PENDING]
P --> R[Accountant opens reconcile screen]
R --> S[PATCH /payments/:ref/reconcile {status}]
S -->|success| C[completed]
S -->|failed| F[failed]
S -->|other / empty| PEND[pending]
- Entry: payments list row with "pending/processing" badge → Reconcile; or gateway callback page.
- Exit: back to list with updated status.
- Permission denial: 403 → reconcile action hidden (client-gated per
payments.reconcile).
4. Search an audit trail (Org Admin)
- Payments list (paged
GET /payments?page&limit, sortedcreatedAt desc) → filter by invoice / status (client-side; no server filters) → detail → view receipt (GET /payments/receipts/:id). - Deep link (forward-looking):
studylyon://payments/:id.
Common journey attributes
| Concern | Behaviour |
|---|---|
| Loading | AppSkeleton lists; button spinner on submit |
| Session expiry | silent refresh; fail → re-login, payment preserved as draft |
| Timeout | 15 s; 429 → countdown + disable retry |
| Conflict resolution | refresh list; server state is source of truth |
| Network loss | offline banner; no optimistic writes |
| Push entry | (forward-looking) PaymentProcessed notification → deep link |
| Email entry | (planned) receipt email via PaymentCompleted→emails routing (dead in map today) |
| Abandonment | forms discarded with confirm prompt |