Magento2 | PWA | GraphQL

Add custom validation for phone number in address form and add country flag dropdown with auto detection flag Magento2


In this post we will check how to Add custom validation for phone number field in Shipping and Billing address form and Address book edit form at frontend.

Here we are using International Telephone Input JS https://intl-tel-input.com/ which is a Jquery Plugin for entering and validating international telephone numbers.

In this module we will integrate this jQuery plugin in Magento2 module. Which adds a country code flag with phone extension prefix dropdown to billing and shipping address telephone field as well as customer edit address book from My Account.

This module also Detects the user's country based on ip and auto select that country flag.

I have also added some code to add additional class to telephone field with which you can add/modify CSS for different purpose.

You can find complete module on Github at Magelearn_CheckoutPhoneValidator








Let's start it by creating custom module.

Create folder inside app/code/Magelearn/CheckoutPhoneValidator

Add registration.php file in it:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magelearn_CheckoutPhoneValidator',
    __DIR__
);

Add composer.json file in it:

{
    "name": "magelearn/checkout-phone-validator",
    "description": "Add custom validation for phone number in shipping address, billing address and Edit Address form Addressbook at frontend in Magento2. Also add additional class to telephone field. Add flag dropdown to telephone fields and auto detects user's country based on ip and preselect that country flag with International Telephone Input JS https://intl-tel-input.com/",
    "type": "magento2-module",
    "license": "proprietary",
    "authors": [
        {
            "name": "vijay rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [ "registration.php" ],
        "psr-4": {
            "Magelearn\\CheckoutPhoneValidator\\": ""
        }
    }
}

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_CheckoutPhoneValidator" setup_version="1.0.0">
		<sequence>
			<module name="Magento_Checkout" />
		</sequence>
	</module>
</config>
First We will add some system configuration options.
Add file at etc/adminhtml/system.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="internationaltelephoneinput" translate="label" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1">
            <class>separator-top</class>
            <label>International Telephone Input</label>
            <tab>customer</tab>
            <resource>Magelearn_CheckoutPhoneValidator::internationaltelephoneinput_config</resource>
            <group id="general" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General Configuration</label>
                <field id="enabled" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Module Enable</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="allow" translate="label" type="multiselect" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Allow Countries</label>
                    <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
                    <can_be_empty>1</can_be_empty>
                </field>
            </group>
        </section>
    </system>
</config>
Now add some default configuration options at etc/config.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <internationaltelephoneinput>
            <general>
                <enabled>1</enabled>
                <allow></allow>
            </general>
        </internationaltelephoneinput>
    </default>
</config>
Add etc/acl.xml file.
<?xml version="1.0"?>
<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_CheckoutPhoneValidator::internationaltelephoneinput_config"
                                      title="Config Section for International Telephone Input" sortOrder="50"/>
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>
Add etc/csp_whitelist.xml file to whitelist the origins of external scripts (https://ipinfo.io) which will be useful to auto-detect the country flag in Phone field.
<?xml version="1.0"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd">
    <policies>
        <policy id="script-src">
            <values>
                <value id="ipinfo" type="host">https://ipinfo.io</value>
            </values>
        </policy>
    </policies>
</csp_whitelist>

Now we will add Magento\Checkout\Block\Checkout\LayoutProcessor Plugin in di.xml file to modify layout for Checkout Page.

Also will add another plugin Magento\Checkout\Block\Checkout\AttributeMerger to add additional class for Phone field.

Add etc/di.xml file.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">    		
	<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
	    <plugin name="custom-phone-validation" type="Magelearn\CheckoutPhoneValidator\Block\Checkout\LayoutProcessor"/>
	</type>
	<type name="Magento\Checkout\Block\Checkout\AttributeMerger">
		<plugin name="customAttributeMerger" type="Magelearn\CheckoutPhoneValidator\Model\Plugin\AttributeMergerPlugin"/>
	</type>
</config>

First, we will check by adding Model/Plugin/AttributeMergerPlugin.php file.

<?php
namespace Magelearn\CheckoutPhoneValidator\Model\Plugin;

class AttributeMergerPlugin
{
    /**
     * @param \Magento\Checkout\Block\Checkout\AttributeMerger $subject
     * @param array $result
     * @return array
     */
    public function afterMerge(
        \Magento\Checkout\Block\Checkout\AttributeMerger $subject,
        array  $result
    ) {
        if (array_key_exists('telephone', $result)) {
            $result['telephone']['additionalClasses'] = 'validate_telephone_number';
        }
        return $result;
    }
}

Now add Block/Checkout/LayoutProcessor.php file.

<?php

namespace Magelearn\CheckoutPhoneValidator\Block\Checkout;

use Magelearn\CheckoutPhoneValidator\Helper\Data;

class LayoutProcessor
{
    /**
     * @var Data
     */
    protected $helper;
    
    /**
     * LayoutProcessor constructor.
     * @param Data $helper
     */
    public function __construct(Data $helper)
    {
        $this->helper = $helper;
    }
    
    /**
     * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject
     * @param array $jsLayout
     * @return array
     */
    public function afterProcess(
        \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
        array  $jsLayout
    ) {
            if (!$this->helper->isModuleEnabled()) {
                return $jsLayout;
            }
        
            /*For shipping address form*/
            
            $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']
            ['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['telephone']['validation']['custom-validate-telephone'] = true;
            
            $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']
            ['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['telephone']['config']['elementTmpl'] = 'Magelearn_CheckoutPhoneValidator/form/element/shipping_phone_template';
            
            /* config: checkout/options/display_billing_address_on = payment_method */
            if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                ['payment']['children']['payments-list']['children']
                )) {
                    
                    foreach ($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                        ['payment']['children']['payments-list']['children'] as $key => $payment) {
                            
                            $method = substr($key, 0, -5);
                            
                            /* telephone */
                            $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                            ['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children']
                            ['telephone'] = $this->helper->telephoneFieldConfig("billingAddress", $method);
                        }
                }
                
                /* config: checkout/options/display_billing_address_on = payment_page */
                if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                    ['payment']['children']['afterMethods']['children']['billing-address-form']
                    )) {
                        
                        $method = 'shared';
                        
                        /* telephone */
                        $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                        ['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children']
                        ['telephone'] = $this->helper->telephoneFieldConfig("billingAddress", $method);
                    }
          
        return $jsLayout;
    }
}

