Magento – How to add custom attribute in BILLING address in Magento 2

billing-addressmagento-2.0

I actually have two new inputs in my customer address, admin, shipping and billing address, but i want just to show them in the billing address of each payment method, I am trying to hide them via xml but it doesn't works, I have the following code:

My install script:

$customerSetup = $this->_customerSetupFactory->create(['setup' => $setup]);
$attributesInfo = [
        'want_bill' => [
            'label'         => 'Want to bill',
            'input'         => 'checkbox',
            'required'      => 0,
            'sort_order'    => 400,
            'visible'       => true,
            'system'        => 0
        ],
        'rfc'       => [
            'label'         => 'RFC',
            'input'         => 'text',
            'required'      => 0,
            'sort_order'    => 410,
            'visible'       => true,
            'system'        => 0
        ],
];

foreach ($attributesInfo as $attributeCode => $attributeParams) {
    $customerSetup->addAttribute('customer_address', $attributeCode, $attributeParams);
}

foreach ($attributesInfo as $attributeCode => $attributeParams) {
    $customAttribute = $customerSetup->getEavConfig()->getAttribute('customer_address', $attributeCode);
    $customAttribute->setData(
        'used_in_forms',
        ['adminhtml_customer_address', 'customer_address_edit', 'customer_register_address']
    );
    $customAttribute->save();
}

When I run:

bin/magento setup:upgrade

Everything works fine and I can show the inputs in the customer address in admin and also in the shipping and billing address.

I need to delete or hide those inputs from the shipping address form, for that I am trying this:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <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">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="shipping-address-fieldset" xsi:type="array">
                                                            <item name="want_bill" xsi:type="array">
                                                                <item name="visible" xsi:type="boolean">false</item>
                                                            </item>
                                                            <item name="rfc" xsi:type="array">
                                                                <item name="visible" xsi:type="boolean">false</item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

But those inputs still showing.

How can I hide them?

Best Answer

Please check below code to save custom address attribute in customer,checkout shipping and billing form and also save in order table.

Module Name : Ccc_Checkout

Script to create custom attribute for address and order

app/code/Ccc/Checkout/Setup/UpgradeData.php

<?php
namespace Ccc\Checkout\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;

class UpgradeData implements UpgradeDataInterface
{

    private $eavSetupFactory;

    /**
     * @var Config
     */
    private $eavConfig;

    /**
     * @var AttributeSetFactory
     */
    private $attributeSetFactory;

    public function __construct(
        Config $eavConfig,
        EavSetupFactory $eavSetupFactory,
        AttributeSetFactory $attributeSetFactory
    )
    {
        $this->eavSetupFactory = $eavSetupFactory;
        $this->eavConfig            = $eavConfig;
        $this->attributeSetFactory  = $attributeSetFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function upgrade(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        $setup->startSetup();
        if (version_compare($context->getVersion(), '0.0.2','<')) { 
            $this->addUnitNumberFieldToAddress($setup);
        }
        if (version_compare($context->getVersion(), '0.0.3','<')) { 
            $this->updateUnitAttribute($setup);
        }
        $setup->endSetup();


    }

    /**
    * put your comment there...
    * 
    * @param mixed $setup
    */
    protected function addUnitNumberFieldToAddress($setup)
    {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
        $eavSetup->addAttribute('customer_address', 'unit_number', [
            'type' => 'varchar',
            'input' => 'text',
            'label' => 'Unit Number',
            'visible' => true,
            'required' => false,
            'user_defined' => true,
            'system'=> false,
            'group'=> 'General',
            'sort_order' => 71,
            'global' => true,
            'visible_on_front' => true,
        ]);       

        $customAttribute = $this->eavConfig->getAttribute('customer_address', 'unit_number');

        $customAttribute->setData(
            'used_in_forms',
            ['adminhtml_customer_address','customer_address_edit','customer_register_address'] 
        );
        $customAttribute->save();


        $installer = $setup;


        $installer->getConnection()->addColumn(
            $installer->getTable('quote_address'),
            'unit_number',
            [
                'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 
                'length' => 255,
                'comment' => 'Unit Number'
            ]
        );

        $installer->getConnection()->addColumn(
            $installer->getTable('sales_order_address'),
            'unit_number',
            [
                'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 
                'length' => 255,
                'comment' => 'Unit Number'
            ]
        );
    }

    public function updateUnitAttribute($setup)
    {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
        $eavSetup->updateAttribute('customer_address', 'unit_number', 'sort_order', '71');
    }
}

app/code/Ccc/Checkout/etc/di.xml

