Home
Wiki
Home
Wiki
  1. 2. Components
  • Back to home
  • 1. Themes
  • Vs Code
    • Getting Started
  • Kitchenware
    • Layout
      • New Layout
      • Legacy Layout
    • Components
      • Announcement
      • Banner Carousel
      • Banner With Products Carousel
      • Blog Category List
      • Blog List
      • Brand List
      • Brands Carousel
      • Breadcrumb
      • Call To Action
      • Cart
      • Categories List
      • Change Password
      • Checkout
      • Cookie Manager
      • Filter list
      • Footer
      • Forgot Password
      • Form
      • Hero Carousel
      • Icon Block
      • Invitation
      • Last Visited Products
      • Layout
      • Login
      • Map
      • Nav Bar
      • Offer
      • Product Attachments
      • Product Attributes
      • Product Documentation
      • Product Expected
      • Product Modal
      • Products Block
      • Products Carousel
      • Product Single
      • Profile
      • Quote
      • Register
      • Related Products
      • Search
      • Stores
      • Subscribe Newsletter
      • Text with Image
      • Top Bar
      • Video
    • Reusables
      • Getting Started
    • Assets
      • Getting Started
    • SDK
      • Products
        • _findProductsByCategory
        • _findProductsByIds
        • _findProductsByTitle
        • _findProductsByFilter
        • _findProductsByCriteria
        • _findProductsAndCalculate
        • _findProductsThenCalculate
        • _getProductAttributeSet
        • _setLastVisited
      • Categories
        • _findCategoryTreeById
        • _findCategoriesByIds
        • _findCategoryByAlias
        • _findCategoryTreeByAlias
        • _getCategoryContent
      • Collections
        • _getCollectionContent
        • _findCollectionsByIds
        • _findCollectionsByIdsThenCalculate
      • Brands
        • _getBrandContent
        • _findBrandsByIds
      • Cart
        • _addToCartMulti
        • _addToCart
        • _setCart
        • _clearCart
        • _setCartListener
        • _removeFromCart
        • _calculateCart
      • Checkout
        • _startCheckout
        • _updateCheckout
        • _completeCheckout
      • Shopping Lists
        • _getShoppingLists
        • _updateShoppingList
        • _createShoppingList
        • _deleteShoppingList
        • _getShoppingListByAlias
      • Navigation
        • _getFooterMenu
        • _getHeaderMenu
      • Users
        • _getUserById
      • Utils
        • _calculateCurrency
        • _getCurrencySymbol
        • _getCulture
        • _subscribeToNewsletter
        • _findUnitsByIds
  • Noir
    • 0. Introduction
    • 1. Structure
      • Overview
      • LayoutA.liquid
      • ComponentsList.liquid
      • Metas.liquid
      • CssVariables.liquid
      • Json.liquid
      • GoogleTagManager.liquid
      • StagingButton.liquid
    • 2. Components
      • Overview
      • Announcement
      • BannerCarousel
      • BlogCategoryList
      • BlogList
      • BrandList
      • Breadcrumb
      • Cart
      • CategoriesList
      • ChangePassword
      • Checkout
      • CookieManager
      • FilterList
      • Footer
      • ForgotPassword
      • Form
      • IconBlock
      • Invitation
      • LastVisitedProducts
      • Login
      • Map
      • NavBar
      • ProductAttachments
      • ProductAttributes
      • ProductComparison
      • ProductDocumentation
      • ProductMixList
      • ProductsBlock
      • ProductsCarousel
      • ProductSingle
      • Profile
      • Register
      • RelatedProducts
      • SingleBlog
      • Stores
      • TextWithImage
      • ThankYouPage
      • TopBar
      • Wishlist
    • 3. Reusables
      • Overview
      • Addresses
      • BillingRetail
      • AddressForm
      • AnnouncementModal
      • BackToTop
      • Company
      • General
      • Login
      • LoginModal
      • Orders
      • Payment
      • ProductAttachments
      • ProductAttributes
      • ProductComparisonButton
      • ProductComparisonFloatingButton
      • ProductGridItem
      • ProductListItem
      • ShoppingListsButton
      • ProductModal
      • ProfileInfo
      • PromptModal
      • Register
      • Shipping
      • ShoppingLists
      • ShoppingListsNavbar
      • Toast
      • Users
      • VariantContent
      • WishlistButton
      • Services
    • 4. Assets
      • Fonts
      • Images
      • Templates
      • Javascript
        • Overview
        • theme.js
      • Css / Scss
        • Overview
        • ThemeClasses
    • 5. SDK
      • Overview
      • LiquidGlobals
      • ServicesSDK
  1. 2. Components

