Magento2 | PWA | GraphQL

Add Order shipping note form (implemented as a UI component) to Magento checkout page before shipping address form


In this post we wiil check how to add shipping note to Magento checkout page before shipping address form. We will implement it using UI Component Form in checkout_index_index.xml file.

We will also add some custom validation when click on Next button from shipping step.

Let's start by creating custom module.

You can find complete module on Github at Magelearn_ShippingNotes





Create folder inside app/code/Magelearn/ShippingNotes

Add registration.php file in it:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magelearn_ShippingNotes',
    __DIR__
);
Add composer.json file in it:
{
    "name": "magelearn/module-shippingnotes",
    "description": "Magento 2 module to add shipping notes field to checkout.",
    "type": "magento2-module",
    "version": "1.0.6",
    "authors": [
        {
            "name": "vijay rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\ShippingNotes\\": ""
        }
    }
}
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_ShippingNotes" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Sales"/>
        </sequence>
    </module>
</config>

Now we will add this attribute to the quote and order entities. For that we will create our install data script.

Create app/code/Magelearn/ShippingNotes/Setup/InstallData.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Quote\Setup\QuoteSetupFactory;
use Magento\Sales\Setup\SalesSetupFactory;
use Magento\Framework\DB\Ddl\Table;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Class InstallData
 * @package Magelearn\ShippingNotes\Setup
 */
class InstallData implements InstallDataInterface
{

	/**
	 * SalesSetupFactory
	 *
	 * @var SalesSetupFactory
	 */
	protected $_salesSetupFactory;

	/**
	 * QuoteSetupFactory
	 *
	 * @var QuoteSetupFactory
	 */
	protected $_quoteSetupFactory;

	/**
	 * ModuleDataSetupInterface
	 *
	 * @var ModuleDataSetupInterface
	 */
	protected $_setup;

	/**
	 * InstallData constructor.
	 *
	 * @param SalesSetupFactory $salesSetupFactory SalesSetupFactory
	 * @param QuoteSetupFactory $quoteSetupFactory QuoteSetupFactory
	 */
	public function __construct(
		SalesSetupFactory $salesSetupFactory,
		QuoteSetupFactory $quoteSetupFactory
	) {
		$this->_salesSetupFactory = $salesSetupFactory;
		$this->_quoteSetupFactory = $quoteSetupFactory;
	}

	/**
	 * Install data
	 *
	 * @param ModuleDataSetupInterface $setup   ModuleDataSetupInterface
	 * @param ModuleContextInterface   $context ModuleContextInterface
	 *
	 * @return void
	 */
	public function install(
		ModuleDataSetupInterface $setup,
		ModuleContextInterface $context
	) {
		$this->_setup = $setup->startSetup();
		$this->installQuoteData();
		$this->installSalesData();
		$this->_setup = $setup->endSetup();
	}

	/**
	 * Install quote custom data
	 *
	 * @return void
	 */
	public function installQuoteData()
	{
		/** @var \Magento\Quote\Setup\QuoteSetup $quoteInstaller */
		$quoteInstaller = $this->_quoteSetupFactory->create(
			[
				'resourceName' => 'quote_setup',
				'setup' => $this->_setup
			]
		);
		$quoteInstaller
			->addAttribute(
				'quote',
				ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES,
				['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true, 'grid' => false]
			);
	}

	/**
	 * Install sales custom data
	 *
	 * @return void
	 */
	public function installSalesData()
	{
		/** @var \Magento\Sales\Setup\SalesSetup $salesInstaller */
		$salesInstaller = $this->_salesSetupFactory->create(
			[
				'resourceName' => 'sales_setup',
				'setup' => $this->_setup
			]
		);
		$salesInstaller
			->addAttribute(
				'order',
				ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES,
				['type' => Table::TYPE_TEXT, 'length' => '64k', 'nullable' => true, 'grid' => false]
			);
	}
}

Now create Create app/code/Magelearn/ShippingNotes/Setup/Uninstall.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Setup;

use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UninstallInterface;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Class Uninstall
 * @package Magelearn\ShippingNotes\Setup
 */
class Uninstall implements UninstallInterface
{
	
	/**
	 * SchemaSetupInterface
	 *
	 * @var SchemaSetupInterface
	 */
	protected $_setup;

	/**
	 * Uninstall data
	 *
	 * @param SchemaSetupInterface   $setup   SchemaSetupInterface
	 * @param ModuleContextInterface $context ModuleContextInterface
	 *
	 * @return void
	 */
	public function uninstall(
		SchemaSetupInterface $setup,
		ModuleContextInterface $context
	) {
		$this->_setup = $setup->startSetup();
		$this->uninstallQuoteData();
		$this->uninstallSalesData();
		$this->_setup = $setup->endSetup();
	}

	/**
	 * Uninstall quote shipping notes
	 *
	 * @return void
	 */
	public function uninstallQuoteData()
	{
		$this->_setup->getConnection()->dropColumn(
			$this->_setup->getTable('quote'),
			ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES
		);
	}

