Skip to main content

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.

This page covers checkout-only usage of the frontend template engine: order summary, totals, form fields, coupon, order bumps, and payment. For cart items and quantity controls, see Cart.
checkout exists only on checkout pages. Guard shared templates with <template-if data-condition="checkout"> when using checkout.*.

Order summary: single-product vs cart

Checkout can show either a single-product block (one product, hero image + title) or a cart block (list of items with thumbnails and qty). Use the scope flags to choose.
ConditionWhen it’s trueUse in template
is_single_productOne product in cartcheckout.product_image, checkout.product_title, checkout.product_price, checkout.retail_price
is_cartMultiple items<template-foreach data-each="item in cartItems"> with [[ item.image ]], [[ item.name ]], [[ item.total ]]
Do not use cartItems[0] in the single-product block. Use checkout.product_* keys only.
<template-if data-condition="is_single_product">
  <div class="order-summary-single">
    <img src="[[ checkout.product_image ]]" alt="[[ checkout.product_title ]]" />
    <h2>[[ checkout.product_title ]]</h2>
    <span>[[ checkout.product_price ]]</span>
    <span>[[ checkout.retail_price ]]</span>
  </div>
</template-if>
<template-else-if data-condition="is_cart">
  <template-foreach data-each="item in cartItems">
    <div class="order-summary-line">
      <img src="[[ item.image ]]" alt="[[ item.name ]]" />
      <span>[[ item.name ]]</span>
      <span>[[ item.total ]]</span>
    </div>
  </template-foreach>
</template-else-if>
Equivalent binding-style variant (often easier to preview/edit in the page builder):
<template-if data-condition="is_single_product">
  <div class="order-summary-single">
    <img ef-src="checkout.product_image" ef-alt="checkout.product_title" />
    <h2><span ef-text="checkout.product_title">Product Title</span></h2>
    <span ef-text="checkout.product_price">$0.00</span>
    <span ef-text="checkout.retail_price">$0.00</span>
  </div>
</template-if>
You can add ef-* attributes from the Data Attributes section (right sidebar) when a component is selected.

Subscription products

On single-product checkout pages, checkout.is_subscription and checkout.subscription are set server-side and available immediately when the page loads — no async fetch required.
KeyTypeDescription
checkout.is_subscriptionbooleantrue when the product is a subscription
checkout.subscriptionobject | nullBilling cadence details — null for one-time products
checkout.subscription.frequencynumberHow often the subscription bills (e.g. 3)
checkout.subscription.frequency_unitstring"day", "week", "month", or "year"
checkout.subscription.trial_daysnumberFree trial length in days (0 = no trial)
checkout.subscription.first_charge_freebooleantrue when the first cycle is free
Use template-if to show subscription-specific UI only when the product is a subscription:
<template-if data-condition="checkout.is_subscription">
  <p class="billing-notice">
    You'll be billed every [[ checkout.subscription.frequency ]] [[ checkout.subscription.frequency_unit ]].
  </p>
</template-if>

<template-if data-condition="checkout.is_subscription && checkout.subscription.trial_days > 0">
  <p class="trial-notice">
    Your [[ checkout.subscription.trial_days ]]-day free trial starts today.
  </p>
</template-if>

<template-if data-condition="checkout.is_subscription && checkout.subscription.first_charge_free">
  <p class="trial-notice">First [[ checkout.subscription.frequency_unit ]] is free.</p>
</template-if>
For cart-mode checkout (multiple items), subscription info is on each cart item instead: item.is_subscription and item.subscription. See Cart — Subscription details.

Totals (display + raw)

Checkout exposes the same totals keys in both modes (is_single_product and is_cart) so one layout can be reused for either flow. Use formatted keys for display ([[ checkout.total ]]). Use *_raw only for conditions/math.
Formatted keyRaw keyMeaning
checkout.product_pricecheckout.product_price_rawMain products amount (excludes bumps).
checkout.retail_pricecheckout.retail_price_rawRetail/original amount for main products.
checkout.bump_totalcheckout.bump_total_rawSelected order bumps total.
checkout.subtotal_before_discountcheckout.subtotal_before_discount_rawProduct + bumps before coupon discount.
checkout.discount_totalcheckout.discount_total_rawCoupon discount amount.
checkout.subtotal_after_discountcheckout.subtotal_after_discount_rawSubtotal after coupon discount, before shipping/tax.
checkout.subtotalcheckout.subtotal_rawBackward-compatible alias of subtotal_after_discount.
checkout.shippingcheckout.shipping_rawShipping amount.
checkout.taxcheckout.tax_rawTax amount.
checkout.totalcheckout.total_rawFinal grand total.
Recommended template bindings:
  • Subtotal row: [[ checkout.subtotal_after_discount ]]
  • Total row: [[ checkout.total ]]
  • Conditions: checkout.bump_total_raw > 0, checkout.tax_raw > 0, checkout.shipping_raw > 0

