0

I have to add a new customer address attribute apellido2, this field is a second last name and must be present in both shipping and billing address. So I after reading a few posts, I started with my custom module:

app/code/Vendor/Module/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\Customer\Api\Data\AddressInterface">
        <attribute code="apellido2" type="string" />
    </extension_attributes>
</config>

app/code/Vendor/Module/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 name="apellido2_layoutProcessors" type="Vendor\Module\Plugin\Checkout\LayoutProcessor" />
    </type>
</config>

app/code/Vendor/Plugin/Checkout/LayoutProcessor.php

<?php
namespace Vendor\Module\Plugin\Checkout;


class LayoutProcessor
{
    /**
     * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject
     * @param array $jsLayout
     * @return array
     */
    public function afterProcess(
        \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
        array  $jsLayout
    ) {
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $customerObj = $objectManager->create('Magento\Customer\Model\Session');
        $customerData = $customerObj->getCustomer();
        $customerApellido2 = (isset($customerData['apellido2']) && !empty($customerData['apellido2'])) ? $customerData['apellido2'] : '' ;

        // Apellido 2 visile in shipping address form
        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
        ['shippingAddress']['children']['shipping-address-fieldset']['children']['apellido2'] = [
            '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' => 'billingAddress.custom_attributes',
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/input',
                'id' => 'segundo',
            ],
            'dataScope' => 'shippingAddress.custom_attributes.apellido2',
            'label' => 'Segundo Apellido',
            'provider' => 'checkoutProvider',
            'visible' => true,
            'validation' => [
                'required-entry' => true,
            ],
            'sortOrder' => 41,
            'id' => 'segundo',
            'options' => [],
            'value' => $customerApellido2
        ];

        // Apellido 2 visile in billing-address-form: Loop all payment methods (because billing address is appended to the payments)
        $configuration = $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'];
        foreach ($configuration as $paymentGroup => $groupConfig) {
            if (isset($groupConfig['component']) and $groupConfig['component'] === 'Magento_Checkout/js/view/billing-address') {

                $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
                ['payment']['children']['payments-list']['children'][$paymentGroup]['children']['form-fields']
                ['children']['apellido2'] = [
                    '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' => 'billingAddress.custom_attributes',
                        'customEntry' => null,
                        'template' => 'ui/form/field',
                        'elementTmpl' => 'ui/form/element/input',
                        'id' => 'segundo',
                    ],
                    'dataScope' => $groupConfig['dataScopePrefix'] . 'custom_attributes' . '.apellido2',
                    'label' => __('Segundo Apellido'),
                    'provider' => 'checkoutProvider',
                    'visible' => true,
                    'validation' => [
                        'required-entry' => true,
                        'min_text_length' => 0,
                    ],
                    'sortOrder' => 41,
                    'id' => 'segundo',
                    'value' => $customerApellido2
                ];
            }
        }


        return $jsLayout;
    }
}

I also developed a JS mixin to poupulate customAtributes:

/app/code/Vendor/Module/view/frontend/requirejs-config.js

var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/action/set-billing-address': {
                'Vendor_Module/js/action/set-billing-address': true
            }
        }
    }
};

/app/code/Vendor/Module/view/frontend/web/js/action/set-billing-address.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'];
                            key = this.attribute_code;
                        }

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

            }

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

In My account, the customer can set their custom attribute without problems and it's stored and shown.

Then in checkout, custom attribute is not present. So i had to modify html templates to render it properly:

app/design/frontend/Vendor/Theme/Magento_Checkout/web/template/shipping-address/address-renderer/default.html

<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<div class="shipping-address-item 1v" css="'selected-item' : isSelected() , 'not-selected-item':!isSelected()">
    <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/>
    <text args="address().lastname"/>
    <if args="address().customAttributes['apellido2']">
        <text args="address().customAttributes['apellido2'].value"/>
    </if>
    <if args="address().customAttributes[0]">
        <!-- ko foreach: address().customAttributes -->
            <!-- ko if: attribute_code == "apellido2"-->
                <text args="value"/>
            <!-- /ko -->
        <!-- /ko -->
    </if>
    <text args="address().suffix"/><br/>
    <text args="_.values(address().street).join(', ')"/><br/>
    <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/>
    <text args="getCountryName(address().countryId)"/><br/>
    <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/>

    <button visible="address().isEditable()" type="button"
            class="action edit-address-link"
            click="editAddress">
        <span translate="'Edit'"></span>
    </button>
    <!-- ko if: (!isSelected()) -->
    <button type="button" click="selectAddress" class="action action-select-shipping-item">
        <span translate="'Ship Here'"></span>
    </button>
    <!-- /ko -->
