Magento – Magento 2.3 – Move billing address below shipping address in checkout

billing-addresscheckoutmagento2magento2.3onepage-checkout

I want to move the billing address form below the shipping address in the checkout, on the first page/step.

Therefore I created a custom module with the following LayoutProcessorPlugin.php.

But now the billing address field is displayed above the shipping address instead of below. And the default checkbox 'My billing and shipping address are the same' is now unchecked by default.

Also the line to remove the billing address on the billing step is not working. So now this is displayed on both places.

EDIT:

We also tried to use the Tigren method (https://www.tigren.com/billing-under-shipping-address-m2/) or the SR module (https://github.com/sohelrana09/modified-checkout). But these both have the same problem, the prefix is changed into an input field, instead of a select field. So it does not match the options from the backend anymore.

Image of the prefix input field (should be a select with values from backend):

enter image description here

Any idea how to solve this issue?

My LayoutProcessorPlugin.php code;

<?php

namespace Redable\ReorderBillingForm\Model\Checkout;

class LayoutProcessorPlugin
{

    /**
     * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject
     * @param array $jsLayout
     * @return array
     */

    public function afterProcess(
        \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
        array $jsLayout
    )
    {
        // get billing address form at billing step
        $billingAddressForm = $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['afterMethods']['children']['billing-address-form'];

        // move address form to shipping step
        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['billing-address-form'] = $billingAddressForm;

        // remove form from billing step
        unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['afterMethods']['children']['billing-address-form']);

        return $jsLayout;
    }
}

Best Answer

I was able to resolve the issue for Sohel's module with below changes

Go to file app/code/SR/ModifiedCheckout/Plugin/Block/LayoutProcessor.php and update as per below.

Update your constructor as below.

public function __construct(
    AttributeMetadataDataProvider $attributeMetadataDataProvider,
    AttributeMapper $attributeMapper,
    AttributeMerger $merger,
    CheckoutSession $checkoutSession,
    \Magento\Customer\Model\Options $options = null
) {
    $this->attributeMetadataDataProvider = $attributeMetadataDataProvider;
    $this->attributeMapper = $attributeMapper;
    $this->merger = $merger;
    $this->checkoutSession = $checkoutSession;
    $this->options = $options ?: \Magento\Framework\App\ObjectManager::getInstance()
        ->get(\Magento\Customer\Model\Options::class);
}

Now update your aroundProcess function with below code.

public function aroundProcess(
    \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
    \Closure $proceed,
    array $jsLayout
) {

    $jsLayoutResult = $proceed($jsLayout);

    if($this->getQuote()->isVirtual()) {
        return $jsLayoutResult;
    }

    $attributesToConvert = [
        'prefix' => [$this->options, 'getNamePrefixOptions'],
        'suffix' => [$this->options, 'getNameSuffixOptions'],
    ];

    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();
        $elements = $this->convertElementsToSelect($elements, $attributesToConvert);
        $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');
    }

    if (isset($jsLayoutResult['components']['checkout']['children']['steps']['children']['billing-step']['children']
            ['payment']['children']['payments-list']['children']
        )) {
            foreach ($jsLayoutResult['components']['checkout']['children']['steps']['children']['billing-step']['children']
                     ['payment']['children']['payments-list']['children'] as $key => $payment) {

                unset($jsLayoutResult['components']['checkout']['children']['steps']['children']['billing-step']['children']
                    ['payment']['children']['payments-list']['children'][$key]);

            }
        }

    return $jsLayoutResult;
}

Once you are done with above changes, add a new function convertElementsToSelect to convert prefix and suffix into a select dropdown.

private function convertElementsToSelect($elements, $attributesToConvert)
{
    $codes = array_keys($attributesToConvert);
    foreach (array_keys($elements) as $code) {
        if (!in_array($code, $codes)) {
            continue;
        }
        // phpcs:ignore Magento2.Functions.DiscouragedFunction
        $options = call_user_func($attributesToConvert[$code]);
        if (!is_array($options)) {
            continue;
        }
        $elements[$code]['dataType'] = 'select';
        $elements[$code]['formElement'] = 'select';

        foreach ($options as $key => $value) {
            $elements[$code]['options'][] = [
                'value' => $key,
                'label' => $value,
            ];
        }
    }

    return $elements;
}
Related Topic