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

# Payments

> Providers, methods, payment intents, tenders, mixed payments, and reopen.

Fire spark keeps one **global catalog** of payment providers and methods. A merchant turns a method on by creating a **payment** in the dashboard. An order records how it is paid as **payment intents**. Each intent is a list of **tenders**. Tenders are the source of truth for method, amount, status, and where the money is collected.

## The model

| Piece | What it is | Who owns it |
| - | - | - |
| **Provider** | The company that moves the money (`firespark`, `kushki`, `transbank`, …) | Fire spark catalog |
| **Payment method** | What the customer uses: `CASH`, `CARD`, `PIN_PAD`, `WALLET`, `BANK_TRANSFER`, `FINANCING`, or `REDEMPTION` | Fire spark catalog |
| **Payment** | A merchant's activation of one method: credentials, environment, rules, and where it applies | Merchant |
| **Payment intent** | One checkout's tenders, plus totals and a rules snapshot | Order |
| **Tender** | One method's share of that checkout | Payment intent |

The method types mean:

* `CASH`: collected by staff at the counter or at the door.
* `CARD`: card not present (web or app checkout).
* `PIN_PAD`: card present, on a terminal next to the kiosk or till.
* `WALLET`: QR or app wallets.
* `BANK_TRANSFER`: the customer pays from their bank and the channel verifies it.
* `FINANCING`: buy now, pay later. It covers part of the order; the rest is paid with another method.
* `REDEMPTION`: vouchers, rewards or stored balance. It is not a method the customer picks at checkout. It comes from a redeem that names the merchant's `REDEMPTION` payment.

`REDEMPTION` is not a real method. Real methods are everything else. `is_mixed` is true when an intent has two or more real tenders. A redemption next to one card does not make the intent mixed. `primary_tender_id` is the first real tender. It is `null` only when every tender is `REDEMPTION`.

An order has at most one `DRAFT` or `LOCKED` intent. Older intents stay on `payment_intents` as `COMPLETED` or `ABANDONED`.

## Where a tender is collected

`collect_at` is `DEVICE`, `KIOSK`, `COUNTER`, `DOOR`, or `OTHER`.

| Scope | `collect_at` | Who charges it |
| - | - | - |
| `digital` | `DEVICE`, and every `REDEMPTION` tender | The channel, on the device |
| `in_person` | `COUNTER`, `KIOSK`, `DOOR`, `OTHER` | A person at the store or the door |

A fulfillment `PAYING` node can set `payment_scope` to `digital` or `in_person`. With no scope, the node waits for every live tender. The node advances to `PAID` when every tender in that scope is settled. An empty scope is paid as soon as the order enters it. A redemption-only scope is paid once those rewards are reserved (`PENDING` with a reservation counts as settled).

## Intent statuses

