# Advanced configuration (/en/integrate/checkout-session/advanced-configuration)

Submit button text, locale, branding, payment-method restrictions, promotion codes, fees, expiration, and metadata. Everything else you can set on a Checkout Session.

The other Checkout Session sub-pages cover the load-bearing fields: line items, the customer, what happens after payment. This page is the catch-all for the rest: how the page reads (`submitType`, `locale`, `brandingSettings`), what payment methods show (`paymentMethodTypes`, `paymentMethodConfigurationId`), what's added to the bill (`feeConfig`, `discounts`), how long the session stays open (`expiresAfterMinutes`), and the freeform key-value store you can attach (`metadata`).

Every field below is optional. Defaults come from your merchant settings or system defaults; sending nothing here is fine.

## Submit button text [#submit-button-text]

`submitType` controls the label on the pay button at the bottom of the hosted form. Default is `pay`.

| Value       | Button label |
| ----------- | ------------ |
| `pay`       | Pay          |
| `subscribe` | Subscribe    |
| `book`      | Book         |
| `donate`    | Donate       |

Use `donate` for fundraising pages, `book` for reservations or appointments, `subscribe` for any recurring sale framing. The default `pay` covers everything else.

`submitType` is rejected when `uiMode: "custom"` (Elements). In that mode you build your own button. It's also locked at session creation: `PATCH` doesn't accept it.

## Locale [#locale]

`locale` controls the language of the hosted checkout and the card-form iframe. Two values today.

| Value | Language |
| ----- | -------- |
| `en`  | English  |
| `ar`  | Arabic   |

Resolution at render time: the value on the session wins, then your merchant default locale (set in dashboard settings), then English. The SDK's runtime `locale` parameter (Drop-in / Elements) overrides everything when the page renders inside an SDK iframe.

```jsonc
{ "locale": "ar" }
```

## Branding [#branding]

`brandingSettings` overrides the dashboard-level brand defaults for one session. Anything you set here wins; anything you omit falls back to the merchant default. Colors merge key by key, so a session can override `primary` without erasing the merchant's `background`.

```jsonc
{
  "brandingSettings": {
    "colorMode": "light",
    "borderStyle": "rounded",
    "spacing": "normal",
    "inputSize": "medium",
    "inputStyle": "outlined",
    "formLayout": "spacious",
    "fontFamily": "Inter, sans-serif",
    "colors": {
      "primary": "#635bff",
      "background": "#ffffff",
      "foreground": "#0a0a0a",
    },
  },
}
```

The shape:

| Field         | Values                                      | Purpose                                               |
| ------------- | ------------------------------------------- | ----------------------------------------------------- |
| `colorMode`   | `light`, `dark`, `system` (default)         | Light vs dark vs follow the customer's OS preference. |
| `borderStyle` | `rounded` (default), `sharp`, `pill`        | Corner radius on inputs, buttons, cards.              |
| `spacing`     | `condensed`, `normal` (default), `spacious` | Vertical density of the page.                         |
| `inputSize`   | `small`, `medium` (default), `large`        | Form field height.                                    |
| `inputStyle`  | `flat`, `outlined` (default), `filled`      | Visual treatment of inputs.                           |
| `formLayout`  | `compact`, `spacious` (default)             | Layout density for the form column.                   |
| `fontFamily`  | CSS font-family value                       | Override the page font. Up to 512 characters.         |
| `colors`      | Object of hex colors (see below)            | Brand colors. Hex only.                               |

`colors` accepts the following keys, each a hex string (`#RGB`, `#RGBA`, `#RRGGBB`, or `#RRGGBBAA`; `#0000` for transparent):

`primary`, `primaryForeground`, `background`, `foreground`, `border`, `input`, `ring`, `muted`, `mutedForeground`, `accent`, `accentForeground`, `destructive`.

Color values that aren't hex are rejected at session creation. `rgb(...)`, named colors, and CSS variables aren't accepted.

For the merchant-side branding setup (logo, business name, favicon), see **Settings → Branding** in the dashboard. Those fields aren't overridable per session.

## Payment methods [#payment-methods]

Every method enabled on your account is already offered to the customer. Most integrations send neither field below.

To change which methods customers see, edit your default configuration under **Settings → Payment methods**. It applies to every new session, no deploy needed. See [Payment methods](/features/checkout-customization/payment-methods).

Two fields override it for one session.

| Field                          | Use it when                                                     |
| ------------------------------ | --------------------------------------------------------------- |
| `paymentMethodConfigurationId` | You keep more than one saved configuration. Pass its `pmc_` ID. |
| `paymentMethodTypes`           | You need a one-off list for a single session.                   |

Prefer the configuration ID. The method list inside the configuration stays editable in the dashboard, so changing it doesn't need a deploy.

The two are mutually exclusive: send one or the other, never both.

```jsonc
// Restrict this session to card and Valu only
{ "paymentMethodTypes": ["card", "valu"] }
```