Checkout

Purpose#

The Checkout component renders and orchestrates the checkout experience.
It supports:
Rendering different checkout states:
payment cancelled (pageState == 'cancel')
payment ok but order creation failed (pageState == 'error')
normal checkout flow (default)
Billing / Shipping / Payment sections (rendered via Reusables and gated by checkout settings flags)
Order summary (cart items, totals, line notes if enabled)
Coupons handling (add/remove)
Points redemption (enable/disable)
Completing checkout (proceed to payment/order)
Redirect flow on success

Inputs (model contract)#

Model shape (storefront example)#

{
  "pageState": null,
  "status": "Start",
  "checkout": {
    "netValue": 0,
    "netValueText": "0.00 €",
    "vatValue": 0,
    "vatValueText": "0.00 €",
    "finalPrice": 0,
    "finalPriceText": "0.00 €",
    "billingAddress": {
      "id": "Address Id",
      "name": "Sample name",
      "firstName": "Sample first name",
      "lastName": "Sample last name",
      "phoneNumber": "Sample phone",
      "address1": "Sample address",
      "city": "Sample city",
      "state": "Sample state",
      "country": "Sample country",
      "countryCode": "XX",
      "postalCode": "Sample postal code",
      "email": "sample@example.com"
    },
    "shippingAddress": {
      "id": "Address Id",
      "name": "Sample name",
      "firstName": "Sample first name",
      "lastName": "Sample last name",
      "phoneNumber": "Sample phone",
      "address1": "Sample address",
      "city": "Sample city",
      "state": "Sample state",
      "country": "Sample country",
      "countryCode": "XX",
      "postalCode": "Sample postal code",
      "email": "sample@example.com"
    },
    "cartItems": [
      {
        "id": "Cart line Id",
        "productId": "Product Id",
        "productVariantId": "Variant Id",
        "quantity": 1,
        "productTitle": "Sample product title",
        "link": "sample-product-link",
        "imageLink": "https://example.com/sample",
        "finalPrice": 0,
        "finalPriceText": "0.00 €",
        "originalPrice": 0,
        "originalPriceText": "0.00 €",
        "subTotalNetValue": 0,
        "subTotalPrice": 0,
        "subTotalVatValue": 0
      },
      ...
    ],
    "pricingBeforeDiscount": {
      "netValue": 0,
      "netValueText": "0.00 €",
      "vatValue": 0,
      "vatValueText": "0.00 €",
      "finalPrice": 0,
      "finalPriceText": "0.00 €"
    }
  },
  "carriers": {
    "globalConfig": {
      "freeAtPrice": 0,
      "freeAtPriceText": "0.00 €",
      "freeAtWeightText": " kg"
    },
    "list": [
      {
        "carrierId": "Carrier Id",
        "carrierCode": "sample",
        "title": "Sample carrier title",
        "type": "Manual",
        "netPrice": 0,
        "totalAmount": 0,
        "totalAmountText": "0.00 €",
        "vatLines": []
      },
      ...
    ]
  },
  "payments": [
    {
      "provider": "Sample provider",
      "providerId": "Provider Id",
      "name": "Sample payment name",
      "message": "<p>Sample message</p>",
      "installmentsEnabled": false
    },
    ...
  ],
  "deliveryZonesEnabled": true,
  "countriesList": [
    {
      "code": "XX",
      "name": "Sample country name"
    },
    ...
  ],
  "addresses": [
    {
      "name": "",
      "isSelectable": true,
      "address": {
        "id": "Address Id",
        "firstName": "Sample first name",
        "lastName": "Sample last name",
        "phoneNumber": "Sample phone",
        "address1": "Sample address",
        "city": "Sample city",
        "state": "Sample state",
        "country": "Sample country",
        "countryCode": "XX",
        "postalCode": "Sample postal code",
        "email": "sample@example.com"
      },
      "requiresInvoice": false
    },
    ...
  ],
  "name": "Checkout",
  "view": "Default",
  "section": "SectionA",
  "settings": {
    "id": "Component Id",
    "section": "SectionA",
    "type": "NoirCheckout",
    "name": "Checkout",
    "configuredInContentApi": true,
    "view": "Default",
    "displayName": "Sample display name",
    "cssClass": ""
  },
  "translations": {
    "completeOrder": "Sample translation",
    "backToCart": "Sample translation",
    "orderSummary": "Sample translation",
    "...": "..."
  }
}