Add Helper/Data.php file.

<?php

namespace Magelearn\CheckoutPhoneValidator\Helper;

use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;

class Data extends AbstractHelper
{

    const XML_PATH_INTERNATIONAL_TELEPHONE_INPUT_MODULE_ENABLED = 'internationaltelephoneinput/general/enabled';

    const XML_PATH_INTERNATIONAL_TELEPHONE_MULTISELECT_COUNTRIES_ALLOWED = 'internationaltelephoneinput/general/allow';

    const XML_PATH_PREFERED_COUNTRY = 'general/store_information/country_id';

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @param Context $context
     */
    public function __construct(
        Context $context,
        StoreManagerInterface $storeManager
    )
    {
        parent::__construct($context);
        $this->storeManager = $storeManager;
    }

    /**
     * @return mixed
     */
    public function isModuleEnabled()
    {
        return $this->getConfig(self::XML_PATH_INTERNATIONAL_TELEPHONE_INPUT_MODULE_ENABLED);
    }

    /**
     * @return mixed
     */
    public function allowedCountries()
    {
        return $this->getConfig(self::XML_PATH_INTERNATIONAL_TELEPHONE_MULTISELECT_COUNTRIES_ALLOWED);
    }

    /**
     * @return mixed
     */
    public function preferedCountry()
    {
        return $this->getConfig(self::XML_PATH_PREFERED_COUNTRY);
    }