	/**
	 * Uninstall sales shipping notes
	 *
	 * @return void
	 */
	public function uninstallSalesData()
	{
		$this->_setup->getConnection()->dropColumn(
			$this->_setup->getTable('sales_order'),
			ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES
		);
	}
}

Now create app/code/Magelearn/ShippingNotes/etc/webapi.xml file.

<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/carts/mine/set-order-shipping-notes" method="PUT">
        <service class="Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface" method="saveShippingNotes"/>
        <resources>
            <resource ref="self" />
        </resources>
        <data>
            <parameter name="cartId" force="true">%cart_id%</parameter>
        </data>
    </route>
    <route url="/V1/guest-carts/:cartId/set-order-shipping-notes" method="PUT">
        <service class="Magelearn\ShippingNotes\Api\ShippingNotesGuestRepositoryInterface" method="saveShippingNotes"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>
</routes>

Now create app/code/Magelearn/ShippingNotes/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">
    <preference for="Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface" type="Magelearn\ShippingNotes\Model\Data\ShippingNotes" />
    <preference for="Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface" type="Magelearn\ShippingNotes\Model\ShippingNotesRepository" />
    <preference for="Magelearn\ShippingNotes\Api\ShippingNotesGuestRepositoryInterface" type="Magelearn\ShippingNotes\Model\ShippingNotesGuestRepository" />
</config>
Now create app/code/Magelearn/ShippingNotes/Api/Data/ShippingNotesInterface.php file.
<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Api\Data;

/**
 * Interface ShippingNotesInterface
 * @package Magelearn\ShippingNotes\Api\Data
 */
interface ShippingNotesInterface
{
	const CHECKOUT_SHIPPING_NOTES = 'checkout_shipping_notes';

	/**
	 * Get checkout shipping notes
	 *
	 * @return string|null
	 */
	public function getCheckoutShippingNotes();

	/**
	 * Set checkout shipping notes
	 *
	 * @param string|null $checkoutShippingNotes
	 * @return ShippingNotesInterface
	 */
	public function setCheckoutShippingNotes(string $checkoutShippingNotes = null);

}

Now Create app/code/Magelearn/ShippingNotes/Api/ShippingNotesRepositoryInterface.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Api;

use Magento\Sales\Model\Order;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Interface ShippingNotesRepositoryInterface
 * @package Magelearn\ShippingNotes\Api
 */
interface ShippingNotesRepositoryInterface
{

	/**
	 * Save checkout shipping notes
	 *
	 * @param int $cartId
	 * @param \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface $shippingNotes
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 */
	public function saveShippingNotes(
		int $cartId,
		ShippingNotesInterface $shippingNotes
	): ShippingNotesInterface;

	/**
	 * Get checkout shipping notes
	 *
	 * @param Order $order Order
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 */
	public function getShippingNotes(Order $order) : ShippingNotesInterface;

}

Now create app/code/Magelearn/ShippingNotes/Api/ShippingNotesGuestRepositoryInterface.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Api;

use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Interface ShippingNotesGuestRepositoryInterface
 * @package Magelearn\ShippingNotes\Api
 */
interface ShippingNotesGuestRepositoryInterface
{

	/**
	 * Save checkout shipping notes
	 *
	 * @param string $cartId
	 * @param \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface $shippingNotes
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 */
	public function saveShippingNotes(
		string $cartId,
		ShippingNotesInterface $shippingNotes
	): ShippingNotesInterface;

}

Now create app/code/Magelearn/ShippingNotes/Model/Data/ShippingNotes.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Model\Data;

use Magento\Framework\Api\AbstractExtensibleObject;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Class ShippingNotes
 * @package Magelearn\ShippingNotes\Model\Data
 */
class ShippingNotes extends AbstractExtensibleObject implements ShippingNotesInterface
{

	/**
	 * Get checkout shipping notes
	 *
	 * @return string|null
	 */
	public function getCheckoutShippingNotes() {
		return $this->_get(self::CHECKOUT_SHIPPING_NOTES);
	}

	/**
	 * Set checkout shipping notes
	 *
	 * @param string|null $checkoutShippingNotes
	 *
	 * @return ShippingNotesInterface
	 */
	public function setCheckoutShippingNotes( string $checkoutShippingNotes = null ) {
		return $this->setData(self::CHECKOUT_SHIPPING_NOTES, $checkoutShippingNotes);
	}

}

Create app/code/Magelearn/ShippingNotes/Model/ShippingNotesRepository.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Model;

use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Sales\Model\Order;
use Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

class ShippingNotesRepository implements ShippingNotesRepositoryInterface
{

	/**
	 * Quote repository.
	 *
	 * @var CartRepositoryInterface
	 */
	protected $_cartRepository;

	/**
	 * ScopeConfigInterface
	 *
	 * @var ScopeConfigInterface
	 */
	protected $_scopeConfig;

	/**
	 * @var ShippingNotesInterface
	 */
	protected $_shippingNotes;

	/**
	 * ShippingNotesRepository constructor.
	 *
	 * @param CartRepositoryInterface $cartRepository CartRepositoryInterface
	 * @param ScopeConfigInterface    $scopeConfig    ScopeConfigInterface
	 * @param ShippingNotesInterface   $shippingNotes   ShippingNotesInterface
	 */
	public function __construct(
		CartRepositoryInterface $cartRepository,
		ScopeConfigInterface $scopeConfig,
		ShippingNotesInterface $shippingNotes
	) {
		$this->_cartRepository = $cartRepository;
		$this->_scopeConfig    = $scopeConfig;
		$this->_shippingNotes   = $shippingNotes;
	}