Required fields#

settings.id
Used for wrapper id: comp-{{ id }}
checkout
Main checkout object used throughout the UI and passed into JS as serialized data.

Optional fields#

settings.cssClass
Applied to wrapper only when non-empty and not (UNDEFINED).
pageState
Controls which layout is rendered:
cancel → Payment cancelled/failed page
error → Order creation failed page
otherwise → normal checkout
addresses, countriesList
Used by Billing/Shipping reusables.
carriers, payments
Used by Shipping/Payment reusables.

JavaScript#

Global object#

The component exposes a global object:
It’s initialized from Liquid via Alpine:
x-data='checkoutdefault.initComponent({{ checkout | serialize | escape }})'
initComponent(...) returns the Alpine component state + methods for:
initializing checkout state
coupon and points operations (via servicesreusabledefault.updateCheckout(...))
completing checkout (via servicesreusabledefault.proceedToCheckout(...))
optional UI helpers (validity checks, IntersectionObserver for the aside button)

initComponent(checkoutData)#

What it does
Factory function that receives the serialized checkout object and returns an Alpine object containing:
state fields (checkout, flags, coupon state, loading flags, etc.)
lifecycle method init()
helper methods:
createObserver(), checkValidity()
handlePoints(...), handleCoupons()
sendSelectEvent(...), completeCheckout(...)
Advice
Keep the signature stable. It’s a Liquid → JS contract.
checkoutData is expected to be a valid object; if it’s missing fields, downstream methods can break, so avoid passing partial objects.

init() (async)#

What it does
1) Reads the checkoutToken cookie:
const checkoutCookie = getCookie("checkoutToken");
If missing: throws an error (No checkout token found in cookies.)
2) Stores the token in localStorage:
localStorage.setItem("checkoutToken", checkoutCookie)
3) Reads cart data from localStorage (already parsed in cartData state field):
If cart has items:
Sends GA event beginCheckout with prepared items
Initializes IntersectionObserver for an aside “Complete order” button:
calls createObserver()
re-creates observer on window resize
Sets:
this.checkout = this.checkoutData
Alpine.store("checkout").set(this.checkout)
Else:
sets this.emptyData = true
Advice
Throwing when checkoutToken is missing is intentional: checkout shouldn’t proceed without a token.
If you want a more user-friendly fallback, catch this in the template and show an error state.
The GA event relies on cartData existing and being in the expected shape.
Event listener cleanup isn’t implemented. This is fine for full page navigation, but for SPA-like behavior add cleanup/guards.

createObserver()#

What it does
Tracks whether the aside “Complete order” button is visible in the viewport.
Reads:
#aside-complete-btn (the target to observe)
.sticky-element (used to compute a dynamic rootMargin)
Calculates:
stickyHeight = stickyElement.offsetHeight || 0
Disconnects an existing observer (if any).
Creates a new IntersectionObserver that sets:
this.isAsideButtonVisible = entries[0].isIntersecting
Uses dynamic rootMargin:
-${stickyHeight}px 0px -${stickyHeight}px 0px
Observes asideButton.
Why this exists
It enables UI logic like “show a floating action button only when the aside button is not visible” (common on long checkouts).
Advice
Ensure both #aside-complete-btn and .sticky-element exist before calling this. In init() it already checks, but createObserver() itself assumes they exist.
If the sticky element height is animated, re-creating the observer on resize may not be enough—consider re-creating it when sticky height changes.

checkValidity()#

What it does
Sets this.validCheckout based on whether the consent checkbox is checked:
#concent-order (note the id spelling in the code)
Advice
This is a “UI gate” only. It does not validate addresses, payment selection, etc.
If you rename the checkbox id in the template, this must be updated.

handlePoints(isEnabled, discountFromPoints, pointsErrorMessage) (async)#

