Magento2 | PWA | GraphQL

Adds a free shipping progress bar on Magento2 shopping cart page to promote increased order value


In this tutorial, we will learn to create Magento 2 module which adds a free shipping progress bar on magento2 shopping cart page to promote increased order value.

Let's start it by creating custom module.

You can find complete module on Github.

Create folder inside app/code/Magelearn/FreeShippingProgressBar

Add registration.php file in it:

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_FreeShippingProgressBar', __DIR__);
Add composer.json file in it:
{
    "name": "magelearn/module-freeshippingprogressbar",
    "description": "Display a progress bar on the shopping cart based on order total to encourage users to spend more to get free shipping",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "info@mage2gen.com",
            "name": "Mage2Gen"
        },
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "require": {
        "magento/module-quote": "*",
        "magento/module-checkout": "*"
    },
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\FreeShippingProgressBar\\": ""
        }
    }
}
Add etc/module.xml file in it:
<?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="Magelearn_FreeShippingProgressBar" setup_version="1.0.0"/>
</config>

 Now, We will give system configuration options and default configurations values for this module.

Add etc/adminhtml/system.xml file in it:
<?xml version="1.0" ?>
<!--
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="checkout">
            <group id="cart">
                <field id="freeshipping_progress_enable" sortOrder="110" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" type="select">
                    <label>Enable Free Shipping Progress Bar</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <comment>Display a free shipping progress bar in cart to promote higher order values.</comment>
                </field>
                <field id="freeshipping_progress_color_field" sortOrder="120" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" type="text">
                    <label>Background Color</label>
                    <comment><![CDATA[Background color]]></comment>
                    <frontend_model>Magelearn\FreeShippingProgressBar\Block\Color</frontend_model>
                    <depends>
                        <field id="freeshipping_progress_enable">1</field>
                    </depends>
                </field>
                <field id="use_freeshipping_method_config" sortOrder="130" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" type="select">
                    <label>Use Free Shipping Method Configuration</label>
                    <comment>If set to yes, the free shipping progress bar will be based on if the core free shipping method is available or not. If set to no, you can use a custom configuration using the fields that will appear below this field.</comment>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <depends>
                        <field id="freeshipping_progress_enable">1</field>
                    </depends>
                </field>
                <field id="freeshipping_progress_min_total" sortOrder="140" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" type="text">
                    <label>Free Shipping Progress Bar Minimum Order Total</label>
                    <comment>Enter the value an order has to be equal to or over to be eligible for free shipping.</comment>
                    <depends>
                        <field id="use_freeshipping_method_config">0</field>
                    </depends>
                </field>
            </group>
        </section>
    </system>
</config>
To give color options in system configurations, as defined in frontend_model node, we will add
Color.php file inside app/code/Magelearn/FreeShippingProgressBar/Block folder.
<?php
namespace Magelearn\FreeShippingProgressBar\Block;

class Color extends \Magento\Config\Block\System\Config\Form\Field
{
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }
    protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        $html = $element->getElementHtml();
        $value = $element->getData('value');
        $html .= '<script type="text/javascript">
            require(["jquery"], function($) {
                $(document).ready(function(e) {
                    $("#' . $element->getHtmlId() . '").css("background-color", "#' . $value . '");
                    $("#' . $element->getHtmlId() . '").colpick({
                        layout: "hex",
                        submit: 0,
                        colorScheme: "dark",
                        color: "#' . $value . '",
                        onChange: function(hsb, hex, rgb, el, bySetColor) {
                            $(el).css("background-color", "#" + hex);
                            if (!bySetColor) $(el).val(hex);
                        }
                    }).keyup(function() {
                        $(this).colpickSetColor(this.value);
                    });
                });
            });
            </script>';
        return $html;
    }
}
Add etc/config.xml file in it and provide default values:
<?xml version="1.0" ?>
<!--
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <checkout>
            <cart>
                <freeshipping_progress_enable>0</freeshipping_progress_enable>
                <freeshipping_progress_color_field>00b052</freeshipping_progress_color_field>
                <use_freeshipping_method_config>1</use_freeshipping_method_config>
            </cart>
        </checkout>
    </default>
</config>
Add etc/acl.xml file in it and add below code:
<?xml version="1.0" ?>
<!--
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magento_Backend::stores">
                    <resource id="Magento_Backend::stores_settings">
                        <resource id="Magento_Config::config">
                            <resource id="Magelearn_FreeShippingProgressBar::config" title="Free Shipping Progress Bar"/>
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>
Now to display freeshipping progress bar on shopping cart page, we will modify layout file and define our custom block file by paasing viewModel <arguments> node. 

Create layout file checkout_cart_index.xml file inside app/code/Magelearn/FreeShippingProgressBar/view/frontend/layout folder.