	/**
	 * Save checkout shipping notes
	 *
	 * @param int $cartId
	 * @param \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface $shippingNotes
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 * @throws NoSuchEntityException
	 * @throws CouldNotSaveException
	 */
	public function saveShippingNotes(
		int $cartId,
		ShippingNotesInterface $shippingNotes
	): ShippingNotesInterface {
		/** @var \Magento\Quote\Model\Quote $cart */
		$cart = $this->_cartRepository->getActive($cartId);
		if (!$cart->getItemsCount()) {
			throw new NoSuchEntityException(__('Cart %1 is empty', $cartId));
		}

		try {
			$cart->setData(
				ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES,
				$shippingNotes->getCheckoutShippingNotes()
			);
			$this->_cartRepository->save($cart);
		} catch (\Exception $e) {
			throw new CouldNotSaveException(__('Custom order data could not be saved!'));
		}

		return $shippingNotes;
	}

	/**
	 * Get checkout shipping notes
	 *
	 * @param Order $order Order
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 * @throws NoSuchEntityException
	 */
	public function getShippingNotes( Order $order ): ShippingNotesInterface {
		if (!$order->getId()) {
			throw new NoSuchEntityException(__('Order %1 does not exist', $order));
		}

		$this->_shippingNotes->setCheckoutShippingNotes(
			$order->getData(ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES)
		);

		return $this->_shippingNotes;
	}

}

Create app/code/Magelearn/ShippingNotes/Model/ShippingNotesGuestRepository.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Model;

use Magento\Quote\Model\QuoteIdMaskFactory;
use Magelearn\ShippingNotes\Api\ShippingNotesGuestRepositoryInterface;
use Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Class ShippingNotesGuestRepository
 * @package Magelearn\ShippingNotes\Model
 */
class ShippingNotesGuestRepository implements ShippingNotesGuestRepositoryInterface
{

	/**
	 * @var QuoteIdMaskFactory
	 */
	protected $_quoteIdMaskFactory;

	/**
	 * @var ShippingNotesRepositoryInterface
	 */
	protected $_shippingNotesRepository;

	/**
	 * @param QuoteIdMaskFactory               $quoteIdMaskFactory
	 * @param ShippingNotesRepositoryInterface $shippingNotesRepository
	 */
	public function __construct(
		QuoteIdMaskFactory $quoteIdMaskFactory,
		ShippingNotesRepositoryInterface $shippingNotesRepository
	) {
		$this->_quoteIdMaskFactory     = $quoteIdMaskFactory;
		$this->_shippingNotesRepository = $shippingNotesRepository;
	}

	/**
	 * Save checkout shipping notes
	 *
	 * @param string $cartId
	 * @param \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface $shippingNotes
	 *
	 * @return \Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface
	 */
	public function saveShippingNotes(
		string $cartId,
		ShippingNotesInterface $shippingNotes
	): ShippingNotesInterface {
		/** @var \Magento\Quote\Model\Quote  $quoteIdMask */
		$quoteIdMask = $this->_quoteIdMaskFactory->create()->load($cartId, 'masked_id');
		return $this->_shippingNotesRepository->saveShippingNotes((int)$quoteIdMask->getQuoteId(), $shippingNotes);
	}

}

Now we will define some system settings and some pre-defined configuration options for this shipping notes.

Add 
app/code/Magelearn/ShippingNotes/etc/adminhtml/system.xml file.

<?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="shipping_note" translate="label" type="text" sortOrder="1001" showInDefault="1" showInWebsite="1" showInStore="1">
            <class>separator-top</class>
            <label>Shipping note</label>
            <tab>sales</tab>
            <resource>Magelearn_ShippingNotes::config_shippingnote</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Shipping Note: General</label>
                <field id="enabled" translate="label" type="select" sortOrder="0" showInDefault="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="is_show_in_myaccount" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Show In Customer Account?</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                   <comment><![CDATA[If <strong>Yes</strong> the shipping note will show on customer frontend account,
					in <strong>"My Account -> My Orders -> View Order"</strong>]]></comment>
                </field>
                <field id="max_length" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Maximum Characters Length (limit)</label>
					<validate>validate-number</validate>
                    <comment><![CDATA[Enter the maximum characters allowed for the shipping note at the checkout page.
						Eg: <strong>200</strong>. Leave empty for no characters limit]]></comment>
                </field>
            </group>
        </section>
    </system>
</config>

Add app/code/Magelearn/ShippingNotes/etc/config.xml file.

<?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>
        <shipping_note>
            <general>
            	<enabled>1</enabled>
                <is_show_in_myaccount>1</is_show_in_myaccount>
                <max_length></max_length>
            </general>
        </shipping_note>
    </default>
</config>

Now, we will check how to save Quote data into order data.
Add app/code/Magelearn/ShippingNotes/etc/extension_attributes.xml file.

<?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\CartInterface">
        <attribute code="checkout_shipping_notes" type="string" />
    </extension_attributes>
    <extension_attributes for="Magento\Sales\Api\Data\OrderInterface">
        <attribute code="checkout_shipping_notes" type="string" />
    </extension_attributes>
