Magento – Magento 2 How to remove billing address

billing-addressmagento2

My site does not need billing address. How to remove the billing address fields around the Magento? Thanks.

Best Answer

The billing address is and should be required for each transaction, but forcing the billing and shipping address to be the same is possible without too much modification to the checkout process.

The example below creates a configuration option to force the same shipping and billing address for non-virtual orders.

Pmclain\CustomerAddress\etc\module.xml

<?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="Pmclain_CustomerAddress" setup_version="0.0.1">
    <sequence>
      <module name="Magento_Quote" />
      <module name="Magento_Checkout" />
    </sequence>
  </module>
</config>

Pmclain\CustomerAddress\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>
    <checkout>
      <options>
        <require_same_billing>0</require_same_billing>
      </options>
    </checkout>
  </default>
</config>

Pmclain\CustomerAddress\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="checkout">
      <group id="options">
        <field id="require_same_billing" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0">
          <label>Require Same Shipping/Billing Address</label>
          <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
          <comment>Force shipping and billing addresses to match during frontend checkout.</comment>
        </field>
      </group>
    </section>
  </system>
</config>

Pmclain\CustomerAddress\etc\frontend\di.xml

Adds plugin for forcing the address match server-side and the checkout config provider for the knockout templates.

<?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\CompositeConfigProvider">
    <arguments>
      <argument name="configProviders" xsi:type="array">
        <item name="pmclain_customeraddress_config_provider" xsi:type="object">Pmclain\CustomerAddress\Model\ConfigProvider</item>
      </argument>
    </arguments>
  </type>
  <type name="Magento\Quote\Model\Quote">
    <plugin name="pmclain_customeraddress_quote_model_quote" type="Pmclain\CustomerAddress\Plugin\Quote" sortOrder="10" />
  </type>
</config>

Pmclain\CustomerAddress\Helper\Data.php

<?php
namespace Pmclain\CustomerAddress\Helper;

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

class Data extends AbstractHelper
{
  const CONFIG_ALLOW = 'checkout/options/require_same_billing';

  public function allowDifferentShippingAddress()
  {
    if ($this->scopeConfig->getValue(self::CONFIG_ALLOW, ScopeInterface::SCOPE_WEBSITE)) {
      return false;
    }

    return true;
  }
}

Pmclain\CustomerAddress\Model\ConfigProvider.php

<?php
namespace Pmclain\CustomerAddress\Model;

use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Checkout\Model\Session as CheckoutSession;
use Pmclain\CustomerAddress\Helper\Data;

class ConfigProvider implements ConfigProviderInterface
{
  /** @var CheckoutSession */
  protected $_checkoutSession;

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

  public function __construct(
    CheckoutSession $checkoutSession,
    Data $helper
  ) {
    $this->_checkoutSession = $checkoutSession;
    $this->_helper = $helper;
  }

  /** @return array */
  public function getConfig()
  {
    return [
      'pmclain_customeraddress' => [
        'allow' => $this->_allowDifferentShippingAddress()
      ]
    ];
  }

  /** @return bool */
  protected function _allowDifferentShippingAddress()
  {
    if ($this->_checkoutSession->getQuote()->isVirtual()) {
      return true;
    }

    return $this->_helper->allowDifferentShippingAddress();
  }
}

Pmclain\CustomerAddress\Plugin\Quote.php

<?php
namespace Pmclain\CustomerAddress\Plugin;

use Pmclain\CustomerAddress\Helper\Data;

class Quote
{
  /** @var Data */
  protected $_helper;

  public function __construct(Data $helper)
  {
    $this->_helper = $helper;
  }

  public function beforeSetBillingAddress(
    \Magento\Quote\Model\Quote $subject,
    \Magento\Quote\Api\Data\AddressInterface $address = null
  ) {
    if ($this->_helper->allowDifferentShippingAddress() || $subject->isVirtual()) {
      return [$address];
    }

    return [$subject->getShippingAddress()];
  }
}

Pmclain\CustomerAddress\view\frontend\requirejs-config.js