    /**
     * @param $configPath
     * @return mixed
     */
    protected function getConfig($configPath)
    {
        return $this->scopeConfig->getValue($configPath, ScopeInterface::SCOPE_STORE, $this->storeManager->getStore()->getId());
    }
    
    /**
     * Prepare telephone field config according to the Magento default config
     * @param $addressType
     * @param string $method
     * @return array
     */
    public function telephoneFieldConfig($addressType, $method = '')
    {
        return  [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [
                'customScope' => $addressType . $method,
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'Magelearn_CheckoutPhoneValidator/form/element/billing_phone_template',
                'tooltip' => [
                    'description' => 'For delivery questions.',
                    'tooltipTpl' => 'ui/form/element/helper/tooltip'
                ],
            ],
            'dataScope' => $addressType . $method . '.telephone',
            'dataScopePrefix' => $addressType . $method,
            'label' => __('Phone Number'),
            'provider' => 'checkoutProvider',
            'sortOrder' => 120,
            'validation' => [
                "required-entry"    => true,
                "custom-validate-telephone" => true,
                "max_text_length"   => 255,
                "min_text_length"   => 1
            ],
            'options' => [],
            'filterBy' => null,
            'customEntry' => null,
            'visible' => true,
            'focused' => false,
        ];
    }
}

Now as per highlighted code in above file, we will add template file for Phone field.
Add file at view/frontend/web/template/form/element/shipping_phone_template.html file.

<!-- input field element and corresponding bindings -->
<input class="input-text shipping-telephone-input" type="text" data-bind="
    value: value,
    valueUpdate: 'keyup',
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': getDescriptionId(),
        'aria-required': required,
        'aria-invalid': error() ? true : 'false',
        id: uid,
        disabled: disabled
    },
    mageInit: {
    'Magelearn_CheckoutPhoneValidator/js/initIntlTel':{}
    }" />
<!-- additional content -->

Now we will check about modifying the billing address telephone form field.As per code highlighted above add view/frontend/web/template/form/element/billing_phone_template.html file.

<!-- input field element and corresponding bindings -->
<input class="input-text billing-telephone-input" type="text" data-bind="
    value: value,
    valueUpdate: 'keyup',
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': getDescriptionId(),
        'aria-required': required,
        'aria-invalid': error() ? true : 'false',
        id: uid,
        disabled: disabled
    },
    mageInit: {
    'Magelearn_CheckoutPhoneValidator/js/initIntlTel':{}
    }" />
<!-- additional content -->

Now to play with the JS file, first we will add view/frontend/requirejs-config.js file.

var config = {
	map: {
	    '*': {
	        "intlTelInput": 'Magelearn_CheckoutPhoneValidator/js/intlTelInput',
	    }
	},
	paths: {
	    "intlTelInput": 'Magelearn_CheckoutPhoneValidator/js/intlTelInput',
	    "intlTelInputUtils": 'Magelearn_CheckoutPhoneValidator/js/utils',
	},
	shim: {
        'intlTelInput': {
            'deps':['jquery', 'knockout']
        }
    },
    config: {
        mixins: {
            'Magento_Ui/js/lib/validation/validator': {
                'Magelearn_CheckoutPhoneValidator/js/validator-mixin': true
            }
        }
    }
};

Now we will add intlTelInput JS Plugin related files.


Add view/frontend/web/js/initIntlTel.js file.

Add view/frontend/web/js/utils.js file.

Add some images in view/frontend/web/img folder.

Now add JS file at view/frontend/web/js/initIntlTel.js

