Magento 2 how to billing address fields under shipping address in shipping page
-
05-02-2021 - |
Solución
Create a plugin that will override the Checkout Layoutpreprocessor -
Magento\Checkout\Block\Checkout\LayoutProcessor
public function aroundProcess(
\Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
\Closure $proceed,
array $jsLayout
) {
$jsLayoutResult = $proceed($jsLayout);
if($this->getQuote()->isVirtual()) {
return $jsLayoutResult;
}
if(isset($jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset'])) {
$jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['street']['children'][0]['placeholder'] = __('Street Address');
$jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['street']['children'][1]['placeholder'] = __('Street line 2');
$elements = $this->getAddressAttributes();
$jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['billing-address'] = $this->getCustomBillingAddressComponent($elements);
$jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['billing-address']['children']['form-fields']['children']['street']['children'][0]['placeholder'] = __('Street Address');
$jsLayoutResult['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['billing-address']['children']['form-fields']['children']['street']['children'][1]['placeholder'] = __('Street line 2');
}
return $jsLayoutResult;
}
/**
* Get all visible address attribute
*
* @return array
*/
private function getAddressAttributes()
{
/** @var \Magento\Eav\Api\Data\AttributeInterface[] $attributes */
$attributes = $this->attributeMetadataDataProvider->loadAttributesCollection(
'customer_address',
'customer_register_address'
);
$elements = [];
foreach ($attributes as $attribute) {
$code = $attribute->getAttributeCode();
if ($attribute->getIsUserDefined()) {
continue;
}
$elements[$code] = $this->attributeMapper->map($attribute);
if (isset($elements[$code]['label'])) {
$label = $elements[$code]['label'];
$elements[$code]['label'] = __($label);
}
}
return $elements;
}
/**
* Prepare billing address field for shipping step for physical product
*
* @param $elements
* @return array
*/
public function getCustomBillingAddressComponent($elements)
{
return [
'component' => 'SR_ModifiedCheckout/js/view/billing-address',
'displayArea' => 'billing-address',
'provider' => 'checkoutProvider',
'deps' => ['checkoutProvider'],
'dataScopePrefix' => 'billingAddress',
'children' => [
'form-fields' => [
'component' => 'uiComponent',
'displayArea' => 'additional-fieldsets',
'children' => $this->merger->merge(
$elements,
'checkoutProvider',
'billingAddress',
[
'country_id' => [
'sortOrder' => 115,
],
'region' => [
'visible' => false,
],
'region_id' => [
'component' => 'Magento_Ui/js/form/element/region',
'config' => [
'template' => 'ui/form/field',
'elementTmpl' => 'ui/form/element/select',
'customEntry' => 'billingAddress.region',
],
'validation' => [
'required-entry' => true,
],
'filterBy' => [
'target' => '${ $.provider }:${ $.parentScope }.country_id',
'field' => 'country_id',
],
],
'postcode' => [
'component' => 'Magento_Ui/js/form/element/post-code',
'validation' => [
'required-entry' => true,
],
],
'company' => [
'validation' => [
'min_text_length' => 0,
],
],
'fax' => [
'validation' => [
'min_text_length' => 0,
],
],
'telephone' => [
'config' => [
'tooltip' => [
'description' => __('For delivery questions.'),
],
],
],
]
),
],
],
];
}
If you see the file has components used for JS - So in order to do that you will have to create JS files for billing-address.js -
/*jshint browser:true*/
/*global define*/
define(
[
'ko',
'underscore',
'jquery',
'Magento_Ui/js/form/form',
'Magento_Customer/js/model/customer',
'Magento_Customer/js/model/address-list',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/action/create-billing-address',
'Magento_Checkout/js/action/select-billing-address',
'Magento_Checkout/js/checkout-data',
'Magento_Checkout/js/model/checkout-data-resolver',
'Magento_Customer/js/customer-data',
'Magento_Checkout/js/action/set-billing-address',
'Magento_Ui/js/model/messageList',
'mage/translate'
],
function (
ko,
_,
$,
Component,
customer,
addressList,
quote,
createBillingAddress,
selectBillingAddress,
checkoutData,
checkoutDataResolver,
customerData,
setBillingAddressAction,
globalMessageList,
$t
) {
'use strict';
var newAddressOption = {
/**
* Get new address label
* @returns {String}
*/
getAddressInline: function () {
return $t('New Address');
},
customerAddressId: null
},
countryData = customerData.get('directory-data'),
addressOptions = addressList().filter(function (address) {
return address.getType() == 'customer-address';
});
addressOptions.push(newAddressOption);
return Component.extend({
defaults: {
template: 'SR_ModifiedCheckout/billing-address'
},
currentBillingAddress: quote.billingAddress,
addressOptions: addressOptions,
customerHasAddresses: addressOptions.length > 1,
/**
* Init component
*/
initialize: function () {
this._super();
},
/**
* @return {exports.initObservable}
*/
initObservable: function () {
this._super()
.observe({
selectedAddress: null,
isAddressFormVisible: false,
isAddressSameAsShipping: true,
saveInAddressBook: 1,
isAddressFormListVisible:false
});
return this;
},
canUseShippingAddress: ko.computed(function () {
return !quote.isVirtual() && quote.shippingAddress() && quote.shippingAddress().canUseForBilling();
}),
/**
* @param {Object} address
* @return {*}
*/
addressOptionsText: function (address) {
return address.getAddressInline();
},
/**
* @return {Boolean}
*/
useShippingAddress: function () {
if (this.isAddressSameAsShipping()) {
this.isAddressFormVisible(false);
this.isAddressFormListVisible(false);
} else {
if(addressOptions.length == 1) {
this.isAddressFormVisible(true);
} else {
this.isAddressFormListVisible(true);
}
}
return true;
},
/**
* @param {Object} address
*/
onAddressChange: function (address) {
if(address) {
this.isAddressFormVisible(false);
} else {
this.isAddressFormVisible(true);
}
},
/**
* @param {int} countryId
* @return {*}
*/
getCountryName: function (countryId) {
return countryData()[countryId] != undefined ? countryData()[countryId].name : '';
},
/**
* Get code
* @param {Object} parent
* @returns {String}
*/
getCode: function (parent) {
return _.isFunction(parent.getCode) ? parent.getCode() : 'shared';
}
});
}
);
Similarly shipping-mixin.js within mixin folder -
define([
'jquery',
'underscore',
'Magento_Ui/js/form/form',
'ko',
'Magento_Customer/js/model/customer',
'Magento_Customer/js/model/address-list',
'Magento_Checkout/js/model/address-converter',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/action/create-shipping-address',
'Magento_Checkout/js/action/select-shipping-address',
'Magento_Checkout/js/action/create-billing-address',
'Magento_Checkout/js/action/select-billing-address',
'Magento_Checkout/js/action/set-shipping-information',
'Magento_Checkout/js/model/step-navigator',
'Magento_Checkout/js/checkout-data'
], function(
$,
_,
Component,
ko,
customer,
addressList,
addressConverter,
quote,
createShippingAddress,
selectShippingAddress,
createBillingAddress,
selectBillingAddress,
setShippingInformationAction,
stepNavigator,
checkoutData
) {
'use strict';
return function (target) {
return target.extend({
defaults: {
template: 'SR_ModifiedCheckout/address'
},
setShippingInformation: function () {
if (this.validateShippingInformation() && this.validateBillingInformation()) {
setShippingInformationAction().done(
function () {
stepNavigator.next();
}
);
}
},
validateBillingInformation: function() {
if($('[name="billing-address-same-as-shipping"]').is(":checked")) {
if (this.isFormInline) {
var shippingAddress = quote.shippingAddress();
var addressData = addressConverter.formAddressDataToQuoteAddress(
this.source.get('shippingAddress')
);
//Copy form data to quote shipping address object
for (var field in addressData) {
if (addressData.hasOwnProperty(field) &&
shippingAddress.hasOwnProperty(field) &&
typeof addressData[field] != 'function' &&
_.isEqual(shippingAddress[field], addressData[field])
) {
shippingAddress[field] = addressData[field];
} else if (typeof addressData[field] != 'function' &&
!_.isEqual(shippingAddress[field], addressData[field])) {
shippingAddress = addressData;
break;
}
}
if (customer.isLoggedIn()) {
shippingAddress.save_in_address_book = 1;
}
var newBillingAddress = createBillingAddress(shippingAddress);
selectBillingAddress(newBillingAddress);
} else {
selectBillingAddress(quote.shippingAddress());
}
return true;
}
var selectedAddress = $('[name="billing_address_id"]').val();
if(selectedAddress) {
var res = addressList.some(function (addressFromList) {
if (selectedAddress == addressFromList.customerAddressId) {
selectBillingAddress(addressFromList);
return true;
}
return false;
});
return res;
}
this.source.set('params.invalid', false);
this.source.trigger('billingAddress.data.validate');
if (this.source.get('params.invalid')) {
return false;
}
var addressData = this.source.get('billingAddress'),
newBillingAddress;
if ($('#billing-save-in-address-book').is(":checked")) {
addressData.save_in_address_book = 1;
}
newBillingAddress = createBillingAddress(addressData);
selectBillingAddress(newBillingAddress);
return true;
}
});
}
});
You will be required to add the requirejs-config.js for mapping -
var config = {
config: {
mixins: {
'Magento_Checkout/js/view/shipping': {
'SR_ModifiedCheckout/js/mixin/shipping-mixin': true
}
}
}
};
The within web/template/billing-address.html
<div class="checkout-billing-address">
<div class="billing-address-same-as-shipping-block field choice" data-bind="visible: canUseShippingAddress()">
<input type="checkbox" name="billing-address-same-as-shipping"
data-bind="checked: isAddressSameAsShipping, click: useShippingAddress, attr: {id: 'billing-address-same-as-shipping'}"/>
<label data-bind="attr: {for: 'billing-address-same-as-shipping'}">
<span data-bind="i18n: 'This address is also my billing address'"></span>
</label>
</div>
<fieldset class="fieldset">
<!-- ko template: 'SR_ModifiedCheckout/billing-address/list' --><!-- /ko -->
<!-- ko template: 'SR_ModifiedCheckout/billing-address/form' --><!-- /ko -->
</fieldset>
</div>
Similarly - web/template/address.html
<li id="shipping" class="checkout-shipping-address" data-bind="fadeVisible: visible()">
<div class="step-title" data-bind="i18n: 'Shipping Address'" data-role="title"></div>
<div id="checkout-step-shipping"
class="step-content"
data-role="content">
<!-- ko if: (!quoteIsVirtual) -->
<!-- ko foreach: getRegion('customer-email') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<!--/ko-->
<!-- ko foreach: getRegion('address-list') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<!-- ko foreach: getRegion('address-list-additional-addresses') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<!-- Address form pop up -->
<!-- ko if: (!isFormInline) -->
<button type="button"
data-bind="click: showFormPopUp, visible: !isNewAddressAdded()"
class="action action-show-popup">
<span data-bind="i18n: 'New Address'"></span></button>
<div id="opc-new-shipping-address" data-bind="visible: isFormPopUpVisible()">
<!-- ko template: 'Magento_Checkout/shipping-address/form' --><!-- /ko -->
</div>
<!-- /ko -->
<!-- ko foreach: getRegion('before-form') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<!-- Inline address form -->
<!-- ko if: (isFormInline) -->
<!-- ko template: 'Magento_Checkout/shipping-address/form' --><!-- /ko -->
<!-- /ko -->
<!-- ko if: (isFormInline) -->
<span class="mandatory" data-bind="i18n: 'Mandatory fields'"></span>
<!-- /ko -->
<!-- Inline Billing address form -->
<!-- ko foreach: getRegion('billing-address') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- /ko -->
</div>
</li>
<!--Shipping method template-->
<li id="opc-shipping_method"
class="checkout-shipping-method"
data-bind="fadeVisible: visible(), blockLoader: isLoading"
role="presentation">
<div class="checkout-shipping-method">
<div class="step-title" data-bind="i18n: 'Shipping Methods'" data-role="title"></div>
<!-- ko foreach: getRegion('before-shipping-method-form') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- /ko -->
<div id="checkout-step-shipping_method"
class="step-content"
data-role="content"
role="tabpanel"
aria-hidden="false">
<!-- ko if: rates().length -->
<form class="form methods-shipping" id="co-shipping-method-form" data-bind="submit: setShippingInformation" novalidate="novalidate">
<div id="checkout-shipping-method-load">
<table class="table-checkout-shipping-method">
<thead>
<tr class="row">
<th class="col col-method" data-bind="i18n: 'Select Method'"></th>
<th class="col col-price" data-bind="i18n: 'Price'"></th>
<th class="col col-method" data-bind="i18n: 'Method Title'"></th>
<th class="col col-carrier" data-bind="i18n: 'Carrier Title'"></th>
</tr>
</thead>
<tbody>
<!--ko foreach: { data: rates(), as: 'method'}-->
<tr class="row" data-bind="click: $parent.selectShippingMethod">
<td class="col col-method">
<!-- ko ifnot: method.error_message -->
<!-- ko if: $parent.rates().length == 1 -->
<input class="radio"
type="radio"
data-bind="attr: {
checked: $parent.rates().length == 1,
'value' : method.carrier_code + '_' + method.method_code,
'id': 's_method_' + method.method_code,
'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code
}" />
<!-- /ko -->
<!--ko ifnot: ($parent.rates().length == 1)-->
<input type="radio"
data-bind="
value: method.carrier_code + '_' + method.method_code,
checked: $parent.isSelected,
attr: {
'id': 's_method_' + method.carrier_code + '_' + method.method_code,
'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code
},
click: $parent.selectShippingMethod"
class="radio"/>
<!--/ko-->
<!-- /ko -->
</td>
<td class="col col-price">
<!-- ko foreach: $parent.getRegion('price') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- /ko -->
</td>
<td class="col col-method"
data-bind="text: method.method_title, attr: {'id': 'label_method_' + method.method_code + '_' + method.carrier_code}"></td>
<td class="col col-carrier"
data-bind="text: method.carrier_title, attr: {'id': 'label_carrier_' + method.method_code + '_' + method.carrier_code}"></td>
</tr>
<!-- ko if: method.error_message -->
<tr class="row row-error">
<td class="col col-error" colspan="4">
<div class="message error">
<div data-bind="text: method.error_message"></div>
</div>
<span class="no-display">
<input type="radio" data-bind="attr: {'value' : method.method_code, 'id': 's_method_' + method.method_code}"/>
</span>
</td>
</tr>
<!-- /ko -->
<!-- /ko -->
</tbody>
</table>
</div>
<div id="onepage-checkout-shipping-method-additional-load">
<!-- ko foreach: getRegion('shippingAdditional') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!-- /ko -->
</div>
<!-- ko if: errorValidationMessage().length > 0 -->
<div class="message notice">
<span><!-- ko text: errorValidationMessage()--><!-- /ko --></span>
</div>
<!-- /ko -->
<div class="actions-toolbar" id="shipping-method-buttons-container">
<div class="primary">
<button data-role="opc-continue" type="submit" class="button action continue primary">
<span><!-- ko i18n: 'Next'--><!-- /ko --></span>
</button>
</div>
</div>
</form>
<!-- /ko -->
<!-- ko ifnot: rates().length > 0 --><div class="no-quotes-block"><!-- ko i18n: 'Sorry, no quotes are available for this order at this time'--><!-- /ko --></div><!-- /ko -->
</div>
</div>
</li>
For adding the billing address form add - web/template/billing-address/form.html
<div class="billing-address-form" data-bind="fadeVisible: isAddressFormVisible">
<div class="step-title" data-bind="i18n: 'Billing Address'" data-role="title"></div>
<!-- ko foreach: getRegion('before-fields') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<fieldset id="billing-new-address-form" class="fieldset address">
<!-- ko foreach: getRegion('additional-fieldsets') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
<!-- ko if: (isCustomerLoggedIn) -->
<div class="field save-address">
<input type="checkbox" class="checkbox" id="billing-save-in-address-book" data-bind="checked: saveInAddressBook" />
<label class="label" for="billing-save-in-address-book">
<span data-bind="i18n: 'Save in address book'"></span>
</label>
</div>
<!-- /ko -->
<span class="mandatory" data-bind="i18n: 'Mandatory fields'"></span>
</fieldset>
</div>
And for billing addresses list - web/template/billing-address/list.html
<!-- ko if: (customerHasAddresses && isAddressFormListVisible)-->
<div class="field field-select-billing">
<div class="control" data-bind="if: (addressOptions.length > 1)">
<select class="select" id="billing_address_id" name="billing_address_id" data-bind="
options: addressOptions,
optionsText: addressOptionsText,
optionsValue: 'customerAddressId',
value: selectedAddress,
event: {change: onAddressChange(selectedAddress())};
"></select>
</div>
</div>
<!-- /ko -->
The above code has been written by Sohel Rana, and the link to the module is this You can also directly install the plugin
Licenciado bajo: CC-BY-SA con atribución
No afiliado a magento.stackexchange