    <?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 disabled="false" name="BillingLayoutProcessor" sortOrder="99" type="Ccc\Checkout\Plugin\Block\Checkout\LayoutProcessor"/>
  </type>
  <type name="Magento\Quote\Model\BillingAddressManagement">
    <plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_BillingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\BillingAddressManagement"/>
  </type>
  <type name="Magento\Quote\Model\Quote\Address\BillingAddressPersister">
    <plugin disabled="false" name="BillingAddressSave" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address\BillingAddressPersister"/>
  </type>
  <type name="Magento\Quote\Model\ShippingAddressManagement">
    <plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_ShippingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\ShippingAddressManagement"/>
  </type>
</config>

app/code/Ccc/Checkout/etc/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
     <event name="sales_model_service_quote_submit_success">
        <observer name="custome_address_attribute_save" instance="Ccc\Checkout\Observer\SaveUnitNumberInOrder"/>
    </event>
</config>

app/code/Ccc/Checkout/etc/extension_attributes.xml

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">    
    <extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
        <attribute code="unit_number" type="string"/>
    </extension_attributes>
</config>

Create Plugin to display custom attribute in checkout billing and shipping form

app/code/Ccc/Checkout/Plugin/Block/Checkout/LayoutProcessor

<?php
namespace Ccc\Checkout\Plugin\Block\Checkout;
use \Magento\Checkout\Block\Checkout\LayoutProcessor as MageLayoutProcessor;
class LayoutProcessor
{
   protected $_customAttributeCode = 'unit_number';
    public function afterProcess(MageLayoutProcessor $subject, $jsLayout)
    {
        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) 
            {                
                $paymentCode = 'billingAddress'.str_replace('-form','',$key);
                $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress($paymentCode);                
            } 

        }   

         if(isset($jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset'])
        ){
            $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress('shippingAddress');
         }

        return $jsLayout;     
    }   

    public function getUnitNumberAttributeForAddress($addressType)
    {        
        return $customField = [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [                
                'customScope' => $addressType.'.custom_attributes',
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/input'
            ],
            'dataScope' => $addressType.'.custom_attributes' . '.' . $this->_customAttributeCode,
            'label' => 'Unit Number',
            'provider' => 'checkoutProvider',
            'sortOrder' => 71,
            'validation' => [
               'required-entry' => false
            ],
            'options' => [],
            'filterBy' => null,
            'customEntry' => null,
            'visible' => true,
        ];
    }    
}

To save custom attribute in checkout

app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/ShippingAddressManagement

<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;

class ShippingAddressManagement
{
    protected $logger;

    public function __construct(
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->logger = $logger;
    }

    public function beforeAssign(
        \Magento\Quote\Model\ShippingAddressManagement $subject,
        $cartId,
        \Magento\Quote\Api\Data\AddressInterface $address
    ) {

        $extAttributes = $address->getExtensionAttributes();        
        if (!empty($extAttributes)) {
            try {
                $address->setUnitNumber($extAttributes->getUnitNumber());
            } catch (\Exception $e) {
                $this->logger->critical($e->getMessage());
            }
        }
    }
}

app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/BillingAddressManagement

<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;

class BillingAddressManagement
{

    protected $logger;

    public function __construct(
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->logger = $logger;
    }

    public function beforeAssign(
        \Magento\Quote\Model\BillingAddressManagement $subject,
        $cartId,
        \Magento\Quote\Api\Data\AddressInterface $address,
        $useForShipping = false
    ) {

        $extAttributes = $address->getExtensionAttributes();
        if (!empty($extAttributes)) {
            try {
                $address->setUnitNumber($extAttributes->getUnitNumber());
            } catch (\Exception $e) {
                $this->logger->critical($e->getMessage());
            }
        }
    }
}

app/code/Ccc/Checkout/Ccc/Checkout/Plugin/Magento/Quote/Model/Quote/Address

<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address;

class BillingAddressPersister
{

    protected $logger;

    public function __construct(
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->logger = $logger;
    }

    public function beforeSave(
        \Magento\Quote\Model\Quote\Address\BillingAddressPersister $subject,
        $quote,
        \Magento\Quote\Api\Data\AddressInterface $address,
        $useForShipping = false
    ) {

        $extAttributes = $address->getExtensionAttributes();
        if (!empty($extAttributes)) {
            try {
                $address->setUnitNumber($extAttributes->getUnitNumber());
            } catch (\Exception $e) {
                $this->logger->critical($e->getMessage());
            }
        }
    }
}

