A Technical Overview On How To Customize The Magento 2 Checkout

Share On Facebook
Share On Twitter
Share On Linkedin

Compared to Magento 1, the checkout in Magento 2 has a number of fundamental improvements and changes to its visual appeal and general flow. The Magento 2 checkout is implemented using the UI components and out of the box, the checkout consists of two steps; shipping information and order review and payment.

Depending on the product type sold on the website, these steps can be combined into one in some cases. Virtual products, for example, don’t have a shipping step. The checkout totals and the corresponding side-bar are only displayed after the first step is completed. Within these steps, there are multiple components, all of which communicate with each other using models and other dependency injection techniques that we’ll explore in this article. We will go further into these steps, explaining in detail aspects like checkout rendering and adding a new payment option.

Magento 2 Checkout Rendering:
Each one of components building the checkout and their parent/child relationship in a large XML file which can be extended or overridden in your own theme or module. Magento 2 parses the XML file checkout_index_index.xml ( the file which gathers all necessary components) and runs it through the layout processor which processes each XML node and reads its configuration, which then inserts it into a large multidimensional associative array, where each key represents a component or a group of a component. This array is then converted into a JSON object which is then passed into the main app checkout component (Magento_Ui/js/core/app) on the main checkout template file (onepage.phtml) and initialized.

<script type="text/x-magento-init">
    {
        "#checkout": {
            "Magento_Ui/js/core/app": <?php /* @escapeNotVerified */ echo $block->getJsLayout();?>
        }
    }

 

The app.js file is responsible for defining rendering components. It looks as follows:

define([
    './renderer/types',
    './renderer/layout',
    '../lib/knockout/bootstrap'
], function (types, layout) {
    'use strict';
 
    return function (data, merge) {
        types.set(data.types);
        layout(data.components, undefined, true, merge);
    };
});

 

This script injects the layout renderer into a dependency./renderer/layout which is then passed to the data.components object which contains all the components that need to be rendered. The renderer then loops through each of these components iteratively rendering each one and checking to see if each node has any children. This simplifies the process of adding new custom components. The only thing to do is to include the component into XML file and then build the component logic. If children are found within a node it loops them in the same manner and renders each component in the list it receives. Once the renderer reaches a component/ node with no children then the layout renderer simply returns as normal and processes the current node with no further rendering of children.

Adding new payment option:
Usually, clients try to find ways to attract and retain customers to their stores and one of the most common ways to build client loyalty is using loyalty programmes. In our case, we will try to build a program called “Store Credit, which is not exactly a loyalty program but is a refill one. In simple words, you buy products which are not physical and you choose their price which is then stored as a credit to be used for future purchases in the store. You can ask yourself, what’s the benefit of this when I pay for what I buy but I don’t pay with money but with store credits? The answer is: the credit will be converted into more points and it has more value than monetary assets when you buy something. Example:

You buy a store credit product for 100$ and that amount is converted into 150 store credit points. In the future you have to buy a product that costs 100$, but you can buy that for 140 store credit points. Woohoo, you just earned 10 points for free !!!

Now let’s go back to the technical approach to achieving this functionality. Some steps will be just mentioned to show how the whole functionality is achieved but we will not go into details since it’s not directly related with checkout rendering. Steps:

    1. 1. Create a product type, which is virtual. This product has zero-price, the client chooses the price of that product in frontend based on the amount of the store credits he wants to earn at that moment.
      2. Add new tab on customer edit page on admin to allow the possibility of adding store credit from admin part to each customer.
      3. Add a custom grid for managing store credits for each user, also each item of the grid, if it will be opened can show a history of credit balance for that customer.
      4. Create a transfer store credit form on customer account on the frontend. This makes possible to transfer credit to an existing/not-existing customer. If a customer does not exist, an email inviting him to register on the store and earn the store credits will be sent to him.
      5. Cron functions to send emails and to update customer balance.

 

CHECKOUT RELATED STEPS (parts of the code will be provided):
Add the component to the checkout:
Under view/frontend/layout create checkout_index_index.xml with the following content:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2017 Shero. All rights reserved.
 * @category Shero Extensions
 * @package Shero_DeclareDiscount
 * @author Shero <www.sherocommerce.com>
 * @link https://www.sherodesigns.com/
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head><css src="Shero_Producttype::css/pay_amount.css"/></head>
    <body>
        <referenceContainer name="content">
            <block class="Shero\Producttype\Block\Checkout\Onepage" name="pay.storecredit" template="Shero_Producttype::paywithstorecredit.phtml"/>
            <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="billing-step" xsi:type="array">
                                                <item name="component" xsi:type="string">uiComponent</item>
                                                <item name="children" xsi:type="array">
                                                    <item name="payment" xsi:type="array">
                                                        <item name="children" xsi:type="array">
                                                            <item name="afterMethods" xsi:type="array">
                                                                <item name="children" xsi:type="array">
                                                                    <item name="declare-discount" xsi:type="array">
                                                                        <item name="component" xsi:type="string">Shero_Producttype/js/view/payment/storecredit</item>
                                                                        <item name="children" xsi:type="array">
                                                                            <item name="errors" xsi:type="array">
                                                                                <item name="sortOrder" xsi:type="string">1</item>
                                                                                <item name="component" xsi:type="string">Shero_Producttype/js/view/payment/storecredit-messages</item>
                                                                                <item name="displayArea" xsi:type="string">messages</item>
                                                                            </item>
                                                                        </item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                    <item name="sidebar" xsi:type="array">
                                        <item name="children" xsi:type="array">
                                            <item name="summary" xsi:type="array">
                                                <item name="children" xsi:type="array">
                                                    <item name="totals" xsi:type="array">
                                                        <item name="children" xsi:type="array">
                                                            <item name="storecredits" xsi:type="array">
                                                                <item name="component"  xsi:type="string">Shero_Producttype/js/view/summary/store-credits</item>
                                                                <item name="config" xsi:type="array">
                                                                    <item name="title" xsi:type="string">Store Credit</item>
                                                                </item>
                                                                <item name="sortOrder" xsi:type="string">100</item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </argument>
                </arguments>
            </referenceBlock>
        </referenceContainer>
    </body>
