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

# Checkout functionality reference

> Canonical reference for ElasticFunnels checkout: scope, fields, checkout_settings, abuse protection, submit flow, and redirects.

This page is the **source of truth** for how ElasticFunnels checkout works: configuration keys, server behavior, and what visitors experience.

For a **step-by-step page build** (HTML patterns, wizard layout, copy-paste examples), use [How to design a checkout page](/guides/how-to-design-a-checkout-page). That guide defers to this page for exact semantics of `checkout_settings` and security options.

For **template syntax** (`[[ ]]`, bindings, `checkout.*` in the page builder), see [Frontend Template Engine: Checkout](/frontend-template-engine/checkout).

***

## 1) Enable checkout on a page

Checkout behavior runs only when the page is marked as a checkout page:

1. Open **Page List** → **Edit Page**
2. Enable **Is checkout page**
3. Save

If this is off, `checkout` scope data, submit handling, payment processing, and post-purchase redirects may not run as documented here.

***

## 2) Checkout scope (frontend)

On checkout pages, the `checkout` scope is available to templates and bindings.

Common keys:

| Area      | Keys                                                                                                                        |
| --------- | --------------------------------------------------------------------------------------------------------------------------- |
| Customer  | `checkout.customer.*` (names, email, phone, shipping/billing address fields)                                                |
| Geography | `checkout.countries`, `checkout.usStates` (dropdown options)                                                                |
| Commerce  | `checkout.coupon`, `checkout.coupon_message`, `checkout.bump_products`, `checkout.selectedBumpLines`                        |
| Totals    | `checkout.subtotal`, `checkout.tax`, `checkout.shipping`, `checkout.total`, plus `*_raw` variants (see template engine doc) |
| Toggle    | `checkout.shipping_same_as_billing`                                                                                         |
| Wallets   | `checkout.wallet_available` (`{ applePay, googlePay }` — set after Collect.js loads)                                        |

Template engine details: [Checkout](/frontend-template-engine/checkout).

***

## 3) Supported form field names

These align with what the checkout runtime validates and submits (shipping-first; billing can mirror shipping).

**Shipping / personal (primary)**

* `first_name`, `last_name`, `email`
* `phone` (optional)
* `shipping_address`, `shipping_address2` (optional)
* `shipping_country`, `shipping_city`, `shipping_state`, `shipping_zip`
* Optional explicit shipping names: `shipping_first_name`, `shipping_last_name`

**Billing** (when shown; if empty, shipping values are used)

* `billing_first_name`, `billing_last_name`
* `billing_address`, `billing_address2`, `billing_country`, `billing_city`, `billing_state`, `billing_zip`

**Toggle**

* `shipping_same_as_billing` → `checkout.shipping_same_as_billing`

**Errors**

* Use `data-checkout-error="<field_name>"` matching the input `name` for inline validation messages.

***

## 4) `checkout_settings` (backend script)

Configure checkout using variables set in a **backend** script on the **same checkout page** (`<script scope="backend">`). The platform reads `checkout_settings` when the page is rendered.

```html theme={null}
<script scope="backend">
var checkout_settings = checkout_settings || {};
// Optional: restrict countries / US states (see below)
// Optional: checkout abuse protection (see section 5)
</script>
```

### 4.1 Countries and US states allowlist

Keys:

* `checkout_settings.countries` → drives `checkout.countries` in the UI
* `checkout_settings.usStates` → drives `checkout.usStates` for US state dropdowns

Allowed entry formats:

* Two-letter codes as strings: `'US'`, `'ca'`, `'tx'`
* `{ value: 'US' }`
* `{ value: 'US', label: 'United States' }`

Behavior:

1. Values are normalized (e.g. uppercase) and validated against built-in lists.
2. Invalid entries are dropped.
3. If nothing valid remains, the full default lists are used (not restricted).
4. The effective allowlist is stored in the visitor **session** and **re-validated on submit**, so blocked values cannot be forced with browser devtools alone.

### 4.2 Example: countries only

```html theme={null}
<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.countries = ['US', 'CA', 'GB'];
</script>
```

