3

Background

We have a custom shipping method "Click & Collect", with this custom shipping method is a custom calculator on the checkout_cart_index page which will return all the nearest stockists to the given postcode.

The Problem

Most of it works fine, but the shipping carrier method and address are not being set and when the user proceeds to checkout they are required to fill this information in again.

The Code

define(
    [
        'ko',
        'jquery',
        'Magento_Ui/js/form/form',
        'uiRegistry',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/resource-url-manager',
        'mage/storage',
        'Magento_Checkout/js/action/create-shipping-address',
        'Magento_Checkout/js/model/error-processor',
        'Magento_Checkout/js/model/shipping-service',
        'Magento_Checkout/js/model/cart/totals-processor/default',
        'Magento_Checkout/js/model/shipping-rate-registry'
    ],
    function (
        ko,
        $,
        Component,
        uiRegistry,
        quote,
        resourceUrlManager,
        storage,
        createShippingAddress,
        errorProcessor,
        shippingService,
        totalsDefaultProvider,
        rateRegistry
    ) {
        'use strict';

        var ClickAndCollectConfig = window.checkout.clickandcollect;

        return Component.extend({
            defaults: {
                template: 'Convert_ClickAndCollect/cart/click-collect-stores'
            },
            formId: "#selectWarehouseForm",
            parent: {},
            postCode: ko.observable(''),
            wareHouses: ko.observableArray(),
            address: {},
            isWarehouseSelected: ko.observable(""),

            /**
             * @override
             */
            initialize: function () {
                this._super();
                $(document).ready(() => this.ready());
                return this;
            },

            ready: function () {
                this.parent = uiRegistry.get(this.parentName);
            },

            selectWarehouse: function(warehouseId) {
                if ($('#s_method_freeshipping').length) {
                    return false;
                }
                if (!this.validateForm(this.formId)) {
                    return false;
                }
                if (warehouseId != null) {
                    let body = $('body').loader();
                    let uri = this.getFormAction();
                    let payload = JSON.stringify({
                        addressInformation: {
                            address: {
                                countryId: 'AU',
                                postcode: this.postCode(),
                                regionId: 0
                            },
                            shipping_carrier_code: ClickAndCollectConfig.shipping.carrier_code,
                            shipping_method_code: warehouseId
                        }
                    });
                    this.address = createShippingAddress(payload);
                    shippingService.isLoading(true);
                    storage.post(
                        uri, payload, true
                    ).done(
                        (resp) => this.selectWarehouseSuccess(resp, this.getInputValue(warehouseId))
                    ).fail(
                        (resp) => this.selectWarehouseFail(resp)
                    ).always(
                        () => this.selectWarehouseAlways()
                    );
                }
                return false;
            },

            selectWarehouseSuccess: function(result, checked) {
                if (result) {
                    rateRegistry.set(this.address.getCacheKey(), result);
                    shippingService.setShippingRates(result);
                    quote.setTotals(result);
                }
                this.isWarehouseSelected(checked);
            },

            selectWarehouseFail: function(response) {
                console.error("There was an error saving shipping the method, no address will be used at this time");
                shippingService.setShippingRates([]);
                errorProcessor.process(response);
            },

            selectWarehouseAlways: function() {
                let body = $('body').loader();
                body.loader('hide');
                shippingService.isLoading(false);
            },

            validateForm: function (form) {
                return $(form).validation() && $(form).validation('isValid');
            },

            getInputValue(warehouseId) {
                return ClickAndCollectConfig.shipping.carrier_code + "_" + warehouseId;
            },

            getFormAction() {
                return resourceUrlManager.getUrlForTotalsEstimationForNewAddress(quote);
            },
        });
    }
);

The main methods of concern here are:

selectWarehouse: This is the main method that gets fired on clicking the radio input. It builds the payload parameters and then performs the POST request which should garner the correct response and set the shipping method and address ready for the next page (checkout).

selectWarehouseSuccess: Where the quote and shipping totals are updated with the response of selectWarehouse.

getInputValue: This is the required format for the radio inputs which goes [Shipping Carrier Code]_[Shipping_Method_Code]

getFormAction: Returns the correct URI endpoint to POST to using the 'Magento_Checkout/js/model/resource-url-manager' which should be either '/guest-carts/:cartId/totals-information' or '/carts/mine/totals-information'.

When building the request, from the network tab of the browsers console, I can see that the correct POST has been sent with my created params and it even returns a valid 200 response. But in selectWarehouseSuccess the result is false and quote.

I am taking my inspiration from Magento_Checkout/js/model/cart/totals-processor/default.js - which does it similarly.

After a couple days of this, I believe it's time for a fresh pair of eyes. Am I doing this correctly? Is there an easier way?

Thanks for reading

2 Answers 2

1

Okay, I have figured out how to set the shipping method programmatically:

Firstly, all my dependencies:

define(
    [
        'ko',
        'jquery',
        'Magento_Ui/js/form/form',
        'uiRegistry',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/resource-url-manager',
        'Magento_Checkout/js/action/create-shipping-address',
        'Magento_Checkout/js/model/error-processor',
        'Magento_Checkout/js/model/shipping-service',
        'Magento_Checkout/js/model/cart/totals-processor/default',
        'Magento_Checkout/js/model/shipping-rate-registry',
        'Magento_Checkout/js/action/select-shipping-method',
        'Magento_Checkout/js/checkout-data'
    ],
    function (
        ko,
        $,
        Component,
        uiRegistry,
        quote,
        resourceUrlManager,
        createShippingAddress,
        errorProcessor,
        shippingService,
        totalsDefaultProvider,
        rateRegistry,
        selectShippingMethodAction,
        checkoutData
    ) { 

    ...

Then, the magic promise:

                setShippingMethod: function (carrier, method, title) {
                    return new Promise((resolve, reject) => {
                        let data = {
                            carrier_code: carrier,
                            method_code: method,
                            carrier_title: title,
                            method_title: title
                        };
                        selectShippingMethodAction(data);
                        checkoutData.setSelectedShippingRate(code + '_' + method);
                        if (quote.shippingMethod()) {
                            resolve(quote.shippingMethod());
                        } else {
                            reject(quote.shippingMethod());
                        }
                    });
                }

So then in your event (or whatever):

                    setShippingMethod(
                        methodData['carrier_code'], methodData['warehouse_id'], methodData['title']
                    ).then(
                        (shippingMethod) => { 
                             totalsDefaultProvider.estimateTotals(quote.shippingAddress());
                        }
                    ).catch(
                        (err) => failHandlerMethod(err)
                    );

Optional, you can also add the shipping address:

                    let payload = JSON.stringify({
                        addressInformation: {
                            address: {
                                countryId: "country",
                                postcode: "postcode",
                                regionId: "region"
                            },
                            shipping_carrier_code: "carrier_code",
                            shipping_method_code: "method_code"
                        }
                    });
                    let addr = createShippingAddress(payload);
                    rateRegistry.set(address.getCacheKey(), addr);
1

I have a similar task to handle and I have played with your code. Thanks for it. I found a small bug. In your setShippingMethod when you are calling method checkoutData.setSelectedShippingRate is a bug with wrong arguments. The code is not defined so I assume you need to pass here correct variable and it will be:

checkoutData.setSelectedShippingRate(carrier + '_' + method);

or

checkoutData.setSelectedShippingRate(data.carrier_code + '_' + data.method_code);

Your Answer

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

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