I have created a custom custom_address attribute "a_mobile_phone". I can save the attribute in the admin and the customer address edit form.
I have added this attribute to the checkout address form with no issue but it is not saving in the database.
I have a plugin:
//Vendor/Module/etc/frontend/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\Block\Checkout\LayoutProcessor">
<plugin name="add-mobile-phone-field"
type="Vendor\Module\Plugin\LayoutProcessorPlugin" sortOrder="10"/>
</type>
</config>
My file:
//Vendor/Module/Plugin/LayoutProcessorPlugin.php
public function afterProcess(
\Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
array $jsLayout
) {
$customAttributeCode = 'a_mobile_phone';
$customField = [
'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' => 'Pour la livraison',
],
],
'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
'label' => 'Téléphone portable',
'provider' => 'checkoutProvider',
'sortOrder' => 200,
'validation' => [
'required-entry' => true
],
'options' => [],
'filterBy' => null,
'customEntry' => null,
'required' => true,
'visible' => true,
'value' => '' // value field is used to set a default value of the attribute
];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;
return $jsLayout;
}
My fieldset:
//Vendor/Module/etc/fieldset.xml
<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_address">
<field name="a_mobile_phone">
<aspect name="to_order_address" />
<aspect name="to_customer_address" />
</field>
</fieldset>
</scope>
</config>
The mixin:
//Vendor/Module/view/frontend/requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/action/set-shipping-information': {
'Vendor_Module/js/action/set-shipping-information-mixin': true
}
}
}
};
The mixin file:
//Vendor/Module/view/frontend/web/js/action/set-shipping-information-mixin.js
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']['a_mobile_phone'] = jQuery('[name="a_mobile_phone"]').val();
return originalAction();
});
};
});
The extension attribute :
//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\Sales\Api\Data\OrderAddressInterface">
<attribute code="a_mobile_phone" type="string" />
</extension_attributes>
<extension_attributes for="Magento\Customer\Api\Data\AddressInterfacee">
<attribute code="a_mobile_phone" type="string" />
</extension_attributes>
<extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
<attribute code="a_mobile_phone" type="string" />
</extension_attributes>
<extension_attributes for="Magento\Checkout\Api\Data\ShippingInformationInterface">
<attribute code="a_mobile_phone" type="string" />
</extension_attributes>
</config>
With all this my attribute is displayed in the checkout. When I set the value in a new address I have the code displayed by the knockoutJs.
But my biggest issue is that the attribute value is not save in the DB when I pass the order.
What am I missing to save the attribute value ? I followed the magento doc for this : https://devdocs.magento.com/guides/v2.3/howdoi/checkout/checkout_new_field.html
EDIT 1 :
So I managed to save the data to the address with :
Adding the field to 2 table in the DB :
$installer->getConnection()
->addColumn(
$setup->getTable('quote_address'),
"a_mobile_phone",
['type' => Table::TYPE_TEXT, 'nullable' => true, 'comment' => 'Phone for delivery']
);
$installer->getConnection()
->addColumn(
$setup->getTable('sales_order_address'),
"a_mobile_phone",
['type' => Table::TYPE_TEXT, 'nullable' => true, 'comment' => 'Phone for delivery']
);
Adding a new mixin :
'Magento_Checkout/js/model/shipping-save-processor/payload-extender': {
'Vendor_Module/js/model/shipping-save-processor/payload-extender': true
}
The mixin : Vendor/Module/view/frontnend/web/js/model/shipping-save-processor/payload-extender.js
define([
'jquery',
'mage/utils/wrapper'
], function (
jQuery,
wrapper
) {
'use strict';
return function (processor) {
return wrapper.wrap(processor, function (proceed, payload) {
payload = proceed(payload);
var shippingAddress = payload.addressInformation.shipping_address;
var aMobilePhone = jQuery('[name="custom_attributes[a_mobile_phone]"]').val();
if(aMobilePhone == "" || aMobilePhone == null){
if(shippingAddress.customAttributes == "undefined" || shippingAddress.customAttributes == null){
aMobilePhone = null;
} else {
if(shippingAddress.customAttributes.a_mobile_phone == "undefined" || shippingAddress.customAttributes.a_mobile_phone == null) {
aMobilePhone = null;
} else {
aMobilePhone = shippingAddress.customAttributes.a_mobile_phone.value;
}
}
}
var goneExtentionAttributes = {
'aMobilePhone': aMobilePhone
};
payload.addressInformation.extension_attributes = _.extend(
payload.addressInformation.extension_attributes,
goneExtentionAttributes
);
return payload;
});
};
});
Modify set-shipping-information-mixin.js
as suggested by Amit Bera :
$.each(shippingAddress.customAttributes, function(index, eachCustomAttribute){
console.log(eachCustomAttribute);
if(eachCustomAttribute.attribute_code == 'a_mobile_phone')
{
shippingAddress['extension_attributes']['a_mobile_phone'] = eachCustomAttribute.value ;
}
});
A new plugin on : Magento\Checkout\Model\ShippingInformationManagement
class ShippingAddressManagementPlugin
{
/**
* @var LoggerInterface $logger
*/
protected $logger;
/**
* ShippingAddressManagementPlugin constructor.
* @param LoggerInterface $logger
*/
public function __construct(
LoggerInterface $logger
) {
$this->logger = $logger;
}
/**
* @param ShippingAddressManagement $subject
* @param $cartId
* @param AddressInterface $address
*/
public function beforeAssign(
ShippingAddressManagement $subject,
$cartId,
AddressInterface $address
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setAMobilePhone($extAttributes->getAMobilePhone());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
I have just one issue left, the attribute code "a_mobile_phone" displayed inside the address summary (see under)
EDIT 2
Thanks to @coderGeek i have fixed th last issue :
I have modified the requirejs-config.js file and added the default.html one
//file : Vendor/Module/view/web/template/shipping-address/address-renderer/default.html
<div class="shipping-address-item" css="'selected-item' : isSelected() , 'not-selected-item':!isSelected()">
<text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/>
<text args="address().lastname"/> <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/>
<each args="data: address().customAttributes, as: 'element'">
<each args="data: Object.keys(element), as: 'attribute'">
<if args="typeof element[attribute] === 'object'">
<if args="element[attribute].label">
<text args="element[attribute].label"/>
</if>
<ifnot args="element[attribute].label">
<if args="element[attribute].value">
<text args="element[attribute].value"/>
</if>
</ifnot>
<br/>
</if>
<if args="typeof element[attribute] === 'string'">
<if args="attribute == 'value'">
<text args="element[attribute]"/>
<br/>
</if>
</if>
</each>
</each>
<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>