define(
    [
        'jquery',
		'Magelearn_CheckoutPhoneValidator/js/utils',
		'intlTelInput'
    ],
    function ($, utiljs) {
        return function () {
			var intl_only_countries = window.checkoutConfig.intl_tel_config_option['only_countries'];
			var intl_preferred_countries = window.checkoutConfig.intl_tel_config_option['preferred_countries'];
			//console.log(intl_phone_config_option);
			var shipping_telephone_input = document.querySelector(".shipping-telephone-input");
			var billing_telephone_input = document.querySelectorAll(".billing-telephone-input");
			
			const intl_phone_config_option = {
			  nationalMode: false,
			  initialCountry: "auto",
			  onlyCountries: intl_only_countries,
			  preferredCountries: intl_preferred_countries,
			  geoIpLookup: function(callback) {
			    $.get('https://ipinfo.io', function() {}, "jsonp").always(function(resp) {
			      var countryCode = (resp && resp.country) ? resp.country : "us";
			      callback(countryCode);
			    });
			  },
			  utilsScript: utiljs // just for formatting/placeholders etc
			};
			
			if(shipping_telephone_input) {
				window.intlTelInput(shipping_telephone_input, intl_phone_config_option);
			}
		    if(billing_telephone_input) {
		    	billing_telephone_input.forEach(function(intlTelItem) {
		    		window.intlTelInput(intlTelItem, intl_phone_config_option);
	    		});

		    }
        };
    }
);

Here in above file we have fetched some options from window.checkoutConfig

For that first add etc/frontend/di.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Model\DefaultConfigProvider">
        <plugin name="add_intl_telephone_config_data" type="Magelearn\CheckoutPhoneValidator\Model\DefaultConfigProviderPlugin" />
    </type>
</config>

As Per highlighted code above add Model/DefaultConfigProviderPlugin.php file.

<?php
namespace Magelearn\CheckoutPhoneValidator\Model;

use Magento\Framework\Serialize\Serializer\Json;
use Magelearn\CheckoutPhoneValidator\Helper\Data;
use Magento\Directory\Api\CountryInformationAcquirerInterface;
use Magento\Checkout\Model\Session as CheckoutSession;

class DefaultConfigProviderPlugin
{
    /**
     * @var Json
     */
    private $jsonHelper;
    
    /**
     * @var CountryInformationAcquirerInterface
     */
    private $countryInformation;

    /**
     * @var Data
     */
    private $helper;
    
    /**
     *
     * @var CheckoutSession
     */
    private $checkoutSession;
    
    /**
     * DefaultConfigProviderPlugin constructor.
     * @param \Magento\Framework\Serialize\Serializer\Json $jsonHelper
     * @param \Magento\Directory\Api\CountryInformationAcquirerInterface $countryInformation
     * @param \Magelearn\CheckoutPhoneValidator\Helper\Data $helper
     * @param CheckoutSession $checkoutSession
     */
    public function __construct(
        Json $jsonHelper,
        CountryInformationAcquirerInterface $countryInformation,
        Data $helper,
        CheckoutSession $checkoutSession
    )
    {
        $this->jsonHelper = $jsonHelper;
        $this->countryInformation = $countryInformation;
        $this->helper = $helper;
        $this->checkoutSession = $checkoutSession;
    }

    public function afterGetConfig(\Magento\Checkout\Model\DefaultConfigProvider $config, $output)
    {
        $output = $this->getPhoneConfig($output);
        return $output;
    }

    /**
     * @return bool|string
     */
    private function getPhoneConfig($output)
    {
        $output['intl_tel_config_option']['preferred_countries'] = [strtolower($this->helper->preferedCountry())];
        
        if ($this->helper->allowedCountries()) {
            $output['intl_tel_config_option']['only_countries'] = explode(",", strtolower($this->helper->allowedCountries()));
        }
        return $output;
    }
}

Now we will modify checkout_index_index.xml file a little to add some CSS in it.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magelearn_CheckoutPhoneValidator::css/intlTelInput.css"/>
    </head>
</page>

Add CSS file at view/frontend/web/css/intlTelInput.css

Now we will modify customer_address_form.xml file to add the country flag dropdown field in customer Addressbook edit form.

