Ajouter un champ d'adresse dans le checkout

Le checkout est certainement la partie la plus compliquée à modifier dans Magento 2, malgré la documentation officielle qui décrit comment faire les tâches les plus communes.

Nous allons ici ajouter un champ dans l'adresse de livraison du client.

1. Ajouter le champ

La documentation présente deux façons de faire, nous utiliserons le layout processor et non le plugin.

Dans notre fichier etc/frontend\di.xml :

<?xml version="1.0" encoding="UTF-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Block\Onepage">
        <arguments>
            <argument name="layoutProcessors" xsi:type="array">
                <item name="adexos_formation_shipping_address_field" xsi:type="object">Adexos\Formation\Block\Checkout\LayoutProcessor</item>
            </argument>
        </arguments>
    </type>
</config>

Le fichier LayoutProcessor.php :

<?php
namespace Adexos\Formation\Block\Checkout;

class LayoutProcessor implements \Magento\Checkout\Block\Checkout\LayoutProcessorInterface
{
    /**
     * Process js Layout of block
     *
     * @param array $jsLayout
     * @return array
     */
    public function process($jsLayout)
    {
        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
        ['shippingAddress']['children']['shipping-address-fieldset']['children']
        ['mobile'] = [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [
                // customScope is used to group elements within a single form (e.g. they can be validated separately)
                'customScope' => 'shippingAddress.custom_attributes',
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/input',
                'tooltip' => [
                    'description' => 'this is what the field is for',
                ],
            ],
            'dataScope' => 'shippingAddress.custom_attributes.mobile',
            'label' => 'Mobile',
            'provider' => 'checkoutProvider',
            'sortOrder' => 150,
            'validation' => [
                'required-entry' => true
            ],
            'options' => [],
            'filterBy' => null,
            'customEntry' => null,
            'visible' => true,
            'value' => '' // value field is used to set a default value of the attribute
        ];

        return $jsLayout;
    }
}

2. Créer la mixin JS et la surcharge du payload

Une mixin en JS peut être assimilée à un observer, le fichier va venir se câbler sur un autre fichier existant. Créons le fichier app/code/Adexos/Formation/view/frontend/web/js/checkout/action/set-shipping-information-mixin.js

/*jshint browser:true jquery:true*/
/*global alert*/
define([
    'jquery',
    'mage/utils/wrapper',
    'Magento_Checkout/js/model/quote'
], function ($, wrapper, quote) {
    'use strict';

    return function (setShippingInformationAction) {

        return wrapper.wrap(setShippingInformationAction, function (originalAction) {
            var shippingAddress = quote.shippingAddress();
            if (shippingAddress['extension_attributes'] === undefined) {
                shippingAddress['extension_attributes'] = {};
            }

            shippingAddress['extension_attributes']['mobile'] = shippingAddress.customAttributes['mobile'];
            // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
            return originalAction();
        });
    };
});

Puis le fichier app/code/Adexos/Formation/view/frontend/web/js/model/shipping-save-processor/payload-extender.js

define([
    'jquery',
], function ($) {
    'use strict';

    return function (payload) {
        payload.addressInformation['extension_attributes'] = {
            mobile: $('[name="custom_attributes[mobile]"]').val()
        };

        return payload;
    };
});

3. Déclarer la mixin et la surcharge

Dans le fichier requirejs-config.js situé dans le dossier view/frontend de notre module, nous allons déclarer notre mixin et notre surcharge :

var config = {
    config: {
        map: {
            '*': {
                'Magento_Checkout/js/model/shipping-save-processor/payload-extender': 'Adexos_Formation/js/model/shipping-save-processor/payload-extender'
            }
        },
        mixins: {
            'Magento_Checkout/js/action/set-shipping-information': {
                'Adexos_Formation/js/checkout/action/set-shipping-information-mixin': true
            }
        }
    }
};

4. Ajouter les extension attributes

Dans le fichier etc/extension_attributes.xml de notre module, ajoutons notre nouveau champ dans les objets correspondants :

<?xml version="1.0" encoding="UTF-8" ?>

<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="mobile" type="string" />
    </extension_attributes>
    <extension_attributes for="Magento\Sales\Api\Data\OrderAddressInterface">
        <attribute code="mobile" type="string" />
    </extension_attributes>
    <extension_attributes for="Magento\Checkout\Api\Data\ShippingInformationInterface">
        <attribute code="mobile" type="string"/>
    </extension_attributes>
</config>

Cela va permettre à Magento de générer les getters et setters pour ces classes.

5. Ajouter un plugin pour récupérer et sauvegarder l'extension attribute

Nous allons nous câbler sur la classe ShippingInformationManagement pour récupérer notre attribut. Dans le fichier etc/di.xml, déclarer le plugin :

<?xml version="1.0" encoding="UTF-8" ?>
<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="adexos_formation_shippinginformation" type="Adexos\Formation\Plugin\Checkout\ShippingInformationManagement" sortOrder="1"/>
    </type>
</config>

Créons la classe Adexos\Formation\Plugin\Checkout\ShippingInformationManagement :

<?php
namespace Adexos\Formation\Plugin\Checkout;

class ShippingInformationManagement
{
    /**
     * @param \Magento\Checkout\Model\ShippingInformationManagement $subject
     * @param $cartId
     * @param \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
     */
    public function beforeSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagement $subject,
        $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ) {
        $extAttributes = $addressInformation->getExtensionAttributes();
        $mobile = $extAttributes->getMobile();

        // Sauvegarder le mobile dans l'extension attribute
    }
}