### 4.3 Example: US states only

```html theme={null}
<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.usStates = ['CA', 'TX', 'NY', 'FL'];
</script>
```

***

## 5) Checkout abuse protection (card-testing / velocity)

Optional **progressive** friction on **main sale** card checkout submissions (`POST /process-checkout`). It is **off by default** unless you set `checkout_settings.abuse_protection.enabled` to `true`.

<Info>
  This is separate from **site-wide** page rate limiting / HTML captcha on normal page views. Abuse protection here applies only to the checkout payment submit path when enabled on the checkout page.
</Info>

### 5.1 Configuration

```html theme={null}
<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.abuse_protection = {
  enabled: true,
  mode: 'progressive',
  thresholds: {
    captcha_requests: 8,
    hard_block_requests: 20,
    window_ms: 60000,
    block_duration_ms: 3600000
  }
};
</script>
```

| Key                                          | Type    | Default when omitted                               |
| -------------------------------------------- | ------- | -------------------------------------------------- |
| `checkout_settings.abuse_protection.enabled` | boolean | `false` (feature off)                              |
| `checkout_settings.abuse_protection.mode`    | string  | `"progressive"` (only mode documented)             |
| `thresholds.captcha_requests`                | integer | `8`                                                |
| `thresholds.hard_block_requests`             | integer | `20`                                               |
| `thresholds.window_ms`                       | integer | `60000` (1 minute)                                 |
| `thresholds.block_duration_ms`               | integer | platform default block window (typically one hour) |

You can omit `thresholds` entirely to use defaults. Partial overrides are merged with defaults for any key you specify.

### 5.2 What is protected

* **In scope:** Main sale submissions to **`POST /process-checkout`** when the request is **not** marked as an upsell (`type` ≠ `upsell`).
* **Out of scope:** One-click upsell charges, PayPal/Klarna flows that do not use the same submit path, and arbitrary API calls. Tighten other surfaces separately if needed.

### 5.3 Session lifecycle

1. When a visitor loads a checkout page, sanitized abuse settings are stored in session (with `brand_id`, checkout `page_id` from the render context, and a timestamp).
2. Each qualifying `POST /process-checkout` is counted per visitor IP inside a **checkout-specific** bucket (not mixed with generic page-view rate limits).
3. If settings are **stale** (e.g. older than 24 hours) or **brand / page** no longer match the request, the stored config is cleared and protection does not apply until the visitor loads checkout again.
4. After a successful **checkout captcha** verification, the visitor receives a **short-lived** “verified” flag for checkout (default **15 minutes**) and the checkout attempt counter for that IP is reset, so legitimate users can complete payment after solving the challenge.

### 5.4 Progressive behavior

Within `window_ms`, counting **checkout submit attempts** for the IP:

| Condition                                                   | HTTP     | Visitor experience                                                                                                                       |
| ----------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Below `captcha_requests`                                    | 200 path | Normal checkout continues.                                                                                                               |
| At or above `captcha_requests`, below `hard_block_requests` | `429`    | JSON response asks for captcha; checkout UI opens a **modal**, visitor solves math challenge, then checkout **resubmits** automatically. |
| At or above `hard_block_requests`                           | `403`    | Submit blocked for a period (`block_duration_ms`); message indicates too many attempts.                                                  |

Local and private IPs are not rate-limited by this checkout guard.

### 5.5 JSON response fields (checkout submit)

When abuse protection returns an error-shaped response, the JSON may include:

| Field              | Meaning                                                   |
| ------------------ | --------------------------------------------------------- |
| `success`          | `false`                                                   |
| `error`            | Human-readable message                                    |
| `error_code`       | Machine code (see below)                                  |
| `error_type`       | `"abuse_protection"` when from this feature               |
| `retry_allowed`    | `true` when captcha path may retry; `false` on hard block |
| `captcha_required` | `true` when the UI should show the captcha step           |
| `captcha_payload`  | `{ question, token }` for the modal challenge             |

**`error_code` values (submit / middleware)**

