---
title: "Payments"
description: "Payments — specs for 14 React components: AmountInput, CurrencySelect, PayeeSelect, PaymentForm and 10 more."
canonical: "https://lopifi.com/docs/catalog/payments"
---

# Payments

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

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

---

# Payments

---

## 24. `AmountInput` {#amountinput}

> The hardest input in fintech. If this is wrong, 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"`. Its scroll wheel increments the value (and *will* silently change someone's payment), it parses decimals differently by locale, and its spinners can't be styled.
- **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 person would type. Format canonically when they leave the field.
- **Never reformat mid-typing.** Inserting a thousands separator mid-typing moves the caret. It's the 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 blocks valid ones too. The check — and the Max button — apply only when `max` is in the field's own currency; a balance in another currency is shown but never compared.
- **Big.** `figure-lg` (28/32) or larger. It's the most important number on the screen and should look 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 signals stress to a user who's 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 the last-paid amount and date inline, the most useful context when choosing a recipient. New-payee flow inline, not a separate route.

## 27. `PaymentForm`
`amount → payee → method → date → memo`. Amount comes first because it's what the user came to do, and it gives context to 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, 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. The full spec, with keyboard and typed-confirmation fallbacks, is in the [motion catalog](https://lopifi.com/md/docs/motion/catalog#hold-to-confirm). Hold can never be the only path; gating money behind a motor skill is an accessibility failure. The typed word is `typedConfirmation`, default `SEND`.

## 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/catalog).

Shows the amount, the destination and the arrival estimate, which is what the user needs before closing the screen.

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

## 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-advances between fields, and **backspace returns to the previous field**, which most implementations forget.

## 36. `BankAccountInput`
Routing + account number. ABA checksum validation, then the institution name appears once valid, so the user knows right away 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 shows how it resolves before it surprises anyone. The preview honors the end condition: the start date is the first payment, "after N payments" and "on a date" cut the list short ("All 2 payments") and state the last payment and the count, and an end date before the start says so instead of listing dates. Uncontrolled via `defaultFrequency` / `defaultStart` / `defaultValue`, or controlled via `value` + `onChange`.