</config>

Add app/code/Magelearn/ShippingNotes/etc/fieldset.xml file.

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">
    <scope id="global">
        <fieldset id="sales_convert_quote">
            <field name="checkout_shipping_notes">
                <aspect name="to_order"/>
            </field>
        </fieldset>
    </scope>
</config>

Add app/code/Magelearn/ShippingNotes/etc/events.xml file.

<?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_before">
        <observer name="magelearn_checkout_shipping_notes_add_to_order" instance="Magelearn\ShippingNotes\Observer\AddShippingNotesToOrder" />
    </event>
</config>

Now add app/code/Magelearn/ShippingNotes/Observer/AddShippingNotesToOrder.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;

/**
 * Class AddShippingNotesToOrder
 * @package Magelearn\ShippingNotes\Observer
 */
class AddShippingNotesToOrder implements ObserverInterface
{
	/**
     * @var \Magento\Framework\DataObject\Copy
     */
    protected $objectCopyService;
	
	/**
     * @param \Magento\Framework\DataObject\Copy $objectCopyService
     */
    public function __construct(
        \Magento\Framework\DataObject\Copy $objectCopyService
    )
    {
        $this->objectCopyService = $objectCopyService;
    }
	
	/**
	 * Execute observer method.
	 *
	 * @param Observer $observer Observer
	 *
	 * @return void
	 */
	public function execute( Observer $observer ) {
		/** @var \Magento\Sales\Model\Order $order */
		$order = $observer->getEvent()->getData('order');
		/** @var \Magento\Quote\Model\Quote $quote */
		$quote = $observer->getEvent()->getData('quote');

		/*$order->setData(
			ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES,
			$quote->getData(ShippingNotesInterface::CHECKOUT_SHIPPING_NOTES)
		);*/
		$order->setCheckoutShippingNotes($quote->getCheckoutShippingNotes());

        $this->objectCopyService->copyFieldsetToTarget('sales_convert_quote', 'to_order', $quote, $order);

        return $this;
	}

}

Now to display this shipping note on checkout page, we will modify checkout_index_index.xml file.

Add app/code/Magelearn/ShippingNotes/view/frontend/layout/checkout_index_index.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" 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="before-form" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="custom-checkout-form-container" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magelearn_ShippingNotes/js/view/shipping-notes-form</item>
                                                                    <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                    <item name="config" xsi:type="array">
                                                                        <item name="template" xsi:type="string">Magelearn_ShippingNotes/shipping-notes-form</item>
                                                                    </item>
                                                                    <item name="children" xsi:type="array">
                                                                        <item name="shipping-notes-form-fieldset" xsi:type="array">
                                                                            <!-- uiComponent is used as a wrapper for form fields (its template will render all children as a list) -->
                                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                                            <!-- the following display area is used in template (see below) -->
                                                                            <item name="displayArea" xsi:type="string">shipping-notes-form-fields</item>
                                                                            <item name="children" xsi:type="array">
                                                                                <item name="shipping_notes_field" xsi:type="array">
                                                                                    <item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item>
                                                                                    <item name="config" xsi:type="array">
                                                                                        <!-- customScope is used to group elements within a single form (e.g. they can be validated separately) -->
                                                                                        <item name="customScope" xsi:type="string">shippingNotesForm</item>
                                                                                        <item name="template" xsi:type="string">ui/form/field</item>
                                                                                        <item name="elementTmpl" xsi:type="string">ui/form/element/textarea</item>
                                                                                        <item name="cols" xsi:type="string">10</item>
                                                                                        <item name="rows" xsi:type="string">4</item>
                                                                                    </item>
                                                                                    <item name="validation" xsi:type="array">
                                                                                        <item name="max_text_length" xsi:type="number">240</item>
                                                                                    </item>
                                                                                    <item name="notice" xsi:type="string" translate="true">Limited to 240 characters.</item>
                                                                                    <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                                    <item name="dataScope" xsi:type="string">shippingNotesForm.checkout_shipping_notes</item>
                                                                                    <item name="label" xsi:type="string" translate="true">Enter Shipping Notes</item>
                                                                                    <item name="sortOrder" xsi:type="string">10</item>
                                                                                </item>
                                                                            </item>
                                                                            <item name="shipping_notes_error_text" xsi:type="array">
								                                            <item name="sortOrder" xsi:type="string">35</item>
								                                            <item name="component"  xsi:type="string">uiComponent</item>
								                                            <item name="config" xsi:type="array">
								                                                <item name="template" xsi:type="string">Magelearn_ShippingNotes/error_text</item>
								                                            </item>
								                                        </item>
                                                                        </item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

This file defines additional form fields on checkout page.

Here all the field defination are defined in XML file. 

we will also check how to add same information by implementing UI Component form fields.
For that we will add modified version of checkout_index_index.xml file.

