Solving Magento

Solutions for Magento E-Commerce Platform

by Oleg Ishenko

Magento OnePage Checkout Part 2: Model, Views, Controller

This is part 2 of the One Page checkout discussion. You can find more about this checkout type in the Magento OnePage Checkout Part 1 and Magento OnePage Checkout Part 3: JavaScript.

Checkout Components

The checkout process described above requires a rather complex system to validate and save data, resolve step dependencies, render and update step forms. Magento implements it in an MVC pattern. First, there is a checkout model containing business logic to process steps (Mage_Checkout_Model_Type_Onepage). Then, there are views – blocks and templates to render steps. Finally, there is a controller (Mage_Checkout_OnepageController) to deal with requests posting and fetching step data. Keeping this design pattern in mind, I will describe the components of One Page checkout in the following sections:

  • Checkout session model
  • One Page checkout model and controller.
  • Checkout layout and templates
  • JavaScript layer

Checkout Session

Magento has a dedicated checkout session class Mage_Checkout_Model_Session which it uses extensively to access quote details during the checkout process. This model extends the Mage_Core_Model_Session_Abstract (that contains the essential session state functionality) and implements a number of checkout-specific methods:

  • getQuote() – returns the current customer quote data that are stored in the session model’s internal property _quote. If this property is not set, then either a quote object is loaded from the database (if the session has a quote ID) or an empty quote is initialized. By default, only active quotes are loaded from the database. Module Mage_Persistent enables loading of inactive quotes for guest carts.
  • loadCustomerQuote() loads a quote for the customer that has just logged in (this method is called from an observer processing the customer_login event). If the customer had new products added to cart before logging in, they will be merged into the quote loaded from the database.
  • setStepData() and getStepData() – manage the checkout steps. In different stages of the checkout the system can call this method to retrieve the step information or to update it. The data that is set to steps is an array containing three possible keys:
  • label – step label, e.g., “Billing Information”, localized
  • is_show – a flag controlling the step display in the checkout progress block (see progress.phtml line 34)
  • allow – flag indicating that the customer can switch to the step.
  • resetCheckout() – this method sets the checkout state to begin. The checkout_state session property is used by Multishipping checkout model to manage checkout steps.

One Page Checkout Model

Checkout models in Magento contain functionality necessary to process data that customers submit in checkout steps and to convert that data along with the shopping cart content into an order. Magento defines an abstract checkout class Mage_Checkout_Model_Type_Abstract that serves as a base for the Multishipping checkout model. Model One Page, however, does not inherit from the abstract checkout class, but implements a similar set of methods:

  • getCheckout() – this method returns a session singleton Mage::getSingleton('checkout/session') that is assigned to the model’s property $checkoutSession in the class construct method.
  • getQuote() – in this method the checkout model returns an object representing the current quote from the checkout session instance. In the following example, the One Page controller’s method saveBillingAction() fetches a quote from the checkout model (that itself is obtained by calling $this->getOnepage()) to check if it contains virtual items only:

    Listing 1. Using the One Page checkout instance to access the current quote, /app/code/core/Mage/Checkout/controllers/OnepageController.php, line 324.

  • getCustomerSession() – this method is similar to the getCheckout() function – it returns a customer session intsance from the checkout object’s internal property _customerSession. The example below uses this method to display a message after a customer creates a new shop account:
            'Account confirmation is required.
            Please, check your e-mail for confirmation link.
            To resend confirmation email please <a href="%s">click here</a>.',

    Listing 2. Using the customer session to display messages to customer in One Page checkout, /app/code/core/Mage/Checkout/Model/Type/Onepage.php, line 741.