What it does
Clears all toasts.
Converts discountFromPoints from a localized string like "12,34 €" into a number:
splits by " €"
replaces comma with dot
parseFloat(...)
Builds loyaltyPricing depending on:
isEnabled (points on/off)
this.isCouponRedeemed (coupon already applied or not)
When enabling points, also sets:
this.checkout.discountValue = discountFromPoints
Writes:
this.checkout.loyaltyPricing = loyaltyPricing
Calls:
servicesreusabledefault.updateCheckout(this.checkout)
On failure:
shows toast using pointsErrorMessage
On success:
updates this.checkout = response.data
Advice
The discount parsing is locale-dependent and fragile. A safer approach is to pass numeric discount from the backend instead of parsing text.
If points are enabled/disabled often, consider disabling the toggle while the request is in-flight to prevent overlapping updateCheckout calls.

handleCoupons()#

What it does
Returns a nested Alpine component object dedicated to coupon UI state.
It provides:
couponInput
state (default / valid / invalid / selected style states)
Computed getters:
stateClass:
maps state to CSS class
couponValue:
returns this.coupons.join(";") (prepping for multi-coupon support)
Methods:
addCoupon(addCouponErrorMessage) (async)
handleBackspace(removeCouponErrorMessage)
removeCoupon(index, removeCouponErrorMessage) (async)
removeCoupons(removeCouponsErrorMessage) (async)
Advice
This nested object intentionally uses this pointing to the nested coupon scope, but it still reads/writes parent state fields like this.checkout, this.coupons, this.isPointsRedeemed. Alpine resolves this because the nested object is created within the parent component context.
Multi-coupon support is clearly “planned” (comments). Right now it behaves like a single-coupon implementation.

addCoupon(addCouponErrorMessage) (inside handleCoupons, async)#

What it does
Guard clauses: returns if:
input is empty, or
already adding (this.isCouponAdding)
Clears toasts.
Resets UI state:
stores couponLastInput
clears status
sets state = "default"
sets isCouponAdding = true
resets this.coupons = []
Builds loyaltyPricing:
usePoints: this.isPointsRedeemed
useCoupon: true
couponCodes: [couponInput]
Assigns to this.checkout.loyaltyPricing, then calls:
servicesreusabledefault.updateCheckout(this.checkout)
On HTTP/service failure:
shows toast with addCouponErrorMessage
On success:
updates this.checkout = response.data
if checkout.loyaltyPricing.errorCode exists:
sets state = "invalid"
maps backend errorCode to a UI couponStatus string (e.g. invalidCode, alreadyUsedCode, discountExceeded, etc.)
else (valid coupon):
sets state = "valid"
sets this.isCouponRedeemed = true
pushes coupon into this.coupons
sets couponStatus = "validCode"
stores couponLabel and couponsDiscountText from checkout response
Always clears isCouponAdding in finally.
Advice
The method catches errors and logs to console but doesn’t toast a generic error. If you want consistent UX, toast on catch as well.
Coupon codes are treated as raw strings; trim before sending to avoid whitespace issues.

handleBackspace(removeCouponErrorMessage) (inside handleCoupons)#

What it does
If input is empty and there is at least one coupon in the list:
removes the last coupon by calling removeCoupon(lastIndex, ...)
Advice
This is a UX shortcut; keep it only if it matches your design guidelines. It can surprise users if they hit backspace and coupons disappear.

removeCoupon(index, removeCouponErrorMessage) (inside handleCoupons, async)#

What it does
Clears toasts.
Removes coupon at index from this.coupons.
Updates this.checkout.loyaltyPricing:
useCoupon = hasCoupons
couponCodes = this.coupons
Calls:
servicesreusabledefault.updateCheckout(this.checkout)
On failure:
shows toast with removeCouponErrorMessage
On success:
updates this.checkout = response.data
sets state to "invalid" if there are still coupons and backend returns errorCode, else "default"
updates couponsDiscountText from response
Advice
Current behavior keeps points state intact via the existing checkout.loyaltyPricing object; make sure points-related flags don’t get lost when refactoring.

removeCoupons(removeCouponsErrorMessage) (inside handleCoupons, async)#

What it does
Clears toasts.
Sets isCouponAdding = true (re-using the “busy” flag).
Builds a loyaltyPricing object:
usePoints: this.isPointsRedeemed
useCoupon: false
couponCodes: []
Calls updateCheckout.
On failure:
shows toast, clears isCouponAdding, returns.
On success:
updates checkout
resets coupon UI state to defaults:
state = "default"
isCouponRedeemed = false
clears coupons list and discount text/status/labels
clears isCouponAdding
Advice
Resetting many fields here is intentional to keep UI consistent. If you add new coupon-related state fields later, remember to reset them here too.

