# Payments

> Payments — specs for 14 React components: AmountInput, CurrencySelect, PayeeSelect, PaymentForm and 10 more.

Source: https://lopifi.com/docs/catalog/03-payments
Part of: Lopifi — US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

# Payments

---

## 24. `AmountInput` {#amountinput}

> The hardest input in fintech. Get this wrong and nothing else matters.

```tsx
<AmountInput
  currency="USD"
  value={amount}
  onChange={setAmount}
  max={availableBalance}
  onMaxClick={() => setAmount(availableBalance)}
/>
```

### The rules

- **`type="text"` + `inputMode="decimal"`.** Never `type="number"` — it has scroll-wheel increment (which *will* silently change someone's payment amount), inconsistent decimal parsing across locales, and unstyleable spinners.
- **16px minimum font size** or iOS Safari zooms the viewport on focus and never zooms back.
- **Parse permissively, format on blur.** Accept `1,234.56`, `1234,56`, `1 234.56`, `$1234.56`, `1.2k`. Reject nothing a human would type. Format canonically when they leave the field.
- **Never reformat mid-typing.** Inserting a thousands separator while someone is typing moves the caret and is the single most infuriating input bug in fintech. Format on blur only.
- **Caret preservation** if you must transform during input — measure the caret offset from the *right*, not the left.
- **Never block paste.**
- **Locale-aware decimal separator.** A German user types `1234,56`. Accept it.
- **Currency-aware precision.** JPY rejects decimals entirely; KWD accepts three.
- **Max is soft by default.** Show "Exceeds available balance of $4,120.00" as an inline error, but let them type it — an overdraft may be legitimate, and blocking input to prevent an invalid state prevents valid ones too.
- **Big.** `figure-lg` (28/32) or larger. This is the most important number on the screen and it should look like it.
- **The symbol is part of the field**, at `--text-tertiary`, not a floating adornment that misaligns as the value grows.

### Accessibility
`aria-label="Amount in US dollars"` · parsed value announced on blur (`"one thousand two hundred thirty-four dollars and fifty-six cents"`) via `aria-describedby` · errors specific and actionable · `aria-invalid` on error.

### Motion
None on the value. Error message: 140ms opacity + 4px translate. Shake on invalid submit is **forbidden** — it's a stress signal in a context where the user is already anxious about money.

---

## 25. `CurrencySelect`
Searchable by code, name, and symbol. Recents pinned at the top. Flag optional, code always. Virtualized (180+ currencies) — fixed-height rows windowed by `hooks/use-option-keys`, with the combobox + listbox pattern. Keyboard typeahead — typing `eu` selects EUR.

## 26. `PayeeSelect`
Fuzzy search across name, account, and reference. Shows last-paid amount and date inline — the single most useful piece of context when choosing a recipient. New-payee flow inline, not a separate route.

## 27. `PaymentForm`
`amount → payee → method → date → memo`. That order — the amount first, because it's what the user came to do and it contextualizes everything after.

- **Progressive disclosure:** memo, reference, and attachments behind "Add details".
- Validation on **blur**, never per-keystroke.
- **Submit is bottom-anchored on phone**, above the safe area and above the keyboard (`visualViewport`), always visible.
- The submit button states the amount: `Send $12,400.00`.
- Fee and arrival estimate update live below the amount, with a value crossfade — never a count-up.

## 28. `PaymentMethodList`
Radio cards with `EntityMark`, masked number, and any per-method fee/timing difference stated inline. Selected state: 2px `--accent` border and an `--accent-subtle` background — **and** a check glyph, because border color alone fails forced-colors mode.

## 29. `CheckoutSummary`
Line items → subtotal (single rule) → fees → tax → total (double rule). Uses the same `Rule` grammar as the statements. Fees are **itemized and named**, never a lump "Fees". Multi-currency lines show conversion with the rate and its timestamp.

## 30. `ConfirmPaymentDialog`

> Amount, destination, timing. In that order. Every time.

```
Send $12,400.00 to Northwind Ltd?
To account ••••4291 · Arrives 8 Sep
This can't be undone once submitted.
[Cancel]  [Hold to send →]
```

`AlertDialog`. Focus lands on **Cancel**, never on the destructive action. `Escape` cancels.

For irreversible movements — wires, ACH past cutoff — the confirm is **hold-to-confirm** (1500ms `linear` fill, 200ms release). Slow where the user is deciding. Full spec including the keyboard and typed-confirmation fallbacks in [motion catalog](https://lopifi.com/md/docs/motion/03-catalog#hold-to-confirm) — this can never be the only path, because gating money behind a motor-skill requirement is an accessibility failure.

## 31. `PaymentStatusTimeline`
`Submitted → Sent → In transit → Settled`, each with a real timestamp and timezone. Failed states show the reason and the remedy. **Pending is a state, not a load** — a `clock` icon, never a spinner.

## 32. `PaymentSuccess`
The sanctioned delight moment. Circle scale → checkmark stroke draw → details fade up, staggered. Total ≤ 700ms. No confetti, no bounce, no sound. [Spec](https://lopifi.com/md/docs/motion/03-catalog).

Shows the amount, the destination, and the arrival estimate — the three things the user needs before they close the screen.

## 33. `ReceiptSheet`
Printable and shareable. Entity, amount, payee, method, reference, timestamp with timezone. Print styles are first-class — this is a document people keep.

## 34. `PayoutSchedule`
Upcoming payouts on a calendar or list, with cutoff times stated in the user's timezone. Business days respected, holidays flagged.

## 35. `CardInput`
PAN / expiry / CVC. `autocomplete="cc-number" | "cc-exp" | "cc-csc"`, Luhn validation, network detection from the IIN with the mark appearing inline (140ms crossfade, blur-masked). Auto-advance between fields, with **backspace returning to the previous field** — the detail everyone forgets.

## 36. `BankAccountInput`
Routing + account number. ABA checksum validation with the institution name resolved and displayed once valid — instant confirmation that they typed the right bank. IBAN mod-97 validation for international, with country-aware length and formatting.

## 37. `RecurringSchedule`
Frequency, start, end condition. **Always previews the next three occurrences** with resolved dates. "Monthly on the 31st" is ambiguous in February; the preview makes the resolution visible before it surprises anyone.