Every value must already be enabled on your account, or the request fails with [`parameter_invalid`](/integrate/errors/api-error-codes#parameter_invalid). The error lists what you do have enabled. Check **Settings → Payment methods** before hardcoding an array.

The response returns each resolved method with its display name, category, refund support, and amount limits. If you passed an explicit list, methods whose limits exclude the session total are dropped from it. With a configuration, they appear at checkout but can't be selected.

## Promotion codes and discounts [#promotion-codes-and-discounts]

Two related fields. Both work with <ApiLink href="/api-reference/objects/coupon">Coupon</ApiLink> records you've created in the dashboard.

`allowPromotionCodes` (boolean, default `false`) shows an "Add promotion code" input on the hosted form. Customers type a promo code, the page validates it server-side, and the discount applies to the session totals immediately.

`discounts` (array, max 1) pre-applies a coupon or promotion code. Use this for personalized links or one-off discount campaigns where you don't want the customer to type anything.

```jsonc
// Customer enters their own code
{ "allowPromotionCodes": true }

// You apply a specific coupon
{ "discounts": [{ "coupon": "coupon_test_AbC123" }] }

// Or a specific promotion code
{ "discounts": [{ "promotionCode": "promo_test_xyz789" }] }
```

Either `coupon` or `promotionCode` per entry, never both. The session response carries the applied discount (with the coupon snapshot) under `discounts[]`, and `totalDetails.amountDiscount` reflects what came off.

Two restrictions worth knowing:

* **Custom-amount lines reject discounts.** A session with a `CUSTOM`-type line item can't combine with `allowPromotionCodes` or `discounts`. The customer chose the amount; layering a coupon breaks the contract.
* **Max one discount per session.** The current API rejects a second.

For the full coupon and promotion code surface (durations, redemption limits, customer restrictions), see the dashboard's **Catalog → Coupons** page.

## Fees and VAT pass-through [#fees-and-vat-pass-through]

<Callout type="warn">
  In compliance with regulations, fee pass-through (`feesPassThrough: true`) is gated on XPay
  approval. Contact your account manager to enable it on your account before using this setting.
</Callout>

`feeConfig` overrides your merchant-default fee handling for a single session. Three fields.

```jsonc
{
  "feeConfig": {
    "feesPassThrough": true,
    "vatCollectionEnabled": true,
    "vatCollectionRate": 1400,
  },
}
```

| Field                  | Effect                                                                                                                                                                             |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `feesPassThrough`      | When `true`, XPay's platform fee is added on top of the line-item totals so the customer pays it instead of you. When `false` (default), the fee comes out of the merchant payout. |
| `vatCollectionEnabled` | When `true`, your product VAT is added on top so the customer pays it. The amount is settled to you and tracked separately in your balance.                                        |
| `vatCollectionRate`    | VAT rate in basis points. `1400` = 14%. Required when `vatCollectionEnabled` is `true`. Range 0 to 10000.                                                                          |

Resolution: the session's `feeConfig` wins, then your merchant default fee config (set in dashboard fee settings), then the system default (no pass-through, no VAT). The session response carries a resolved `feeConfig` object with a `source` field telling you which level provided the values.

When `feesPassThrough` or `vatCollectionEnabled` is on, the session response also carries a `fees` object with the exact platform fee amount, percentage, and (for cards) BIN-aware breakdown. That's what you'd display in your own UI if you wanted to surface platform fee or VAT as separate line items.

## Expiration [#expiration]

`expiresAfterMinutes` controls how long the session stays open before XPay marks it `expired`. Default `1440` (24 hours). Minimum `30`. Locked at creation; `PATCH` doesn't accept it.

```jsonc
{ "expiresAfterMinutes": 60 }
```

When the timer runs out, the session's `status` flips to `expired`, the hosted page shows a terminal "you're all done here" state, and a `checkout.session.expired` webhook fires. You can also expire a session early with `POST /checkout/sessions/:id/expire` (covered in [Overview → Expiration](/integrate/checkout-session/overview#expiration)).

## Metadata [#metadata]

`metadata` is a freeform key-value map you attach to the session. XPay stores it verbatim and never reads it. It travels with the session everywhere it goes.

```jsonc
{
  "metadata": {
    "user_id": "u_42",
    "order_id": "ord_193",
    "campaign": "spring-sale",
  },
}
```

Where it shows up:

* On the session retrieve response (`GET /checkout/sessions/:id`).
* On the `checkout.session.completed` webhook payload, in `data.object.metadata`.
* Forwarded to the Payment Intent created at payment time, so it's also on `paymentIntent.metadata` and on every Charge under that PI.

Use it to thread your own IDs through the payment flow. Most useful pattern: set `metadata.user_id` so your webhook handler knows which of your users paid without joining tables.

Keep values to strings. Don't put PII or secrets here.

## Where to next [#where-to-next]

<Cards>
  <Card icon="<Webhook />" title="Set up a webhook endpoint" href="/integrate/webhooks/setting-up-an-endpoint">
    Receive `checkout.session.completed` and read the metadata you attached.
  </Card>

  <Card icon="<TimerReset />" title="After completion" href="/integrate/checkout-session/after-completion">
    Configure where the customer lands once payment succeeds.
  </Card>

  <Card icon="<Tag />" title="Line items & pricing" href="/integrate/checkout-session/line-items-and-pricing">
    Existing prices vs inline `priceData`, quantities, custom amounts.
  </Card>
</Cards>