</div>

app/design/frontend/Vendor/Theme/Magento_Checkout/web/template/billing-address/details.html

<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<div data-bind="text: ko.computed(function() { debugger; })"></div>
<div if="isAddressDetailsVisible() && currentBillingAddress()" class="billing-address-details">
    <text args="currentBillingAddress().prefix" /> <text args="currentBillingAddress().firstname" />
    <text args="currentBillingAddress().middlename" />
    <text args="currentBillingAddress().lastname" /> <text args="currentBillingAddress().suffix" />
    <text args="currentBillingAddress().customAttributes.apellido2.value" />

    <each args="data: currentBillingAddress().customAttributes, as: 'element'">
        <if args="typeof element === 'object'">
            <if args="element.label">
                <text args="element.label" />
            </if>
            <ifnot args="element.label">
                <if args="element.value">
                    <text args="element.value" />
                </if>
            </ifnot>
        </if>
        <if args="typeof element === 'string'">
            <text args="element" />
        </if><br />
    </each>
    <br />
    <text args="_.values(currentBillingAddress().street).join(', ')" /><br />
    <text args="currentBillingAddress().city " />, <span text="currentBillingAddress().region"></span> <text
        args="currentBillingAddress().postcode" /><br />
    <text args="getCountryName(currentBillingAddress().countryId)" /><br />
    <a if="currentBillingAddress().telephone" attr="'href': 'tel:' + currentBillingAddress().telephone"
        text="currentBillingAddress().telephone"></a><br />

    <button visible="!isAddressSameAsShipping()" type="button" class="action action-edit-address" click="editAddress">
        <span translate="'Edit'"></span>
    </button>
</div>

Overwriting this two templates make my attribute visible. In shipping address: enter image description here

In billing address: enter image description here

The issue arises trying to create a new address during the checkout process. My custom field is shown and validated in the form:

enter image description here

But, clicking into "Update Address" raises a js error:

Uncaught TypeError: Unable to process binding "text: function(){return currentBillingAddress().customAttributes.apellido2.value }"
Message: Cannot read property 'apellido2' of undefined
    at text (eval at createBindingsStringEvaluator (knockout.js:1), <anonymous>:3:104)
    at update (knockout.js:4659)
    at ko.dependentObservable.disposeWhenNodeIsRemoved (knockout.js:3373)
    at Function.evaluateImmediate_CallReadThenEndDependencyDetection (knockout.js:2173)
    at Function.evaluateImmediate_CallReadWithDependencyDetection (knockout.js:2140)
    at Function.evaluateImmediate (knockout.js:2101)
    at Object.ko.computed.ko.dependentObservable (knockout.js:1954)
    at knockout.js:3371
    at Object.arrayForEach (knockout.js:159)
    at applyBindingsToNodeInternal (knockout.js:3343)

enter image description here

My custom attribute is not available anymore. The knockout context shows that customAttributes are empty:

"currentBillingAddress":
canUseForBilling: ƒ ()
city: "New City"
company: ""
countryId: "ES"
customAttributes: undefined
customerId: undefined
email: undefined
fax: undefined
firstname: "Antonio"
getCacheKey: ƒ ()
getKey: ƒ ()
getType: ƒ ()
isDefaultBilling: ƒ ()
isDefaultShipping: ƒ ()
isEditable: ƒ ()
lastname: "Carrión"
middlename: undefined
postcode: "30001"
prefix: undefined
region: "A Coruña"
regionCode: "A Coruсa"
regionId: "391"
saveInAddressBook: 1
street: ["New address"]
suffix: undefined
telephone: "666666666"
vatId: ""

so my call to <text args="currentBillingAddress().customAttributes.apellido2.value" /> fails because of being undefined.

So my question: how can I make customAttributes available in checkout when I try to add a new address? I read several posts here and can't figure how to fix it. On the other hand, I can't understand how default Magento templates don't show customAttributes. What am I missing?

I'm using Magento 2.3.3

Any help will be appreciated. Thanks in advance

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.