08 — Form Specifications (Payments)
- F1 — Record payment (
POST /payments) - F2 — Refund (
POST /payments/refund) - F3 — Reconcile (
PATCH /payments/:transactionRef/reconcile) - F4 — Local filters (S1)
Every form field, derived from
process-payment.dto.ts,refund-payment.dto.ts, and the reconcile endpoint body.
F1 — Record payment (POST /payments)
| Field | Type / control | Label | Hint / placeholder | Keyboard | Validation (server) | Error message |
|---|---|---|---|---|---|---|
| amount | AppTextField decimal | Amount | e.g. 250.00 | numeric-decimal | @IsNumber @Min(0) | "Enter a valid amount (min 0)" |
| currency | AppTextField (read-only default) | Currency | USD | text | @IsString @IsOptional | — |
| gateway | AppDropdown | Gateway | required | — | @IsEnum(PaymentGateway) | "Select payment method" |
| gatewayTransactionId | AppTextField | Gateway reference | optional, gateway methods | text | @IsString @IsOptional | — |
| invoiceId | AppDropdown searchable | Invoice | optional (search by invoice no.) | — | @IsString @IsOptional | — |
| invoiceType | AppTextField | Invoice type | optional | text | @IsString @IsOptional | — |
| payerId | AppTextField | Payer ID | optional | text | @IsString @IsOptional | — |
| payerEmail | AppTextField | Payer email | @IsString @IsOptional | "Enter a valid email" (client) | ||
| payerName | AppTextField | Payer name | optional | text | @IsString @IsOptional | — |
| description | AppTextField multiline | Description | optional | text | @IsString @IsOptional | — |
- Defaults: currency
USD; amounts empty; gateway none. - Dependencies/conditional: selecting an invoice prefills amount + payerName (editable). Offline methods (cash/cheque/bank_transfer) recommend payerName; gateway methods show gatewayTransactionId.
- Autocomplete: payer name/email from recent payers (client cache)
(proposed). - Submission: client validates →
POST /payments; button loading; no optimistic update (server is source of truth). - Success: inline payment + receipt cards; offer "View receipt", "Done".
- Failure: 400 field errors mapped; 409 conflict banner + refresh; 5xx generic + retry; values preserved.
- Undo: not applicable (confirmatory commitment).
F2 — Refund (POST /payments/refund)
| Field | Label | Validation | Notes |
|---|---|---|---|
| paymentId | hidden (from S2 context) | @IsString | locked |
| amount | Refund amount | @IsNumber @Min(0) @IsOptional; max = amount − refundedAmount | default full refundable; 0 → inline error |
| reason | Reason | @IsString @IsOptional; client min 3 chars | show in confirmation |
- Submission: loading; on success snackbar + status updated; on 409 "Only completed payments can be refunded."/"Refund amount exceeds payment amount." → inline/banner.
- Optimistic: no.
F3 — Reconcile (PATCH /payments/:transactionRef/reconcile)
| Field | Type | Validation |
|---|---|---|
| status (body) | AppRadio | "success"→completed, "failed"→failed, else pending |
| gatewayResponse (optional client field) | text | informational |
- Submission: loading; result status reflected after refresh.
- Error 404: payment not found → error state.
F4 — Local filters (S1)
- Search term + status chips — client-side only (no server query support).
- Debounce 300 ms; clear restores.
- No validation.