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:
- Open Page List → Edit Page
- Enable Is checkout page
- 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.
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.
<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:
- Values are normalized (e.g. uppercase) and validated against built-in lists.
- Invalid entries are dropped.
- If nothing valid remains, the full default lists are used (not restricted).
- 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
<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.countries = ['US', 'CA', 'GB'];
</script>
4.3 Example: US states only
<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.
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
<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
- 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).
- Each qualifying
POST /process-checkout is counted per visitor IP inside a checkout-specific bucket (not mixed with generic page-view rate limits).
- 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.
- 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:
<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.
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:
<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:
<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 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:
<style>
:root {
--ef-primary: #FF4500; /* your brand color */
}
</style>
You can also scope it to a specific container instead of :root:
<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:
<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.
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 |