10

When you're filling out the Shipping Address form in Magento 2, periodically Magento will make an ajax request for the URL

http://magento.example.com/rest/default/V1/guest-carts/[hash]/estimate-shipping-methods

and use this data to update the shipping methods section of the UI.

Programmatically speaking, which RequireJS modules setup the events and/or Knockout observables or data-bindings that enable this functionality. Tracing backwards through the code from the URL manager

#File: vendor/magento/module-checkout/view/frontend/web/js/model/resource-url-manager.js
        getUrlForEstimationShippingMethodsForNewAddress: function(quote) {
            var params = (this.getCheckoutMethod() == 'guest') ? {quoteId: quote.getQuoteId()} : {};
            var urls = {
                'guest': '/guest-carts/:quoteId/estimate-shipping-methods',
                'customer': '/carts/mine/estimate-shipping-methods'
            };
            return this.getUrl(urls, params);
        },

has branched too many times for this to be an easy thing to figure out. I'm hoping someone here already has the solution in hand.

4 Answers 4

28

in vendor/magento/module-checkout/view/frontend/web/js/model/shipping-rate-service.js:21 there is a subscribe to the shipping address observerable quote.shippingAddress.

    quote.shippingAddress.subscribe(function () {
        var type = quote.shippingAddress().getType();

        if (processors[type]) {
            processors[type].getRates(quote.shippingAddress());
        } else {
            processors.default.getRates(quote.shippingAddress());
        }
    });

In the callback funtion this calls to the getRates function in vendor/magento/module-checkout/view/frontend/web/js/model/shipping-rate-processor/new-address.js

getRates does an ajax call using the url build via the getUrlForEstimationShippingMethodsForNewAddress method you mentioned in your question and uses the result to update the rateRegistry and sets the shipping rates on the shippingRates observerable of the shipping service.

       getRates: function (address) {
            shippingService.isLoading(true);
            var cache = rateRegistry.get(address.getCacheKey()),
                serviceUrl = resourceUrlManager.getUrlForEstimationShippingMethodsForNewAddress(quote),
                payload = JSON.stringify({
                        address: {
                            'street': address.street,
                            'city': address.city,
                            'region_id': address.regionId,
                            'region': address.region,
                            'country_id': address.countryId,
                            'postcode': address.postcode,
                            'email': address.email,
                            'customer_id': address.customerId,
                            'firstname': address.firstname,
                            'lastname': address.lastname,
                            'middlename': address.middlename,
                            'prefix': address.prefix,
                            'suffix': address.suffix,
                            'vat_id': address.vatId,
                            'company': address.company,
                            'telephone': address.telephone,
                            'fax': address.fax,
                            'custom_attributes': address.customAttributes,
                            'save_in_address_book': address.saveInAddressBook
                        }
                    }
                );

            if (cache) {
                shippingService.setShippingRates(cache);
                shippingService.isLoading(false);
            } else {
                storage.post(
                    serviceUrl, payload, false
                ).done(
                    function (result) {
                        rateRegistry.set(address.getCacheKey(), result);
                        shippingService.setShippingRates(result);
                    }
                ).fail(
                    function (response) {
                        shippingService.setShippingRates([]);
                        errorProcessor.process(response);
                    }
                ).always(
                    function () {
                        shippingService.isLoading(false);
                    }
                );
            }
        }

The above show how the shipping methods get updated below will show how the quote.shippingAddres.subscribe is triggered:

The short version Each shipping method has validation rules set against some of the shipping fields, these fields plus the postcode fields are observered via the knockoutjs 'value' binding and once this is triggered it updates the shipping address and this in turn as mentioned previously updates the shipping methods.

The long version I will now explain the long version looking at the free shipping method, as this seems to be the same for all other included shipping methods.

on page load The view component Magento_OfflineShipping/js/view/shipping-rates-validation/freeshipping gets loaded via vendor/magento/module-offline-shipping/view/frontend/layout/checkout_index_index.xml

This makes a call to

defaultShippingRatesValidationRules.registerRules('freeshipping', freeshippingShippingRatesValidationRules); 

which uses the following

