Magento2 | PWA | GraphQL

Add New Step in checkout with Product widget Magento2


In this post we wiil check how to add new step in checkout in Magento2. In this new step we will add Product widget as a content and display some products.

Let's start by creating custom module.

You can find complete module on Github at Magelearn_NewStepCheckout





Create folder inside app/code/Magelearn/NewStepCheckout

Add registration.php file in it:

1
2
3
4
5
<?php
 
use Magento\Framework\Component\ComponentRegistrar;
 
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_NewStepCheckout', __DIR__);

Add composer.json file in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "name": "magelearn/module-new-step-checkout",
    "description": "Add New Step in checkout with Product widget Magento2",
    "type": "magento2-module",
    "require": {},
    "authors": [
        {
            "name": "vijay rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "license": "proprietary",
    "minimum-stability": "dev",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\NewStepCheckout\\": ""
        }
    }
}

Add etc/module.xml file in it:

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magelearn_NewStepCheckout">
        <sequence>
            <module name="Magento_Checkout"/>
        </sequence>
    </module>
</config>

Now to add new step in checkout first we will modify checkout_index_index.xml file.

Add app/code/Magelearn/NewStepCheckout/view/frontend/layout/checkout_index_index.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="new-step-products" xsi:type="array">
                                            <item name="component" xsi:type="string">Magelearn_NewStepCheckout/js/view/new-step</item>
                                            <item name="sortOrder" xsi:type="string">0</item>
                                            <!--To display step content before shipping step "sortOrder" value should be < 1-->
                                            <!--To display step content between shipping step and payment step  1 < "sortOrder" < 2 -->
                                            <!--To display step content after payment step "sortOrder" > 2 -->
                                            <item name="children" xsi:type="array">
                                                <!--add here child component declaration for your step-->
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Now as per defined in checkout_index_index.xml file, we will add our JS component file.

Add app/code/Magelearn/NewStepCheckout/view/frontend/web/js/view/new-step.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
define([
    'ko',
    'jquery',
    'uiComponent',
    'underscore',
    'Magento_Checkout/js/model/step-navigator',
    'mage/translate',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/cart/totals-processor/default'
], function (ko, $, Component, _, stepNavigator, $t, quote, totalsDefaultProvider) {
    'use strict';
 
    return Component.extend({
        defaults: {
            template: 'Magelearn_NewStepCheckout/new-step'
        },
 
        isVisible: ko.observable(true),
        quoteIsVirtual: quote.isVirtual(),
 
        /**
         * @returns {*}
         */
        initialize: function () {
            this._super();
 
            stepNavigator.registerStep(
                // step code will be used as step content id in the component template
                'new_step',
                // step alias
                null,
                // step title value
                $t('Checkout Products'),
                // observable property with logic when display step or hide step
                this.isVisible,
 
                _.bind(this.navigate, this),
                /**
                 * sort order value
                 * 'sort order value' < 10: step displays before shipping step;
                 * 10 < 'sort order value' < 20 : step displays between shipping and payment step
                 * 'sort order value' > 20 : step displays after payment step
                 */
                1
            );
 
            return this;
        },
 
        navigate: function () {
            this.isVisible(true);
        },
 
        /**
         * @returns void
         */
        navigateToNextStep: function () {
            stepNavigator.next();
        },
 
        /**
         * Estimate totals
         */
        updateOrder: function () {
            totalsDefaultProvider.estimateTotals(quote.shippingAddress());
        }
    });
});

If your new step is the first step, you have to create mixins for the payment and shipping steps.

Otherwise, two steps will be activated on the loading of the checkout.

Now we will add app/code/Magelearn/NewStepCheckout/view/frontend/requirejs-config.js file and add slick JS to display products in slider on checkout page and will add shipping-payment-mixin to display this new step at first.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var config = {
    paths: {           
        'slick': "Magelearn_NewStepCheckout/js/slick"
    },
    'config': {
        'mixins': {
            'Magento_Checkout/js/view/shipping': {
                'Magelearn_NewStepCheckout/js/view/shipping-payment-mixin': true
            },
            'Magento_Checkout/js/view/payment': {
                'Magelearn_NewStepCheckout/js/view/shipping-payment-mixin': true
            }
        }
    },
    shim: {
        'slick': {
            deps: ['jquery']
        }
    }
}

As per defined in above file we will add app/code/Magelearn/NewStepCheckout/view/frontend/web/js/view/shipping-payment-mixin.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
 * If your new step is the first step,
 you have to create mixins for the payment and shipping steps.
 Otherwise, two steps will be activated on the loading of the checkout.
 */
define([
    'ko'
], function (ko) {
    'use strict';
 
    var mixin = {
 
        initialize: function () {
            // set visible to be initially false to have additional step show first
            this.visible = ko.observable(false);
            this._super();
 
            return this;
        }
    };
 
    return function (target) {
        return target.extend(mixin);
    };
});

