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
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 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.
{ "locale": "ar" }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.
{
"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
By default, every payment method you've enabled on your merchant account is offered to the customer. Two ways to narrow the list per session.
| Field | Use it when |
|---|---|
paymentMethodTypes | You want a one-off list for this session: ["card", "valu"]. |
paymentMethodConfigurationId | You've saved a named configuration in the dashboard and want to reference it across many sessions. |
The two are mutually exclusive: send one or the other, never both. Sending neither uses your merchant's default configuration.
// Restrict this session to card and Valu only
{ "paymentMethodTypes": ["card", "valu"] }Sending a method that isn't enabled on your account is rejected with a clear error at session create. The session response carries the resolved paymentMethodTypes array with the display name, category, refund support, and any min/max amount limits XPay will enforce, so you don't need to maintain that mapping yourself.
The values you can pass in paymentMethodTypes:
| Value | Method |
|---|---|
card | Credit and debit cards |
valu, sympl, tabby, tamara | BNPL providers |
fawry, aman | Cash-collection networks |
vodafone_cash, etisalat_cash, orange_cash, we_pay, instapay | Mobile wallets |
apple_pay, google_pay, samsung_pay | Wallet pay buttons |
bank_transfer | Direct bank transfer |
cash_on_delivery | Cash on delivery |
Whether each value actually appears at checkout depends on whether you've enabled it on your merchant account and whether the session amount falls within the method's processor limits.
Promotion codes and discounts
Two related fields. Both work with CouponAPI 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.
// 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 withallowPromotionCodesordiscounts. 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
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.
feeConfig overrides your merchant-default fee handling for a single session. Three fields.
{
"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
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.
{ "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).
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.
{
"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.completedwebhook payload, indata.object.metadata. - Forwarded to the Payment Intent created at payment time, so it's also on
paymentIntent.metadataand 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
After completion
Send the customer back to your site, or let XPay show a hosted thank-you page. Plus how to handle failed payments and why the webhook is the source of truth.
Refunds
Reverse a successful payment in full or in part. One POST against the Payment Intent or Charge, plus an optional GET to read it back.