Categories


Archives


Recent Posts


Categories


Magento 2: Checkout Application Order Placing

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!

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 foreaches 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.

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 17th November 2016