Add app/code/Magelearn/ShippingNotes/view/frontend/layout/checkout_index_index.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" 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="before-form" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="custom-checkout-form-container" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magelearn_ShippingNotes/js/view/shipping-notes-form</item>
                                                                    <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                    <item name="config" xsi:type="array">
                                                                        <item name="template" xsi:type="string">Magelearn_ShippingNotes/shipping-notes-form</item>
                                                                    </item>
                                                                    <item name="children" xsi:type="array">
                                                                        <item name="shipping-notes-form-fieldset" xsi:type="array">
                                                                            <!-- uiComponent is used as a wrapper for form fields (its template will render all children as a list) -->
                                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                                            <!-- the following display area is used in template (see below) -->
                                                                            <item name="displayArea" xsi:type="string">shipping-notes-form-fields</item>
                                                                            <item name="children" xsi:type="array"></item>
                                                                        </item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page> 

Now to proceed with this method, we will add Plugin for Magento\Checkout\Block\Checkout\LayoutProcessor and add our logic inside afterProcess method.

We will also check how to add additional variables in checkout configuration.

For that add app/code/Magelearn/ShippingNotes/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\Block\Checkout\LayoutProcessor">
        <plugin name="ml_shippingnotes_layout_pl" type="Magelearn\ShippingNotes\Plugin\Model\Checkout\LayoutProcessor" sortOrder="100"/>
    </type>
    <type name="Magento\Checkout\Model\CompositeConfigProvider">
        <arguments>
            <argument name="configProviders" xsi:type="array">
                <item name="shipping_note_config_provider" xsi:type="object">Magelearn\ShippingNotes\Model\ShippingNoteConfig</item>
            </argument>
        </arguments>
    </type>
</config>
As per above file, we will add app/code/Magelearn/ShippingNotes/Model/ShippingNoteConfig.php file.
<?php

namespace Magelearn\ShippingNotes\Model;

use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Authorization\Model\UserContextInterface;


class ShippingNoteConfig implements ConfigProviderInterface
{
    /**
     *  Config Paths
     */
    const XML_PATH_GENERAL_ENABLED = 'shipping_note/general/enabled';
    const XML_PATH_GENERAL_IS_SHOW_IN_MYACCOUNT = 'shipping_note/general/is_show_in_myaccount';
    const XML_PATH_GENERAL_MAX_LENGTH = 'shipping_note/general/max_length';


    /**
     * @var \Magento\Authorization\Model\UserContextInterface
     */
    private $userContext;


    /**
     * @var \Magento\Quote\Api\CartRepositoryInterface
     */
    private $quoteRepository;


    /**
     * @var \Magento\Framework\App\Action\Context
     */
    private $context;

    /**
     * @var \Magento\Quote\Api\Data\CartInterface
     */
    private $quote;

    /**
     * @var ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        UserContextInterface $userContext,
        \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
        \Magento\Checkout\Model\Session $session,
        ScopeConfigInterface $scopeConfig
    )
    {
        $this->userContext = $userContext;
        $this->quoteRepository = $quoteRepository;
        $this->scopeConfig = $scopeConfig;
        $this->session = $session;
    }

    public function isEnabled()
    {
        return $this->scopeConfig->getValue(
            self::XML_PATH_GENERAL_ENABLED,
            ScopeInterface::SCOPE_STORE
        );
    }

    /**
     * Check if show order note to customer account
     *
     * @return bool
     */
    public function isShowNoteInAccount()
    {
        return $this->scopeConfig->getValue(
            self::XML_PATH_GENERAL_IS_SHOW_IN_MYACCOUNT,
            ScopeInterface::SCOPE_STORE
        );
    }
    /**
     * Get order note max length
     *
     * @return int
     */
    public function getConfig()
    {
        $show_shipping_note = $this->getItemInternalShippingNote();
        return [
            'max_length' => (int)$this->scopeConfig->getValue(self::XML_PATH_GENERAL_MAX_LENGTH, ScopeInterface::SCOPE_STORE),
            'show_shipping_note' => $show_shipping_note
        ];
    }

    public function getItemInternalShippingNote()
    {
        $showShippingNote = false;
        if (!$this->isEnabled()) {
            return $showShippingNote;
        } else {
        	$showShippingNote = true;
        }

        return $showShippingNote;
    }


    /**
     * Get quote.
     *
     * @return \Magento\Quote\Api\Data\CartInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getQuote()
    {
        if ($this->quote === null) {
            try {
                if ($this->userContext->getUserId()) {
                    $this->quote = $this->quoteRepository->getActiveForCustomer($this->userContext->getUserId());
                } else {
                    $this->quote = $this->session->getQuote();
                }

            } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
                // $this->quote = $this->quoteRepository->get($id);
            }
        }

        return $this->quote;
    }
}

Now you are able to get window.checkoutConfig.show_shipping_note and window.checkoutConfig.max_length variables value on checkout page.

We will also add app/code/Magelearn/ShippingNotes/Plugin/Model/Checkout/LayoutProcessor.php file.

<?php
namespace Magelearn\ShippingNotes\Plugin\Model\Checkout;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

class LayoutProcessor
{
	/**
     *  Config Paths
     */
    const XML_PATH_GENERAL_MAX_LENGTH = 'shipping_note/general/max_length';
		  
