Categories


Archives


Recent Posts


Categories


More Magento 2 Checkout Notes: Shipping to Payment

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

More notes on Magento 2’s checkout application. This post has a 1,000 ft. view of the code that runs when a user clicks the the Next button to move from the shipping step to the payment step. Apologies up front that this isn’t as in-depth as I’d like, or too in depth at other parts, but my Patrons are hard task masters 🙂

If the concepts below confuse you, reading through my Magento 2 Advanced Javascript tutorial is a good place to start, and after that the UI Components series

The shipping step form submits to a

`setShippingInformation`

function.

#File: vendor/magento/module-checkout/view/frontend/web/js/view/shipping.js
setShippingInformation: function () {
    if (this.validateShippingInformation()) {
        setShippingInformationAction().done(
            function () {
                stepNavigator.next();
            }
        );
    }
},    

The setShippingInformationAction method comes from a RequireJS module that returns a function.

Magento_Checkout/js/action/set-shipping-information

Which wraps a call to the saveShippingInformation method on another module (Magento_Checkout/js/model/shipping-save-processor)

#File: vendor/magento//module-checkout/view/frontend/web/js/model/shipping-save-processor.js
define(
    [
        '../model/quote',
        'Magento_Checkout/js/model/shipping-save-processor'
    ],
    function (quote, shippingSaveProcessor) {
        'use strict';
        return function () {
            return shippingSaveProcessor.saveShippingInformation(quote.shippingAddress().getType());
        }
    }
);    

This processor module

#File: vendor/magento//module-checkout/view/frontend/web/js/model/shipping-save-processor.js

define(
    [
        'Magento_Checkout/js/model/shipping-save-processor/default'
    ],
    function(defaultProcessor) {
        'use strict';
        var processors = [];
        processors['default'] =  defaultProcessor;

        return {
            registerProcessor: function(type, processor) {
                processors[type] = processor;
            },
            saveShippingInformation: function (type) {
                var rates = [];
                if (processors[type]) {
                    rates = processors[type].saveShippingInformation();
                } else {
                    rates = processors['default'].saveShippingInformation();
                }
                return rates;
            }
        }
    }
);

appears to be able to process multiple request types. In this case the type

requirejs('Magento_Checkout/js/model/quote').shippingAddress().getType()

resolves to

new-customer-address

This may be different if customer is logged in. All that said – a quick grep through the codebase indicates that registerProcessor is never called. This means Magento processes the save with

Magento_Checkout/js/model/shipping-save-processor/default

Regardless of the value passed to saveShippingInformation.

This default processor

#File: vendor/magento//module-checkout/view/frontend/web/js/model/shipping-save-processor/default.js

/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
/*global define,alert*/
define(
    [
        /* ... */,
        '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',
        /* ... */,
        /* ... */,
    ],
    function (
        /* ... */,
        quote,
        resourceUrlManager,  
        storage,
        paymentService,
        methodConverter,
        errorProcessor,
        /* ... */,
        /* ... */,
    ) {
        'use strict';

        return {
            saveShippingInformation: function () {
                /* ... */                    

                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();
                    }
                );
            }
        };
    }
);

Returns a call to requirejs('mage/storage').post(...). This is a wrapper to jQuery’s $.ajax method – the done and fail are standard jQuery ajax handlers.

The URL for this ajax request comes from the Magento_Checkout/js/model/resource-url-manager module.

#File: vendor/magento//module-checkout/view/frontend/web/js/model/resource-url-manager.js

getUrlForSetShippingInformation: function(quote) {
    var params = (this.getCheckoutMethod() == 'guest') ? {cartId: quote.getQuoteId()} : {};
    var urls = {
        'guest': '/guest-carts/:cartId/shipping-information',
        'customer': '/carts/mine/shipping-information'
    };
    return this.getUrl(urls, params);
},

On a successful request

#File: vendor/magento//module-checkout/view/frontend/web/js/model/shipping-save-processor/default.js
quote.setTotals(response.totals);
paymentService.setPaymentMethods(
    methodConverter(response.payment_methods)
);

The setTotals call updates some values in the Magento_Checkout/js/model/quote object/module. Some of these values are observables, so might trigger further UI updates.

After this, we get a call to setPaymentMethods on the payment service (Magento_Checkout/js/model/payment-service). This appears to set the available payment methods on another RequireJS object/module which will, (presumably) be used in the next step. This is another example of using RequireJS to hold on to global state. The Magento_Checkout/js/model/payment/method-converter/methodConverter is a bit of transformation of the server side JSON data into a format the client side code wants it.

On a failure

#File: vendor/magento//module-checkout/view/frontend/web/js/model/shipping-save-processor/default.js
errorProcessor.process(response);

The response is processed by the error processor module

#File: vendor/magento//module-checkout/view/frontend/web/js/model/error-processor.js

define(
    [
        'mage/url',
        'Magento_Ui/js/model/messageList'
    ],
    function (url, globalMessageList) {
        'use strict';

        return {
            process: function (response, messageContainer) {
                messageContainer = messageContainer || globalMessageList;
                if (response.status == 401) {
                    window.location.replace(url.build('customer/account/login/'));
                } else {
                    var error = JSON.parse(response.responseText);
                    messageContainer.addErrorMessage(error);
                }
            }
        };
    }
);    

This will pull the entire text of a error response and add it to the messageList object/module. This module eventually adds the error messages to some knockout observables, so its possible that errors will update the UI. Also of note, if the HTTP status code of the error is 401, the application will force a user to login.

Jumping back up into our original form handler

#File: vendor/magento/module-checkout/view/frontend/web/js/view/shipping.js
setShippingInformation: function () {
    if (this.validateShippingInformation()) {
        setShippingInformationAction().done(
            function () {
                stepNavigator.next();
            }
        );
    }
}, 

we’ll remember setShippingInformationAction returns a standard jQuery ajax handler. Here it adds one more done function, which signals to the step navigator it should move to the next step. One final thing worth noting – if validateShippingInformation fails no ajax request is made. If the ajax request in setShippingInformationAction fails, done callbacks never get called.

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 12th November 2016