/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
/*global define*/
define(
[],
function () {
    "use strict";
    return {
        getRules: function() {
            return {
                'country_id': {
                    'required': true
                }
            };
        }
    };
}

from vendor/magento/module-offline-shipping/view/frontend/web/js/model/shipping-rates-validation-rules/freeshipping.js

to set up the rateRules in vendor/magento/module-checkout/view/frontend/web/js/model/shipping-rates-validation-rules.js

after this the vendor/magento/module-checkout/view/frontend/web/js/view/shipping.js module gets loaded, which on initialize calls:

shippingRatesValidator.initFields(fieldsetName);

from vendor/magento/module-checkout/view/frontend/web/js/model/shipping-rates-validator.js

initFields: function (formPath) {
            var self = this,
                elements = shippingRatesValidationRules.getObservableFields();

            if ($.inArray(postcodeElementName, elements) === -1) {
                // Add postcode field to observables if not exist for zip code validation support
                elements.push(postcodeElementName);
            }

            $.each(elements, function (index, field) {
                uiRegistry.async(formPath + '.' + field)(self.doElementBinding.bind(self));
            });
        },

where

elements = shippingRatesValidationRules.getObservableFields();

uses the previously set up rate rules to get the fields to observe

it then adds the postcode fields if it is not set up yet and calls doElementBinding method on each element which calls this.bindHandler, which has the following:

  element.on('value', function () {
                    clearTimeout(self.validateAddressTimeout);
                    self.validateAddressTimeout = setTimeout(function () {
                        if (self.postcodeValidation()) {
                            self.validateFields();
                        }
                    }, delay);
                });

which calls self.validateFields();

        validateFields: function () {
            var addressFlat = addressConverter.formDataProviderToFlatData(
                    this.collectObservedData(),
                    'shippingAddress'
                ),
                address;

            if (this.validateAddressData(addressFlat)) {
                address = addressConverter.formAddressDataToQuoteAddress(addressFlat);
                selectShippingAddress(address);
            }
        },

which calls selectShippingAddress(address); which in turn sets the shippingAddress observerable.

  /**
  * Copyright © 2016 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
 /*global define*/
 define(
   [
    'Magento_Checkout/js/model/quote'
   ],
    function(quote) {
      'use strict';
         return function(shippingAddress) {
            quote.shippingAddress(shippingAddress);
       };
   }
);
7
  • 1
    +1 for useful information, but this doesn't explain what triggers the observable on the shipping address object. Are there blur events on the front end fields that update the quote object -- other observables? Something else? Commented Nov 13, 2016 at 22:12
  • @AlanStorm updated my answer Commented Nov 15, 2016 at 22:22
  • Thanks @martijn, marking this as best for now. I've been working on my own version and (as you seem to understand) a single Stack Overflow answer isn't going to do it. Commented Nov 16, 2016 at 22:11
  • 1
    And my version of same, for anyone that's interested in a different path magento-quickies.alanstorm.com/post/153277368585/… Commented Nov 16, 2016 at 23:33
  • Hi @alan,@Martjin ->example.com/rest/default/V1/carts/mine/… //sometime url not loading proper on cart estimation summary due to js issue so can we load it hardly even there are some JS issues in Console?If not then in which file can we check url return data so can debug properly?
    – akgola
    Commented Sep 4, 2017 at 6:52
3

I got it working with basic knockout valueHasMutated() this triggers the subscribe :

define([
    'uiComponent',
    'jquery',
    'Magento_Checkout/js/model/quote'
], function (uiComponent, $, quote) {
    'use strict';

    /**
     * Return the UI Component
     */
    return uiComponent.extend({
        /**
         * Initialization method
         */
        initialize: function () {
            this._super();
        },

        clickCheck: function () {
            quote.shippingAddress.valueHasMutated();
        }
    });
});
2

To force reloading of shipping methods try,

define(
    [
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/shipping-rate-processor/new-address',
        'Magento_Checkout/js/model/shipping-rate-processor/customer-address',
        'Magento_Checkout/js/model/shipping-rate-registry'

    ],
    function (quote, defaultProcessor, customerAddressProcessor, rateRegistry) {
       'use strict';

       var processors = [];

       rateRegistry.set(quote.shippingAddress().getCacheKey(), null);

       processors.default =  defaultProcessor;
       processors['customer-address'] = customerAddressProcessor;

       var type = quote.shippingAddress().getType();

       if (processors[type]) {
          processors[type].getRates(quote.shippingAddress());
       } else {
          processors.default.getRates(quote.shippingAddress());
       }

    }
);
4
  • not post the updated address when I use this one code. Commented Mar 7, 2018 at 11:22
  • I did reload the the shipping-method-item.html for me with this code. Commented May 23, 2018 at 11:27
  • @Renon,is it possible to reload shipping rates and tax in checkout payment step directly,Actually in my case i am redirecting to checkout payment step Commented May 7, 2019 at 13:09
  • in my case tax amount and shipping amount not adding to the total amount and also not appearing in Checkout Payment Step Commented May 7, 2019 at 13:10
0

You can first call selectShippingAddressAction. That will set your shipping address to quote.shippingAddress. After that you can setSelectedShippingAddress to checkoutData.

    define([
    'underscore',
    'uiComponent',
    'Magento_Customer/js/model/address-list',
    'Magento_Checkout/js/action/select-shipping-address',
    'Magento_Checkout/js/checkout-data',
    'Magento_Checkout/js/model/shipping-rate-service'
    ], 
    function (_, Component, addressList, selectShippingAddressAction, checkoutData) {
    'use strict';

    return Component.extend({
        /** @inheritdoc */
        initObservable: function () {
            var neededAddress = "42";

            _.each(addressList(), function(address) {
                if (address.getKey() === neededAddress) {
                    selectShippingAddressAction(address);
                    checkoutData.setSelectedShippingAddress(address.getKey());
                }
            }, this);

            return this;
        }
    });
});

Shipping rates are recalculated automatically because quote.shippingAddress has a subscription in Magento_Checkout/js/model/shipping-rate-service.js but your shipping address should be set to quote first.

If you want to trigger shipping rates to update on checkout you can define Magento_Checkout/js/model/shipping-rate-service dependency to trigger Magento_Checkout/js/model/shipping-rate-processor/new-address.js::getRates method directly.

I defined shipping-rate-service.js in dependencies for it to trigger getRates methods because otherwise getRates method is called before our needed shipping address is set to quote. That's why without this dependency our shipping rates won't be estimated and there will be no AJAX request to estimate-shipping-methods

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.