We will also add js file for slick slider at app/code/Magelearn/NewStepCheckout/view/frontend/web/js/slick.js

And css file at app/code/Magelearn/NewStepCheckout/view/frontend/web/css/source/_module.less

Now we will add our template file at app/code/Magelearn/NewStepCheckout/view/frontend/web/template/new-step.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--The 'step_code' value from the .js file should be used-->
<li id="new_step" data-bind="fadeVisible: isVisible">
    <div class="step-title" data-bind="i18n: 'You May Also Like'" data-role="title"></div>
    <div id="checkout-step-title" class="step-content" data-role="content">
        <div class="step-products" data-bind="html: window.checkoutConfig.checkout_products_block_content"></div>
        <form data-bind="submit: navigateToNextStep" novalidate="novalidate">
            <div class="actions-toolbar">
                <div class="primary">
                    <button data-role="opc-continue" type="submit" class="button action continue primary">
                        <span><!-- ko i18n: 'Next'--><!-- /ko --></span>
                    </button>
                </div>
            </div>
        </form>
    </div>
</li>

As per highlighted code above, we will add checkout_products_block_content in window.checkoutConfig

For that we will add app/code/Magelearn/NewStepCheckout/etc/frontend/di.xml file.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Model\CompositeConfigProvider">
        <arguments>
            <argument name="configProviders" xsi:type="array">
                <item name="cms_block_config_provider" xsi:type="object">Magelearn\NewStepCheckout\Model\ConfigProvider</item>
            </argument>
        </arguments>
    </type>
</config>

Now add app/code/Magelearn/NewStepCheckout/Model/ConfigProvider.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
 
namespace Magelearn\NewStepCheckout\Model;
 
use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Framework\View\LayoutInterface;
 
class ConfigProvider implements ConfigProviderInterface
{
    /** @var LayoutInterface  */
    protected $_layout;
 
    public function __construct(LayoutInterface $layout)
    {
        $this->_layout = $layout;
    }
 
    public function getConfig()
    {
        $checkoutBlockId = "checkout_products_block"; // CMS Block Identifier
 
        return [
            'checkout_products_block_content'=> $this->_layout->createBlock('Magento\Cms\Block\Block')->setBlockId($checkoutBlockId)->toHtml()
        ];
    }
}

Now as per image attached in this article we will create a CMS Block with Identifier "checkout_products_block" and will Insert Product List widget with template option "Checkout Products Grid Template".

For that we will add app/code/Magelearn/NewStepCheckout/etc/widget.xml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
         xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
    <widget id="products_list" class="Magento\CatalogWidget\Block\Product\ProductsList">
        <label translate="true">Catalog Products List</label>
        <description translate="true">List of Products</description>
        <parameters>
            <parameter name="template" xsi:type="select" required="true" visible="true">
                <options>
                    <option name="checkoutproducts" value="Magelearn_NewStepCheckout::product/widget/content/grid.phtml">
                        <label translate="true">Checkout Products Grid Template</label>
                    </option>
                </options>
            </parameter>
        </parameters>
        <containers>
            <container name="content">
                <template name="checkoutproducts" value="checkoutproducts"/>
            </container>
            <container name="content.top">
                <template name="checkoutproducts" value="checkoutproducts"/>
            </container>
            <container name="content.bottom">
                <template name="checkoutproducts" value="checkoutproducts"/>
            </container>
        </containers>
    </widget>
</widgets>

Now as per highlighted code above we will add app/code/Magelearn/NewStepCheckout/view/frontend/templates/product/widget/content/grid.phtml file.

We can take the referance from vendor/magento/module-catalog-widget/view/frontend/templates/product/widget/content/grid.phtml file and will modify our file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?php
 
use Magento\Framework\App\Action\Action;
 
/** @var \Magento\CatalogWidget\Block\Product\ProductsList $block */
 
