Magento 2: Add a Custom Field to Checkout Shipping Address

In this tutorial, I will demonstrate how to add a custom field, 'cutomvar', to shipping address form fields and save the value into database.

Posted on February 20, 2019 in Magento2

Introduction

Recently, I tried to understand how to add a custom field to shipping address form fields and save its value into database. The Magento DevDoc doesn’t help a lot. It will only add a field in the shipping address form but will not save it into database. In this tutorial, I will show you how to add a custom field in the shipping address form and save the value into database.

Create a new Module

For this tutorial, I will create a module called, ‘Jeff_CustomField’. You can find howto about creation Magento 2 extension in my previous blog. In order to save the value into database, I will create InstallScema.php to add column to ‘quote’ and ‘sales_order’.


<?php
namespace Jeff\CustomField\Setup;

class InstallSchema implements \Magento\Framework\Setup\InstallSchemaInterface
{
    public function install(\Magento\Framework\Setup\SchemaSetupInterface $setup, \Magento\Framework\Setup\ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();

        $eavTable1 = $installer->getTable('quote');
        $eavTable2 = $installer->getTable('sales_order');

        $columns = [
            'customvar' => [
                'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                'nullable' => true,
                'comment' => 'Custom Var'
            ]
        ];

        $connection = $installer->getConnection();

        foreach($columns as $name => $definition) {
            $connection->addColumn($eavTable1, $name, $definition);
            $connection->addColumn($eavTable2, $name, $definition);
        }

        $installer->endSetup();
    }
}

Add the field to the shipping address form dynamically

First, we add a plugin for the LayoutProcessor:


<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="checkout_custom_shipping_fields" type="Jeff\CustomField\Plugin\Checkout\LayoutProcessorPlugin" sortOrder="20" />
    </type>
</config>

And the Plugin class:


<?php
namespace Jeff\CustomField\Plugin\Checkout;

use Magento\Checkout\Block\Checkout\LayoutProcessor;

class LayoutProcessorPlugin
{
    protected $logger;

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

    public function afterProcess(LayoutProcessor $subject, array $jsLayout)
    {
        $customAttributeCode = 'customvar';

        $customField = [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [
                'customScope' => 'shippingAddress.custom_attributes',
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/input',
                'tooltip' => [
                    'description' => 'this is custom var field',
                ],
            ],

            'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
            'label' => 'Custom Var',
            'provider' => 'checkoutProvider',
            'sortOrder' => 0,
            'validation' => [
                'required-entry' => false
            ],
            'options' => [],
            'filterBy' => null,
            'customEntry' => null,
            'visible' => true,
        ];

        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;

        return $jsLayout;
    }
}

After creating these files and clear cache, a new field will show up in the address form field: custom linked product

Create our own ‘shipping-save-processor/default.js’

I will rewrite the ‘module-checkout/view/frontend/web/js/model /shipping-save-processor/default.js’ with my own file ‘shipping-save-processor.js’.


#Jeff/CustomField/view/frontend/web/js/shipping-save-processor.js

define([
    'jquery',
    'ko',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/resource-url-manager',
    'mage/storage',
    'Magento_Checkout/js/model/payment-service',
    'Magento_Checkout/js/model/payment/method-converter',
    'Magento_Checkout/js/model/error-processor',
    'Magento_Checkout/js/model/full-screen-loader',
    'Magento_Checkout/js/action/select-billing-address'
], function(
    $,
    ko,
    quote,
    resourceUrlManager,
    storage,
    paymentService,
    methodConverter,
    errorProcessor,
    fullScreenLoader,
    selectBillingAddressAction
) {
    'use strict';

    return {
        saveShippingInformation: function() {
            var payload;
            var customvar = $('[name="custom_attributes[customvar]"]').val();

            if(!quote.billingAddress()) {
                selectBillingAddressAction(quote.shippingAddress());
            }

            payload = {
                addressInformation: {
                    shipping_address: quote.shippingAddress(),
                    billing_address: quote.billingAddress(),
                    shipping_method_code: quote.shippingMethod().method_code,
                    shipping_carrier_code: quote.shippingMethod().carrier_code,
                    extension_attributes: {
                        customvar: customvar
                    }
                }
            };

            console.log('debue customvar', customvar);

            fullScreenLoader.startLoader();

            return storage.post(
                resourceUrlManager.getUrlForSetShippingInformation(quote),
                JSON.stringify(payload)
            ).done(
                function(response) {
                    quote.setTotals(response.totals);
                    paymentService.setPaymentMethods(methodConverter(response.payment_methods));
                    fullScreenLoader.stopLoader();
                }
            ).fail( 
                function(response) {
                    errorProcessor.process(response);
                    fullScreenLoader.stopLoader();
                }
            );
        }
    };
});

And create a ‘requirejs-config.js’ file, let Magento use our own javascript instead of the default.


var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/action/set-shipping-information': {
                'Jeff_CustomField/js/action/set-shipping-information-mixin' : true
            }
        }
    },
    "map": {
        "*": {
            "Magento_Checkout/js/model/shipping-save-processor/default" : "Jeff_CustomField/js/shipping-save-processor"
        }
    }
};

Above the mixins, which is adding some additional functions for the original function for Javascript, is from the Magento DevDoc. This is redundant part and make people confusing.

Create extension attributes ShippingInformationInterface


#Jeff/CustomField/etc/extension_attributes.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Checkout\Api\Data\ShippingInformationInterface">
        <attribute code="customvar" type="string" />
    </extension_attributes>
</config>

Save the custom field value into quote

update our ‘di.xml’ file by adding additional plugin to save the value to quote.


#Jeff/CustomField/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <type name="Magento\Checkout\Model\ShippingInformationManagement">
        <plugin name="save_to_quote_table" type="Jeff\CustomField\Plugin\Quote\SaveToQuote" sortOrder="10" />
    </type>
........
</config>

And Plugin class:


<?php
namespace Jeff\CustomField\Plugin\Quote;

use Magento\Quote\Model\QuoteRepository;

class SaveToQuote
{
    protected $quoteRepository;

    public function __construct(QuoteRepository $quoteRepository) {
        $this->quoteRepository = $quoteRepository;
    }

    public function beforeSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagement $subject,
        $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ) {
        if(!$extAttributes = $addressInformation->getExtensionAttributes())
        {
            return;
        }

        $quote = $this->quoteRepository->getActive($cartId);

        $quote->setCustomvar($extAttributes->getCustomvar());
    }
}

Save the value into sales order

We are going to use ‘sales_model_service_quote_submit_before’ event to save the value into sales_order tables.


#Jeff/CustomField/etc/events.xml

<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_before">
        <observer name="custom_fields_sales_address_save" instance="Jeff\CustomField\Observer\SaveCustomFieldsInOrder" />
    </event>
</config>

And the Observer class:


<?php
namespace Jeff\CustomField\Observer;

class SaveCustomFieldsInOrder implements \Magento\Framework\Event\ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer) {
        $order = $observer->getEvent()->getOrder();
        $quote = $observer->getEvent()->getQuote();

        $order->setData('customvar', $quote->getCustomvar());

        return $this;
    }
}

Final Results

After all these files placed, You can save the custom field into database (quote and sales_order). custom linked product

You can download the source code at my GitHub


comments powered by Disqus