> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinbankroll.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payments

> Charge the user's Bankroll balance to your wallet, and verify the payment on your server.

`bankroll.pay()` charges the user's Bankroll balance and settles the funds to
**your** wallet — the `merchantWallet` in your [manifest](/build/manifest).
Your page cannot name a recipient; it can only name an amount.

```ts theme={null}
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

| Field         | Type   | Description                                                                                                                           |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `amountCents` | number | The amount to charge, in whole US cents. Must be a positive integer — anything else throws `invalid_amount` before reaching the host. |
| `memo`        | string | Optional. A short human-readable label attached to the payment (e.g. an order or item id). Trimmed and truncated to 80 characters.    |

<Warning>
  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.
</Warning>

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

<Note>
  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.
</Note>

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.code`             | What happened                                                                                              | What to do                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `insufficient_funds` | The user can't cover the charge. Bankroll already showed its "add cash" sheet and funnels them to funding. | Quietly stop — no error.                |
| `consent_declined`   | The user declined the payment consent prompt.                                                              | Quietly return them to where they were. |

```ts theme={null}
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
}
```

<Warning>
  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.
</Warning>

## Error codes

All codes `identity()` and `pay()` can throw, as `BankrollError.code`:

| Code                        | Thrown by     | Meaning                                                            |
| --------------------------- | ------------- | ------------------------------------------------------------------ |
| `unavailable`               | identity, pay | Not in a Bankroll host (mirrors `status()`).                       |
| `update_required`           | identity, pay | The Bankroll app is too old for this SDK (mirrors `status()`).     |
| `consent_declined`          | identity, pay | The user declined the consent sheet.                               |
| `insufficient_funds`        | pay           | Balance too low — the host already showed its add-cash sheet.      |
| `invalid_amount`            | pay           | `amountCents` isn't a positive integer.                            |
| `capability_not_registered` | identity, pay | The capability isn't declared in your [manifest](/build/manifest). |
| `manifest_error`            | identity, pay | Your `/.well-known/bankroll.json` is missing or malformed.         |
| `unknown`                   | any           | An 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.
