[Mini Tutorial] –
Change Step Order in Magento’s One Step Checkout

“If it ain’t broke, don’t fix it…
unless you can never be satisfied with what
you have and constantly desire change.”

…I believe that’s the whole quote. It’s becoming more popular lately to see an alternative, more slimmed-down order to Magento’s One Page Checkout steps, particularly in mobile.

Before we dig into changing the step order in Magento’s one step check out, download our free eBook containing all of the Magento Transactional emails for your eCommerce business by clicking below:





Download ebook: Magento Transactional Emails




Magento’s One Step Checkout

A more slimmed down version of Magento’s One Step Checkout is usually in the following order:

  • Shipping
  • Shipping Method
  • Billing/Payment
  • Review

As opposed to the tried and true:

  • Billing
  • Shipping
  • Shipping Method
  • Payment
  • Review

I’ve included here the necessary code changes in order to apply this functionality to a site using Magento’s One Step Checkout.

Step 1:

Create the following file structure:

app/code/local/Demac/Checkout/Block/Onepage.php
app/code/local/Demac/Checkout/Block/Onepage/Billing.php
app/code/local/Demac/Checkout/Block/Onepage/Shipping.php
app/code/local/Demac/Checkout/controllers/OnepageController.php
app/code/local/Demac/Checkout/etc/config.xml
app/etc/modules/Demac_Checkout.xml

Step 2:

For the first file, we’re going to extend Mage_Checkout_Block_Onepage in order to edit the getSteps() and getActiveSteps() functions.
In getSteps() we will be changing the the step codes to reflect our new order, and changing the active step in getActiveSteps() from “billing” to “shipping”.

app/code/local/Demac/Checkout/Block/Onepage.php

<?php

class Demac_Checkout_Block_Onepage extends Mage_Checkout_Block_Onepage
{
    public function getSteps()
    {
        $steps = array();

        if (!$this->isCustomerLoggedIn()) {
            $steps['login'] = $this->getCheckout()->getStepData('login');
        }

        $stepCodes = array('shipping', 'shipping_method', 'billing', 'review');

        foreach ($stepCodes as $step) {
            $steps[$step] = $this->getCheckout()->getStepData($step);
        }
        return $steps;
    }

    public function getActiveStep()
    {
        return $this->isCustomerLoggedIn() ? 'shipping' : 'login';
    }
}

Extend Mage_Checkout_Block_Onepage_Billing and add only the following construct method into the file.
app/code/local/Demac/Checkout/Block/Onepage/Billing.php

protected function _construct()
{	
	parent::_construct();	
	$this->getCheckout()->setStepData('billing', 'allow', false);	
}

Extend Mage_Checkout_Block_Onepage_Shipping and add only the following construct method into the file.
app/code/local/Demac/Checkout/Block/Onepage/Shipping.php

protected function _construct()
{		
	parent::_construct();
	if ($this->isCustomerLoggedIn()) {
		$this->getCheckout()->setStepData('shipping', 'allow', true);
	}
}

What will do is change the initial open step from Billing to Shipping.

Step 3:

Next we need to extend the OnePageContoller to change saveShippingMethodAction() and saveBillingAction().
app/code/local/Demac/Checkout/controllers/OnepageController.php

<?php
require_once 'Mage/Checkout/controllers/OnepageController.php';
class Demac_Checkout_OnepageController extends Mage_Checkout_OnepageController
{
	public function saveShippingMethodAction()
	{
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
			$data = $this->getRequest()->getPost('shipping_method', '');
			$result = $this->getOnepage()->saveShippingMethod($data);
			/*
			$result will have erro data if shipping method is empty
			*/
			if(!$result) {
				Mage::dispatchEvent('checkout_controller_onepage_save_shipping_method',
						array('request'=>$this->getRequest(),
							'quote'=>$this->getOnepage()->getQuote()));
				$this->getOnepage()->getQuote()->collectTotals();
				$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));

				$result['goto_section'] = 'billing';
			}
			$this->getOnepage()->getQuote()->collectTotals()->save();
			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
		}
	}

	public function saveBillingAction()
	{
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
	//            $postData = $this->getRequest()->getPost('billing', array());
	//            $data = $this->_filterPostData($postData);
			$billing_data = $this->getRequest()->getPost('billing', array());
			$customerAddressId = $this->getRequest()->getPost('billing_address_id', false);

			if (isset($data['email'])) {
				$data['email'] = trim($data['email']);
			}
			$billing_result = $this->getOnepage()->saveBilling($billing_data, $customerAddressId);

			$payment_data = $this->getRequest()->getPost('payment', array());
			$payment_result = $this->getOnepage()->savePayment($payment_data);

			$redirectUrl = $this->getOnepage()->getQuote()->getPayment()->getCheckoutRedirectUrl();

			if (empty($billing_result['error']) && empty($payment_data['error']) && !$redirectUrl) {
				$this->loadLayout('checkout_onepage_review');
				$result['goto_section'] = 'review';
				$result['update_section'] = array(
					'name' => 'review',
					'html' => $this->_getReviewHtml()
				);
			}
			if ($redirectUrl) {
				$result['redirect'] = $redirectUrl;
			}

			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
		}
	}
}