	/**
     * @var ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig
    )
    {
        $this->scopeConfig = $scopeConfig;
    }
	
    public function afterProcess(
        \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
        array  $jsLayout
    ) {

        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['before-form']['children']['custom-checkout-form-container']['children']['shipping-notes-form-fieldset']['children']['checkout_shipping_notes'] = [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [
                'customScope' => 'shippingNotesForm',
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/textarea',
                'cols'		  => 10,
                'rows'		  => 4,
            ],
            'notice' => ($this->getMaxNoteLength() > 0) ? "Limited to ".$this->getMaxNoteLength()." characters.": '',
            'provider' => 'checkoutProvider',
            'dataScope' => 'shippingNotesForm.checkout_shipping_notes',
            'label' => 'Enter Shipping Notes',
            'sortOrder' => 10,
            'validation' => [
                'max_text_length' => $this->getMaxNoteLength(),
            ],
        ];
		
		$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['before-form']['children']['custom-checkout-form-container']['children']['shipping-notes-form-fieldset']['children']['shipping_notes_error_text'] = [
            'component' => 'uiComponent',
            'config' => [
                'template' => 'Magelearn_ShippingNotes/error_text',
            ],
            'sortOrder' => 35,
        ];

        return $jsLayout;
    }
	
	public function getMaxNoteLength () {
		$max_length = (int)$this->scopeConfig->getValue(self::XML_PATH_GENERAL_MAX_LENGTH, ScopeInterface::SCOPE_STORE);
		if($max_length > 0) {
			return $max_length;
		} else {
			return false;
		}
	}
}

Here we have defined our form container fields and error text html.

Now, we will add our JS Component and html files.

Add app/code/Magelearn/ShippingNotes/view/frontend/web/template/shipping-notes-form.html file.

<div data-bind=" if: isShowShippingNoteTextField()">
<li id="shipping-notes" class="checkout-shipping-notes">
    <div class="step-title" data-role="title" data-bind="i18n: 'Shipping Notes'"></div>
    <div id="checkout-step-notes" class="step-content" data-role="content">
        <form id="shipping-notes-form" class="form form-shipping-address"
              data-bind="attr: {'data-hasrequired': $t('* Required Fields')}, event: {change: onFormChange()}">
            <!-- ko foreach: getRegion('shipping-notes-form-fields') -->
            <!-- ko template: getTemplate() --><!-- /ko -->
            <!--/ko-->
        </form>
    </div>
</li>
</div>

Add app/code/Magelearn/ShippingNotes/view/frontend/web/js/view/shipping-notes-form.js file.

/*global define*/
define([
    'knockout',
    'jquery',
    'mage/url',
    'Magento_Ui/js/form/form',
    'Magento_Customer/js/model/customer',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/url-builder',
    'Magento_Checkout/js/model/error-processor',
    'Magento_Checkout/js/model/cart/cache',
    'Magelearn_ShippingNotes/js/model/checkout/shipping-notes-form',
    'Magento_Checkout/js/model/full-screen-loader'
], function(
    ko,
    $,
    urlFormatter,
    Component,
    customer,
    quote,
    urlBuilder,
    errorProcessor,
    cartCache,
    formData,
    fullScreenLoader
) {
    'use strict';
    return Component.extend({
        shippingNotes: ko.observable(null),
        formData: formData.shippingNotesData,

        /**
         * Initialize component
         *
         * @returns {exports}
         */
        initialize: function () {
            var self = this;
            this._super();
            formData = this.source.get('shippingNotesForm');
            var formDataCached = cartCache.get('shipping-notes-form');
            if (formDataCached) {
                formData = this.source.set('shippingNotesForm', formDataCached);
            }

            this.shippingNotes.subscribe(function(change){
                self.formData(change);
            });

            return this;
        },

        /**
         * Trigger save method if form is change
         */
        onFormChange: function () {
            this.saveShippingNotes();
        },

        /**
         * Form submit handler
         */
        saveShippingNotes: function() {
            this.source.set('params.invalid', false);
            this.source.trigger('shippingNotesForm.data.validate');
			
            if (!this.source.get('params.invalid')) {
                fullScreenLoader.startLoader();
                var formData = this.source.get('shippingNotesForm');
                // do something with form data
                //console.dir(formData);
                console.log(formData.checkout_shipping_notes);
                var quoteId = quote.getQuoteId();
                var isCustomer = customer.isLoggedIn();
                var url;

                if (isCustomer) {
                    url = urlBuilder.createUrl('/carts/mine/set-order-shipping-notes', {});
                } else {
                    url = urlBuilder.createUrl('/guest-carts/:cartId/set-order-shipping-notes', {cartId: quoteId});
                }

                var payload = {
                    cartId: quoteId,
                    shippingNotes: formData
                };
                var result = true;
                $.ajax({
                    url: urlFormatter.build(url),
                    data: JSON.stringify(payload),
                    global: false,
                    contentType: 'application/json',
                    type: 'PUT',
                    async: true
                }).done(
                    function (response) {
                        cartCache.set('shipping-notes-form', null);
                        result = true;
                        $(".shippipng-notes-error").hide(); 
                    }
                ).fail(
                    function (response) {
                        result = false;
                        errorProcessor.process(response);
                    }
                ).always(
                    function () {
                        fullScreenLoader.stopLoader();
                    }
                );

                return result;
            }
        },
        /**
         * Show/hide shipping note Text Field based on condition
         *
         * @return {String}
         */
        isShowShippingNoteTextField: function () {
            return window.checkoutConfig.show_shipping_note > 0;
        }
    });
});

Now add app/code/Magelearn/ShippingNotes/view/frontend/web/js/model/checkout/shipping-notes-form.js file.

define(
    [
        'ko',
    ],
    function(ko) {
        'use strict';
        return{
            shippingNotesData: ko.observable(null)
        }
    }
);

Now, when click on "NEXT" button from shipping page, we will add some validation for this shipping note field.

For that, we will override Magento_Checkout/js/model/shipping-save-processor/default.js file.
Add app/code/Magelearn/ShippingNotes/view/frontend/requirejs-config.js file.

var config = {
    "map": {
       "*": {
           "Magento_Checkout/js/model/shipping-save-processor/default" : "Magelearn_ShippingNotes/js/shipping-save-processor/default"
       }
   }
}

Add app/code/Magelearn/ShippingNotes/view/frontend/web/js/shipping-save-processor/default.js file.

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

define([
    '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',
    'Magento_Checkout/js/model/shipping-save-processor/payload-extender',
    'jquery',
    'Magento_Ui/js/modal/alert'
], function (
    ko,
    quote,
    resourceUrlManager,
    storage,
    paymentService,
    methodConverter,
    errorProcessor,
    fullScreenLoader,
    selectBillingAddressAction,
    payloadExtender,
    $,
    alert
) {
    'use strict';

    return {
        /**
         * @return {jQuery.Deferred}
         */
        saveShippingInformation: function () {
            var payload;
			
			if (window.checkoutConfig.show_shipping_note > 0 && window.checkoutConfig.max_length) {
			var shipping_notes = $('textarea[name="checkout_shipping_notes"]').val();
			//console.log(shipping_notes.length);
          	/*============ Customization Start =============*/
          	//if(quote.shippingMethod().method_code=='flaterate_flaterate'){ // Check if flaterate is selected
            if(shipping_notes.length > window.checkoutConfig.max_length){
              alert({
                title: $.mage.__('Error'),
                content: $.mage.__('Please correct shipping notes character to further proceed.')
              });
              $(".shippipng-notes-error").show(); // show hidden message
              $('textarea[name="checkout_shipping_notes"]').focus();// move cursor to the point
              return false;
             }
           }
          // }
          /*============ Customization End =============*/
             
            if (!quote.billingAddress() && quote.shippingAddress().canUseForBilling()) {
                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']
                }
            };

            payloadExtender(payload);

            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();
                }
            );
        }
    };
});