| Code                        | Typical HTTP | Meaning                                  |
| --------------------------- | ------------ | ---------------------------------------- |
| `CHECKOUT_CAPTCHA_REQUIRED` | 429          | Solve captcha, then retry submit.        |
| `CHECKOUT_HARD_BLOCKED`     | 403          | Too many attempts; wait before retrying. |

Captcha verification (handled inside checkout UI, not a merchant integration surface) may return `CHECKOUT_CAPTCHA_INVALID`, `CHECKOUT_CAPTCHA_DISABLED`, `CHECKOUT_CAPTCHA_SAVE_FAILED`, or `CHECKOUT_CAPTCHA_ERROR` if something goes wrong; the UI refreshes the challenge or shows an error.

### 5.6 Tuning and false positives

* Corporate NAT or shared IPs can concentrate traffic on one address; if legitimate buyers are challenged too often, **raise** `captcha_requests` or **widen** `window_ms`.
* For testing, use **low** thresholds only on a **sandbox** checkout or test brand.
* If protection “never fires,” confirm the checkout page was **loaded in the same browser session** before submit (session must contain the config).

***

## 6) Payment panel and submit wiring

Recommended card flow:

```html theme={null}
<checkout-cc-panel data-accepted-cards="visa,mastercard,amex,discover"></checkout-cc-panel>
<div data-checkout-message class="checkout-message"></div>
<button type="submit" data-checkout-submit>Pay now</button>
```

### `<checkout-cc-panel>` attributes

| Attribute              | Values                             | Default                         | Description                                                                                                                                                                                                                                                                |
| ---------------------- | ---------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-accepted-cards`  | Comma-separated card names         | `visa,mastercard,amex,discover` | Card brands shown in the payment tab and validated during input                                                                                                                                                                                                            |
| `data-applepay`        | `"false"` to disable               | enabled                         | When `"false"`, Apple Pay and Google Pay buttons are not rendered inside this panel. Use with `<checkout-apple-pay />` for standalone placement.                                                                                                                           |
| `data-applepay-layout` | `"tab"`, `"separated"`, `"inline"` | `"tab"`                         | Controls how Apple Pay is displayed. `"tab"` renders Apple Pay as a radio tab alongside Card/PayPal/Klarna. `"separated"` renders Apple Pay above the card panel with an "OR PAY ANOTHER WAY" divider. `"inline"` is the legacy layout (wallet buttons above card fields). |
| `data-paypal`          | Merchant ID                        | —                               | Enables PayPal tab (resolved server-side)                                                                                                                                                                                                                                  |
| `data-klarna`          | Merchant ID                        | —                               | Enables Klarna tab (resolved server-side)                                                                                                                                                                                                                                  |

The panel builds its UI client-side from these attributes and `efScope.checkout`. When PayPal or Klarna is configured, the panel renders as a tabbed accordion (PayPal / Klarna / Card). Otherwise it renders card fields only.

### Smart field detection

The card panel automatically detects which checkout fields are present on the page and fills in any gaps:

* **Cardholder name:** If `first_name` and `last_name` inputs are not on the page, a "Cardholder Name" field appears inside the card panel.
* **Billing details:** If no `billing_address` or `shipping_address` inputs are on the page, the card panel shows a billing details section (name, country, address line).
* **Auto-copy:** When billing fields are inside the card panel and no shipping fields exist on the page, billing values are automatically copied to shipping during submission (and vice versa).

This means a minimal checkout page with only email and the payment panel still collects all required billing information.

### Wallet contact field auto-detection

Apple Pay and Google Pay automatically detect which contact fields to request from the wallet based on which checkout fields are on the page:

| Page has                                | Wallet requests                            |
| --------------------------------------- | ------------------------------------------ |
| Shipping address fields                 | Shipping postal address                    |
| Billing address fields                  | Billing postal address                     |
| Name fields (`first_name`, `last_name`) | Name (in shipping or billing contact)      |
| Email field                             | Email                                      |
| Phone field                             | Phone                                      |
| None of the above (card-only)           | No contact fields — just the payment token |

No configuration is needed. For digital products that only need email and payment, the wallet skips address collection entirely.

### `<checkout-apple-pay />`

Standalone Apple Pay button placement. Renders a container where the Apple Pay button appears (when the customer's device supports it). Use this to position Apple Pay separately from the card panel:

```html theme={null}
<checkout-apple-pay />
<checkout-cc-panel data-accepted-cards="visa,mastercard,amex,discover" data-applepay="false"></checkout-cc-panel>
```

### Wallet conditional display

After the payment form loads, `checkout.wallet_available` is set with `{ applePay: true/false, googlePay: true/false }`. Use `template-if` to show or hide elements:

```html theme={null}
<template-if data-condition="checkout.wallet_available.applePay">
  <div style="text-align:center; padding: 20px;">
    <checkout-apple-pay />
    <p>Or fill out the form below</p>
  </div>