var config = {
  map: {
    '*': {
      'Magento_Checkout/template/billing-address.html':
        'Pmclain_CustomerAddress/template/billing-address.html',
      'Magento_Checkout/template/shipping.html':
        'Pmclain_CustomerAddress/template/shipping.html'
    }
  }
};

Pmclain\CustomerAddress\view\frontend\web\template\billing-address.html

The modified billing address template will now display based on the value of window.checkoutConfig.pmclain_customeraddress.allow passed from the checkout config provider.

<div class="checkout-billing-address">
  <!-- ko if: window.checkoutConfig.pmclain_customeraddress.allow -->
    <div class="billing-address-same-as-shipping-block field choice" data-bind="visible: canUseShippingAddress()">
      <input type="checkbox" name="billing-address-same-as-shipping"
             data-bind="checked: isAddressSameAsShipping, click: useShippingAddress, attr: {id: 'billing-address-same-as-shipping-' + getCode($parent)}"/>
      <label data-bind="attr: {for: 'billing-address-same-as-shipping-' + getCode($parent)}"><span
          data-bind="i18n: 'My billing and shipping address are the same'"></span></label>
    </div>

    <!-- ko template: 'Magento_Checkout/billing-address/details' --><!-- /ko -->
    <fieldset class="fieldset" data-bind="visible: !isAddressDetailsVisible()">
      <!-- ko template: 'Magento_Checkout/billing-address/list' --><!-- /ko -->
      <!-- ko template: 'Magento_Checkout/billing-address/form' --><!-- /ko -->
      <div class="actions-toolbar">
        <div class="primary">
          <button class="action action-update" type="button" data-bind="click: updateAddress">
            <span data-bind="i18n: 'Update'"></span>
          </button>
          <button class="action action-cancel" type="button" data-bind="click: cancelAddressEdit">
            <span data-bind="i18n: 'Cancel'"></span>
          </button>
        </div>
      </div>
    </fieldset>
  <!-- /ko -->
</div>

Pmclain\CustomerAddress\view\frontend\web\template\shipping.html

The only change from the original shipping address and method template below is the updated address section header text when the shipping and billing addresses must be the same. Overriding the entire template for such a small change seems heavy handed and is only needed for giving users a visual indicator that only one address will be allowed.

<li id="shipping" class="checkout-shipping-address" data-bind="fadeVisible: visible()">
  <div class="step-title" data-role="title">
    <span data-bind="visible: !window.checkoutConfig.pmclain_customeraddress.allow">
      <span data-bind="i18n: 'Shipping & Billing Address'"></span>
    </span>
    <span data-bind="visible: window.checkoutConfig.pmclain_customeraddress.allow">
      <span data-bind="i18n: 'Shipping Address'"></span>
    </span>
  </div>
  <div id="checkout-step-shipping"
       class="step-content"
       data-role="content">

    <!-- ko if: (!quoteIsVirtual) -->
    <!-- ko foreach: getRegion('customer-email') -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!--/ko-->
    <!--/ko-->

    <!-- ko foreach: getRegion('address-list') -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!--/ko-->

    <!-- ko foreach: getRegion('address-list-additional-addresses') -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!--/ko-->

    <!-- Address form pop up -->
    <!-- ko if: (!isFormInline) -->
    <button type="button"
            data-bind="click: showFormPopUp, visible: !isNewAddressAdded()"
            class="action action-show-popup">
      <span data-bind="i18n: 'New Address'"></span></button>
    <div id="opc-new-shipping-address" data-bind="visible: isFormPopUpVisible()">
      <!-- ko template: 'Magento_Checkout/shipping-address/form' --><!-- /ko -->
    </div>
    <!-- /ko -->

    <!-- ko foreach: getRegion('before-form') -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!--/ko-->

    <!-- Inline address form -->
    <!-- ko if: (isFormInline) -->
    <!-- ko template: 'Magento_Checkout/shipping-address/form' --><!-- /ko -->
    <!-- /ko -->
  </div>
</li>