| Status | Meaning |
| - | - |
| `DRAFT` | Saved on an `OPEN` cart. Replace it with [Save payment intent](https://firespark.cloud/docs/storefront-api/orders/\[id]/payment-intent/put). |
| `LOCKED` | Frozen at [Place order](https://firespark.cloud/docs/storefront-api/orders/\[id]/place/post). The workflow charges these tenders. |
| `COMPLETED` | The locked intent's payment status became `PAID` or `REFUNDED`. |
| `ABANDONED` | Replaced before place, or left behind when place built a new intent. |

## Tender statuses

These are also the order's `payment_status`, derived from the live tenders of the active intent.

| Status | Meaning |
| - | - |
| `IDLE` | Nothing charged yet. Cash stays here until the store collects. |
| `PENDING` | Waiting on the provider or the customer. |
| `PAID` | Approved. |
| `FAILED` | Declined or errored. The tender keeps the error. |
| `CANCELLED` | Voided before settlement. |
| `REFUNDED` | Money returned after settlement. A partial refund stays `PAID` and increases `refunded_total`. |

Derivation: no tenders is `IDLE`. Any `FAILED` is `FAILED`. All `CANCELLED` is `CANCELLED`. All `REFUNDED` or `CANCELLED` is `REFUNDED`. All settled is `PAID`. All `IDLE` is `IDLE`. Anything else is `PENDING`.

Every tender has an `attempt_reference`. Retries use a new one. The same reference never creates a second charge.

## Mixed payments and allocation

Send tenders before place. The server computes each tender's `amount` and `allocation.amounts`. You send `allocation` without `amounts`.

`allocation` is `{ lines: [{ line_uid, units? }], fees: [line_uid] }` or `null`.

* One real tender may send `allocation: null`. That tender covers whatever no other tender covers.
* A `REDEMPTION` tender always sends `allocation: null`. It covers an amount, not lines.
* With more than one real tender, a real tender that omits allocation is rejected (`PAYMENT_UNALLOCATED_LINES`).
* Tip and delivery sit only on the primary real tender.
* At most one tender is `CASH`.

## Redemptions

A redeem is a voucher, reward or balance. Every redeem needs `payment_id` (an active `REDEMPTION` payment of the merchant) and `amount` (the money it covers for the whole entry, not per unit). `quantity` is informational. A redeem without `payment_id` is rejected with `REDEEM_PAYMENT_REQUIRED`.

The redeem shows as a discount on its line (`lines[].redeem`) or on the order (`redeem`), but line and order money never change: `group_total`, the taxes and `totals.total` stay full. The `REDEMPTION` tender pays the redeemed amount, and the customer's method pays the rest.

`before_taxes` (default `true`) caps a redeem at the pre-tax value of its line or order, so the taxes stay for the customer's method. With `before_taxes: false` the redeem may also cover the taxes. Every redeem is also capped at the order total minus tip and delivery.

| Product 10 + 15% tax (total 11.50), redeem of 10 or more | Tenders |
| - | - |
| `before_taxes: true` (default) | `REDEMPTION` 10 and the customer's method (for example `CASH`) 1.50 |
| `before_taxes: false` | `REDEMPTION` 11.50 only |

Redeems that share a `payment_id` become one `REDEMPTION` tender. `details.rewards` lists each reward. Each reward has its capped `amount`, and `details.amount` is their sum. The tender amount is that sum. A tender that does not match the grouped redeems is `PAYMENT_REDEMPTION_MISMATCH`.

Each redeem also has a step in `redemption_steps`, in application order: root-line redeems in line order, then order redeems. A step carries `payment_id`, `before_taxes`, `target` (`LINE` with `target_id`, or `ORDER`) and `discount_amount`, the capped `amount`. `before` and `after` hold `value` (what the customer's method still pays), `discount_amount` (covered so far) and `discount_rate` (covered / total); `LINE` steps also have `target_before` and `target_after`. So the steps of a `payment_id` add up to its `REDEMPTION` tender, and the line steps add up to the discount shown on the line. The server recomputes and stores the steps. Validate and place reject client steps that list other redeems or another order (`REDEMPTION_STEPS_MISMATCH`) or other money (`REDEMPTION_MISMATCH`). `redemptionSteps(order)` in the SDK gives the same steps.

```json theme={null}
{
  "reward_id": "J9Y8PT9WVA",
  "reward_quantity": 1,
  "payment_id": "c32bc0b5-39bd-4448-88b1-a3ebbbb5dd3e",
  "before_taxes": true,
  "target": "LINE",
  "target_id": "11111111-1111-4111-8111-111111111111",
  "action": "UPDATE",
  "discount_amount": 10,
  "before": { "value": 11.5, "quantity": 1, "discount_amount": 0, "discount_rate": 0 },
  "after": { "value": 1.5, "quantity": 1, "discount_amount": 10, "discount_rate": 0.8695652173913043 },
  "target_before": { "value": 11.5, "quantity": 1, "discount_amount": 0, "discount_rate": 0 },
  "target_after": { "value": 1.5, "quantity": 1, "discount_amount": 10, "discount_rate": 0.8695652173913043 }
}
```

A tender carries no discount data. Its `amount` is `{ currency, total }`: what that tender pays. A `REDEMPTION` tender adds only what void and refund need: `payment_id` and `details` (`rewards[]` with `reward_id`, `quantity` and `amount`, plus `details.amount`).

`services.payments.list` hides `REDEMPTION` payments. Pass `{ includeRedemptions: true }` when a redeem flow needs them. That flag is not a query parameter: the API still returns those payments, and the SDK filters them.

## Reopen

[Reopen order](https://firespark.cloud/docs/storefront-api/orders/\[id]/reopen/post) voids unpaid tenders (reserved redemptions are released), cancels the source as `VOIDED`, and returns a new `OPEN` order. The link is `metadata.reopen`: `{ as_order_id }` on the source and `{ from_order_id }` on the new order. The new order can carry both once it is reopened again. A live real tender that is already `PAID` or `REFUNDED` returns `400` `ORDER_PAYMENT_CAPTURED`. An order already `VOIDED` / `REVERSED` / legacy `CANCELLED` and never fulfilled is reopened as-is once every real tender is `CANCELLED`, `FAILED`, or fully `REFUNDED` (for example after a failed injection reversed on the device); a `PAID` tender there is still `400` and an `IDLE` / `PENDING` one is `422`. Optional `order_id` can be any id `genId` builds (`isGenId`: `[A-Za-z0-9-]`, a run of at least 8 `[0-9a-z]`, up to 64 characters; `genOrderId` is one option). Example: `BB2192100000001`. A bad format is `400` `ORDER_ID_INVALID` and a taken id is `409` `ORDER_ID_EXISTS`. Omit `order_id` and behavior stays the same. `REDEMPTION` does not block reopen. On a kiosk, the Cancel button during the checkout-release wait calls this endpoint.

## Related

> [card] [Supported payment methods](https://firespark.cloud/docs/integrations/payments/index.md)
>
> What is available today and what is coming

> [card] [Use payments in the SDK](https://firespark.cloud/docs/guides/payments/use-payments-in-the-sdk.md)
>
> Install a package and charge an order

> [card] [Orders](https://firespark.cloud/docs/concepts/orders.md)
>
> What a payment intent belongs to

> [card] [Payment errors](https://firespark.cloud/docs/guides/payments/errors.md)
>
> Provider codes, retries, and what to show the customer


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