Skip to main content
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. 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.

1) Enable checkout on a page

Checkout behavior runs only when the page is marked as a checkout page:
  1. Open Page ListEdit 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: Template engine details: Checkout.

3) Supported form field names

These align with what the checkout runtime validates and submits (shipping-first; billing can mirror shipping). Names Two name pairs exist and both are recognised:
  • shipping_first_name, shipping_last_name — what the Checkout Details builder block emits, and what the server requires (with fallbacks). Use these on shipping-collecting pages.
  • first_name, last_name — what <checkout-personal-details> emits, and the pair the card panel checks when deciding whether to inject its own “Cardholder Name” field.
The cardholder-name detector only looks for first_name / last_name. A page that collects shipping_first_name / shipping_last_name (the builder default) will therefore also get a redundant Cardholder Name field injected into the card panel. Add first_name / last_name inputs — hidden ones are fine — if you want to suppress it.
Where a name is missing, the server backfills it: personal ← shipping ← billing ← the cardholder name (split into first / last). Shipping / personal
  • email (required), phone (optional)
  • shipping_address, shipping_address2 (optional)
  • shipping_country (required, 2-letter, uppercased), shipping_city, shipping_state, shipping_zip
Client-side validation requires any of these that are present on the page; server-side, only email, shipping_country and the shipping name pair are strictly required — address, city, state and ZIP are accepted empty. shipping_country defaults to US when left blank.
Billing (when shown; if empty, shipping values are used)
  • billing_first_name, billing_last_name — never client-side required
  • billing_address, billing_address2, billing_country, billing_city, billing_state, billing_zip — validated client-side only when a billing field is visible; all optional server-side
Toggle
  • shipping_same_as_billingcheckout.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.

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

4.3 Example: US states only

4.4 Card panel billing fields

checkout_settings.billing_fields controls which inputs the card panel’s auto-injected billing block renders. It is surfaced to templates as checkout.panel_billing_fields.
If the resulting set is empty, the billing block is not rendered at all.

4.5 Wallet labels

checkout_settings.wallet is passed through to checkout.wallet_settings and used on the wallet payment sheet for subscriptions:

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

5.1 Configuration

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 (typeupsell).
  • 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: 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: error_code values (submit / middleware) 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:
Submission is formless: the checkout runtime never submits a <form>. It listens for a click on [data-checkout-submit] (or .checkout-button), calls preventDefault(), and collects every named / data-template-value field from the page wrapper. type="submit", method="POST" and form="..." are cosmetic.

<checkout-cc-panel> attributes

Written on the custom tag, these are read server-side and re-emitted onto the generated <div class="checkout-cc-panel">: The panel builds its UI client-side from these attributes and efScope.checkout. When PayPal, Klarna, or the Apple Pay tab is active, the panel renders as a tabbed accordion; otherwise it renders card fields only.
data-applepay-layout is not in this list. The client-side panel does read data-applepay-layout ("separated", "tab", "inline", or unset for auto), but the server-side tag expander does not forward it — the generated <div> carries only the attributes above. Setting it on <checkout-cc-panel> has no effect. Use <checkout-wallet /> for an above-the-panel wallet.
data-applepay="false" does not always suppress Google Pay. On NMI, a #googlePay container is still injected when the visitor is on Android. On Stripe, both wallets share one mount point, so it disables both.

Layout selection

With no wallet tag elsewhere on the page, the panel picks its layout automatically: Because the tab layout requires that no #applePay element already exists, adding <checkout-wallet /> anywhere on the page automatically stops the panel from rendering its own wallet tab.

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 or last_name is missing from the page, a “Cardholder Name” field appears inside the card panel. Note this checks only that pair — shipping_first_name / shipping_last_name do not satisfy it.
  • Billing block: If neither billing_zip nor shipping_zip is on the page, the card panel renders a billing block containing a Country select and a ZIP / postal code input. It never adds a name or street-address field. Which of the two appear is controlled by checkout_settings.billing_fields (section 4.4).
  • 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 enough billing information for AVS.

Wallet contact field auto-detection

NMI / Collect.js only. On Stripe, the payment request is always created with requestPayerName, requestPayerEmail and requestShipping enabled and no phone request, regardless of which fields are on the page.
Only first-name variants are checked; last_name alone does not trigger the name request. No configuration is needed — for digital products that only need email and payment, the wallet skips address collection entirely.

<checkout-wallet />

Standalone wallet placement. Renders <div class="ef-wallet-standalone"> containing both the #applePay and #googlePay containers, so the buttons can sit anywhere on the page:
Both are plain attributes, not data- prefixed. The tag may be self-closing. <checkout-apple-pay /> is the legacy single-wallet form of this tag. It accepts force="true" and renders only the Apple Pay container. Prefer <checkout-wallet /> on new pages.
<checkout-wallet> and <checkout-apple-pay> accept a self-closing form. The other checkout tags — <checkout-cc-panel>, <checkout-personal-details>, <checkout-details>, <checkout-bump>require an explicit closing tag. Written self-closing they are neither expanded nor stripped, and pass through to the browser as unknown markup.

Tag content handling

<checkout-personal-details> and <checkout-details> generate their own inputs (from data-fields and data-shipping-fields respectively) — you do not write the inputs yourself. <checkout-personal-details> attributes <checkout-details> attributes

Wallet conditional display

Prefer wallet_visible for layout decisions — it will not flicker on first paint the way wallet_available does:

window.ef.checkout API

A small JavaScript API for programmatic wallet control: See Apple Pay and 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: Set it in the page’s <style> block to match your brand:
You can also scope it to a specific container instead of :root:
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 divider between the wallet buttons and the card panel uses the ef-wallet-divider classes. The platform emits the text in lowercase — or pay another way in the separated layout, or pay with card in the inline layout — and applies no styling of its own, so define these classes in your page’s <style> block (the text-transform below is what makes it render uppercase):

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.

8) Troubleshooting