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

# Use payments in the SDK

> Install payment packages, register them on the order, and charge it.

Each payment method has its own package, `@fire-spark/payments-<provider>-<method>`. Packages that need native code end in `-rn` and only run in React Native or Expo apps. You install only the methods your channel offers.

## Install

```bash theme={null}
pnpm add @fire-spark/storefront @fire-spark/payments-cash
```

Every package depends on `@fire-spark/payments`, the shared contract. You only import it for helpers such as `formatPaymentError`.

## Register the adapters

Register every installed package on the order. Fire spark matches each merchant payment to its adapter by provider and method.

```typescript theme={null}
import { cashPayments } from "@fire-spark/payments-cash";

order.setPaymentAdapters([cashPayments()]);
order.setPaymentOptions({
  isProduction: merchant.env_is_prod,
  collectAt: "KIOSK", // "DEVICE" on web or app
  cashCollectAt: "COUNTER", // "DOOR" on delivery
  deviceSettings: kioskConfig.overrides.payment_settings, // kiosks only, from getKioskConfig
  deviceId: kioskConfig.device.id // kiosks only
});
```

## Show the methods

`getPaymentOptions` keeps only the merchant payments that have an installed adapter.

```typescript theme={null}
import { services } from "@fire-spark/storefront";

const payments = await services.payments.list({ order_id: order.id });
const options = order.getPaymentOptions(payments);
```

`services.payments.list` leaves out `REDEMPTION` payments (vouchers, rewards or balance): they are applied through the order's `redeem` entries, not picked as a method. Pass `{ includeRedemptions: true }` as the second argument when a redeem flow needs them, or filter a list you already have with `selectablePayments(payments)`.

For kiosks, `getMissingPaymentFields(payment)` lists the device settings that are still empty. Hide or disable the method until they are set.

## Charge the order

Save the tenders, then charge the device ones. `payIntent` places the order the first time and runs each real `DEVICE` tender in sequence.

```typescript theme={null}
await order.setTenders([
  {
    method: payment.method.method,
    provider: payment.provider.provider_id,
    payment_id: payment.id,
    collect_at: "DEVICE",
    allocation: null
  }
]);

const result = await order.payIntent({
  onProgress: progress => render(progress) // QR, waiting on terminal, …
});

if (result.status === "FAILED") {
  showError(result.result.error);
}
```

`order.pay(payment)` still works and calls `payIntent({ payment })`. That shortcut places one real tender. Prefer `setTenders` when the checkout has more than one method.

1. The first call places the order and locks the intent. Later calls charge the tenders already on that intent.
2. Each method's package talks to the terminal, shows the QR, or asks Fire spark to charge on the server.
3. Each result is saved on its tender. Retries, unknown results, and QR expiry follow the [error rules](https://firespark.cloud/docs/guides/payments/errors.md).

`PAID` means every live tender is settled. `PENDING` means in-person tenders are still open. The fulfillment `PAYING` step advances when the tenders in its scope are settled.

## Mixed payments

Put every method on the draft before place. `allocation: null` on a single real tender means it covers the rest. With two or more real tenders, name the lines (and optional `units`) each one covers. The server fills `amount` and `allocation.amounts`.

```typescript theme={null}
await order.setTenders([
  {
    method: "FINANCING",
    provider: "cashea",
    payment_id: financing.id,
    collect_at: "DEVICE",
    allocation: { lines: [{ line_uid: lineUid, units: 1 }], fees: [] }
  },
  {
    method: "CARD",
    provider: "kushki",
    payment_id: card.id,
    collect_at: "DEVICE",
    allocation: null
  }
]);
await order.payIntent();
```

`getRemainingAmount()` is the order total minus settled tenders (and tenders that wait for injection). Some methods hold the first charge until the rest succeeds. Call `order.confirmPaymentsAfterInject()` after the order reaches the kitchen to confirm them. If the order cannot be completed, `order.reversePayments()` voids or refunds every paid tender. When a provider doesn't allow it, the tender is flagged for manual reversal and appears in monitoring.

To back out before anything is captured, call `order.reopen()`. It voids the tenders and returns a new `OPEN` order. Pass `genOrderId(storeId)` or any `genId()` when the new order should use that id; omit it to keep the generated id. `genId().hash(length, format)` sets the hash length (at least 8, default 12) and format (`alphanumeric`, `numeric`, or `alphabetic`). `genOrderId` always keeps the `ord-<store>-` prefix. `genId().literal("BB21921").hash(8, "numeric").build()` is an id like `BB2192100000001`. A hash shorter than 12 characters warns, and a numeric hash shorter than 16 digits warns; both are still returned. Shorter hashes collide more often. The request body field is `order_id`. A bad format is `400` `ORDER_ID_INVALID`. An id that already exists is `409` `ORDER_ID_EXISTS`.

## Pay with rewards

Vouchers, rewards or stored balance are a redeem. `payment_id` (the merchant's `REDEMPTION` payment) and `amount` are required. Pass `line_uid` to redeem a product line, or omit it for the whole order:

```typescript theme={null}
order.applyRedeem({
  reward_id: "rw_burger",
  quantity: 1,
  payment_id: rewardsPayment.id,
  amount: 10,
  line_uid: burger.uid
});
```

`amount` is the money total for the whole entry, not per unit: two combos worth 3 each are `amount: 6`. `quantity` is informational. When you remove some units with `removeRedeem`, the kept units keep their share of `amount`.

The redeem shows as the line (or order) discount. Line and order totals keep their full value. `before_taxes` (default `true`) caps the redeem at the pre-tax value, so with a burger of 10 + 15% tax the redeem covers 10 and the customer pays 1.50. With `before_taxes: false` it may cover the full 11.50. `redeemedTotal(line.redeem)` and `orderPayableTotal(order)` give what to show: the discount and what is left for the customer.

The SDK adds one `REDEMPTION` tender per `payment_id` for the capped amount, and `getTenders(method)` adds the customer's method for the rest. In the example that is `REDEMPTION` 10 and `CASH` 1.50, or `REDEMPTION` 11.50 alone. Redeems that share a `payment_id` go in the same tender, which lists each reward in `details.rewards` and sums their `amount` in `details.amount`. Redeems with different `payment_id`s get separate tenders.

## Terminal prompts and storage

Card-present packages need to ask the customer things (installments, account type, confirmation) and remember terminal counters between sessions. Give them your UI and storage once:

```typescript theme={null}
order.setPaymentUI({
  message: ({ text, kind }) => showBanner(text, kind),
  select: ({ title, options }) => openPicker(title, options),
  input: ({ label, type }) => openInput(label, type),
  confirm: ({ text }) => openConfirm(text),
  action: action => showAction(action) // QR, deeplink, or redirect
});

order.setPaymentStorage({
  get: key => AsyncStorage.getItem(key),
  set: (key, value) => AsyncStorage.setItem(key, value),
  remove: key => AsyncStorage.removeItem(key)
});
```

Packages never draw UI themselves. Your channel decides how prompts look.

## Terminal operations

Kiosk admin screens can run terminal operations such as a connection test or batch close:

```typescript theme={null}
await order.runPaymentTerminal(payment, "test");
await order.runPaymentTerminal(payment, "closeBatch");
```

Only one operation runs on a terminal at a time. A failed terminal operation is reported to [monitoring](https://firespark.cloud/docs/guides/monitoring-alerts.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.