4

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;
    }
}
10
  • I'm asking this as I've seen a lot of posts where people write layout changes in PHP like this (the core does it sometimes to), it's exactly what layout XML is for so what is the advantage of writing it in PHP?
    – Ben Crook
    Feb 13, 2020 at 9:49
  • @BenCrook It's not possible to change this using the layout XML file, because the billing address can not be moved by changing the layout XML. That's why everybody is using PHP changes for this.
    – JGeer
    Feb 13, 2020 at 13:04
  • 1
    @BenCrook, Would it hurt you to post answer using XML !! I would love to follow standards :)
    – anonymous
    Feb 20, 2020 at 11:16
  • 1
    so you want to move billing address from payment to below shipping address in shipping step. right? Feb 21, 2020 at 8:46
  • 1
    @anonymous - It would definitely hurt me mentally, I try to avoid touching Magento 2 unless I have to.
    – Ben Crook
    Feb 21, 2020 at 9:11

5 Answers 5

5
+200

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;
}
10
  • Seems to work almost perfect, but in fact we want to change the select field into a radio button. This is quite easy by changing the code into $elements[$code]['dataType'] = 'checkbox-set'; $elements[$code]['formElement'] = 'checkbox-set'; The only problem we have is that we want to check the first value by default. Any idea how to solve this?
    – JGeer
    Mar 5, 2020 at 20:43
  • And it also seems that the SR module does not remove the Billing Address checkbox on the payment step. So now there are two checkboxes in the checkout for the billing address.
    – JGeer
    Mar 5, 2020 at 21:06
  • Sure, @JGeer, I will check with above details. Would you please let me know if you are using default Magento checkout or any third party plugin for checkout page? Mar 6, 2020 at 4:29
  • @JGeer, Please check updated aroundProcess method, I have added code to remove billing addresses from payment section. I will check if there is any way to select radio button by default. Mar 6, 2020 at 5:04
  • Thanks again! Seems that we also needed to change the default M2 setting "Display Billing Address On" in the backend to "Payment Method" instead of below. Otherwise it still shows the billing address. Also any idea how to translate the Address: Line 1 phrase?
    – JGeer
    Mar 6, 2020 at 7:52
1

You can try this below extension given here in my blog:

https://marketplace.magento.com/ananta-module-improvedcheckout.html

Tested and worked with 2.4.5

enter image description here enter image description here

0

Please follow the below URL. hope it useful for you.

https://www.tigren.com/billing-under-shipping-address-m2/

if you still need to change just let me know.

3
  • Thanks for the reply. I already tried this, but that creates a entire new module and form. I only want to move the default form, instead of creating a new module and form.
    – JGeer
    Feb 21, 2020 at 11:33
  • Okay, I can Understand thanks Feb 21, 2020 at 12:03
  • Thanks again! The only problem we have with this that the prefix does not work, it is displayed as an input field instead of a select box. Any idea how to solve this?
    – JGeer
    Feb 25, 2020 at 8:06
0

Hi many developers want to perform this : , You can take a help from this open source code available over github : https://github.com/sohelrana09/modified-checkout

I hope it helps..

1
  • Thanks! The only problem we have with this that the prefix does not work, it is displayed as an input field instead of a select box. Any idea how to solve this?
    – JGeer
    Feb 25, 2020 at 8:06
0

You can use the Js for that!

map: {
        '*': {
            "Magento_Checkout/js/model/shipping-save-processor/default":"Magento_ShippingChange/js/shipping-save-processor"
        }
    }

Then overwrite the address like this

define([
    'ko',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/resource-url-manager',
    'mage/storage',
    'Magento_Checkout/js/model/payment-service',
    'Magento_Checkout/js/model/payment/method-converter',
    'Magento_Checkout/js/model/error-processor',
    'Magento_Checkout/js/model/full-screen-loader',
    'Magento_Checkout/js/action/select-billing-address',
    'Magento_Checkout/js/action/create-shipping-address',
    'Magento_Checkout/js/action/select-shipping-address',
    'Magento_Checkout/js/checkout-data',
], function (
    ko,
    quote,
    resourceUrlManager,
    storage,
    paymentService,
    methodConverter,
    errorProcessor,
    fullScreenLoader,
    selectBillingAddressAction,
    createShippingAddress,
    selectShippingAddress,
    checkoutData,
) {
    'use strict';

    return {
        saveShippingInformation: function () {
            var payload,
                addressData,
                newShippingAddress;
            //Use this step if you want to change only field
            quote.billingAddress().firstname = quote.shippingAddress().firstname;
            //Use  this step if you want to replace whole address.
            quote.billingAddress()= quote.shippingAddress();

                payload = {
                    addressInformation: {
                        'shipping_address': quote.shippingAddress(),
                        'billing_address': quote.billingAddress(),
                        'shipping_method_code': quote.shippingMethod().method_code,
                        'shipping_carrier_code': quote.shippingMethod().carrier_code,
                        extension_attributes: {

                        }
                    }
                };
            }else{
                if (!quote.billingAddress()) {
                    selectBillingAddressAction(quote.shippingAddress());
                }

                payload = {
                    addressInformation: {
                        'shipping_address': quote.shippingAddress(),
                        'billing_address': quote.billingAddress(),
                        'shipping_method_code': quote.shippingMethod()['method_code'],
                        'shipping_carrier_code': quote.shippingMethod()['carrier_code'],
                        extension_attributes: {

                        }
                    }
                };
            }

            fullScreenLoader.startLoader();

            return storage.post(
                resourceUrlManager.getUrlForSetShippingInformation(quote),
                JSON.stringify(payload)
            ).done(
                function (response) {
                    quote.setTotals(response.totals);
                    paymentService.setPaymentMethods(methodConverter(response.payment_methods));
                    fullScreenLoader.stopLoader();
                }
            ).fail(
                function (response) {
                    errorProcessor.process(response);
                    fullScreenLoader.stopLoader();
                }
            );
        }
    };
}

);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.