Here’s a quick run down on the javascript involved in placing a Magento 2 order.
Once again, specifics here are Magento 2.1.1 but concepts should apply across versions. If you need to get started with Magento’s javascript my Advanced Javascript and UI Components tutorial series are great places to start. Also, a lot of the module/template matching/identifying was done with Commerce Bug 3.2.
The first thing to understand about the billing step is, each payment method has its own Place Order
button in a Knockout template. This also means each payment method has a view model.
The view models for payment methods are child models in the following view model.
reg = requirejs('uiRegistry');
reg.get('checkout.steps.billing-step.payment.payments-list')
This view model’s constructor factory/component is the Magento_Checkout/js/view/payment/list
RequireJS module. Its remote template urn is Magento_Checkout/payment-methods/list
, and looks like this
<!-- File: vendor/magento//module-checkout/view/frontend/web/template/payment-methods/list.html -->
<div class="items payment-methods">
<!-- ko foreach: { data: getRegion('payment-method-items'), as: 'element'} -->
<!-- ko template: element.getTemplate() --><!-- /ko -->
<!-- /ko -->
</div>
<!-- ko ifnot: getRegion('payment-method-items')().length > 0 --><div class="no-payments-block"><!-- ko i18n: 'No Payment Methods'--><!-- /ko --></div><!-- /ko -->
This template foreach
es over the items in the payment-method-item
region. If you’re not familiar with them, regions are a sort “shadow hierarchy” for view models, used when a component developer doesn’t want to render all a view model’s children.
The payment method view models live in this region. One of them, the venerable “Check and Money Order” (checkmo
), has a view model constructor factory of Magento_OfflinePayment/js/view/payment/method-renderer/checkmo-method
and a Knockout remote template of Magento_OfflinePayments/payment/checkmo
. If we look at the template, we’ll see the source for the Place Order
button.
#File: vendor/magento/module-offline-payments/view/frontend/web/template/payment/checkmo.html
<button class="action primary checkout"
type="submit"
data-bind="
click: placeOrder,
attr: {title: $t('Place Order')},
css: {disabled: !isPlaceOrderActionAllowed()},
enable: (getCode() == isChecked())
"
disabled>
<span data-bind="i18n: 'Place Order'"></span>
</button>
The button has a Knockout.js click
data binding, that will call the view model’s placeOrder
button. While it’s standard Magento practice to name this method the same in all payment method templates, people do weird things sometimes, so be wary. If we look at the source for the Magento_OfflinePayment/js/view/payment/method-renderer/checkmo-method
module (the view model constructor factory/component for this template)
#File: vendor/magento//module-offline-payments/view/frontend/web/js/view/payment/method-renderer/checkmo-method.js
define(
[
'Magento_Checkout/js/view/payment/default'
],
function (Component) {
'use strict';
return Component.extend({
defaults: {
template: 'Magento_OfflinePayments/payment/checkmo'
},
/** Returns send check to info */
getMailingAddress: function() {
return window.checkoutConfig.payment.checkmo.mailingAddress;
},
/** Returns payable to info */
getPayableTo: function() {
return window.checkoutConfig.payment.checkmo.payableTo;
}
});
}
);
Hmmmm. No placeOrder
method. What gives?
If you look a little closer, you’ll notice that Component.extend
is actually a Magento_Checkout/js/view/payment/default
model, and not the usual uiComponent
. This means the checkmo view model extends the Magento_Checkout/js/view/payment/default
view model. If we look at Magento_Checkout/js/view/payment/default
’s source
#File: vendor/magento//module-checkout/view/frontend/web/js/view/payment/default.js
placeOrder: function (data, event) {
var self = this;
if (event) {
event.preventDefault();
}
if (this.validate() && additionalValidators.validate()) {
this.isPlaceOrderActionAllowed(false);
this.getPlaceOrderDeferredObject()
.fail(
function () {
self.isPlaceOrderActionAllowed(true);
}
).done(
function () {
self.afterPlaceOrder();
if (self.redirectAfterPlaceOrder) {
redirectOnSuccessAction.execute();
}
}
);
return true;
}
return false;
}
There’s some validation methods that we’ll skip for now, but they’re worth investigating on your own. The call to getPlaceOrderDeferredObject
is what we want. This method returns the jQuery ajax request (actually a jQuery “deferred” object that does an ajax request, so essentially the same thing. Don’t worry about it unless you want to. ) that places the order, with the fail
and done
handlers handling success and failure cases. These callbacks are also worth investigating on your own, as they handle order failure/success cases.
In the getPlaceOrderDeferredObject
method.
#File: vendor/magento//module-checkout/view/frontend/web/js/view/payment/default.js
getPlaceOrderDeferredObject: function () {
return $.when(
placeOrderAction(this.getData(), this.messageContainer)
);
},
We’re interested in the placeOrderAction
function. If we look at the module definition
define(
[
/* ... */,
'Magento_Checkout/js/action/place-order',
/* ... */
],
function (
/* ... */,
placeOrderAction,
/* ... */,
){/* ... */}
)
We see that placeOrderAction
is a Magento_Checkout/js/action/place-order
module. If we look at this module’s source
#File: vendor/magento/module-checkout/view/frontend/web/js/action/place-order.js
function (quote, urlBuilder, customer, placeOrderService) {
'use strict';
return function (paymentData, messageContainer) {
var serviceUrl, payload;
payload = {
cartId: quote.getQuoteId(),
billingAddress: quote.billingAddress(),
paymentMethod: paymentData
};
if (customer.isLoggedIn()) {
serviceUrl = urlBuilder.createUrl('/carts/mine/payment-information', {});
} else {
serviceUrl = urlBuilder.createUrl('/guest-carts/:quoteId/payment-information', {
quoteId: quote.getQuoteId()
});
payload.email = quote.guestEmail;
}
return placeOrderService(serviceUrl, payload, messageContainer);
};
}
We see that Magento creates an API payload object, and creates a service URL depending on the logged in status of the customer. Then, this information is passed on the the placeOrderService
function (from the Magento_Checkout/js/model/place-order
module)
#File: vendor/magento/module-checkout/view/frontend/web/js/model/place-order.js
define(
[
'mage/storage',
'Magento_Checkout/js/model/error-processor',
'Magento_Checkout/js/model/full-screen-loader'
],
function (storage, errorProcessor, fullScreenLoader) {
'use strict';
return function (serviceUrl, payload, messageContainer) {
fullScreenLoader.startLoader();
return storage.post(
serviceUrl, JSON.stringify(payload)
).fail(
function (response) {
errorProcessor.process(response, messageContainer);
fullScreenLoader.stopLoader();
}
);
};
}
);
Using the mage/storage
RequireJS module (a wrapper for jQuery ajax functions), Magento calls the API URL passed in with the payload passed in. This either places the order, or results in a failure. Both cases are handled by the various fail
and done
callbacks registered above.