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.