سؤال

As i need custom fields for billing address under shipping address in shipping page and text entered should display as billing address all over website where billing address will display

enter image description here

هل كانت مفيدة؟

المحلول

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى magento.stackexchange
scroll top