</template-if>

<checkout-cc-panel data-accepted-cards="visa,mastercard,amex,discover" data-applepay="false"></checkout-cc-panel>
```

See [Apple Pay and Google Pay](/merchants/apple-pay-google-pay) for full setup, configuration, and layout examples.

### Styling checkout fields

Checkout input fields (floating labels, focus rings, active label color) respond to a single CSS variable:

| Variable       | Default   | Controls                                                    |
| -------------- | --------- | ----------------------------------------------------------- |
| `--ef-primary` | `#3b82f6` | Focus border color, focus glow, active floating label color |

Set it in the page's `<style>` block to match your brand:

```html theme={null}
<style>
  :root {
    --ef-primary: #FF4500; /* your brand color */
  }
</style>
```

You can also scope it to a specific container instead of `:root`:

```html theme={null}
<style>
  .checkout-form-section {
    --ef-primary: #2b3a5c;
  }
</style>
```

All standard input fields (`ef-floating-input`, `ef-form-input`) and quiz fields (`ef-quiz-floating-input`) inherit the value automatically. No other changes are needed.

### Styling the wallet divider

The "or pay another way" divider between Apple Pay and the card panel uses the `ef-wallet-divider` classes. These are **not styled by the platform** — define them in your page's `<style>` block so each page can match its own design:

```html theme={null}
<style>
  .ef-wallet-divider {
    display: flex;
    align-items: center;
    margin: 1rem 0;
    gap: 0.75rem;
  }
  .ef-wallet-divider-line {
    flex: 1;
    height: 1px;
    background-color: #e5e7eb;
  }
  .ef-wallet-divider-text {
    color: #9ca3af;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
  }
</style>
```

***

## 7) After successful payment (redirects)

* If the server returns `redirectUrl`, the visitor is sent there.
* Otherwise, the default purchase confirmation route is used (query parameters typically include order and email).

Details: [Checkout redirect behavior](/funnels/checkout-redirect-behavior).

***

## 8) Troubleshooting

| Issue                             | What to check                                                                                      |
| --------------------------------- | -------------------------------------------------------------------------------------------------- |
| Country/state restriction ignored | `checkout_settings` on backend scope; valid codes; session + server enforcement after page load    |
| Abuse protection never triggers   | `enabled: true`; visitor opened checkout page first; thresholds not too high; not upsell-only flow |
| Too many captchas                 | Increase `captcha_requests` or `window_ms`; check shared IP / VPN concentration                    |
| Hard blocks too aggressive        | Increase `hard_block_requests` or `window_ms`; review `block_duration_ms`                          |

***

## Related docs

* [How to design a checkout page](/guides/how-to-design-a-checkout-page) — build walkthrough and HTML patterns
* [Frontend Template Engine: Checkout](/frontend-template-engine/checkout) — `checkout.*` in templates
* [Frontend Template Engine: Cart](/frontend-template-engine/cart) — cart lines, bumps, qty controls
* [Checkout redirect behavior](/funnels/checkout-redirect-behavior) — post-checkout routing
* [reCAPTCHA setup](/domains/recaptcha-setup) — site-wide captcha (separate from checkout abuse modal)