Form fields and validation

Binding fields

Use data-template-value="checkout.customer.<field>" on each input/select. Paths follow the checkout field spec (e.g. checkout.customer.email, checkout.customer.shipping_first_name).
  • Wrap each field in a container (e.g. .floating-label-group).
  • Add <div class="form-error" data-checkout-error="<id>"></div> so the form errors adapter can show validation messages.
  • Use checkout.countries and checkout.usStates for select options (each { value, label }).

Enforce country/state restrictions server-side

Use backend code to enforce checkout country/state restrictions on submit. Set checkout_settings in <script scope="backend"> with setVariable(...):
<script scope="backend">
setVariable('checkout_settings', {
  countries: ['US'],
  usStates: ['CA', 'TX', 'FL']
});
</script>
This does two things:
  • Restricts the rendered checkout.countries / checkout.usStates lists.
  • Enforces the same allowlist in process-checkout (invalid country/state returns validation errors).
Use code values in options:
<select name="shipping_country" data-template-value="checkout.customer.shipping_country">
  <option value="">Select Country</option>
  <template-foreach data-each="opt in checkout.countries">
    <option value="[[ opt.value ]]">[[ opt.label ]]</option>
  </template-foreach>
</select>

Shipping same as billing

  • Bind the checkbox with data-template-value="checkout.shipping_same_as_billing".
  • Wrap the shipping address block in <template-if data-condition="!checkout.shipping_same_as_billing">.
  • Use <template-else> to show a short message when shipping equals billing.

Billing section visibility

The entire billing info section can be wrapped in <template-if data-condition="checkout.shipping_same_as_billing == false"> so the billing block is hidden when “same as billing” is checked. Inside that section, use <template-if data-condition="!checkout.shipping_same_as_billing"> for the visible billing form and <template-else> for a short message plus hidden billing inputs so the backend still receives billing data.

Validation errors

  • errors is an object keyed by field id: errors['email'], errors['shipping_city'], etc.
  • The form errors adapter looks for [data-checkout-error="<id>"] and sets the message; it also adds .error to the input and .has-error to the wrapper.
  • You can show the message in the template: [[ errors[f.id] ]] or data-ef-text="errors['email']".

Coupon

ScopePurpose
query.couponURL has ?coupon=... — use for showing the “automatically applied” block.
checkout.couponApplied coupon: applied, code, name, value_formatted.
checkout.coupon_messageValidation error message (e.g. invalid code).
  • Apply button: data-apply-coupon (plugin binds to validate + refresh).
  • Clear button: data-clear-coupon; show when checkout.coupon.applied.
  • Error block: <template-if data-condition="checkout.coupon_message"> with [[ checkout.coupon_message ]].

Order bumps

ScopePurpose
checkout.bump_productsAll available bumps (each: code, name, description, price formatted, price_raw, image, added).
checkout.selectedBumpLinesSelected bumps (each: code, name, price, price_raw).
  • Display [[ bump.price ]] and [[ line.price ]] as-is (already formatted).
  • Checkbox pattern: Use data-bump-code, data-bump-checkbox, data-bump-toggle and name="bump", value="[[ bump.code ]]" so the bump plugin can toggle and style. See Cart for the full offer vs added pattern.
  • Button + @click pattern: Alternatively, use <button @click="bump.added = true">…</button> for “Add to order” and <button @click="bump.added = false">…</button> for “Remove”. Mutating bump.added triggers re-render. Both checkbox + data-bump-* and button + @click are valid.

Payment panel