</page>

 

Add extra data to be available during checkout
Under view/frontend/templates create paywithstorecredit.phtml with the following content:

<?php /** @var $block Shero\Producttype\Block\Checkout\Onepage */ ?>
<?php
if($block->showForm()){
   $isStoreCreditEnabled = 'true';
}else{
$isStoreCreditEnabled = 'false';
}?>
<script>
   var sheroStoreCreditData = {
       "isStoreCreditEnabled": "<?php echo $isStoreCreditEnabled; ?>",
       "creditInstructions": "<?php echo $block->getInstructions(); ?>",
       "avaliableCredit": "<?php echo $block->getAvaliableCredit()?>",
       'amountSubstracted': "<?php echo $block->getUseStoreCredit() != 0 ? 1 : 0 ; ?>",
       'usedAmount': "<?php echo $block->getUsedStoreCreditAmount()?>"
   }
</script>

 

This file will be rendered at checkout and will be used to get extra data we need to process during checkout. The information this template will give are as following:

isStoreCreditEnabled: check if module is enabled or not
creditInstructions: instructions text added by admin in the module configurations
avaliableCredit: available store credit amount for current customer
amountSubstracted: check if store credit option is selected as payment or not
usedAmount: used amount of store credits

Create and render the components:
Next step is creating the component which will process the data: There are three component types we need to create, as they were defined in the checkout_index_index.xml:
store credits component → will process store credits data during checkout
view/frontend/web/js/view/payment create storecredit.js:

 define([
       'jquery',
       'ko',
       'uiComponent',
       'Shero_Producttype/js/action/add-storecredit',
       'Shero_Producttype/js/action/remove-storecredit',
       'Magento_Checkout/js/model/quote',
       'Magento_Checkout/js/model/totals',
   ],
              /**
            * Payment application
            */
           apply: function() {
               if (this.validate()) {
                   addStoreCreditAction(storeCredits(), isApplied);
               }},
           /**
            * Cancel Payment
            */
           cancel: function() {
               if (this.validate()) {
                   storeCredits('');
                   removeStoreCreditAction(isApplied);
               }},
       });
   }
);

 

As you can see, this script defines two other scripts which will process the actions (apply/cancel):
‘Shero_Producttype/js/action/add-storecredit’,
‘Shero_Producttype/js/action/remove-storecredit’.

messages component → will render messages
view/frontend/web/js/view/payment create storecredit-messages.js

define([
   'Magento_Ui/js/view/messages',
   '../../model/payment/storecredit-messages'
], function (Component, messageContainer) {
   'use strict';
   return Component.extend({
       initialize: function (config) {
           return this._super(config, messageContainer);
       }
   });
});

 

It will extend the checkout messages component and will be responsible for rendering messages related with store credit process.
totals component → will render totals and update them on submit actions
view/frontend/web/js/view/summary create store-credits.js

define(
   [
       'Magento_Checkout/js/view/summary/abstract-total',
       'Magento_Checkout/js/model/totals',
       'Magento_Checkout/js/model/quote'
   ],
   function (Component, totals,quote,storeCredits) {
       return Component.extend({
           defaults: {
               template: 'Shero_Producttype/summary/store-credit'
           },
           totals: totals.totals(),
           getPureValue: function () {
               var price = 0;
               if (this.totals) {
                   var segment = totals.getSegment('storecredits');
                   if (segment) {
                       price = segment['value'];
                   }}
               return price;
           },
           getValue: function () {
               return this.getFormattedPrice(this.getPureValue());
           },
           isAvailable: function() {
               return this.isFullMode() && this.getPureValue() != 0;
           }
       });
   });

 

This component is called after the reload-totals is called, it will check if there are available price segment ‘storecredits’, if yes will render that price in the price sidebar on the checkout page.

In addition to what was created in the previous sections, there are templates which need to be created. Templates need to display payment option and totals on the sidebar. The templates will be filled with data from knockout components and will be rendered in the defined place on the checkout page.
Demo:

Payment Option in Checkout Page:

Sidebar Totals:

Transfer Credit:

Summary:
Depending on your store needs, you can choose if the default Magento checkout is enough or third-party solution like OneStep Checkout is more convenient. By default, Magento provides options for customization and improvements, at some point, you have to choose between customizing and extending the default Magento 2 checkout or integrating a third party solution.

Fully customize the default checkout requires more effort, but it’s easier to do customizations in the future. Integrating a third-party solution is easier and time effective but has the disadvantage of limited customizations.

Every Magento store is different in their own way. As a developer, you must choose what is best for the eCommerce store and think about how the checkout can be improved to increase conversions and lower cart abandonment. What we wanted to show with this post was the way checkout works in Magento 2 and how we can customize that to improve checkout process. Adding new payment options and other related loyalty-programmes are a big step toward achieving this.

Note: Files shown on the blog are not as they are in the module, only main parts are shown. If you need to apply this functionality to your site, please contact us.

Rate this post