Skip to content
advanced Phase 47 · Checkout Advanced

Custom Checkout Features

Custom checkout steps, custom validation, and one-step checkout patterns in Magento 2

1h
0 problems
Topic Progress 0%

Adding Custom Checkout Steps

Step Navigator Configuration

// Custom step component
define([
    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/step-navigator'
], function ($, ko, Component, stepNavigator) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Checkout/custom-step'
        },

        initialize: function () {
            this._super();

            stepNavigator.registerStep(
                'custom-step',
                'custom-step',
                'Custom Information',
                this.isVisible,
                ko.observable(3),
                'shipping'
            );

            return this;
        },

        isVisible: ko.observable(true),

        navigate: function () {
            this.isVisible(true);
        },

        stepCode: 'custom-step',
        stepTitle: 'Custom Information'
    });
});

Step Registration in Layout

<!-- checkout_index_index.xml -->
<item name="custom-step" xsi:type="array">
    <item name="component" xsi:type="string">Vendor_Checkout/js/view/custom-step</item>
    <item name="sortOrder" xsi:type="string">2</item>
    <item name="children" xsi:type="array">
        <item name="custom-fieldset" xsi:type="array">
            <item name="component" xsi:type="string">Vendor_Checkout/js/view/custom-fieldset</item>
        </item>
    </item>
</item>

Custom Field Validation

Validation Rules

// Custom field with validation
define([
    'jquery',
    'ko',
    'uiComponent',
    'mage/validation'
], function ($, ko, Component) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Checkout/custom-field',
            validation: {
                'required-entry': true,
                'min-text-length': 2,
                'max-text-length': 255
            }
        },

        initialize: function () {
            this._super();
            this.value = ko.observable();
            return this;
        },

        validate: function () {
            var $el = this.$el;
            return $el.valid();
        }
    });
});

Custom Validation Rule

// Register custom validation rule
require(['jquery'], function ($) {
    $.validator.addMethod(
        'validate-phone',
        function (value) {
            return /^\+?[0-9]{10,15}$/.test(value);
        },
        'Please enter a valid phone number'
    );
});

Server-Side Validation

namespace Vendor\Checkout\Model\Checkout\ConfigProvider;

class ValidationConfigProvider implements \Magento\Checkout\Model\ConfigProviderInterface
{
    public function getConfig(): array
    {
        return [
            'validation' => [
                'custom_field' => [
                    'required' => true,
                    'min_length' => 2,
                    'max_length' => 255,
                    'pattern' => '^[a-zA-Z]+$'
                ]
            ]
        ];
    }
}

One-Step Checkout Patterns

Single-Page Checkout

// OneStepCheckout component
define([
    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/action/set-shipping-information',
    'Magento_Checkout/js/action/set-payment-information'
], function ($, ko, Component, quote, setShippingAction, setPaymentAction) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Checkout/one-step-checkout'
        },

        initialize: function () {
            this._super();

            this.shippingAddress = ko.observable();
            this.shippingMethod = ko.observable();
            this.paymentMethod = ko.observable();
            this.isProcessing = ko.observable(false);

            return this;
        },

        placeOrder: function () {
            var self = this;
            self.isProcessing(true);

            setShippingAction().done(function () {
                setPaymentAction().done(function () {
                    // Place order
                    self.processOrder();
                });
            });
        },

        processOrder: function () {
            var self = this;
            $.ajax({
                url: '/rest/V1/carts/mine/payment-information',
                method: 'POST',
                data: JSON.stringify({
                    paymentMethod: { method: self.paymentMethod() },
                    billingAddress: self.getBillingAddress()
                }),
                success: function (orderId) {
                    window.location = '/checkout/onepage/success/?order_id=' + orderId;
                }
            });
        }
    });
});

Combined Layout XML

<!-- One-step checkout layout -->
<item name="onestep" xsi:type="array">
    <item name="component" xsi:type="string">Vendor_Checkout/js/view/onestep</item>
    <item name="sortOrder" xsi:type="string">1</item>
    <item name="children" xsi:type="array">
        <item name="address" xsi:type="array">
            <item name="component" xsi:type="string">Vendor_Checkout/js/view/address</item>
        </item>
        <item name="shipping-method" xsi:type="array">
            <item name="component" xsi:type="string">Vendor_Checkout/js/view/shipping-method</item>
        </item>
        <item name="payment-method" xsi:type="array">
            <item name="component" xsi:type="string">Vendor_Checkout/js/view/payment-method</item>
        </item>
    </item>
</item>

Complete Custom Checkout Module

Module Structure

Vendor/CustomCheckout/
├── etc/
│   ├── module.xml
│   ├── di.xml
│   └── frontend/
│       └── routes.xml
├── view/
│   └── frontend/
│       ├── layout/
│       │   └── checkout_index_index.xml
│       ├── web/
│       │   ├── js/
│       │   │   └── view/
│       │   │       ├── custom-step.js
│       │   │       └── custom-fieldset.js
│       │   └── template/
│       │       └── custom-step.html
│       └── templates/
└── registration.php

Complete Module Example

// registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Vendor_CustomCheckout',
    __DIR__
);
<!-- etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module:/etc/module.xsd">
    <module name="Vendor_CustomCheckout" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Checkout"/>
        </sequence>
    </module>
</config>

Quiz

1. How do you register a custom checkout step?

Question 1 options

2. What does one-step checkout combine?

Question 2 options

3. How to add custom validation to checkout fields?

Question 3 options

Flashcards

Question

How to add a checkout step?

Answer

stepNavigator.registerStep() + JS component + layout XML

Question

One-step checkout benefit?

Answer

Reduces checkout friction by showing all fields on one page

Question

Custom validation method?

Answer

$.validator.addMethod('rule-name', validationFn, message)

Question

Step navigation flow?

Answer

shipping → custom-step → review (order defined by sortOrder)

Revision Notes

Key Takeaways

  • 1. Custom steps are registered via stepNavigator.registerStep()
  • 2. One-step checkout combines address, shipping, and payment on one page
  • 3. Custom validation uses jQuery validation or custom rules
  • 4. Layout XML defines the component hierarchy and sort order
  • 5. Module structure follows standard Magento 2 conventions

Interview Tips

  • Describe the step registration process and sort order impact
  • Discuss pros and cons of one-step vs multi-step checkout
  • Explain how to validate custom fields both client and server side

Cheat Sheet

Custom Checkout:
  Register: stepNavigator.registerStep(code, title, sortOrder)
  Component: extend uiComponent with template
  Layout: add children under checkout > steps

One-Step Checkout:
  Combine address + shipping + payment
  Single placeOrder() action
  Set shipping + payment before order

Validation:
  Client: $.validator.addMethod()
  Server: ConfigProvider + custom rules