// phpcs:disable Generic.Files.LineLength.TooLong
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundHelper
?>
<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())): ?>
    <?php
    $type = 'widget-product-grid';
    $mode = 'grid';
    $image = 'new_products_content_widget_grid';
    $items = $block->getProductCollection()->getItems();
    $showWishlist = false;
    $showCompare = false;
    $showCart = true;
    $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
    $description = false;
    ?>
    <div class="block widget block-products-list <?= /* @noEscape */ $mode ?>">
        <?php if ($block->getTitle()): ?>
            <div class="block-title">
                <strong><?= $block->escapeHtml(__($block->getTitle())) ?></strong>
            </div>
        <?php endif ?>
        <div class="block-content">
            <?= /* @noEscape */ '<!-- ' . $image . '-->' ?>
            <div class="custom-checkout-product-slider products-<?= /* @noEscape */ $mode ?> <?= /* @noEscape */ $mode ?>">
                    <?php foreach ($items as $_item): ?>
                        <div class="item">
                            <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-photo">
                                <?= $block->getImage($_item, $image)->toHtml() ?>
                            </a>
                            <div class="product-item-details">
                                <strong class="product-item-name">
                                    <a title="<?= $block->escapeHtml($_item->getName()) ?>"
                                       href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>"
                                       class="product-item-link">
                                        <?= $block->escapeHtml($_item->getName()) ?>
                                    </a>
                                </strong>
                                <?php if ($templateType): ?>
                                    <?= $block->getReviewsSummaryHtml($_item, $templateType) ?>
                                <?php endif; ?>
 
                                <?= $block->getProductPriceHtml($_item, $type) ?>
 
                                <?= $block->getProductDetailsHtml($_item) ?>
 
                                <?php if ($showWishlist || $showCompare || $showCart): ?>
                                    <div class="product-item-inner-checkout">
                                        <div class="product-item-actions">
                                            <?php if ($showCart): ?>
                                                <div class="actions-primary">
                                                    <?php if ($_item->isSaleable()): ?>
                                                        <?php $postParams = $block->getAddToCartPostParams($_item); ?>
                                                        <form data-role="tocart-form" data-product-sku="<?= $block->escapeHtml($_item->getSku()) ?>" action="<?= $block->escapeUrl($postParams['action']) ?>" method="post">
                                                            <input type="hidden" name="product" value="<?= $block->escapeHtmlAttr($postParams['data']['product']) ?>">
                                                            <input type="hidden" name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>" value="<?= /* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED] ?>">
                                                            <?= $block->getBlockHtml('formkey') ?>
                                                            <button type="submit"
                                                                    title="<?= $block->escapeHtml(__('Add to Cart')) ?>"
                                                                    class="action tocart primary">
                                                                <span><?= $block->escapeHtml(__('Add to Cart')) ?></span>
                                                            </button>
                                                        </form>
                                                    <?php else: ?>
                                                        <?php if ($_item->isAvailable()): ?>
                                                            <div class="stock available"><span><?= $block->escapeHtml(__('In stock')) ?></span></div>
                                                        <?php else: ?>
                                                            <div class="stock unavailable"><span><?= $block->escapeHtml(__('Out of stock')) ?></span></div>
                                                        <?php endif; ?>
                                                    <?php endif; ?>
                                                </div>
                                            <?php endif; ?>
                                            <?php if ($showWishlist || $showCompare): ?>
                                                <div class="actions-secondary" data-role="add-to-links">
                                                    <?php if ($this->helper(\Magento\Wishlist\Helper\Data::class)->isAllow() && $showWishlist): ?>
                                                        <a href="#"
                                                           data-post='<?= /* @noEscape */ $block->getAddToWishlistParams($_item) ?>' class="action towishlist" data-action="add-to-wishlist" title="<?= $block->escapeHtmlAttr(__('Add to Wish List')) ?>">
                                                            <span><?= $block->escapeHtml(__('Add to Wish List')) ?></span>
                                                        </a>
                                                    <?php endif; ?>
                                                    <?php if ($block->getAddToCompareUrl() && $showCompare): ?>
                                                        <?php $compareHelper = $this->helper(\Magento\Catalog\Helper\Product\Compare::class);?>
                                                        <a href="#" class="action tocompare" data-post='<?= /* @noEscape */ $compareHelper->getPostDataParams($_item) ?>' title="<?= $block->escapeHtmlAttr(__('Add to Compare')) ?>">
                                                            <span><?= $block->escapeHtml(__('Add to Compare')) ?></span>
                                                        </a>
                                                    <?php endif; ?>
                                                </div>
                                            <?php endif; ?>
                                        </div>
                                    </div>
                                <?php endif; ?>
                            </div>
                        </div>
                    <?php endforeach ?>
            </div>
            <?= $block->getPagerHtml() ?>
        </div>
    </div>
    <?php if($block->getBlockHtml('formkey')): ?>
    <script type="text/x-magento-init">
    {
        ".block.widget [data-role=tocart-form]": {
            "Magento_Catalog/js/validate-product": {}
        }
    }
    </script>
    <?php endif;?>
<?php endif;?>
<script>
 require(["jquery","slick"],function($,slick){
     $(document).ready(function(){
        $('.custom-checkout-product-slider').slick({
            slidesToShow: 3,
            slidesToScroll: 1,
            dots: true,
            arrows: true,
            centerMode: true,
            centerPadding: 0,
            focusOnSelect: true,
            responsive: [
                {
                    breakpoint: 767,
                    settings: {
                        slidesToShow: 3,
                    }
                },
                {
                    breakpoint: 560,
                    settings: {
                        slidesToShow: 3,
                    }
                },
                {
                    breakpoint: 480,
                    settings: {
                        slidesToShow: 2,
                    }
                }
            ]
        });
     })
 })
</script>

Related Post:

0 Comments On "Add New Step in checkout with Product widget Magento2"

Back To Top