The One Page model Mage_Checkout_Model_Type_Onepage implements a number of methods that correspond to the steps of One Page checkout:

  • saveBilling() – this method processes the data submitted in the Billing Information step and saves the quote’s billing address. If a customer is logged in, he can choose one of his addresses that he may previously have saved to his account. In this case, this method receives an ID of that address and loads it from the database. If no existing address has been chosen, the method creates an address object from the address form data submitted by the customer. These data have to be validated first, and if anything goes wrong, the system stops the further processing, and the method returns an array containing a validation error message. It is also possible that the customer decides to use the billing address for shipping. In this case, the system clones the billing address object and copies its data to the shipping address object. This may happen only if the quote is not virtual, i.e. physical delivery is required.
  • saveShipping() – this method deals with the data received from the Shipping Information step and is quite similar to the saveBilling() function. It also can receive an address ID to load the shipping address from the database. And just as in the billing address step, it can create a shipping address from the form data. These data also have to be validated, and the method returns an array with an error message if the validation fails. After a shipping address is saved, the system must re-calculate shipping costs for the quote. For this purpose the system sets the address object’s property collect_shipping_rates to true , collects the quote’s totals, and saves the quote:
    /** code skipped for brevity */

    Listing 3. Making sure that shipping costs are re-calculated after a shipping address is saved. app/code/core/Mage/Checkout/Model/Type/Onepage.php, lines 555 – 562.

    The collect_shipping_rates property plays a role in function Mage_Sale_Model_Quote_Address::collectShippingRates() that is called during the collection of totals. If this property is true, it causes the system to request new shipping rates based on the current address data.

  • saveShippingMethod() – here, the system processes the Shipping Method step. This function checks if there is a valid shipping rate associated with the selected shipping method:
    $rate = $this->getQuote()->getShippingAddress()->getShippingRateByCode($shippingMethod);
    if (!$rate) {
        return array(
            'error' => -1,
            'message' => Mage::helper('checkout')->__('Invalid shipping method.')

    Listing 4. Checking the shipping method, /app/code/core/Mage/Checkout/Model/Type/Onepage.php, line 582.

    If no rate is found, the system stops the checkout process and displays an error message to the customer. If everything is fine, the system sets the shipping method to the quote and allows the customer to proceed to the next step.

  • savePayment() – this method processes the payment option selected by the customer in step Payment Information. The $data parameter passed to this function is an array with at least one element – “method” – containing the name of the selected option, e.g., “checkmo” for “Check or Money Order”. The name of the payment method is set to the quote address – billing in case of a virtual quote, or shipping otherwise. Some payment methods may cause changes in the shipping rates which is why this function also forces the quote to recalculate shipping costs:
    // shipping totals may be affected by payment method
    if (!$quote->isVirtual() && $quote->getShippingAddress()) {
    $payment = $quote->getPayment();
     * Payment availability related with quote totals.
     * We have recollect quote totals before checking
    if (!$method->isAvailable($this->getQuote())) {
            Mage::helper('sales')->__('The requested Payment Method is not available.')

    Listing 6. Resetting the shipping costs and setting up the payment object, /app/code/core/Mage/Checkout/Model/Type/Onepage.php line 615.

    In the listing above, the system also sets up the payment object, $payment. This object is an instance of the Mage_Sales_Model_Quote_Payment class and is attached to the quote that uses it to process payment data. Its importData() method initiates collection of totals for the quote – the process that checks if the selected method is available for the current quote. If yes, the system instantiates a payment method object. Payment method models extend the basic payment class Mage_Payment_Model_Method_Abstract and implement functionality required to process payments: validating, submitting, refunding, etc. In a case of a payment by “Check Money Order”, the system creates an object from class Mage_Payment_Model_Method_Checkmo and invokes its method assignData() that saves the payment data submitted by the customer in the checkout step form to the payment method instance for the future use.

One Page Checkout Controller

In the MVC pattern that Magento implements for its One Page checkout, “C” stands for controller. The One Page Controller works tightly with the One Page checkout model whose methods it calls with parameters composed of data it receives from checkout HTTP requests. An instance of the One Page checkout model is available to the controller as a session reference to a singleton object:

public function getOnepage()
    return Mage::getSingleton('checkout/type_onepage');

Listing 7. Getting an instance of the One Page checkout model, /app/code/core/Mage/Checkout/controllers/OnepageController.php, line 153.

The controller uses the results produced by the checkout model methods to prepare data that it then returns as a HTTP response. For each step of One Page checkout the controller class implements a similarly named method (action), e.g., saveBillingAction or successAction. Since the main feature of One Page checkout is the accommodation of all steps in one page, the checkout requests and responses are made using Ajax. The exceptions from this rule are the indexAction that is invoked when the One Page checkout is started, the successAction that is loaded after the order is submitted, and the failureAction that is called when something wrong goes with the order submission and the checkout process can’t be completed.

Before any controller action is called, the system invokes the preDispatch() method to perform three important tasks:

  • If the customer is logged in, the controller validates his data.
  • The controller checks if the current quote is labelled as Multishipping (which can happen if the customer switches from Multishipping checkout to One Page). If that is the case, the system removes all addresses previously saved to the quote.
  • If the system settings prohibit checkout for unregistered users and the requested action is not “index” (initial checkout step) , the controller forwards the request to the No-Route action and the customer is shown a 404 page.

The indexAction() method initializes One Page checkout and renders its steps complete with forms. Only the first step is shown while others are hidden by the CSS rules. Later in the process, the content of the steps may be updated by Ajax requests. These updates are managed by the JavaScript layer that is composed of objects representing the checkout steps and the checkout itself (more on this layer later). The first step to be shown can be either “Checkout Method (Login)” if the customer is not logged in or “Billing Information” if yes. You can find the code that sets the active step and the initializes the JavaScript layer in the One Page checkout’s main template:

<script type="text/javascript">// <![CDATA[
var accordion = new Accordion('checkoutSteps', '.step-title', true);
    <?php if($this->getActiveStep()): ?>
    accordion.openSection('opc-<?php echo $this->getActiveStep() ?>');
    <?php endif ?>
    var checkout = new Checkout(accordion,{
        progress: '<?php echo $this->getUrl('checkout/onepage/progress') ?>',
        review: '<?php echo $this->getUrl('checkout/onepage/review') ?>',
        saveMethod: '<?php echo $this->getUrl('checkout/onepage/saveMethod') ?>',
        failure: '<?php echo $this->getUrl('checkout/cart') ?>'}
// ]]></script>

Listing 8. Setting the active step and initializing an instance of the Checkout JavaScript object, /app/design/frontend/base/default/template/checkout/onepage.phtml, line 47.

Besides rendering the checkout HTML, the index action checks the validity of the current quote: whether the quote has items and if the quote amount is above the minimum set in the configuration. If One Page checkout is disabled, this setting will be also checked in the index action:

if (!Mage::helper('checkout')->canOnepageCheckout()) {
    Mage::getSingleton('checkout/session')->addError($this->__('The onepage checkout is disabled.'));
$quote = $this->getOnepage()->getQuote();
if (!$quote->hasItems() || $quote->getHasError()) {
if (!$quote->validateMinimumAmount()) {
    $error = Mage::getStoreConfig('sales/minimum_order/error_message') ?
        Mage::getStoreConfig('sales/minimum_order/error_message') :
        Mage::helper('checkout')->__('Subtotal must exceed minimum order amount');


Listing 9. Checks in the index action of the One Page checkout controller, /app/code/core/Mage/Checkout/controllers/OnepageController.php, Line 161.

If the first checkout step is “Checkout Method (Login)”, i.e. the customer is not authenticated, then the customer can either log in or choose to checkout as guest or register a new account. In the latter two cases the customer choice is saved by the controller’s saveMethodAction() that uses the One Page checkout model to write the selected method to the session. Note that in the beginning of this action (and also other “step” actions) the controller calls its protected method _expireAjax():

if ($this->_expireAjax()) {

Listing 10. Controlling checkout validity before calling a step action method, /app/code/core/Mage/Checkout/controllers/OnepageController.php, line 293.

This method is called to check if the checkout step can be processed, that is:

  • the quote has items
  • the quote has no errors
  • the quote is not marked as multishipping (in case the customer switches to Multishipping checkout)
  • the shopping cart was not updated (e.g., items added or removed)

If one of these conditions is not true, the controller responds with a “403 Session Expired” header and redirects the customer to the shopping cart page. These checks are necessary to make sure the checkout is processed correctly and any external changes (made, for example, in another browser tab) are taken into account.

Other “step actions” (saveBillingAction(), saveShippingAction(), saveShippingMethodAction(), savePaymentAction()) share a similar structure: they receive form data posted by the customer in the respective step, call a One Page checkout model method, and return an JSON-formatted array. This result array contains instructions for the One Page JavaScript: name of the next step to be displayed, HTML-update for the next step container, as well as other step-specific data (such as a flag “use billing address for shipping”). The HTML-update is required when customer data submitted in the previous step influence the content of the next step (e.g., the list of shipping methods available to the entered shipping address). In such cases, One Page JavaScript replaces the pre-rendered step content with the update received from the controller.

public function saveBillingAction()
     * Code omitted for brevity
    $data = $this->getRequest()->getPost('billing', array());
    $customerAddressId = $this->getRequest()->getPost('billing_address_id', false);

    if (isset($data['email'])) {
        $data['email'] = trim($data['email']);

    $result = $this->getOnepage()->saveBilling($data, $customerAddressId);

        if (!isset($result['error'])) {
            /* check quote for virtual */
            if ($this->getOnepage()->getQuote()->isVirtual()) {
                $result['goto_section'] = 'payment';
                $result['update_section'] = array(
                    'name' => 'payment-method',
                    'html' => $this->_getPaymentMethodsHtml()
            } elseif (isset($data['use_for_shipping']) && $data['use_for_shipping'] == 1) {
                $result['goto_section'] = 'shipping_method';
                $result['update_section'] = array(
                    'name' => 'shipping-method',
                    'html' => $this->_getShippingMethodsHtml()

                $result['allow_sections'] = array('shipping');
                $result['duplicateBillingInfo'] = 'true';
            } else {
                $result['goto_section'] = 'shipping';


Listing 11. Controller method to process the “Billing Information” step, /app/code/core/Mage/Checkout/controllers/OnepageController.php, line 306

The listing above shows the saveBillingAction(). This method extracts variables $data and $customerAddressId from the POST-request and passes them to a checkout model method. If the checkout model encounters a problem, it returns an array with an “error” element and a message. In this case, the controller responds with the error array and the One Page JavaScript displays the error message to the customer. If the step data processing was successful, the One Page Javascript is told to open the “shipping” step next (or “shipping_method” if the billing address is used for shipping, or “payment_method” if the quote is virtual and requires no shipping). JavaScript uses the “update_section” sub-element “html” to update the next script with either a list of either shipping methods or payment options.

When a checkout step is displayed the One Page Javascript also requests a progress block update. This request is processed by the One Page checkout controller’s method progressAction() that renders HTML with the current state of the checkout – what steps are completed and what data is saved. The controller method itself doesn’t implement that logic – it uses the progress block – Mage_Checkout_Block_Onepage_Progress and its template, progress.html. This block gets the progress information from the checkout session.

Submitting An Order

The most critical part in the checkout is converting a quote into an order. In One Page checkout this process begins when the checkout controller invokes its method saveOrderAction() in response to the customer clicking the “Submit Order” button in the “Order Review” step. The action method checks if the customer has agreed to the required checkout agreements and then calls the One Page checkout model’s saveOrder() function. At this stage the checkout model has two tasks:

  1. Process the checkout method the customer selected in first step: “guest”, “register” or “customer”. For the “register” method the system extracts the billing and shipping addresses from the quote, assigns them to the new customer account and sets them as default billing and default shipping addresses. A similar routine is performed for the “customer” method – if the customer account has no default billing or default shipping addresses, the respective addresses in the quote are saved to the customer account and assigned as default.
  2. Submit the order using an instance of the Mage_Sales_Model_Service_Quote class ($service):
    $service = Mage::getModel('sales/service_quote', $this->getQuote());

    Listing 12. Using the quote service to submit and order, /app/code/core/Mage/Checkout/Model/Type/Onepage.php, line 773.

    The service model plays the main part in the creation of an order. Calling $service->submitAll() triggers the Mage_Sales_Model_Service_Quote::submitOrder() method where the actual quote transformation happens. There, the process is as follows:

  3. The system creates a transaction instance and adds customer and quote objects to it:
    $transaction = Mage::getModel('core/resource_transaction');
    if ($quote->getCustomerId()) {

    Listing 13. Setting up a transaction to save the customer and the quote, /app/code/core/Mage/Sales/Model/Service/Quote.php, line 141.

  4. The service instance uses its _convertor object (of type Mage_Sales_Model_Convert_Quote) to create an order object and copies quote and quote address data into it. After that, the system adds the order object to the transaction.
    if ($isVirtual) {
       $order = $this->_convertor->addressToOrder($quote->getBillingAddress());
    } else {
       $order = $this->_convertor->addressToOrder($quote->getShippingAddress());
    if ($quote->getBillingAddress()->getCustomerAddress()) {
    if (!$isVirtual) {
        if ($quote->getShippingAddress()->getCustomerAddress()) {
    foreach ($this->_orderData as $key => $value) {
        $order->setData($key, $value);
    foreach ($quote->getAllItems() as $item) {
        $orderItem = $this->_convertor->itemToOrderItem($item);
        if ($item->getParentItem()) {

    Listing 14. Creating an order object and setting its data, /app/code/core/Mage/Sales/Model/Service/Quote.php, line 148.

  5. The transaction is saved. Any exception within the transaction triggers a transaction rollback so that the order submission failures do not write corrupted data.

After the order is created, the saveOrder method of the One Page checkout model checks if the payment method requires a redirect (e.g. to the PayPal website). If not, the system sends a new order email to the customer and (if you are not using billing agreements or recurring payments) the order submission is completed.

Checkout Layout

The Mage_Checkout module’s layout updates file is located at /app/design/frontend/base/default/layout/checkout.xml and contains block definitions for the one-page checkout under handle <checkout_onepage_index>. The default checkout page has a two-column layout with a progress block in the right and the checkout steps accordion in the left:

Figure 1. Checkout layout.

The progress column is composed of blocks corresponding to the checkout steps: billing and shipping addresses, shipping method, and payment method. They (and their wrapper block) have the same class checkout/openpage_progess and differ only in templates, The template files for the progress blocks can be found in checkout/onepage/progress/ folder (the wrapper’s block template is checkout/onepage/progress.phtml).

The checkout steps accordion is encased in a block of type checkout/onepage whose template file is checkout/onepage.phtml. Every step has a different block type:

  • Login: checkout/onepage_login, template checkout/onepage/login.phtml
  • Billing address: checkout/onepage_billing, template checkout/onepage/billing.phtml
  • Shipping address: checkout/onepage_shipping, template checkout/onepage/shipping.phtml
  • Shipping method: checkout/onepage_shipping_method, template checkout/onepage/shipping_method.phtml
  • Payment method: checkout/onepage_payment, template checkout/onepage/payment.phtml
  • Checkout review: checkout/onepage_review, template checkout/onepage/review.phtml

Some of these blocks have child layout elements that I will review when discussing the individual checkout steps.

When the layout is rendered the checkout steps are output hidden. The displaying of steps is controlled by JavaScript objects accordion and checkout.

The discussion of Magento One Page checkout is continued in part 3 where I describe the One Page checkout JavaScript.

16 thoughts on “Magento OnePage Checkout Part 2: Model, Views, Controller

  1. “One Page checkout model checks if the payment method requires a redirect (e.g. to the PayPal website). If not, the system sends a new order email to the customer and ”
    and what happen next if requires a redirect?
    Thank you.

    • Some payment methods, such as PayPal, redirect to their websites to complete the payment. In such case, after the customer clicks the “Submit Order” button, Magento redirects him to the payment provider website. The customer must then complete the payment by entering the required details. With PayPal, the customer may be required to log in into a Paypal account and confirm the payment. Once the process on the payment provider website is complete, the customer is redirected to the shop’s “return URL”, which can be the checkout success page. At any point while on the payment provider’s website, the customer can choose to return to the checkout to select a different payment option or to cancel the order.

      • And in which part is where Magento does the redirection?
        I’m asking this because I’m trying to instead of redirect load the payment within an iframe.

        • The redirect is performed in the nextStep function of the Review object that is defined in file /skin/frontend/base/default/js/opcheckout.js in line 831. There, Magento checks the JSON object returned by the OnePage checkout controller in response to the submit order request sent from the “Order Review” step. If the JSON object contains a “redirect” property, Magento redirects the customer to the specified URL.

              if (response.redirect) {
                  this.isSuccess = true;
                  location.href = response.redirect;

          /skin/frontend/base/default/js/opcheckout.js, line 873

          Otherwise, the Review object’s nextStep function redirects to the shop’s “checkout success” page.

          • Oh yes I saw that part, but do you know where stores the url to redirect. Because in that part just does the redirection but the url is in the parameter “transport”, right?

          • Correct, this URL is in the transport object.

            If you want to know where this URL came from, take a look at line 797 in file app/code/core/Mage/Checkout/Model/Type/Onepage.php. There, Magento calls the getOrderPlaceRedirectUrl function of the quote’s payment object. If a payment model requires a redirect after an order is placed, it must implement this function like, for example, Paypal Standard does:

                public function getOrderPlaceRedirectUrl()
                      return Mage::getUrl('paypal/standard/redirect', array('_secure' => true));

            File /app/code/core/Mage/Paypal/Model/Standard.php, 108

            In the function above, Magento generates a URL that references the redirect action of the standard PayPal controller. This controller action outputs a 'paypal/standard_redirect' block whose function _toHtml displays a message warning the customer about the redirect. This block also contains a form that is submitted by JavaScript in 10 seconds timeout to a URL that Magento fetches from the Mage_PayPal module config model’s method getPaypalUrl.

            If you want to implement a similar redirect, you must make sure that your payment model has a getOrderPlaceRedirectUrl method that returns a URL to the payment provider, or, like PayPal, an intermediate URL that displays a redirect message to keep the customer informed before redirecting to a third-party website.

  2. Really nice technical explination (the best I’ve read so far in the Internet).

    But still don’t understand what would cause that Magento ignore the overriding of the Mage_Checkout_Block_Onepage class, (but not the the rest of the block classes). Maybe the class name doesn’t follow the expected structure? the location of the file is one up level over the rest of the checkout blocks classes (in the Onestep folder).

  3. Hi all,
    Very nice, accurate and readable website! Many thanks.

    Typo detected part OnePageCheckoutController:

    […] The controller method itself doesn’t implement that logic – it uses the progress block – Mage_Checkout_Block_Onepage_Progress and its template, progress.html. This block gets the progress information from the checkout session.

    the template is in fact progress.phtml (note that the link is good)

  4. Thank you for that detailed explanation of the Magento checkout process. Don’t you have anything similar for order creation and managing flow (creation of invoices, shipments, credit memos)?
    What I’m trying to achieve is a partial payments for the customers. The first thing I need to learn is how to create multiple invoices for order with just one product. What can you suggest to look at for implementing that feature with minimum amount of rewrites? Will be grateful for any hint.

  5. I have a problem by the checkout after submitting billing information it redirects me to cart
    the log gives me
    The user or account could not be authenticated.”;i:1;s:2091:”#0 /chroot/home/skysaver/ SoapClient->__soapCall(‘Validate’, Array, NULL, Array)
    what could be? thank you so much!

  6. hello!
    i also have problem in onepage checkout process, it redirects me to cart. always with 500 internal server error that i see in firebug. would you please help me?

Leave a Reply

Your email address will not be published. Required fields are marked *

Theme: Esquire by Matthew Buchanan.