@get-asset/react

Transaction Match

Styled, drop-in matching panel: a Payments block with one chip per payment already matched to an invoice or bill, and a searchable Select Deposit picker over the bank transactions around its due date (Select Transactionson a bill). Ticking a transaction records the payment and unticking it takes the payment off again — each is its own request, so there is no save — and every chip carries its own remove control. A layout prop swaps the standalone block for an inline stack with a payment total, for surfaces that introduce the panel themselves. AssetInvoices embeds this panel already; mount it yourself to put matching on a surface of your own. See Using it standalone.

Preview

Loading preview…

Required scopes

The access token used by AssetProvider must include these scopes for this primitive to fetch data successfully:

  • invoice:create
  • invoice:delete
  • bill:create
  • bill:delete
  • transaction:read
  • external_account:read

API reference

Root props

PropTypeDefaultDescription
kindrequired"invoice" | "bill"Which side of the ledger. Picks the endpoints the panel writes to, and the sign a candidate must have — an invoice is settled by money in, a bill by money out.
objectIdrequiredstringThe invoice or bill being settled.
outstandingrequirednumberWhat is still owed. The candidates are ranked against it, and zero (to the cent) is what makes the panel read as settled.
dueAtrequiredstringThe record's due date, ISO-8601. The candidate window centres on it — a year back and a quarter forward.
matchedMatchedPayment[][]The payments already recorded, from whichever list owns the record. These are the rows in the panel and what the picker shows ticked; their descriptions are also what a settled record renders, since it fetches nothing.
pageSizenumber50Rows per candidate request. The API's ceiling is 100.
creditsCreditSource[]Credits the customer or vendor holds, offered as a second group in the picker. Nothing is fetched — the host supplies these. Omit it and the picker holds one unlabelled list of transactions. See Credits.
applyCredit(credit: CreditSource, amount: number) => voidDraws amount from credit. The host's write: without it a credit can be shown and priced but picking one commits nothing.
removeCredit(row: MatchRow) => voidRemoves an applied credit, called by that row's bin. Without it a credit-sourced row detaches through the ordinary payments endpoint.
onChanged() => voidCalled after each committed change. The panel stays open — writes land one at a time — so this is for reacting to a match, not for closing over it.
layout"panel" | "inline""panel"How the surface presents itself. "panel" is the standalone block — a Payments head carrying the picker, the chips, and an empty-state box before anything is matched; it is what the row expander mounts. "inline" drops the head and the empty box and adds a payment total under a dashed rule, for embedding inside a surface that already introduces it — the document view's summary band.
paidTotalnumberWhat the record itself says has been paid, stated by the inline layout's "Total paid" instead of the sum of the matched chips. Those carry their transactions' currencies, which needn't be the record's, and a payment drawn from a credit is not a chip at all. Omit it and the footer falls back to the chips' sum.
currencystring | nullISO 4217 code paidTotal is in — the record's own. Shown beside the figure, and used for its symbol.
closeActionReactNodeRendered in the panel's footer, and the only thing in it. Closing belongs to the host: the panel lives inside whatever opened it and has no idea how that collapses. Omit it and there is no footer.
candidatesMatchCandidateOverride[]Override the transactions the picker offers (skips both requests). The direction filter and the ranking still apply. An empty array is still an override, and shows the picker with nothing in it. Used by previews and tests.
state"loading" | "error" | "empty"Force a specific state for testing. Ignored once nothing is outstanding, which has its own state.
classNamestringForwarded to the panel's outer element.

States

Loading

Loading preview…

Success$2,100 outstanding, one payment matched, candidates in the picker.

Loading preview…

EmptyNothing in the window to offer — the picker opens on its empty message.

Loading preview…

Error

Loading preview…

SettledNothing outstanding: the picker is disabled, the payments still read.

Loading preview…

With creditsA second group in the picker. This preview supplies no writer, so a pick commits nothing.

Loading preview…

No paymentsThe same block, saying what would go in it.

Loading preview…

Behaviour

As a panel — the default — it is one block whatever state it is in: a head that names it and carries the picker, the chips for what is already matched, and an empty-state box before anything is — not a missing list but the same container saying what would go in it, so the block never changes shape as the first payment lands. The inline layout drops the head and the empty box and adds a payment total under a dashed rule, for a surface that already introduces the block in its own words. In both, the trigger names what it offers by where the money went — Select Deposit against an invoice, Select Transactionsagainst a bill — and the amounts carry the same sign, + for money in, for money out.

The picker is a dropdown rather than a list on the page, which is what lets the candidate window be a generous year without the panel growing to match: you search and scroll inside it. Each option sits on three fixed tracks — date, description, amount — so the columns line up down the whole list, which is what scanning a dozen rows for the one that fits actually requires. The date leads because two payments from the same payer are told apart by when they landed; the amount is last because it is the figure being judged.

Ticking an option records the payment; unticking it detaches the payment again. Already-matched transactions come back ticked, so the control states the current position rather than only offering additions, and only the row being written goes quiet while its request is in flight. Transactions moving money the wrong way are never offered at all — Guardrails has the four decisions behind that, including how a transaction worth more than the balance is still offered while the API accounts for its excess.

Each matched payment reads as a chip: the bank description, when it landed and through which account, the amount it applied, and a remove control. The control is always visible rather than revealed on hover — it is the only reversal on the chip, and there is no hover to find it with on touch. The amount is read-only for every source, because it is the allocation already recorded by the API or host; when a transaction settled the record for less than it moved, its own figure sits struck through beside the applied one.

The inline layout's total (“Payment total” on an invoice, “Transaction total” on a bill) is the running answer to “how much of this record is covered”. A settled record reads it plain. An overpaid one reads it in red on both kinds — the direction differs, an invoice took too much in (+) where a bill sent too much out (−), but the alarm doesn't. The fuller reconciliation (what the record is worth, what that leaves) is in neither layout: only the host knows what the record is worth, so it owns those figures; AssetInvoicesputs them in the column beside the expander's panel.

A settled record keeps the picker, disabled: you can still read and undo what paid it, and hiding the only control would make a settled record look like a different component. It also costs nothing — no candidates are fetched. See Settled records.

Using it standalone

AssetInvoices mounts this panel in the expanded row and in the document view's summary band, so a host rendering that table needs nothing here — matchCandidates on the table is how its previews and tests keep it offline.

Mounting it yourself means supplying the record's facts, because the panel settles one record and does not fetch it: kind, the objectId, its outstanding balance, its dueAt date, and the payments already against it as matched. Those payments are what make the panel a two-way surface rather than an add-only one, and they are the only descriptions a settled record has to show. A successful write invalidates the invoice and bill lists, so a table beside the panel moves on its own; onChanged is for anything else that needs to react.

For a layout of your own — a different picker, the difference column this one leaves out, a per-row exact-match badge — compose the headless TransactionMatch parts directly. Every value and control in this panel is one of its slots.

Credits

A credit the party already holds can settle a record beside the bank transactions, and it is host-supplied end to end: credits is the list, applyCredit is the write, and removeCreditis what a drawn credit's bin calls. Nothing here queries a credit balance. Pass credits without applyCredit and the group is a preview: the rows are priced but a pick commits nothing.

The picker labels the two groups, because they behave differently once picked. A transaction lands at its own value — the bank moved exactly that much. A credit is a pool, so what lands is a figure someone chooses: the amount on the right of a credit option is what is left on the credit, and the tooltip is what applying it here would draw — the smaller of the two, so a $1,000 credit against a $100 invoice takes $100 and leaves $900. Unlabelled they would read as one list where half the rows silently ask a follow-up question.