Skip to main content
bankroll.pay() charges the user’s Bankroll balance and settles the funds to your wallet — the merchantWallet in your manifest. Your page cannot name a recipient; it can only name an amount.
import { bankroll } from "@joinbankroll/sdk";

const signature = await bankroll.pay({
  amountCents: 500,   // whole US cents, > 0
  memo: "order:1234", // optional label, see below
});
The host shows the user the charge, moves the funds to your merchant wallet, and resolves the settled payment’s signature (an opaque string). Because the promise resolves only once the payment has settled, your server can confirm it the moment pay() returns. It may stay pending for a while when the host shows native sheets (the consent sheet, settlement confirmation) — don’t wrap it in a short timeout.

Input

FieldTypeDescription
amountCentsnumberThe amount to charge, in whole US cents. Must be a positive integer — anything else throws invalid_amount before reaching the host.
memostringOptional. A short human-readable label attached to the payment (e.g. an order or item id). Trimmed and truncated to 80 characters.
Calling pay() again creates a second charge — don’t blind-retry from the client. If a call fails ambiguously, reconcile on your server first: every settled payment has a unique signature, so store the signatures you’ve granted and never grant twice.

Verify on your server

Treat the signature as a claim to check, not proof. Before you grant anything of value, confirm on your server that the payment:
  1. has settled,
  2. was for the amount you expected,
  3. to your merchant wallet,
  4. and hasn’t already been redeemed (guard against replay by storing the signature — it’s unique per payment).
A verification helper is provided by the Bankroll team when your app is enabled; use it to confirm a signature’s amount and recipient. Wire your amount and recipient checks against it before going live.
The optional memo is a short label shown with the payment; match a settled payment back to its order by the signature pay() returns.

Handling the two non-error rejections

Every failure is a BankrollError with a stable snake_case code (the host’s original message is preserved in message). Two codes are expected outcomes, not errors — the host has already handled the user experience, so don’t surface a duplicate error toast:
e.codeWhat happenedWhat to do
insufficient_fundsThe user can’t cover the charge. Bankroll already showed its “add cash” sheet and funnels them to funding.Quietly stop — no error.
consent_declinedThe user declined the payment consent prompt.Quietly return them to where they were.
import { bankroll, BankrollError } from "@joinbankroll/sdk";

try {
  const signature = await bankroll.pay({ amountCents: 500 });
  await confirmOnServer(signature);
} catch (e) {
  if (e instanceof BankrollError) {
    if (e.code === "insufficient_funds" || e.code === "consent_declined") return;
  }
  throw e; // anything else is a real failure
}
Never grant the purchased item from the client on a resolved pay() alone. A resolved promise means the payment settled, but only your server’s verification of the signature (amount + recipient + not-yet-redeemed) should release value.

Error codes

All codes identity() and pay() can throw, as BankrollError.code:
CodeThrown byMeaning
unavailableidentity, payNot in a Bankroll host (mirrors status()).
update_requiredidentity, payThe Bankroll app is too old for this SDK (mirrors status()).
consent_declinedidentity, payThe user declined the consent sheet.
insufficient_fundspayBalance too low — the host already showed its add-cash sheet.
invalid_amountpayamountCents isn’t a positive integer.
capability_not_registeredidentity, payThe capability isn’t declared in your manifest.
manifest_erroridentity, payYour /.well-known/bankroll.json is missing or malformed.
unknownanyAn unmapped host reason — the original message is preserved.
The code union also reserves superseded_consent, per_charge_declined, blocked_origin, and charge_cap_exceeded — stable names you can switch on today.