Now we will add our error-text.html file.
Add app/code/Magelearn/ShippingNotes/view/frontend/web/template/error_text.html file.

<div class="shippipng-notes-error message error" role="alert" style="display:none;">
	<span class="shipping-notes-error-message">Please correct shipping notes character to further proceed.</span>
</div>

To display this shipping notes on front end MyAccount >> Order View Page,

We will add app/code/Magelearn/ShippingNotes/view/frontend/layout/sales_order_view.xml file.

<?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">
    <body>
        <referenceContainer name="content">
            <block class="Magelearn\ShippingNotes\Block\Order\ShippingNotes" as="ordershippingnotes"
                   name="sales.order.shippingnotes" after="sales.order.info"/>
        </referenceContainer>
    </body>
</page>

Now, we will add our Block file at app/code/Magelearn/ShippingNotes/Block/Order/ShippingNotes.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Block\Order;

use Magento\Framework\View\Element\Template;
use Magento\Framework\View\Element\Template\Context;
use Magento\Framework\Registry;
use Magento\Sales\Model\Order;
use Magelearn\ShippingNotes\Api\Data\ShippingNotesInterface;
use Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface;
use Magelearn\ShippingNotes\Model\ShippingNoteConfig;

/**
 * Class ShippingNotes
 * @package Magelearn\ShippingNotes\Block\Order
 */
class ShippingNotes extends Template
{
	/**
     * @var ShippingNoteConfig
     */
    protected $shippingNoteConfig;

	/**
	 * @var Registry|null
	 */
	protected $_coreRegistry = null;

	/**
	 * @var ShippingNotesRepositoryInterface
	 */
	protected $_shippingNotesRepository;

	/**
	 * ShippingNotes constructor.
	 *
	 * @param Context $context
	 * @param Registry $registry
	 * @param ShippingNotesRepositoryInterface $shippingNotesRepository
	 * @param array $data
	 */
	public function __construct(
		Template\Context $context,
		Registry $registry,
		ShippingNotesRepositoryInterface $shippingNotesRepository,
		ShippingNoteConfig $shippingNoteConfig,
		array $data = []
	) {
		$this->_coreRegistry = $registry;
		$this->_shippingNotesRepository = $shippingNotesRepository;
		$this->shippingNoteConfig = $shippingNoteConfig;
		$this->_isScopePrivate = true;
		$this->_template = 'order/view/shipping_notes.phtml';
		parent::__construct( $context, $data );

	}
	
	/**
     * Check if show shipping note to customer account
     *
     * @return bool
     */
    public function isShowNoteInAccount()
    {
        return $this->shippingNoteConfig->isShowNoteInAccount();
    }
	