<?xml version="1.0"?>
<!--
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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="Magelearn_FreeShippingProgressBar::css/custom.css" rel="stylesheet" type="text/css"/>
    </head>
    <body>
        <referenceContainer name="cart.summary">
            <block name="checkout.cart.freeshipping.progress.bar"
                   before="checkout.cart.shipping"
                   template="Magelearn_FreeShippingProgressBar::cart/freeshipping_progress_bar.phtml"
                   ifconfig="checkout/cart/freeshipping_progress_enable">
                <arguments>
                    <argument name="viewModel" xsi:type="object">Magelearn\FreeShippingProgressBar\ViewModel\Cart\ProgressBar</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

Create template file freeshipping_progress_bar.phtml file inside app/code/Magelearn/FreeShippingProgressBar/view/frontend/templates/cart folder.

<?php
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

use Magelearn\FreeShippingProgressBar\ViewModel\Cart\ProgressBar;
use Magento\Framework\View\Element\Template;

/** @var Template $block */
/** @var ProgressBar $viewModel */
$viewModel = $block->getData('viewModel');

$shippingLabel = 'FREE SHIPPING';

?>
<div class="freeship-progress">
    <?php if (!$viewModel->isFreeShippingEligible()): ?>
        <?php
        $freeshippingProgress = $viewModel->getFreeShippingCompletionPercent();
		$freeshippingProgresscolor = $viewModel->getProgressbarBackground();
        $background = "background: linear-gradient(to right, #".$freeshippingProgresscolor." 0%, #".$freeshippingProgresscolor." 
            ". $freeshippingProgress . "%, lightgreen);";
        if ($freeshippingProgress > 90) {
            $background = "background: linear-gradient(to right, #".$freeshippingProgresscolor." 0%, #".$freeshippingProgresscolor." 95%, lightgreen);";
        }
        ?>
        <p class="upsell">
        <?= /* @noEscape */ __(
            'Add <span class="freeship-price">%1</span> more to get <span class="freeship-bold">%2!</span>',
            $viewModel->getFormattedPrice($viewModel->getFreeShippingDifference()),
            $shippingLabel
        ) ?>
        </p>
        <div class="freeship-progress-bar-wrapper">
            <span class="min"><?= /* @noEscape */ $viewModel->getFormattedPrice(0, 0) ?></span>
            <div class="freeship-progress-bar">
                <div class="freeship-progress-bar-bg">
                    <div class="freeship-progress-bar-fill freeship-progress-bar-progress"
                         style="width: <?= /* @noEscape */ $viewModel->getFreeShippingCompletionPercent() ?>%;
                         <?= /* @noEscape */ $background ?>">
                    </div>
                </div>
            </div>
            <span class="max">
                <?= /* @noEscape */ $viewModel->getFormattedPrice($viewModel->getFreeShippingMinValue(), 0) ?>
            </span>
        </div>
    <?php else: ?>
        <div class="freeship-text">
            <p>
                <?= /* @noEscape */ __(
                    'Your order is eligible for <span class="freeship-bold">%1!</span>',
                    $shippingLabel
                ) ?>
            </p>
        </div>
    <?php endif; ?>
</div>

Create ViewModel file ProgressBar.php inside app/code/Magelearn/FreeShippingProgressBar/ViewModel/Cart folder.