sendSelectEvent(item, listName, position)#

What it does
Sends GA “select item” event:
prepares item via prepareListProducts([item])
calls sendGAEvent("selectItem", { listName, items, position })
Advice
Keep analytics event naming consistent with the rest of the theme (selectItem, beginCheckout, etc.).

completeCheckout(completeCheckoutErrorMessage) (async)#

What it does
Final step to place the order / proceed to payment:
1.
Clears toasts.
2.
Reads the latest checkout state from store:
const checkoutData = Alpine.store("checkout").data;
3.
Copies addresses/invoice from the store into this.checkout:
billingAddress, shippingAddress, invoiceData
4.
Sets this.isCompleting = true
5.
Calls:
servicesreusabledefault.proceedToCheckout(this.checkout)
6.
On failure:
shows toast with completeCheckoutErrorMessage
clears isCompleting and returns
7.
On success:
updates this.checkout = response.data
clears isCompleting
8.
If this.checkout.status == "Completed":
sets isRedirecting = true
reads token from localStorage
clears cart + checkout tokens from localStorage
clears cookies (cartToken, checkoutToken)
redirects to:
/checkout/order-completed/<checkoutToken>
Advice
This assumes the checkout store is always up-to-date. If a reusable updates store state asynchronously, ensure it’s committed before calling complete.
Clearing storage is critical to prevent stale cart/checkout in subsequent sessions.
If proceedToCheckout returns a payment redirect URL (for card payments), you may need to handle that case separately; current logic focuses on status == "Completed".

Global Alpine stores (used by Checkout)#

$store.checkout (checkout shared state)#

Checkout uses a global store to share updated addresses/invoice data with the final “complete order” action.
Key usage:
On init:
Alpine.store("checkout").set(this.checkout)
On complete:
reads Alpine.store("checkout").data
copies billing/shipping/invoice into this.checkout before proceeding
This store allows reusables (Billing/Shipping/Payment) to update the checkout state in one place.

$store.toast (user feedback)#

Used for:
coupon errors
points redemption errors
checkout completion errors
general update errors
Common pattern:
Alpine.store("toast").removeAll();
Alpine.store("toast").add(message, "ic-warning", "error");

Services / API calls#

Checkout calls the services layer for key operations:
servicesreusabledefault.updateCheckout(checkout)
Used when:
enabling/disabling points
adding/removing coupons
recalculating totals after loyalty pricing changes
servicesreusabledefault.proceedToCheckout(checkout)
Used when user confirms the order (final step).
On success (checkout.status == "Completed"), it:
clears cart + checkout tokens from local storage/cookies
redirects to:
/checkout/order-completed/<checkoutToken>

Dependencies#

Alpine.js
Alpine stores: checkout, toast
Cookies/localStorage:
checkoutToken cookie
cartData localStorage (GA + summary consistency)
Services layer methods:
updateCheckout(...)
proceedToCheckout(...)

Notes#

Checkout requires a checkoutToken available in cookies. If missing, init throws an error.
Coupons UI currently supports a single coupon input flow (the code includes TODO comments for multi-coupon support).
On successful completion, the component clears cart-related storage. This is important to avoid stale cart state after placing an order.

Extras#

Template behavior (Liquid + Alpine)#

State-driven rendering#

If pageState == 'cancel' → renders a “payment failed/cancelled” UI and CTA back to checkout.
If pageState == 'error' → renders a “checkout error” UI and CTA back to checkout.
Else → renders the normal checkout flow and initializes JS with:
x-data='checkoutdefault.initComponent({{ checkout | serialize | escape }})'

Sections rendered via Reusables#

The template renders these blocks depending on checkout settings flags:
Billing (Reusables\\BillingRetail\\Default)
Shipping (Reusables\\Shipping\\Default)
Payment (Reusables\\Payment\\Default)
Visibility is controlled by config flags (injected globally as window.checkoutConfig and also available as Liquid settings).

Coupons area#

If coupons are enabled via global settings, the template renders the coupons UI, which is controlled by the JS helper handleCoupons().

Order summary#

Renders checkout cart items and totals, and optionally product line notes if enabled by global settings.
Modified at 2026-04-14 13:18:56
Previous
ChangePassword
Next
CookieManager
Built with