	/**
	 * Get current order
	 *
	 * @return Order
	 */
	public function getOrder() : Order {
		return $this->_coreRegistry->registry('current_order');
	}

	/**
	 * Get checkout shipping notes
	 *
	 * @param Order $order
	 *
	 * @return ShippingNotesInterface
	 */
	public function getShippingNotes( Order $order ) {
		return $this->_shippingNotesRepository->getShippingNotes($order);
	}

}

Now add app/code/Magelearn/ShippingNotes/view/frontend/templates/order/view/shipping_notes.phtml file.

<?php
/** @var Magelearn\ShippingNotes\Block\Order\ShippingNotes $block */

/** @var Magelearn\ShippingNotes\Model\Data\ShippingNotes $shippingNotes */
$shippingNotes = $block->getShippingNotes($block->getOrder());
?>
<?php if ($block->isShowNoteInAccount()): ?>
<?php if($shippingNotes): ?>
	<div class="block block-order-details-view">
		<div class="block-content">
			<div class="box">
				<strong class="box-title">
					<span><?php /* @escapeNotVerified */ echo __('Shipping Notes') ?></span>
				</strong>
				<div class="box-content">
					<?php echo nl2br($this->escapeHtml($shippingNotes->getCheckoutShippingNotes())); ?>
				</div>
			</div>
		</div>
	</div>
<?php endif; ?>
<?php endif; ?>

Now, to display this shipping notes at admin, we will add app/code/Magelearn/ShippingNotes/view/adminhtml/layout/sales_order_view.xml file.

<?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">
    <body>
        <block class="Magento\Backend\Block\Template" name="order_shipping_notes"
               template="Magelearn_ShippingNotes::order/view/shipping_notes.phtml" />
    </body>
</page>

To display the data properly in .phtml file, we will add plugin for Magento\Sales\Block\Adminhtml\Order\View\Info and add our code afterToHtml method.

For that first add app/code/Magelearn/ShippingNotes/etc/adminhtml/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\Sales\Block\Adminhtml\Order\View\Info">
        <plugin name="magelearn_checkout_shipping_notes_show_fields"
                type="Magelearn\ShippingNotes\Plugin\Block\Adminhtml\ShippingNotesPlugin" sortOrder="99999" />
    </type>
</config>

Now add app/code/Magelearn/ShippingNotes/Plugin/Block/Adminhtml/ShippingNotesPlugin.php file.

<?php

declare(strict_types=1);

namespace Magelearn\ShippingNotes\Plugin\Block\Adminhtml;

use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Block\Adminhtml\Order\View\Info;
use Magelearn\ShippingNotes\Api\ShippingNotesRepositoryInterface;

/**
 * Class ShippingNotesPlugin
 * @package Magelearn\ShippingNotes\Plugin\Block\Adminhtml
 */
class ShippingNotesPlugin
{

	/**
	 * @var ShippingNotesRepositoryInterface
	 */
	protected $_shippingNotesRepository;

	/**
	 * ShippingNotesPlugin constructor.
	 *
	 * @param ShippingNotesRepositoryInterface $shippingNotesRepository
	 */
	public function __construct(
		ShippingNotesRepositoryInterface $shippingNotesRepository
	) {
		$this->_shippingNotesRepository = $shippingNotesRepository;
	}

	/**
	 * Add shipping notes to order view block
	 *
	 * @param Info $subject
	 * @param string $result
	 *
	 * @return string
	 * @throws LocalizedException
	 */
	public function afterToHtml( Info $subject, $result ) {
		$block = $subject->getLayout()->getBlock('order_shipping_notes');
		if ($block !== false) {
			$block->setOrderShippingNotes(
				$this->_shippingNotesRepository->getShippingNotes($subject->getOrder())
			);
			$result = $result . $block->toHtml();
		}

		return $result;
	}

}

Now add app/code/Magelearn/ShippingNotes/view/adminhtml/templates/order/view/shipping_notes.phtml file.

<?php
/** @var Magento\Backend\Block\Template $block */

/** @var Magelearn\ShippingNotes\Model\Data\ShippingNotes $shippingNotes */
$shippingNotes = $block->getOrderShippingNotes();
?>
<?php if($shippingNotes): ?>
	<section class="admin__page-section">
		<div class="admin__page-section-title">
			<span class="title"><?php /* @escapeNotVerified */ echo __('Other information') ?></span>
		</div>
		<div class="admin__page-section-content">
			<div class="order-information">
				<div class="box">
					<strong class="box-title"><span><?php /* @escapeNotVerified */ echo __('Shipping Notes') ?></span></strong>
					<div class="box-content">
						<?php echo nl2br($this->escapeHtml($shippingNotes->getCheckoutShippingNotes())); ?>
					</div>
				</div>
			</div>
		</div>
	</section>
<?php endif; ?>

You can also add some translation for your module at app/code/Magelearn/ShippingNotes/i18n/en_US.csv file.

After adding above code, you can notice that new textarea field will be added before shipping form.

 You can also manage its configuration from admin at Store >> Configuration >> Sales >> Shipping note tab.

And Validation for this text length will be also applied properly (error message + alert) before going to the next step from shipping step.

0 Comments On "Add Order shipping note form (implemented as a UI component) to Magento checkout page before shipping address form"

Back To Top