Add file at view/frontend/layout/customer_address_form.xml

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Magelearn_CheckoutPhoneValidator::css/intlTelInput.css"/>
    </head>
    <body>
        <referenceContainer name="main">
            <block class="Magelearn\CheckoutPhoneValidator\Block\PhoneNumber"
                   ifconfig="internationaltelephoneinput/general/enabled"
                   template="Magelearn_CheckoutPhoneValidator::js-address.phtml"
                   name="international.telephone.input"/>
        </referenceContainer>
    </body>
</page>

Now as per highlighted code above we will add our block file and template file.

Add file at Block/PhoneNumber.php

<?php

namespace Magelearn\CheckoutPhoneValidator\Block;

use Magento\Framework\View\Element\Template;
use Magento\Framework\View\Element\Template\Context;
use Magento\Framework\Serialize\Serializer\Json;
use Magelearn\CheckoutPhoneValidator\Helper\Data;
use Magento\Directory\Api\CountryInformationAcquirerInterface;

class PhoneNumber extends Template
{

    /**
     * @var Json
     */
    protected $jsonHelper;

    /**
     * @var Data
     */
    protected $helper;

    /**
     * @var CountryInformationAcquirerInterface
     */
    protected $countryInformation;

    /**
     * PhoneNumber constructor.
     * @param Context $context
     * @param Json $jsonHelper
     */
    public function __construct(
        Context $context,
        Json $jsonHelper,
        CountryInformationAcquirerInterface $countryInformation,
        Data $helper
    )
    {
        $this->jsonHelper = $jsonHelper;
        $this->helper = $helper;
        $this->countryInformation = $countryInformation;
        parent::__construct($context);
    }

    public function phonePrefferedCountryConfig()
    {
        $preffered_country = [strtolower($this->helper->preferedCountry())];

        return $this->jsonHelper->serialize($preffered_country);
    }
    
    public function phoneOnlyCountryConfig()
    {
        $onlycountry = array();
        
        if ($this->helper->allowedCountries()) {
            $onlycountry = explode(",", strtolower($this->helper->allowedCountries()));
        }
        return $this->jsonHelper->serialize($onlycountry);
    }
}

Add Template file at view/frontend/templates/js-address.phtml file.

<?php
/**
 * @var $block  Magelearn\CheckoutPhoneValidator\Block\PhoneNumber
 */
?>
<script>
    require(['jquery', 'Magelearn_CheckoutPhoneValidator/js/utils', 'intlTelInput'], function($, utiljs) {
    	var telephone_input = document.querySelector(".form-address-edit input[name='telephone']");
    	var intl_only_countries = <?php echo $block->phoneOnlyCountryConfig(); ?>;
		var intl_preferred_countries = <?php echo $block->phonePrefferedCountryConfig(); ?>;
		
    	const intl_phone_config_option = {
  			  nationalMode: false,
  			  initialCountry: "auto",
  			  onlyCountries: intl_only_countries,
  			  preferredCountries: intl_preferred_countries,
  			  geoIpLookup: function(callback) {
  			    $.get('https://ipinfo.io', function() {}, "jsonp").always(function(resp) {
  			      var countryCode = (resp && resp.country) ? resp.country : "us";
  			      callback(countryCode);
  			    });
  			  },
  			  utilsScript: utiljs // just for formatting/placeholders etc
  			};
	    window.intlTelInput(telephone_input, intl_phone_config_option);
    });
</script>

Add last we will add our validation Mixin file at view/frontend/web/js/validator-mixin.js

define([
    'jquery'
], function ($) {
    'use strict';

    return function (validator) {
        validator.addRule(
            'custom-validate-telephone',
            function (value, params) {
                var phoneno = /^\(?([0-9]{3})\)?[-]?([0-9]{3})[-]?([0-9]{4})$/;

                if((value.match(phoneno))){
                    return true;
                }else{
                    return false;
                }                        
            },
            $.mage.__("Please enter phone number in form of 123-456-7890.")
        );

        return validator;
    };
});
0 Comments On "Add custom validation for phone number in address form and add country flag dropdown with auto detection flag Magento2"

Back To Top