Try to follow all step:
Step 1: SR/ModifiedCheckout/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="SR_ModifiedCheckout" setup_version="2.0.0">
<sequence>
<module name="Magento_Checkout"/>
</sequence>
</module>
</config>
Step 2: SR/ModifiedCheckout/registration.php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'SR_ModifiedCheckout',
__DIR__
);
Step 3: SR/ModifiedCheckout/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="sr_modified_checkout_layout_processor" type="SR\ModifiedCheckout\Plugin\Block\LayoutProcessor" sortOrder="1"/>
</type>
</config>
Step 4: SR/ModifiedCheckout/Plugin/Block/LayoutProcessor.php
namespace SR\ModifiedCheckout\Plugin\Block;
use Magento\Customer\Model\AttributeMetadataDataProvider;
use Magento\Ui\Component\Form\AttributeMapper;
use Magento\Checkout\Block\Checkout\AttributeMerger;
use Magento\Checkout\Model\Session as CheckoutSession;
class LayoutProcessor
{
/**
* @var AttributeMetadataDataProvider
*/
public $attributeMetadataDataProvider;
/**
* @var AttributeMapper
*/
public $attributeMapper;
/**
* @var AttributeMerger
*/
public $merger;
/**
* @var CheckoutSession
*/
public $checkoutSession;
/**
* @var null
*/
public $quote = null;
/**
* LayoutProcessor constructor.
*
* @param AttributeMetadataDataProvider $attributeMetadataDataProvider
* @param AttributeMapper $attributeMapper
* @param AttributeMerger $merger
* @param CheckoutSession $checkoutSession
*/
public function __construct(
AttributeMetadataDataProvider $attributeMetadataDataProvider,
AttributeMapper $attributeMapper,
AttributeMerger $merger,
CheckoutSession $checkoutSession
) {
$this->attributeMetadataDataProvider = $attributeMetadataDataProvider;
$this->attributeMapper = $attributeMapper;
$this->merger = $merger;
$this->checkoutSession = $checkoutSession;
}
/**
* Get Quote
*
* @return \Magento\Quote\Model\Quote|null
*/
public function getQuote()
{
if (null === $this->quote) {
$this->quote = $this->checkoutSession->getQuote();
}
return $this->quote;
}
/**
* @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject
* @param array $jsLayout
* @return array
*/
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.'),
],
],
],
]
),
],
],
];
}
}
Step 5: SR/ModifiedCheckout/view/frontend/layout/checkout_index_index.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shipping-step" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shippingAddress" xsi:type="array">
<item name="config" xsi:type="array">
<item name="template" xsi:type="string">SR_ModifiedCheckout/address</item>
</item>
</item>
<item name="overwriteShippingComponent" xsi:type="array">
<item name="component" xsi:type="string">SR_ModifiedCheckout/js/view/shipping</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
Step 6:SR/ModifiedCheckout/view/frontend/web/js/view/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';
}
});
}
);
Step 7:SR/ModifiedCheckout/view/frontend/web/js/view/shipping.js
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/view/shipping',
'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,
shipping,
createBillingAddress,
selectBillingAddress,
setShippingInformationAction,
stepNavigator,
checkoutData
) {
'use strict';
_.extend(shipping.prototype, {
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;
}
});
return Component.extend({});
}
);
Download full module from here
Best Answer
You could do the following:
step 1. Overwritting a default magento checkout-data-resolver.js
1.1 Create your module. YourCompany\Checkout
1.2 Add file \app\code\YourCompany\Checkout\view\frontend\requirejs-config.js
with following content
step 2. Modify applyBillingAddress function
2.1 in new checkout-data-resolver.js file find applyBillingAddress. Insert following cod after
var shippingAddress;
and beforeif (quote.billingAddress())
This code actually goes through customer addresses and checks if any of them has defaultBilling set to true. If such an address exists it will be used as the billing address. If not the script will fall back to the default magento behavior