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

# Save payment intent

> Create or replace the draft payment intent, including every tender, before place.

Creates or replaces the `DRAFT` payment intent on an `OPEN` order. `tenders` is the list of payments for this checkout. The array order is `sequence`. The first real tender (anything except `REDEMPTION`) is the primary tender.

The server computes each tender's `amount` and `allocation.amounts`. Send `allocation` without `amounts`. A single real tender may send `allocation: null`, which means it covers whatever no other tender covers. A `REDEMPTION` tender always sends `allocation: null`: it covers an amount, not lines.

> [!NOTE]
>
> Requires a Storefront API access token from [Token
> exchange](https://firespark.cloud/docs/storefront-api/oauth/exchange/post.md).

In `@fire-spark/storefront`, call `order.setTenders(tenders)`. `REDEMPTION` tenders come from `order.applyRedeem` with a `payment_id`, not from this list. `order.placeOrder()` saves the draft and then places with its `intent_id`.

## Path parameters

| Parameter | Required | Description |
| - | - | - |
| `id` | Yes | External order identifier |

## Request body

| Field | Required | Type | Description |
| - | - | - | - |
| `tenders` | Yes | array | At least one tender. See below. |
| `metadata` | No | object | Stored on the intent. Defaults to `{}`. |

Each tender:

| Field | Required | Description |
| - | - | - |
| `method` | Yes | `CASH`, `CARD`, `BANK_TRANSFER`, `WALLET`, `PIN_PAD`, `FINANCING`, or `REDEMPTION` |
| `provider` | Yes | Catalog provider id, for example `firespark` or `kushki` |
| `collect_at` | Yes | `DEVICE`, `KIOSK`, `COUNTER`, `DOOR`, or `OTHER` |
| `payment_id` | For `REDEMPTION` | Merchant payment id. Required on `REDEMPTION`. |
| `allocation` | No | `{ lines: [{ line_uid, units? }], fees: [line_uid] }` or `null` |
| `details` | For `REDEMPTION` | Method-specific data. `details.method` must match `method`. |
| `id` | No | Stable tender id. Omitted ids are generated. |
| `attempt_reference` | No | Idempotency key for the current attempt, 10–64 characters. |

`collect_at: DEVICE` is charged on the device. `COUNTER`, `KIOSK`, `DOOR`, and `OTHER` are collected in person. Tip and delivery land on the primary real tender.

One `REDEMPTION` tender per `payment_id`. `details.rewards[]` lists each reward. Each reward has its `amount`, and `details.amount` is the sum of `rewards`. `amount` on a redeem is the money total for the whole entry, not per unit, trimmed to its `before_taxes` cap. The real tenders pay the rest of the order total: with `before_taxes` (default), at least the taxes.

```bash theme={null}
curl -X PUT "https://firespark.cloud/api/storefront/v1/orders/ord-48291/payment-intent" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tenders": [
      {
        "method": "CARD",
        "provider": "kushki",
        "collect_at": "DEVICE",
        "allocation": {
          "lines": [{ "line_uid": "f6a7b8c9-d0e1-2345-f678-901234567890" }],
          "fees": []
        }
      },
      {
        "method": "CASH",
        "provider": "firespark",
        "collect_at": "DOOR",
        "allocation": null
      }
    ]
  }'
```

## Response

Returns `200` with the order. The active intent has `status: DRAFT`, `is_mixed: true` when there are two or more real tenders, and `primary_tender_id` set to the first real tender.

## Error responses

Payment errors return `{ "error": "...", "details": { "code": "..." } }`. `422` unless noted.

| `details.code` | When |
| - | - |
| `PAYMENT_INTENT_LOCKED` | The order is not `OPEN`, or the intent is already `LOCKED` (`409`) |
| `PAYMENT_INTENT_STALE` | The order total changed after the intent was saved (`409`) |
| `MIXED_PAYMENTS_DISABLED` | Two or more real methods while mixed payments are off. `details.reason` is `RULE_DISABLED`, `KILL_SWITCH`, or `WORKFLOW_NO_MIXED_LANE` |
| `PAYMENT_TOO_MANY_METHODS` | More real methods than the rule allows |
| `PAYMENT_TOO_MANY_ITEMS` | A method covers more items than its cap |
| `PAYMENT_AMOUNT_EXCEEDED` | A method's total is over its cap |
| `PAYMENT_UNALLOCATED_LINES` | More than one real tender omits `allocation`, or a redemption-only intent does not cover the order |
| `PAYMENT_INVALID_ALLOCATION` | A line, unit, or fee does not belong on that tender |
| `PAYMENT_INVALID_PRIMARY` | Discounts leave the primary tender below zero |
| `PAYMENT_LINE_NOT_SPLITTABLE` | A line's amounts cannot be split by units |
| `PAYMENT_REDEMPTION_EXCEEDED` | Redemptions exceed the order total minus tip and delivery |
| `PAYMENT_REDEMPTION_MISMATCH` | A `REDEMPTION` tender does not match the redeems for its `payment_id` |
| `REDEEM_PAYMENT_METHOD_NOT_CONFIGURED` | `redeem.payment_id` is missing or is not a `REDEMPTION` payment. `details.reason` is `NOT_FOUND` (also when the payment is not active) or `NOT_REDEMPTION` |
| `PAYMENT_METHOD_UNAVAILABLE` | The payment does not apply to this order |
| `PAYMENT_INVALID_COMBINATION` | Method and payment do not match, or the order has more than one cash tender |
| `404` | Order not found |


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