Skip to content

Saving an extra checkout shipping address field in Magento 2.4.5

I’ve created a module to add an extra field to the checkout shipping address. I can see the extra field on the checkout but the data doesn’t seem to be saving in the database using a plugin on beforeAssign method followed by an event observer for sales_model_service_quote_submit_success.

Example/CheckoutFields/etc/db_schema.xml

<?xml version="1.0" ?>

<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="quote_address" resource="checkout" comment="Sales Flat Quote Address">
        <column xsi:type="varchar" name="extra_field" nullable="true" length="255" comment="Extra Field"/>
    </table>
    <table name="sales_order_address" resource="sales" engine="innodb" comment="Sales Flat Order Address">
        <column xsi:type="varchar" name="extra_field" nullable="true" length="255" comment="Extra Field"/>
    </table>
</schema>

Example/CheckoutFields/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="MagentoCheckoutModelShippingInformationManagement">
        <plugin name="save_extra_field" type="ExampleCheckoutFieldsModelPluginSaveAddressInformation"
                sortOrder="100"/>
    </type>
</config>

Example/CheckoutFields/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="custom_address_attribute_save"
                  instance="ExampleCheckoutFieldsObserverSaveAddressAttribute"/>
    </event>
</config>

Example/CheckoutFields/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="MagentoQuoteApiDataAddressInterface">
        <attribute code="extra_field" type="string"/>
    </extension_attributes>
</config>

Example/CheckoutFields/Plugin/ShippingAddressManagementAssign.php

<?php

namespace ExampleCheckoutFieldsPlugin;

use MagentoQuoteModelShippingAddressManagement;
use MagentoQuoteApiDataAddressInterface;
use PsrLogLoggerInterface as Logger;

class ShippingAddressManagementAssign
{
    /**
     * @var Logger
     */
    protected Logger $logger;

    /**
     * @param Logger $logger
     */
    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @param ShippingAddressManagement $subject
     * @param $cartId
     * @param AddressInterface $address
     * @return void
     */
    public function beforeAssign(
        ShippingAddressManagement $subject,
                                  $cartId,
        AddressInterface          $address
    )
    {
        $extAttributes = $address->getExtensionAttributes();

        if (!$extAttributes) {
            return;
        }

        $extraField = $extAttributes->getExtraField();

        if ($extraField !== null) {
            try {
                $extraField = trim((string)$extraField);
                $address->setExtraField($extraField);
            } catch (Exception $e) {
                $this->logger->critical($e->getMessage());
            }
        }
    }
}

Example/CheckoutFields/Observer/SaveAddressAttribute.php

<?php

namespace ExampleCheckoutFieldsObserver;

use MagentoFrameworkEventObserver;
use MagentoFrameworkEventObserverInterface;
use PsrLogLoggerInterface as Logger;

class SaveAddressAttribute implements ObserverInterface
{
    /**
     * @var Logger
     */
    protected Logger $logger;

    /**
     * @param Logger $logger
     */
    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @param Observer $observer
     * @return void
     */
    public function execute(Observer $observer)
    {
        try {
            $order = $observer->getEvent()->getOrder();
            $quote = $observer->getEvent()->getQuote();

            if (!$quote->isVirtual()) {
                $order->getShippingAddress()->setExtraField($quote->getShippingAddress()->getExtraField());
            }

            $order->save();
        } catch (Exception $e) {
            $this->logger->critical($e->getMessage());
        }
    }
}