<!--Shipping method template-->
<li id="opc-shipping_method"
    class="checkout-shipping-method"
    data-bind="fadeVisible: visible(), blockLoader: isLoading"
    role="presentation">
  <div class="checkout-shipping-method">
    <div class="step-title" data-bind="i18n: 'Shipping Methods'" data-role="title"></div>
    <!-- ko foreach: getRegion('before-shipping-method-form') -->
    <!-- ko template: getTemplate() --><!-- /ko -->
    <!-- /ko -->
    <div id="checkout-step-shipping_method"
         class="step-content"
         data-role="content"
         role="tabpanel"
         aria-hidden="false">
      <!-- ko if: rates().length  -->
      <form class="form methods-shipping" id="co-shipping-method-form" data-bind="submit: setShippingInformation" novalidate="novalidate">
        <div id="checkout-shipping-method-load">
          <table class="table-checkout-shipping-method">
            <thead>
            <tr class="row">
              <th class="col col-method" data-bind="i18n: 'Select Method'"></th>
              <th class="col col-price" data-bind="i18n: 'Price'"></th>
              <th class="col col-method" data-bind="i18n: 'Method Title'"></th>
              <th class="col col-carrier" data-bind="i18n: 'Carrier Title'"></th>
            </tr>
            </thead>
            <tbody>

            <!--ko foreach: { data: rates(), as: 'method'}-->
            <tr class="row" data-bind="click: $parent.selectShippingMethod">
              <td class="col col-method">
                <!-- ko ifnot: method.error_message -->
                <!-- ko if: $parent.rates().length == 1 -->
                <input class="radio"
                       type="radio"
                       data-bind="attr: {
                                                    checked: $parent.rates().length == 1,
                                                    'value' : method.carrier_code + '_' + method.method_code,
                                                    'id': 's_method_' + method.method_code,
                                                    'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code
                                                 }" />
                <!-- /ko -->
                <!--ko ifnot: ($parent.rates().length == 1)-->
                <input type="radio"
                       data-bind="
                                                value: method.carrier_code + '_' + method.method_code,
                                                checked: $parent.isSelected,
                                                attr: {
                                                    'id': 's_method_' + method.carrier_code + '_' + method.method_code,
                                                    'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code
                                                },
                                                click: $parent.selectShippingMethod"
                       class="radio"/>
                <!--/ko-->
                <!-- /ko -->
              </td>
              <td class="col col-price">
                <!-- ko foreach: $parent.getRegion('price') -->
                <!-- ko template: getTemplate() --><!-- /ko -->
                <!-- /ko -->
              </td>

              <td class="col col-method"
                  data-bind="text: method.method_title, attr: {'id': 'label_method_' + method.method_code + '_' + method.carrier_code}"></td>

              <td class="col col-carrier"
                  data-bind="text: method.carrier_title, attr: {'id': 'label_carrier_' + method.method_code + '_' + method.carrier_code}"></td>
            </tr>

            <!-- ko if:  method.error_message -->
            <tr class="row row-error">
              <td class="col col-error" colspan="4">
                <div class="message error">
                  <div data-bind="text: method.error_message"></div>
                </div>
                <span class="no-display">
                                    <input type="radio" data-bind="attr: {'value' : method.method_code, 'id': 's_method_' + method.method_code}"/>
                                </span>
              </td>
            </tr>
            <!-- /ko -->

            <!-- /ko -->
            </tbody>
          </table>
        </div>

        <div id="onepage-checkout-shipping-method-additional-load">
          <!-- ko foreach: getRegion('shippingAdditional') -->
          <!-- ko template: getTemplate() --><!-- /ko -->
          <!-- /ko -->
        </div>
        <!-- ko if: errorValidationMessage().length > 0 -->
        <div class="message notice">
          <span><!-- ko text: errorValidationMessage()--><!-- /ko --></span>
        </div>
        <!-- /ko -->
        <div class="actions-toolbar" id="shipping-method-buttons-container">
          <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>
      <!-- /ko -->
      <!-- ko ifnot: rates().length > 0 --><div class="no-quotes-block"><!-- ko i18n: 'Sorry, no quotes are available for this order at this time'--><!-- /ko --></div><!-- /ko -->
    </div>
  </div>
</li>

Pmclain\CustomerAddress\registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
  \Magento\Framework\Component\ComponentRegistrar::MODULE,
  'Pmclain_CustomerAddress',
  __DIR__
);