In saveShippingMethodAction() we’re just changing the next step from payment to billing. In saveBillingAction() we’re changing the next step as well, but we’re also submitting the payment data that we are now including within the billing step for the Magento One Step Checkout.

Step 4:

Now we will just set up the standard rewrites in our config file.

app/code/local/Demac/Checkout/etc/config.xml
<?xml version="1.0" ?>
<config>
    <modules>
        <Demac_Checkout>
            <version>0.0.1</version>
        </Demac_Checkout>
    </modules>
    <global>
        <blocks>
            <checkout>
                <rewrite>
                    <onepage>Demac_Checkout_Block_Onepage</onepage>
                    <onepage_shipping>Demac_Checkout_Block_Onepage_Shipping</onepage_shipping>
                    <onepage_billing>Demac_Checkout_Block_Onepage_Billing</onepage_billing>
                </rewrite>
            </checkout>
        </blocks>
    </global>
    <frontend>
        <routers>
            <checkout>
                <args>
                    <modules>
                        <Demac_Checkout before="Mage_Checkout">Demac_Checkout</Demac_Checkout>
                    </modules>
                </args>
            </checkout>
        </routers>
    </frontend>
</config>
app/etc/modules/Demac_Checkout.xml
<?xml version='1.0' ?>
<config>
    <modules>
        <Demac_Checkout>
            <active>true</active>
            <codePool>local</codePool>
        </Demac_Checkout>
    </modules>
</config>

For the following, we need to make a few changes to opcheckout.js.
skin/frontend/enterprise/mobile/js/opcheckout.js
Change
this.steps = ['login', 'billing', 'shipping', 'shipping_method', 'payment', 'review'];
to
this.steps = ['login', 'shipping', 'shipping_method', 'billing', 'review'];

Within setMethod: function() on line 96, change both occurences of this.gotoSection(‘billing’) to this.gotoSection(‘shipping)

Add checkout.setShippingMethod() under checkout.reloadProgressBlock() line 611

Remove checkout.setShippingMethod() on line 619

Within the review save function on page 856 change the line
var params = Form.serialize(payment.form);
to
var params = Form.serialize(billing.form);

Step 5:

Now we’ll need to insert the payment template as a child of the billing template in order to insert it within the billing step.
app/design/frontend/enterprise/daddies/layout/local.xml

<checkout_onepage_index>	
	<reference name="content">
		<remove name="checkout.onepage" />
		<block type="checkout/onepage" name="new.checkout.onepage" template="checkout/onepage.phtml">
			<block type="checkout/onepage_login" name="checkout.onepage.login" as="login" template="checkout/onepage/login.phtml">
				<block type="page/html_wrapper" name="checkout.onepage.login.before" as="login_before" translate="label">
					<label>Login/Registration Before</label>
					<action method="setMayBeInvisible"><value>1</value></action>
				</block>
			</block>
			<block type="checkout/onepage_shipping" name="checkout.onepage.shipping" as="shipping" template="checkout/onepage/shipping.phtml"/>			
			<block type="checkout/onepage_billing" name="checkout.onepage.billing" as="billing" template="checkout/onepage/billing.phtml">
				<block type="checkout/onepage_payment" name="checkout.onepage.payment" as="payment" template="checkout/onepage/payment.phtml">
					<block type="checkout/onepage_payment_methods" name="checkout.payment.methods" as="methods" template="checkout/onepage/payment/methods.phtml">
						<action method="setMethodFormTemplate"><method>purchaseorder</method><template>payment/form/purchaseorder.phtml</template></action>
					</block>
				</block>
			</block>
			<block type="checkout/onepage_shipping_method" name="checkout.onepage.shipping_method" as="shipping_method" template="checkout/onepage/shipping_method.phtml">
				<block type="checkout/onepage_shipping_method_available" name="checkout.onepage.shipping_method.available" as="available" template="checkout/onepage/shipping_method/available.phtml"/>
				<block type="checkout/onepage_shipping_method_additional" name="checkout.onepage.shipping_method.additional" as="additional" template="checkout/onepage/shipping_method/additional.phtml"/>
			</block>
			<block type="checkout/onepage_review" name="checkout.onepage.review" as="review" template="checkout/onepage/review.phtml"/>
		</block>
	</reference>
</checkout_onepage_index>

Copy the /checkout/onepage/billing.phtml file into your design theme and add getChildHtml('payment'); ?> just before the form submit button. Then copy /checkout/onepage/shipping.phtml
into your theme and remove the wrapping form element and submit buttons. This way, the payment fields will now be added into the billing form and will all be submitted together.

Done!

Pending any styling, the new streamlined Magento One Step Checkout process is now functional!

Related: Mini-tutorial: Hole-Punching Configurable Product Options