To set custom attribute in extension attribute

app/code/Ccc/Checkout/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/model/payment/method-group': {
                'Ccc_Checkout/js/model/payment/method-group-mixin': true
            },
             'Magento_Checkout/js/action/set-billing-address': {
                'Ccc_Checkout/js/action/set-billing-address-mixin': true
            },
            'Magento_Checkout/js/action/set-shipping-information': {
                'Ccc_Checkout/js/action/set-shipping-information-mixin': true
            },
            'Magento_Checkout/js/action/create-shipping-address': {
                'Ccc_Checkout/js/action/create-shipping-address-mixin': true
            },
            'Magento_Checkout/js/action/place-order': {
                'Ccc_Checkout/js/action/set-billing-address-mixin': true
            },
            'Magento_Checkout/js/action/create-billing-address': {
                'Ccc_Checkout/js/action/set-billing-address-mixin': true
            }
        }
    }
};

app/code/Ccc/Checkout/view/frontend/web/js/action/create-shipping-address-mixin.js

define([
    'jquery',
    'mage/utils/wrapper',
    'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
    'use strict';

    return function (setShippingInformationAction) {
        return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {

            if (messageContainer.custom_attributes != undefined) {
                $.each(messageContainer.custom_attributes , function( key, value ) {
                    messageContainer['custom_attributes'][key] = {'attribute_code':key,'value':value};
                });
            }

            return originalAction(messageContainer);
        });
    };
});

app/code/Ccc/Checkout/view/frontend/web/js/action/set-billing-address-mixin.js

define([
    'jquery',
    'mage/utils/wrapper',
    'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
    'use strict';

    return function (setBillingAddressAction) {
        return wrapper.wrap(setBillingAddressAction, function (originalAction, messageContainer) {

            var billingAddress = quote.billingAddress();            
            if(billingAddress != undefined) {

                if (billingAddress['extension_attributes'] === undefined) {
                    billingAddress['extension_attributes'] = {};
                }

                if (billingAddress.customAttributes != undefined) {
                    $.each(billingAddress.customAttributes, function (key, value) {                        
                        if($.isPlainObject(value)){
                            value = value['value'];
                        }

                        billingAddress['extension_attributes'][key] = value;
                    });
                }

            }

            return originalAction(messageContainer);
        });
    };
});

app/code/Ccc/Checkout/view/frontend/web/js/action/set-shipping-information-mixin.js

define([
    'jquery',
    'mage/utils/wrapper',
    'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
    'use strict';

    return function (setShippingInformationAction) {
        return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {

            var shippingAddress = quote.shippingAddress();

            if (shippingAddress['extension_attributes'] === undefined) {
                shippingAddress['extension_attributes'] = {};
            }

            if (shippingAddress.customAttributes != undefined) {
                $.each(shippingAddress.customAttributes , function( key, value ) {

                    if($.isPlainObject(value)){
                        value = value['value'];
                    }
                    shippingAddress['customAttributes'][key] = value;
                    shippingAddress['extension_attributes'][key] = value;

                });
            }

            return originalAction(messageContainer);
        });
    };
});

To save custom attribute in orders

app/code/Ccc/Checkout/Observer/SaveUnitNumberInOrder.php

<?php
namespace Ccc\Checkout\Observer;

class SaveUnitNumberInOrder implements \Magento\Framework\Event\ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer) {
        $order = $observer->getEvent()->getOrder();
        $quote = $observer->getEvent()->getQuote();
         if ($quote->getBillingAddress()) {
              $order->getBillingAddress()->setUnitNumber($quote->getBillingAddress()->getExtensionAttributes()->getUnitNumber());
          }
          if (!$quote->isVirtual()) {            
              $order->getShippingAddress()->setUnitNumber($quote->getShippingAddress()->getUnitNumber());
          }
        return $this;
    }
}

To display custom attribute in customer account you need to overright customer edit.phtml file from vendor to your theme like below :

app/design/frontend/custom_theme/theme_name/Magento_Customer/templates/address/edit.phtml

<div class="field unit_number">
            <label class="label" for="unit_number"><span><?php echo $block->escapeHtml(__('Unit Number')) ?></span></label>
            <div class="control">

                <input type="text" name="unit_number" value="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>" title="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>" class="input-text <?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>" id="unit_number">
            </div>
        </div>
Related Topic