Payee Check

Asks the bank who holds an account and compares that with the name typed, like Confirmation of Payee, so money doesn't go to the wrong account.

StopsMoney sent to the wrong account

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/payee-check.json

The files land in your project. They're yours to edit.

Usage

payee-check-usage.tsx
import { PayeeCheck, type PayeeDetails, type PayeeLookupResult } from "@/components/ui/payee-check"

<PayeeCheck
  value={details}
  onValueChange={setDetails}
  lookup={lookup}
  expectedAccountType={type}
  amount={250000}
  formatAmount={(n) => inr.format(n)}
  newPayee
  largeAmount={50000}
  onTestPayment={() => setStatus("Sent ₹1 test payment.")}
  testPaymentLabel="Send ₹1 first"
  onPay={(d, verdict) => setStatus(`Paid ${d.name} (${verdict}).`)}
/>

What it catches

  • Typed name that doesn't match the account holder
  • Close matches and typos (Pirya vs Priya), titles and legal suffixes ignored
  • First payment to a new payee
  • Large first payments, with an optional test payment

Props

PayeeCheck props
PropTypeDefault
value

Payee details (controlled).

PayeeDetails—
defaultValue

Initial payee details when uncontrolled.

PayeeDetails{ name: "", account: "", code: "" }
onValueChange

Called with the details on every change.

(value: PayeeDetails) => void—
lookup* (required)

Asks the payee's bank who holds the account. Called again only when the account or code changes.

(details: Pick<PayeeDetails, "account" | "code">, signal: AbortSignal) => Promise<PayeeLookupResult>—
isComplete

Whether the account details are complete enough to look up.

(details: PayeeDetails) => booleandefaultComplete
amount

Payment amount, for the large-payment callout.

number—
formatAmount

Formats `amount`, e.g. with Intl.NumberFormat.

(amount: number) => string(n) => n.toLocaleString()
newPayee

True when the user has never paid this account before.

booleanfalse
largeAmount

A first payment at or above this is called out.

number50_000
onPay

Sends the payment. Return a promise to show a pending state.

(details: PayeeDetails, verdict: PayeeVerdict | "unchecked") => void | Promise<void>—
onTestPayment

Offers a small test payment for a large first payment.

(details: PayeeDetails) => void—
testPaymentLabel

Button text for a small test payment.

string"Send a small test first"
nameLabel

Label for the payee name.

string"Payee name"
accountLabel

Label for the account number.

string"Account number"
codeLabel

Label for the bank code (IFSC, sort code, routing).

string"IFSC"
acknowledgeLabel

Checkbox text to pay despite no match.

string"I've checked with the payee and want to pay anyway"
expectedAccountType

The account type the user said they're paying. A different type from the bank is a close match, as Confirmation of Payee requires.

"personal" | "business"—
messages

Override any other user-visible string (see `PAYEE_MESSAGES`).

Partial<PayeeMessages>—

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Account, code and name inputs have visible labels
  • Name input has aria-invalid on no match and is described by a polite result region
  • Loading state has text; icons are aria-hidden

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Run the name check and payment authorization on the server, and log pay-anyway acknowledgements.

  • Needs a lookup backed by your bank or provider's name-check API
  • Name matching is fuzzy and can't settle every case
  • Doesn't stop a payer who chooses pay-anyway

What it stops

Money sent to the wrong account

Also known as

payee · beneficiary · confirmation of payee · account number · ifsc · name match · bank transfer · upi id

Field type

Dependencies

Pairs well with