<?php
/**
 * Copyright All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magelearn\FreeShippingProgressBar\ViewModel\Cart;

use Magento\Checkout\Model\Session;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Store\Model\ScopeInterface;

class ProgressBar implements ArgumentInterface
{
    /**
     * System XML config path for Magelearn_FreeShippingProgressBar - Uses default checkout cart section
     */
    protected const CHECKOUT_CART_XML_CONFIG_PATH = 'checkout/cart/';

    /**
     * System XML config path for core Free Shipping method
     */
    protected const CARRIERS_FREE_SHIPPING_XML_CONFIG_PATH = 'carriers/freeshipping/';

    /**
     * @var ScopeConfigInterface $scopeConfig
     */
    protected $scopeConfig;

    /**
     * @var Session $session
     */
    protected $session;

    /**
     * @var PriceCurrencyInterface $priceCurrency
     */
    protected $priceCurrency;

    /**
     * Countdown constructor.
     *
     * @param ScopeConfigInterface      $scopeConfig
     * @param Session                   $session
     * @param PriceCurrencyInterface    $priceCurrency
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        Session $session,
        PriceCurrencyInterface $priceCurrency
    ) {
        $this->scopeConfig = $scopeConfig;
        $this->session = $session;
        $this->priceCurrency = $priceCurrency;
    }

    /**
     * Get minimum order total required to be eligible for free shipping
     *
     * @return float
     */
    public function getFreeShippingMinValue(): float
    {
        if ($this->scopeConfig->getValue(self::CHECKOUT_CART_XML_CONFIG_PATH
                . 'use_freeshipping_method_config', ScopeInterface::SCOPE_STORE)
            && $this->scopeConfig->getValue(self::CARRIERS_FREE_SHIPPING_XML_CONFIG_PATH
                . 'active', ScopeInterface::SCOPE_STORE)
        ) {
            return $this->getFreeShippingMethodMinValue();
        }

        return (float)$this->scopeConfig->getValue(self::CHECKOUT_CART_XML_CONFIG_PATH
            . 'freeshipping_progress_min_total', ScopeInterface::SCOPE_STORE);
    }

    public function getFreeShippingMethodMinValue(): float
    {
        return (float)$this->scopeConfig->getValue(self::CARRIERS_FREE_SHIPPING_XML_CONFIG_PATH
            . 'free_shipping_subtotal', ScopeInterface::SCOPE_STORE);
    }

    /**
     * Get current quote/cart total
     *
     * @return float
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function getCurrentTotal(): float
    {
        return (float)$this->session->getQuote()->getSubtotalWithDiscount();
    }

    /**
     * Validate if current order total is free shipping eligible
     *
     * @return bool
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function isFreeShippingEligible(): bool
    {
        $currentTotal = $this->getCurrentTotal();
        if ($this->scopeConfig->getValue(self::CHECKOUT_CART_XML_CONFIG_PATH
            . 'use_freeshipping_method_config', ScopeInterface::SCOPE_STORE)) {
            if ($this->scopeConfig->getValue(
                self::CARRIERS_FREE_SHIPPING_XML_CONFIG_PATH . 'active',
                ScopeInterface::SCOPE_STORE
            )) {
                return ($currentTotal >= $this->getFreeShippingMethodMinValue());
            }
            return false;
        }

        return ($currentTotal >= $this->getFreeShippingMinValue());
    }

    /**
     * Get difference between minimum order total for free shipping current order total
     *
     * @return float
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function getFreeShippingDifference(): float
    {
        return $this->getFreeShippingMinValue() - $this->getCurrentTotal();
    }

    /**
     * Get percentage completed towards free shipping
     *
     * @return float
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function getFreeShippingCompletionPercent(): float
    {
        return ($this->getCurrentTotal() / $this->getFreeShippingMinValue()) * 100;
    }

    /**
     * @param float $price
     * @param int   $precision
     *
     * @return string
     */
    public function getFormattedPrice(float $price, int $precision = 2): string
    {
        return $this->priceCurrency->format($price, false, $precision);
    }
	public function getProgressbarBackground(): string
    {
        return $this->scopeConfig->getValue(self::CHECKOUT_CART_XML_CONFIG_PATH
                . 'freeshipping_progress_color_field', ScopeInterface::SCOPE_STORE);
    }
}

Finally add CSS file custom.less file inside app/code/Magelearn/FreeShippingProgressBar/view/frontend/web/css folder to apply final effects.

.freeship-progress {
    padding: 15px;

    background-color: transparent;
    border-top: 1px solid #ccc;
    @media screen and (min-width: 768px) {
        padding: 16px 0;
    }

    .freeship-text {
        color: #000;
        margin-bottom: 0;
        max-width: 100%;

        p {
            margin-bottom: 0;
        }
    }

    p {
        &.upsell {
            text-align: center;
            @media screen and (min-width: 768px) {
                text-align: left;
            }
        }

        .freeship-price {
            color: red;
            font-weight: 600;
        }

        .freeship-bold {
            font-weight: 600;
        }
    }

    .freeship-progress-bar-wrapper {
        color: #000;

        span {
            display: inline-block;

            &.min {
                width: 17px;
            }

            &.max {
                width: 33px;
                text-align: right;
            }
        }

        .freeship-progress-bar-bg {
            background-color: rgba(0, 0, 0, .1);
            height: 8px;
            border-radius: 3px;
            overflow: hidden;
            position: relative;
            width: 100%;
        }

        .freeship-progress-bar-fill {
            height: 8px;
            display: block;
            border-radius: 3px;
            animation-name: shippingProgressBar;
            animation-duration: 1.25s;
            animation-timing-function: ease;
            animation-delay: 0s;
            animation-iteration-count: 1;
            animation-direction: normal;
            animation-fill-mode: none;
            animation-play-state: running;
            -webkit-animation: shippingProgressBar 1.25s 1;
            animation: shippingProgressBar 1.25s 1;
            transition: width .667s cubic-bezier(.37, .16, .22, .89);
        }

        .freeship-progress-bar-progress {
            background: #00b052;
        }

        .freeship-progress-bar {
            margin: 0 auto;
            width: ~'calc(100% - 58px)';
            display: inline-block;
        }
    }

    @-webkit-keyframes shippingProgressBar {
        0% {
            -webkit-transform: translateX(-100%);
            transform: translateX(-100%)
        }

        to {
            -webkit-transform: translateX(0);
            transform: translateX(0)
        }
    }
    @keyframes shippingProgressBar {
        0% {
            -webkit-transform: translateX(-100%);
            transform: translateX(-100%)
        }

        to {
            -webkit-transform: translateX(0);
            transform: translateX(0)
        }
    }
}
0 Comments On "Adds a free shipping progress bar on Magento2 shopping cart page to promote increased order value"

Back To Top