Pattern A (generic): Replace the payment block with only:
<checkout-cc-panel></checkout-cc-panel>
The app injects the full payment UI (method choice, card inputs, etc.). Do not keep duplicate payment method pickers or card sections in the template. Pattern B (explicit gateway): When the source already has an explicit gateway structure (e.g. NMI), preserve it. Use a wrapper <div class="checkout-cc-panel" data-gateway="nmi"> (or other gateway). Inside: (1) optional cardholder name in <template-if data-condition="!(checkout && checkout.customer && checkout.customer.first_name)"> with data-template-value="checkout.customer.cardholder_name" and <div class="form-error" id="cc-name-error"></div>; (2) a secure-card-element div with data-brand-id, data-domain-id, data-merchant-code, data-accepted-cards, plus a sibling <div class="form-error" id="nmi-card-errors"></div> for card errors.

Smart field detection

The payment panel uses isFieldOnPage() to check whether specific checkout fields are present on the page. Based on what’s missing, it automatically adds fields inside the card section:
  • No first_name/last_name on page → shows “Cardholder Name” input
  • No billing_address/shipping_address on page → shows billing details (name, country, address)
You can use isFieldOnPage() in your own template-if conditions:
<template-if data-condition="!isFieldOnPage('checkout.customer.first_name')">
  <p>Name will be collected in the payment panel.</p>
</template-if>
The function checks for visible, non-hidden inputs bound via data-template-value, data-ef-value, or name attribute. Elements inside the payment panel itself are excluded to prevent self-referencing.

Wallet (Apple Pay / Google Pay)

Scope key

KeyTypeDescription
checkout.wallet_available{ applePay: bool, googlePay: bool }Which wallets are available on the current device/browser. Updated after CollectJS initializes.
Use in template conditions to show wallet-specific content:
<template-if data-condition="checkout.wallet_available.applePay">
  <p class="wallet-hint">You can pay instantly with Apple Pay.</p>
</template-if>
<template-if data-condition="checkout.wallet_available.googlePay">
  <p class="wallet-hint">Google Pay is available on this device.</p>
</template-if>

Wallet-first layout

Wallet buttons are rendered inside <checkout-cc-panel>. To make them prominent (e.g. for quiz funnels), place the payment panel above the shipping address section in your template. Both layouts use the same blocks and backend processing.
<!-- Email + phone first -->
<section>...</section>

<!-- Payment panel early = wallet buttons appear above shipping -->
<checkout-cc-panel></checkout-cc-panel>

<!-- Shipping address below -->
<section>...</section>

<!-- Submit button -->
<button type="submit" data-checkout-submit>COMPLETE ORDER</button>
See How to design a checkout page — Example 2 for a full copy-paste template.

ef.checkout API

For programmatic wallet control, a small JavaScript API is available on window.ef.checkout:
  • ef.checkout.walletAvailable — same data as checkout.wallet_available scope key
  • ef.checkout.payWithWallet(method) — triggers 'applePay' or 'googlePay' programmatically
  • ef.checkout.onWalletComplete(callback) — fires after wallet payment attempt
Full reference: Checkout functionality reference — window.ef.checkout API.

Summary

  • Use is_single_product / is_cart and checkout.product_* vs cartItems for order summary.
  • Subscriptions: checkout.is_subscription + checkout.subscription on single-product pages (server-side, available immediately); item.is_subscription + item.subscription on cart items.
  • Prefer explicit subtotal keys: checkout.subtotal_before_discount / checkout.subtotal_after_discount.
  • checkout.subtotal remains as a backward-compatible alias of checkout.subtotal_after_discount.
  • Use _raw keys only for conditions/math or explicit formatting with formatPrice.
  • Bind fields with data-template-value, use data-checkout-error and errors for validation.
  • Coupon: query.coupon, checkout.coupon, checkout.coupon_message; data-apply-coupon, data-clear-coupon.
  • Bumps: checkout.bump_products, checkout.selectedBumpLines; data-bump-* attributes.
  • Payment: Pattern A<checkout-cc-panel></checkout-cc-panel> only; Pattern B — explicit gateway div with data-gateway, optional cardholder, secure-card-element.
  • Wallet: checkout.wallet_available for conditional content; ef.checkout.payWithWallet() for programmatic triggers; place <checkout-cc-panel> above shipping for wallet-first layouts.