Checkout
The checkout component manages the entire checkout flow: collecting customer details, selecting shipping and payment methods, placing orders, and processing payments. It stores checkout state in the session and provides AJAX handlers for each step.
Component Declaration
[checkout]The Checkout component has no configurable properties. Add it to your checkout page.
Checkout Modes
The component supports two address models. The mode is detected automatically from what your theme posts, and it is sticky for the checkout session (readable through the checkout.hasSeparateAddresses() method).
Unified address (contact only)
The theme collects a single address block using the unprefixed contact fields (first_name, address_line1, and so on) via post_contact_details. As long as no shipping address is posted, the checkout stays unified: the shipping and billing addresses both fall back to these contact details, so the one unified address is used for everything. Billing fields are never required in this mode. This suits digital stores or any checkout that does not need separate addresses.
Separate addresses (shipping and billing)
The checkout switches to separate-address mode as soon as the theme posts a distinct address, and stays there. Either address can establish it:
- Posting a shipping address (
shipping_prefixed fields viapost_shipping_details) enters separate mode. Billing then reuses the shipping address unless the customer opts out by postingbilling_same_as_shipping = 0, which reveals a separate billing address (billing_prefixed fields viapost_billing_details). This is the familiar retail "Billing address same as shipping" pattern. - Posting a distinct billing address (
billing_fields withbilling_same_as_shipping = 0) also enters separate mode, even when no shipping address has been posted. This suits a "Bill to a different address" checkout where billing is the address that leads.
billing_same_as_shipping defaults to false, so posting billing_ fields means a distinct billing address: its fields are validated as required. The "same as shipping" option only applies when a shipping address was entered first; when billing leads, there is nothing to reuse and the billing address simply stands on its own. Selecting a saved billing address book entry also puts the checkout into separate mode.
The billing address resolves through the chain billing → shipping → contact, so a partially completed checkout still produces a valid billing address.
Page Variables
The component sets these page variables on every page load and after each AJAX request. Use them in your Twig templates to display checkout state.
Order Preview
| Variable | Type | Description |
|---|---|---|
order | Order | A mock order calculated from the current cart and checkout data (not saved to database) |
invoice | Invoice | The mock order's associated invoice |
items | CartItemCollection | Active cart items |
Contact and Billing
| Variable | Type | Description |
|---|---|---|
address | CheckoutAddress | The customer's contact details |
billingAddress | CheckoutAddress | The billing address. In separate mode it follows the shipping address unless a separate billing address is entered; in unified mode it follows the contact details |
billingSameAsShipping | boolean | Whether the billing address reuses the shipping address (false by default). Only meaningful in separate mode; true makes billingAddress follow the shipping address |
paymentMethod | PaymentMethod|null | The currently selected payment method |
paymentMethods | Collection | Payment methods available for this order |
hasPaymentMethod | boolean | Whether a payment method is selected |
allPaymentMethods | Collection | All enabled payment methods |
Store Credit
| Variable | Type | Description |
|---|---|---|
creditBalance | int | The logged-in customer's available store credit balance (0 if credit is disabled or guest) |
Coupon
| Variable | Type | Description |
|---|---|---|
couponCode | string | The currently applied coupon code |
Shipping
| Variable | Type | Description |
|---|---|---|
shippingRequired | boolean | Whether the cart contains shippable products |
shippingMethods | array | Shipping methods available for the current address |
shippingAddress | CheckoutAddress | The shipping address (falls back to contact details if not set separately) |
shippingMethod | ShippingMethod|null | The selected shipping method with quote |
hasShippingAddress | boolean | Whether a shipping location has been provided |
hasShippingMethod | boolean | Whether a shipping method is selected |
hasShippingQuote | boolean | Whether both address and method are set (quote is available) |
Twig API
checkout.user()
Returns the currently logged-in user, or null for guest checkout.
checkout.isCartEmpty()
Returns true if the cart has no items. Use this to show an empty cart message instead of the checkout form.
checkout.hasSeparateAddresses()
Returns true once the checkout is in separate-address mode (a distinct shipping or billing address has been posted) rather than using a single unified contact address. The mode is sticky for the checkout session, so a toggle bound to this keeps its state across AJAX refreshes.
AJAX Handlers
onRefreshCheckout
The primary checkout handler. It processes any pending checkout step data from the POST request, then refreshes the page variables. This is the handler used for navigating between checkout steps.
When called, onRefreshCheckout processes all post_* flags found in the request (see POST Parameters below), updates the checkout session, and returns the updated page variables for partial rendering.
<button
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'shipping' }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }">
Continue to Shipping
</button>If the cart contents have changed since the checkout started, onRefreshCheckout triggers a page refresh to recalculate totals. If the order has already been paid, it redirects to the receipt page.
onPlaceOrder
Creates the order, generates an invoice, and redirects the customer to the payment page. This is the final step before payment.
<button
data-request="checkout::onPlaceOrder"
class="btn btn-primary btn-lg">
Place Order
</button>Before creating the order, this handler processes any checkout step data in the request (contact details, addresses, methods). This allows you to submit all checkout data in a single request if desired.
If store credit covers the full order total (i.e., amount_due is zero), the order is marked as paid immediately and the customer is redirected to the receipt page, bypassing the payment gateway entirely.
Returns: A redirect to the payment page by default (or receipt page if fully covered by credit). If no_redirect is posted, returns a JSON object instead:
{
"order_hash": "abc123...",
"invoice_hash": "def456...",
"return_url": "/shop/payment/def456..."
}| Parameter | Type | Description |
|---|---|---|
no_redirect | boolean | Return JSON instead of redirecting |
onPrepareOrder
Creates the order without emptying the cart or finalizing. This is used for API integrations where the order may need amendments before payment.
Returns: A JSON object:
{
"order_hash": "abc123...",
"invoice_hash": "def456..."
}The order hash is stored in the session so subsequent calls to onPlaceOrder or onPrepareOrder update the existing order rather than creating a new one.
onPay
Processes the payment form submission on the payment page. This handler is used after the order has been placed and the customer is on the payment page.
| Parameter | Type | Description |
|---|---|---|
pay_from_profile | boolean | Use the customer's stored payment method instead of the posted form data |
{# Standard payment form #}
<form data-request="checkout::onPay">
<!-- Payment form fields rendered by the payment method driver -->
<button type="submit">Pay Now</button>
</form>
{# Pay with stored payment method #}
<button
data-request="checkout::onPay"
data-request-data="{ pay_from_profile: true }">
Pay with Stored Card
</button>On successful payment, the customer is redirected to the receipt page. The payment method driver may also return a custom redirect (e.g., to a third-party payment gateway).
onApplyCredit
Applies the logged-in customer's store credit to the order. The amount applied is the lesser of the customer's available balance and the order total. Requires store credit to be enabled in the Responsiv.Pay settings and the customer to be logged in.
<button
data-request="checkout::onApplyCredit"
data-request-update="{ 'shop-checkout/order-summary': '#shopCheckoutOrderSummary' }">
Apply Store Credit
</button>If the applied credit covers the full order total, onPlaceOrder will skip the payment gateway and mark the order as paid automatically.
onRemoveCredit
Removes store credit from the current checkout session.
<button
data-request="checkout::onRemoveCredit"
data-request-update="{ 'shop-checkout/order-summary': '#shopCheckoutOrderSummary' }">
Remove Store Credit
</button>POST Parameters
The checkout component processes POST parameters to update checkout state. Each parameter group is triggered by a corresponding flag.
Contact Details
Triggered by post_contact_details = true.
| Parameter | Type | Description |
|---|---|---|
first_name | string | Customer first name |
last_name | string | Customer last name |
email | string | Email address |
phone | string | Phone number |
company | string | Company name |
address_line1 | string | Street address |
address_line2 | string | Apartment, suite, etc. |
city | string | City |
zip | string | Postal code |
country_id | integer | Country ID |
state_id | integer | State ID |
tax_id_number | string | Tax ID / VAT number |
Address Book Preset
Triggered by post_address_book_preset = true. Loads a saved address from the customer's address book into the contact details (requires RainLab.UserPlus). The selected address_book_id is remembered across AJAX refreshes rather than resetting to "New address".
| Parameter | Type | Description |
|---|---|---|
address_book_id | integer | The saved address ID to load |
Billing Address Book Preset
Triggered by post_billing_address_book_preset = true. Loads a saved address from the customer's address book into the billing address, mirroring the contact address book preset (requires RainLab.UserPlus). Pass an empty value to reset the billing address to a new entry.
| Parameter | Type | Description |
|---|---|---|
billing_address_book_id | integer | The saved address ID to load as the billing address |
Billing Details
Triggered by post_billing_details = true. Uses the same fields as contact details with a billing_ prefix.
| Parameter | Type | Description |
|---|---|---|
billing_first_name | string | Billing first name |
billing_last_name | string | Billing last name |
billing_email | string | Billing email |
billing_phone | string | Billing phone |
billing_company | string | Billing company |
billing_address_line1 | string | Billing street address |
billing_address_line2 | string | Billing apartment, suite, etc. |
billing_city | string | Billing city |
billing_zip | string | Billing postal code |
billing_country_id | integer | Billing country ID |
billing_state_id | integer | Billing state ID |
billing_tax_id_number | string | Billing tax ID / VAT number |
billing_same_as_shipping | boolean | Whether the billing address reuses the shipping address (1) or a separate billing address is being entered (0); defaults to 0 |
Posting billing details puts the checkout into separate-address mode. Post billing_same_as_shipping = 1 to reuse a shipping address that was entered first; the billing_ fields are then ignored. When it is 0 (the default), the billing_ fields are validated as required and stored as a distinct billing address, whether or not a shipping address exists yet. The flag is stored on the checkout session and exposed as the billingSameAsShipping page variable, so a "Billing address is the same as shipping" toggle keeps its state across AJAX refreshes. Selecting a billing address book preset also switches to separate-address mode automatically.
Shipping Details
Triggered by post_shipping_details = true. Uses the same fields as contact details with a shipping_ prefix.
| Parameter | Type | Description |
|---|---|---|
shipping_first_name | string | Shipping first name |
shipping_last_name | string | Shipping last name |
shipping_company | string | Shipping company |
shipping_phone | string | Shipping phone |
shipping_address_line1 | string | Shipping street address |
shipping_address_line2 | string | Shipping apartment, suite, etc. |
shipping_city | string | Shipping city |
shipping_zip | string | Shipping postal code |
shipping_country_id | integer | Shipping country ID |
shipping_state_id | integer | Shipping state ID |
shipping_is_business | boolean | Business address flag |
If shipping details are not explicitly submitted, the shipping address inherits from the contact details. Posting a shipping address switches the checkout to separate-address mode.
Shipping Address Book Preset
Triggered by post_shipping_address_book_preset = true. Loads a saved address from the customer's address book into the shipping address, mirroring the contact and billing address book presets (requires RainLab.UserPlus). It also switches the checkout to separate-address mode. Pass an empty value to reset the shipping address to a new entry.
| Parameter | Type | Description |
|---|---|---|
shipping_address_book_id | integer | The saved address ID to load as the shipping address |
Shipping Method
Triggered by post_shipping_method = true.
| Parameter | Type | Description |
|---|---|---|
shipping_method | integer | The shipping method ID. For methods with child options, this may include the child option identifier |
Payment Method
Triggered by post_payment_method = true.
| Parameter | Type | Description |
|---|---|---|
payment_method | integer | The payment method ID |
Additional Parameters
These parameters are processed on every request, without requiring a flag:
| Parameter | Type | Description |
|---|---|---|
user_notes | string | Order notes or special instructions |
coupon | string | Coupon code to apply |
cart_name | string | Cart name (default: main) |
skip_validation | boolean | Disable validation for partial/eager updates |
checkout_step | string | Used by templates to track which step is displayed |
Guest Registration
These parameters are processed during order placement:
| Parameter | Type | Description |
|---|---|---|
register_user | boolean | Create a customer account for the guest |
user_password | string | Password for the new account |
user_auto_login | boolean | Auto-login after registration |
user_register_notification | boolean | Send registration email |
Checkout Flow
The Commerce Theme implements a three-step checkout flow. Each step submits its data to the onRefreshCheckout handler, which stores it in the session and returns updated page variables.
Step 1: Contact Details & Address
↓ post_contact_details = true
Step 2: Shipping Method
↓ post_shipping_method = true
Step 3: Payment Method
↓ post_payment_method = true
Place Order → onPlaceOrder
↓ Redirect to payment page
Payment Page → onPay
↓ Redirect to receiptFor carts that contain only digital products, shippingRequired is false and the shipping step is skipped automatically. A special "No Shipping Required" method is set behind the scenes.
Step Navigation with skip_validation
When navigating backward between steps (e.g., "Return to Details"), pass skip_validation: true to prevent validation errors on the current step's incomplete data:
<a href="javascript:;"
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'details', skip_validation: true }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }">
Return to Details
</a>Cart Integrity
The component detects when cart contents change during checkout (items added/removed in another tab). When this happens, the checkout state is reset and the page is refreshed to recalculate totals. This prevents the customer from placing an order with stale pricing.
Similarly, if the order has already been paid (e.g., the customer navigates back to the checkout page after paying), the component redirects to the receipt page.
Complete Examples
Checkout Page
{# pages/shop/checkout.htm #}
##
url = "/shop/checkout"
layout = "default"
title = "Checkout"
[checkout]
==
{% if not checkout.isCartEmpty %}
{% put scripts %}
{% for method in allPaymentMethods %}
{{ method.renderPaymentScripts()|raw }}
{% endfor %}
{% endput %}
<div id="shopCheckoutView">
{% partial 'shop/checkout-view' %}
</div>
{% else %}
<div class="container">
<h1>Checkout</h1>
<p>Your shopping cart is empty.</p>
<a href="{{ 'shop/index'|page }}" class="btn btn-primary">Continue Shopping</a>
</div>
{% endif %}WARNING
Payment method scripts must be loaded regardless of which step the customer is on. The renderPaymentScripts() call loads JavaScript required by payment gateways (e.g., Stripe.js) that may need to be initialized before the payment form is displayed.
Step Router
The checkout view partial acts as a step router, displaying different content based on the checkout_step POST value:
{# partials/shop/checkout-view.htm #}
{% set checkoutStep = post('checkout_step', 'details') %}
<div class="row">
<div class="col-lg-7">
<form id="checkoutForm" data-control="checkout-form" data-request-flash>
{% if checkoutStep == 'details' %}
{% partial 'shop/checkout-step-details' %}
<input type="hidden" name="post_contact_details" value="true" />
<button
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'shipping' }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }"
data-attach-loading
class="btn btn-primary btn-lg">
Continue to Shipping
</button>
{% elseif checkoutStep == 'shipping' %}
{% partial 'shop/checkout-step-shipping' %}
<input type="hidden" name="post_shipping_method" value="true" />
<a href="javascript:;"
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'details', skip_validation: true }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }">
Return to Details
</a>
<a href="javascript:;"
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'payment' }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }"
data-attach-loading
class="btn btn-primary btn-lg">
Continue to Payment
</a>
{% elseif checkoutStep == 'payment' %}
{% partial 'shop/checkout-step-payment' %}
<input type="hidden" name="post_payment_method" value="true" />
<a href="javascript:;"
data-request="onRefreshCheckout"
data-request-data="{ checkout_step: 'shipping', skip_validation: true }"
data-request-update="{ 'shop/checkout-view': '#shopCheckoutView' }">
Return to Shipping
</a>
{% endif %}
</form>
{% if checkoutStep == 'payment' %}
<div id="shopPaymentForm">
{% partial 'shop/payment-form' %}
</div>
{% endif %}
</div>
<div class="col-lg-5" id="shopCheckoutOrderSummary">
{% partial 'shop/order-summary' %}
</div>
</div>Contact Details Step
{# partials/shop/checkout-step-details.htm #}
<div class="d-flex align-items-center mb-2">
<h4 class="m-0">Contact Details</h4>
{% if not checkout.user %}
<div class="ms-auto small">
Already have an account?
<a href="{{ 'account/login'|page }}">Sign in first</a>
</div>
{% endif %}
</div>
{% if checkout.user %}
<div class="alert alert-light">
Bill to <strong>{{ checkout.user.full_name }}, {{ checkout.user.email }}</strong>
<a href="javascript:;" data-request="onLogout" class="ms-auto">Logout</a>
</div>
{% else %}
<div class="form-floating mb-2">
<input name="email" type="text" class="form-control"
value="{{ address.email }}" placeholder="Email Address" required />
<label>Email Address</label>
</div>
<label>
<input type="checkbox" name="register_customer" value="1" />
Save my details for faster checkout
</label>
{% endif %}
<h4>{{ shippingRequired ? 'Shipping Details' : 'Billing Details' }}</h4>
{% if checkout.user %}
<div class="form-floating">
{% partial 'account/select-address'
user=checkout.user
addressBookId=address.address_book_id %}
<label>Address Book</label>
</div>
{% endif %}
<div class="row g-3">
<div class="col-6">
<div class="form-floating">
<input name="first_name" type="text" class="form-control"
value="{{ address.first_name }}" placeholder="First Name" required />
<label>First Name</label>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="last_name" type="text" class="form-control"
value="{{ address.last_name }}" placeholder="Last Name" required />
<label>Last Name</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<input name="address_line1" type="text" class="form-control"
value="{{ address.address_line1 }}" placeholder="Address" />
<label>Address</label>
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<input name="city" type="text" class="form-control"
value="{{ address.city }}" placeholder="City" />
<label>City</label>
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<input name="zip" type="text" class="form-control"
value="{{ address.zip }}" placeholder="Zip / Postal Code" />
<label>Zip / Postal Code</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
{% partial 'account/select-country' countryId=address.country_id %}
<label>Country</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
{% partial 'account/select-state'
countryId=address.country_id
stateId=address.state_id %}
<label>State</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<input name="phone" type="text" class="form-control"
value="{{ address.phone }}" placeholder="Phone" />
<label>Phone</label>
</div>
</div>
</div>Shipping Method Step
{# partials/shop/checkout-step-shipping.htm #}
<h4>Shipping Method</h4>
{% if hasShippingAddress %}
{% if shippingMethods is not empty %}
<ul class="list-group mt-3">
{% for method in shippingMethods %}
{% if method.hasChildOptions %}
{% for childOption in method.childOptions %}
<li class="list-group-item">
<label class="d-flex w-100">
<input type="radio" name="shipping_method"
value="{{ childOption.id }}"
{{ shippingMethod.id == childOption.id ? 'checked' }} />
<div class="ps-2">
<p class="mb-0">{{ method.name }} - {{ childOption.name }}</p>
{% if method.description %}
<small class="text-muted">{{ method.description }}</small>
{% endif %}
</div>
<div class="ms-auto fw-bold">
{{ childOption.quote|currency }}
</div>
</label>
</li>
{% endfor %}
{% else %}
<li class="list-group-item">
<label class="d-flex w-100">
<input type="radio" name="shipping_method"
value="{{ method.id }}"
{{ shippingMethod.id == method.id ? 'checked' }} />
<div class="ps-2">
<p class="mb-0">{{ method.name }}</p>
{% if method.description %}
<small class="text-muted">{{ method.description }}</small>
{% endif %}
</div>
<div class="ms-auto fw-bold">
{% if method.isFree %}
Free
{% else %}
{{ method.quote|currency }}
{% endif %}
</div>
</label>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<p>No shipping methods available for your location.</p>
{% endif %}
{% else %}
<p>Enter your shipping address to view available shipping methods.</p>
{% endif %}Payment Method Step
{# partials/shop/checkout-step-payment.htm #}
<h4>Payment Method</h4>
{% if paymentMethods is not empty %}
<ul class="list-group mt-3">
{% for method in paymentMethods %}
<li class="list-group-item">
<label class="d-flex w-100">
<input type="radio" name="payment_method"
value="{{ method.id }}"
{{ paymentMethod.id == method.id ? 'checked' }} />
<div class="ps-2">
<p class="mb-0">{{ method.name }}</p>
{% if method.description %}
<small class="text-muted">{{ method.description }}</small>
{% endif %}
</div>
</label>
</li>
{% endfor %}
</ul>
{% else %}
<p>No payment methods available.</p>
{% endif %}Order Summary
{# partials/shop/order-summary.htm #}
<ul class="list-group list-group-flush">
{% for item in items %}
{% set product = item.product %}
<li class="list-group-item py-3">
<div class="row align-items-center">
<div class="col-3">
<div class="position-relative">
{% if product.images is not empty %}
<img class="img-fluid"
src="{{ product.images.first|resize(0, 100, { mode: 'auto' }) }}"
alt="{{ product.name }}" />
{% endif %}
<span class="badge rounded-pill bg-secondary position-absolute top-0 start-100 translate-middle">
{{ item.quantity }}
</span>
</div>
</div>
<div class="col-6">
<h5 class="mb-1">{{ product.name }}</h5>
<div class="small">
{% for option in item.options %}
{{ option.name }}: {{ option.value }}{{ not loop.last ? ', ' }}
{% endfor %}
{% for extra in item.extras %}
<br />+ {{ extra.description }}
{% endfor %}
</div>
</div>
<div class="col-3 text-end small">
{{ item.display_line_price|currency }}
</div>
</div>
</li>
{% endfor %}
</ul>
<div class="card">
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between small">
<span>Subtotal</span>
<span>{{ order.final_subtotal|currency }}</span>
</li>
<li class="list-group-item d-flex justify-content-between small">
<span>Shipping</span>
{% if not hasShippingAddress %}
<small class="text-muted">Enter address</small>
{% elseif not hasShippingQuote %}
<small class="text-muted">Select shipping</small>
{% else %}
<span>{{ order.final_shipping_quote|currency }}</span>
{% endif %}
</li>
<li class="list-group-item d-flex justify-content-between">
<span class="fw-bold">Total</span>
<span class="fw-bold fs-5">{{ order.total|currency }}</span>
</li>
<li class="list-group-item small">
Includes {{ order.total_tax|currency }} in taxes
</li>
</ul>
</div>Payment Page
After the order is placed, the customer is redirected to the payment page. This page uses the [payment] component from Responsiv.Pay:
{# pages/shop/payment.htm #}
##
url = "/shop/payment/:hash"
layout = "default"
title = "Order"
[payment]
isDefault = 1
==
{% set order = invoice.related %}
{% if not order or not order.order_number %}
{% do abort(404) %}
{% endif %}
{% put scripts %}
{% for method in paymentMethods %}
{{ method.renderPaymentScripts()|raw }}
{% endfor %}
{% endput %}
<div class="container">
<div class="row">
<div class="col-lg-7">
<p>Order #{{ order.order_number }}</p>
<h2>Thank you, {{ order.billing_first_name }}!</h2>
{% if order.is_payment_processed %}
<div class="card mb-3">
<div class="card-body">
<h5>Your Order is Confirmed</h5>
<p>We have processed your payment and accepted your order.</p>
</div>
</div>
{% else %}
<div class="card mb-3">
<div class="card-body">
<h5>Your Order is Pending</h5>
<p>Please use a payment method below to complete this order.</p>
</div>
</div>
{% if paymentMethods is not empty %}
<ul class="list-group mb-3">
{% for method in paymentMethods %}
<li class="list-group-item">
<label class="d-flex w-100">
<input type="radio" name="payment_method"
value="{{ method.id }}"
data-request="onUpdatePaymentMethod"
data-request-update="{
'shop/payment-form': '#shopPaymentForm',
'shop/order-details': '#shopOrderDetails'
}"
{{ order.payment_method_id == method.id ? 'checked' }} />
<div class="ps-2">{{ method.name }}</div>
</label>
</li>
{% endfor %}
</ul>
{% endif %}
<div id="shopPaymentForm">
{% partial 'shop/payment-form' %}
</div>
{% endif %}
</div>
<div class="col-lg-5" id="shopCheckoutOrderSummary">
{% partial 'shop/order-summary' items=order.items %}
</div>
</div>
</div>The checkout-form.js Control
The Commerce Theme includes a checkout-form.js JavaScript control that enhances the checkout experience. When applied to a form via data-control="checkout-form", it:
- Listens for changes to country, state, and address book selectors.
- Automatically submits updated address data when the customer changes their country or selects an address book entry.
- Updates the order summary and available shipping/payment methods without the customer clicking a button.
<form id="checkoutForm" data-control="checkout-form" data-request-flash>
<!-- Checkout steps -->
</form>This control is optional. The checkout works without it, but the customer would need to click "Continue" to see updated shipping rates after changing their address.
Events
The checkout process fires several events. See the Events documentation for details:
shop.beforePlaceOrder: Before the order is created.shop.beforeCreateOrderRecord: Before a new order is saved to the database.shop.beforeUpdateOrderRecord: Before an existing order is updated (amendment).shop.newOrder: After a new order is created.shop.checkout.beforeSetCouponCode: Before a coupon code is validated (allows modification).