Here we will learn, How to Add Billing Address Step on Magento2 Checkout page
1. Create new file Webkul/CheckoutCustomization/view/frontend/web/js/view/billing-address-step.js
define( [ 'ko', 'uiComponent', 'underscore', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/step-navigator', 'mage/translate' ], function ( ko, Component, _, quote, stepNavigator, $t ) { 'use strict'; return Component.extend({ defaults: { template: 'Webkul_CheckoutCustomization/view/billing-step' }, //add here your logic to display step, isVisible: ko.observable(quote.isVirtual()), isVirtual: quote.isVirtual(), /** * * @returns {*} */ initialize: function () { this._super(); if (!quote.isVirtual()) { //update condition if you need to enable for virtual products // register your step stepNavigator.registerStep( 'custom-billing-step', 'custom-billing-step', $t('Billing Address'), this.isVisible, _.bind(this.navigate, this), 11 ); } }, /** * The navigate() method is responsible for navigation between checkout step * during checkout. You can add custom logic, for example some conditions * for switching to your custom step */ navigate: function () { }, /** * @returns void */ navigateToNextStep: function () { stepNavigator.next(); } }); } );
2. Now we need to create html template file (billing-step.html) defined in above file.
File: Webkul/CheckoutCustomization/view/frontend/web/template/billing-step.html
<!--The 'step_code' value from the .js file should be used--> <li id="custom-billing-step" data-bind="fadeVisible: isVisible"> <div class="step-title" data-bind="i18n: 'Billing Information'" data-role="title"></div> <div id="checkout-step-title" class="step-content" data-role="content"> <form data-bind="submit: navigateToNextStep" novalidate="novalidate"> <!-- ko foreach: getRegion('afterMethods') --> <!-- ko template: getTemplate() --> <!-- /ko --> <!-- /ko --> <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>
3. Create Webkul/CheckoutCustomization/view/frontend/layout/checkout_index_index.xml
<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="Webkul_CheckoutCustomization::css/style.css"/> </head> <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"> <!-- The new step you add --> <item name="custom-billing-step" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="sortOrder" xsi:type="string">2</item> <item name="children" xsi:type="array"> <item name="custom-billing" xsi:type="array"> <item name="component" xsi:type="string">Webkul_CheckoutCustomization/js/view/billing-address-step</item> <item name="config" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Billing Address</item> </item> <item name="children" xsi:type="array"> <item name="customer-email" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/form/element/email</item> <item name="displayArea" xsi:type="string">customer-email</item> <item name="tooltip" xsi:type="array"> <item name="description" xsi:type="string" translate="true">We'll send your order confirmation here.</item> </item> <item name="children" xsi:type="array"> <item name="before-login-form" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-login-form</item> <item name="children" xsi:type="array"> <!-- before login form fields --> </item> </item> <item name="additional-login-form-fields" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">additional-login-form-fields</item> <item name="children" xsi:type="array"> <!-- additional login form fields --> </item> </item> </item> </item> <!-- merge your payment methods here --> <item name="afterMethods" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">afterMethods</item> <item name="children" xsi:type="array"> </item> </item> </item> </item> </item> </item> </item> </item> </item> </item> </item> </argument> </arguments> </referenceBlock> </body> </page>
Now, add css file: Webkul/CheckoutCustomization/view/frontend/web/css/style.css
@media (min-width: 768px), print { #custom-billing-step .actions-toolbar .primary { float: right; margin: 0; } } #custom-billing-step > .actions-toolbar > .primary .action.primary{ line-height: 2.2rem; padding: 14px 17px; font-size: 1.8rem; } @media (min-width: 768px), print{ #custom-billing-step .actions-toolbar .action-update { margin: 6px 20px 0 0; } } #custom-billing-step .checkout-billing-address{ margin-bottom: 10px; }
4. Create Webkul/CheckoutCustomization/Block/Checkout/LayoutProcessor.php to insert Billing Address form in the xml.
<?php namespace Webkul\CheckoutCustomization\Block\Checkout; use Magento\Checkout\Helper\Data; use Magento\Framework\App\ObjectManager; class LayoutProcessor { /** * @var \Magento\Customer\Model\AttributeMetadataDataProvider */ private $attributeMetadataDataProvider; /** * @var \Magento\Ui\Component\Form\AttributeMapper */ protected $attributeMapper; /** * @var AttributeMerger */ protected $merger; /** * @var \Magento\Customer\Model\Options */ private $options; /** * @var Data */ private $checkoutDataHelper; /** * @param \Magento\Customer\Model\AttributeMetadataDataProvider $attributeMetadataDataProvider * @param \Magento\Ui\Component\Form\AttributeMapper $attributeMapper * @param AttributeMerger $merger */ public function __construct( \Magento\Customer\Model\AttributeMetadataDataProvider $attributeMetadataDataProvider, \Magento\Ui\Component\Form\AttributeMapper $attributeMapper, \Magento\Checkout\Block\Checkout\AttributeMerger $merger ) { $this->attributeMetadataDataProvider = $attributeMetadataDataProvider; $this->attributeMapper = $attributeMapper; $this->merger = $merger; } /** * @deprecated * @return \Magento\Customer\Model\Options */ private function getOptions() { if (!is_object($this->options)) { $this->options = ObjectManager::getInstance()->get(\Magento\Customer\Model\Options::class); } return $this->options; } /** * @return array */ private function getAddressAttributes() { /** @var \Magento\Eav\Api\Data\AttributeInterface[] $attributes */ $attributes = $this->attributeMetadataDataProvider->loadAttributesCollection( 'customer_address', 'customer_register_address' ); $elements = []; foreach ($attributes as $attribute) { $code = $attribute->getAttributeCode(); if ($attribute->getIsUserDefined()) { continue; } $elements[$code] = $this->attributeMapper->map($attribute); if (isset($elements[$code]['label'])) { $label = $elements[$code]['label']; $elements[$code]['label'] = __($label); } } return $elements; } /** * Convert elements(like prefix and suffix) from inputs to selects when necessary * * @param array $elements address attributes * @param array $attributesToConvert fields and their callbacks * @return array */ private function convertElementsToSelect($elements, $attributesToConvert) { $codes = array_keys($attributesToConvert); foreach (array_keys($elements) as $code) { if (!in_array($code, $codes)) { continue; } $options = call_user_func($attributesToConvert[$code]); if (!is_array($options)) { continue; } $elements[$code]['dataType'] = 'select'; $elements[$code]['formElement'] = 'select'; foreach ($options as $key => $value) { $elements[$code]['options'][] = [ 'value' => $key, 'label' => $value, ]; } } return $elements; } /** * Process js Layout of block * * @param array $jsLayout * @return array */ public function process($jsLayout) { $attributesToConvert = [ 'prefix' => [$this->getOptions(), 'getNamePrefixOptions'], 'suffix' => [$this->getOptions(), 'getNameSuffixOptions'], ]; $elements = $this->getAddressAttributes(); $elements = $this->convertElementsToSelect($elements, $attributesToConvert); // The following code is a workaround for custom address attributes if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children'] )) { $jsLayout['components']['checkout']['children']['steps']['children']['custom-billing-step']['children'] ['custom-billing']['children'] = $this->processNewStepsChildrenComponents( $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children'], $jsLayout['components']['checkout']['children']['steps']['children']['custom-billing-step']['children'] ['custom-billing']['children'], $elements ); } return $jsLayout; } /** * Appends billing address form component to payment layout * * @param array $paymentLayout * @param array $elements * * @return array */ private function processNewStepsChildrenComponents( array $paymentLayout, array $newStepsLayout, array $elements ) { if (!isset($paymentLayout['payments-list']['children'])) { $paymentLayout['payments-list']['children'] = []; } if (!isset($newStepsLayout['afterMethods']['children'])) { $newStepsLayout['afterMethods']['children'] = []; } $component['billing-address-form'] = $this->getBillingAddressComponent( 'shared', $elements ); $newStepsLayout['afterMethods']['children'] = array_merge_recursive( $component, $newStepsLayout['afterMethods']['children'] ); return $newStepsLayout; } /** * Gets billing address component details * * @param string $paymentCode * @param array $elements * * @return array */ private function getBillingAddressComponent($paymentCode, $elements) { return [ 'component' => 'Magento_Checkout/js/view/billing-address', 'displayArea' => 'billing-address-form-' . $paymentCode, 'provider' => 'checkoutProvider', 'deps' => 'checkoutProvider', 'dataScopePrefix' => 'billingAddress' . $paymentCode, 'sortOrder' => 1, 'children' => [ 'form-fields' => [ 'component' => 'uiComponent', 'displayArea' => 'additional-fieldsets', 'children' => $this->merger->merge( $elements, 'checkoutProvider', 'billingAddress' . $paymentCode, [ 'country_id' => [ 'sortOrder' => 115, ], 'region' => [ 'visible' => false, ], 'region_id' => [ 'component' => 'Magento_Ui/js/form/element/region', 'config' => [ 'template' => 'ui/form/field', 'elementTmpl' => 'ui/form/element/select', 'customEntry' => 'billingAddress' . $paymentCode . '.region', ], 'validation' => [ 'required-entry' => true, ], 'filterBy' => [ 'target' => '${ $.provider }:${ $.parentScope }.country_id', 'field' => 'country_id', ], ], 'postcode' => [ 'component' => 'Magento_Ui/js/form/element/post-code', 'validation' => [ 'required-entry' => true, ], ], 'company' => [ 'validation' => [ 'min_text_length' => 0, ], ], 'fax' => [ 'validation' => [ 'min_text_length' => 0, ], ], 'telephone' => [ 'config' => [ 'tooltip' => [ 'description' => __('For delivery questions.'), ], ], ], ] ), ], ], ]; } }
Lets call our custom Webkul/CheckoutCustomization/Block/Checkout/LayoutProcessor Block.
5. create file# Webkul/CheckoutCustomization/etc/frontend/di.xml.
<?xml version="1.0"?> <!-- /** * Webkul Software * * @category Webkul * @package Webkul_MarketplaceCustomization * @author Webkul * @copyright Webkul Software Private Limited (https://webkul.com) * @license https://store.webkul.com/license.html */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Checkout\Block\Onepage"> <arguments> <argument name="layoutProcessors" xsi:type="array"> <item name="customization_billing" xsi:type="object">Webkul\CheckoutCustomization\Block\Checkout\LayoutProcessor</item> </argument> </arguments> </type> </config>
6. Now overwrite https://github.com/magento/magento2/blob/2.1.9/app/code/Magento/Checkout/view/frontend/web/js/view/payment.js file to stop Payment Page Step before our Billing Address Step
Create File Webkul/CheckoutCustomization/view/frontend/requirejs-config.js
var config = { map: { '*': { "Magento_Checkout/js/view/payment": "Webkul_CheckoutCustomization/js/view/payment" } } };
Now, create this file in our CheckoutCustomization Module.
#File: Webkul/CheckoutCustomization/view/frontend/web/js/view/payment.js
/*jshint browser:true jquery:true*/ /*global alert*/ define( [ 'jquery', "underscore", 'uiComponent', 'ko', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/step-navigator', 'Magento_Checkout/js/model/payment-service', 'Magento_Checkout/js/model/payment/method-converter', 'Magento_Checkout/js/action/get-payment-information', 'Magento_Checkout/js/model/checkout-data-resolver', 'mage/translate' ], function ( $, _, Component, ko, quote, stepNavigator, paymentService, methodConverter, getPaymentInformation, checkoutDataResolver, $t ) { 'use strict'; /** Set payment methods to collection */ paymentService.setPaymentMethods(methodConverter(window.checkoutConfig.paymentMethods)); return Component.extend({ defaults: { template: 'Magento_Checkout/payment', activeMethod: '' }, isVisible: ko.observable(false), quoteIsVirtual: quote.isVirtual(), isPaymentMethodsAvailable: ko.computed(function () { return paymentService.getAvailablePaymentMethods().length > 0; }), initialize: function () { this._super(); checkoutDataResolver.resolvePaymentMethod(); stepNavigator.registerStep( 'payment', null, $t('Review & Payments'), this.isVisible, _.bind(this.navigate, this), 20 ); return this; }, navigate: function () { var self = this; getPaymentInformation().done(function () { self.isVisible(true); }); }, getFormKey: function () { return window.checkoutConfig.formKey; } }); } );
7. Finally copy https://github.com/magento/magento2/tree/2.1.9/app/code/Magento/Checkout/view/frontend/web/js/view/payment directory and place in our Billing Step Module
Webkul/CheckoutCustomization/view/frontend/web/js/view
Note: Here, we have copied the js files of payment directory from Magento 2.1.9. So, you can manage this as per your magento version.
Now Clear the cache.
It will look like this:
Thanks
How to Move Billing Address On First Step ??