Magento Onepage Checkout – Add New Step

Banners
In this blog post, we are going to see how to add a new step to magento onepage checkout.

We will see 3 different scenario’s i.e adding a new step which is the first step, adding a new step in middle, adding a dynamic step. This blog is a continuation of previous blog written to add custom fields to magento checkout. This blog is pretty long and complex so i am attaching the source files here.
Error... Unable to load download template. Search single-download-template.tpl in your plugin folder!

Adding New Step at First Position

So here we are going to add a new step just after login step or if user is already logged ours will be the first step. The title of step added is “Excellence Blog Review” and id of step would be “excellence”, the step will have a single drop down field. The code for saving this field to database is not explained in this blog, as it was already covered in the previous blog.

Checkout Page With New Step Added

Checkout Page With New Step Added


Now lets see the steps to make this happen Here the module name i am using is Excellence_Custom
Step1: Creating the HTML
The simplest step as always, first we will create our phtml file excellence.phtml at location custom\checkout\onepage\excellence.phtml. This file contains the html code for our new tab and field in the tab.

<form id="co-excellence-form" action="">
<fieldset>
    <ul class="form-list">
    <li id="excellence-form">
        <fieldset>
            <ul>
                <li class="wide">
                    <label for="excellence:like" class="required"><em>*</em><?php echo $this->__('Do you like Excellence Magento Blog?') ?></label>
                    <div class="input-box">
                        <select class="required-entry" name="excellence[like]" id="excellence:like">
                        	<option value=''><?php echo $this->__('Please Choose..');?></option>
                        	<option value='1' <?php if($this->getQuote()->getExcellenceLike() == 1){echo 'selected="selected"';} ?>><?php echo $this->__('Yes');?></option>
                        	<option value='2' <?php if($this->getQuote()->getExcellenceLike() == 2){echo 'selected="selected"';} ?>><?php echo $this->__('No');?></option>
                        </select>
                    </div>
                </li>
            </ul>
        </fieldset>
     </li>
    </ul>
    <div class="buttons-set" id="excellence-buttons-container">
        <p class="required"><?php echo $this->__('* Required Fields') ?></p>
        <button type="button" title="<?php echo $this->__('Continue') ?>" class="button" onclick="excellence.save()"><span><span><?php echo $this->__('Continue') ?></span></span></button>
        <span class="please-wait" id="excellence-please-wait" style="display:none;">
            <img src="<?php echo $this->getSkinUrl('images/opc-ajax-loader.gif') ?>" alt="<?php echo $this->__('Loading next step...') ?>" title="<?php echo $this->__('Loading next step...') ?>" class="v-middle" /> <?php echo $this->__('Loading next step...') ?>
        </span>
    </div>
</fieldset>
</form>
<script type="text/javascript">
//<![CDATA[
    var excellence = new ExcellenceMethod('co-excellence-form','<?php echo $this->getUrl('custom/onepage/saveExcellence') ?>');
    var excellenceForm = new VarienForm('co-excellence-form');
//]]>
</script>

I have added here a new drop down field. For your tab, just replace the name excellence with your tab name. The field id’s and class names are important for proper working on onestep checkout’s javascript features. Another thing to note here is the “new ExcellenceMethod” javascript class created, which i will show later. Next we need to have our own onepage.phtml file as at location custom/checkout/onepage.phtml. This file will replace the default checkout/onepage.phtml file.

<div class="page-title">
    <h1><?php echo $this->__('Checkout') ?></h1>
</div>
<script type="text/javascript" src="<?php echo $this->getJsUrl('varien/accordion.js') ?>"></script>
<script type="text/javascript" src="<?php echo $this->getSkinUrl('js/opcheckout.js') ?>"></script>
<!-- New Code Added Below  -->
<script type="text/javascript" src="<?php echo $this->getSkinUrl('js/excellencecheckout.js') ?>"></script>
<script type="text/javascript">countryRegions = <?php echo $this->helper('directory')->getRegionJson() ?></script>
<ol class="opc" id="checkoutSteps">
<?php $i=0; foreach($this->getSteps() as $_stepId => $_stepInfo): ?>
<?php if (!$this->getChild($_stepId) || !$this->getChild($_stepId)->isShow()): continue; endif; $i++ ?>
    <li id="opc-<?php echo $_stepId ?>" class="section<?php echo !empty($_stepInfo['allow'])?' allow':'' ?><?php echo !empty($_stepInfo['complete'])?' saved':'' ?>">
        <div class="step-title">
            <span class="number"><?php echo $i ?></span>
            <h2><?php echo $_stepInfo['label'] ?></h2>
            <a href="#"><?php echo $this->__('Edit') ?></a>
        </div>
        <div id="checkout-step-<?php echo $_stepId ?>" class="step a-item" style="display:none;">
            <?php echo $this->getChildHtml($_stepId) ?>
        </div>
    </li>
<?php endforeach ?>
</ol>
<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 ?>
    //New Code Added Below
    var checkout = new Excellence(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>

So if you look at code and compare it with default onepage.phtml file, i have included a new javascript file called excellencecheckout.js and instead of “new Checkout” i have used “new Excellence”. Next we need to change the html of the progress.phtml file (The progress blocks which show on the right side). For this i have created my own progress.phtml file at location custom/checkout/onepage/progress.phtml. Since i am adding the step at first position, i will add the below code before the billing step

<dl>
        <?php if ($this->getCheckout()->getStepData('excellence', 'is_show')): ?>
        <?php if($this->getCheckout()->getStepData('excellence', 'complete')): ?>
            <dt class="complete">
                <?php echo $this->__('Excellence Review') ?> <span class="separator">|</span> <a href="#excellence" onclick="checkout.accordion.openSection('opc-excellence'); return false;"><?php echo $this->__('Change') ?></a>
            </dt>
            <dd class="complete">
                <div>
                	<?php echo $this->__('Excellence Review Is: ');?>
                	<?php 
                		$like = $this->getQuote()->getExcellenceLike();
                		if($like == 1){
                			echo $this->__('Yes');
                		} else{
                			echo $this->__('No');
                		}
                	?>
                </div>
            </dd>
        <?php else: ?>
            <dt>
                <?php echo $this->__('Excellence Review') ?>
            </dt>
        <?php endif; ?>
        <?php endif; ?>

The full source of this file is there in the module attached.
Next we need to include these files in our layout xml file, so in my module layout file custom.xml file i have added code

<?xml version="1.0"?>
<layout version="0.1.0">
    <sales_order_view> <!-- Code From Previous Module -->
        <reference name="my.account.wrapper">
            <block type="custom/custom_order" name="custom.order" template="custom/order.phtml" after='sales.order.info' />
        </reference>
    </sales_order_view>
    <checkout_onepage_index> <!-- Adding our new step to onepage block  -->
    	<reference name='checkout.onepage'>
    		 <action method='setTemplate'><template>custom/checkout/onepage.phtml</template></action>
    		 <block type="custom/checkout_onepage_excellence" name="checkout.onepage.excellence" as="excellence" template="custom/checkout/onepage/excellence.phtml"/> <!-- Here we change the template of onepage block to our custom template -->
    	</reference>
    	<reference name='checkout.progress'> <!-- Change the template of progress block -->
    	      <action method='setTemplate'><template>custom/checkout/onepage/progress.phtml</template></action>
    	</reference> <!-- Here we change the template of the progress block to our custom template created -->
    </checkout_onepage_index>
    <checkout_onepage_progress>  <!-- Change the template of progress block -->
    	<reference name='root'>
    		<action method='setTemplate'><template>custom/checkout/onepage/progress.phtml</template></action>
    	</reference>
    </checkout_onepage_progress>
</layout> 

Now we have created all the required html files and made the changes in layout files to include them in the checkout page.
Step2: Javascript Classes
In the previous code we have included one new javascipt file excellencecheckout.js and created two new javascript classes Excellence and ExcellenceMethod. So first i will create the javascript file excellencecheckout.js in my skin folder at location skin\frontend\default\default\js\excellencecheckout.js and put the code shown below.
First lets look at the class Excellence, which we used in the new onepage.phtml file. The class Excellence actually replaces the default magento Checkout class. If you look at onepage.phtml this is the code i had written is

var checkout = new Excellence(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') ?>'}
    );

and the default onepage.phtml file has

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') ?>'}
    );

So basically, this Excellence class replaces the Checkout class. So using prototype.js object inheritance i have created the new Excellence class and changed only what is required.

var Excellence = Class.create(Checkout, {
	initialize: function($super,accordion, urls){
		$super(accordion, urls);
		//New Code Addded
		this.steps = ['login', 'excellence' ,'billing', 'shipping', 'shipping_method', 'payment', 'review'];
	},
	setMethod: function(){
	    if ($('login:guest') && $('login:guest').checked) {
	        this.method = 'guest';
	        var request = new Ajax.Request(
	            this.saveMethodUrl,
	            {method: 'post', onFailure: this.ajaxFailure.bind(this), parameters: {method:'guest'}}
	        );
	        Element.hide('register-customer-password');
	        this.gotoSection('excellence'); //New Code Here
	    }
	    else if($('login:register') && ($('login:register').checked || $('login:register').type == 'hidden')) {
	        this.method = 'register';
	        var request = new Ajax.Request(
	            this.saveMethodUrl,
	            {method: 'post', onFailure: this.ajaxFailure.bind(this), parameters: {method:'register'}}
	        );
	        Element.show('register-customer-password');
	        this.gotoSection('excellence'); //New Code Here
	    }
	    else{
	        alert(Translator.translate('Please choose to register or to checkout as a guest'));
	        return false;
	    }
	}
});

So as you can see, i have added my new step “excellence” in the javascript file and also changed the code so that, in the login step when the user clicks on continue button, it goes to the “excellence” step.
Next we will look at ExcellenceMethod class, which was used in the excellence.phtml (new step phtml file). The ExellenceMethod class is used for the ajax operations, to save the drop down value of our new step. The code we have used in excellence.phtml file is

<script type="text/javascript">
//<![CDATA[
    var excellence = new ExcellenceMethod('co-excellence-form','<?php echo $this->getUrl('custom/onepage/saveExcellence') ?>');
    var excellenceForm = new VarienForm('co-excellence-form');
//]]>
</script>

As you can see here, we are using URL custom/onepage/saveExcellence in the ajax. the code ExcellenceMethod class is

var ExcellenceMethod = Class.create();
ExcellenceMethod.prototype = {
    initialize: function(form, saveUrl){
        this.form = form;
        if ($(this.form)) {
            $(this.form).observe('submit', function(event){this.save();Event.stop(event);}.bind(this));
        }
        this.saveUrl = saveUrl;
        this.validator = new Validation(this.form);
        this.onSave = this.nextStep.bindAsEventListener(this);
        this.onComplete = this.resetLoadWaiting.bindAsEventListener(this);
    },

    validate: function() {
        if(!this.validator.validate()) {
            return false;
        }
        return true;
    },

    save: function(){

        if (checkout.loadWaiting!=false) return;
        if (this.validate()) {
            checkout.setLoadWaiting('excellence');
            var request = new Ajax.Request(
                this.saveUrl,
                {
                    method:'post',
                    onComplete: this.onComplete,
                    onSuccess: this.onSave,
                    onFailure: checkout.ajaxFailure.bind(checkout),
                    parameters: Form.serialize(this.form)
                }
            );
        }
    },

    resetLoadWaiting: function(transport){
        checkout.setLoadWaiting(false);
    },

    nextStep: function(transport){
        if (transport && transport.responseText){
            try{
                response = eval('(' + transport.responseText + ')');
            }
            catch (e) {
                response = {};
            }
        }

        if (response.error) {
            alert(response.message);
            return false;
        }

        if (response.update_section) {
            $('checkout-'+response.update_section.name+'-load').update(response.update_section.html);
        }


        if (response.goto_section) {
            checkout.gotoSection(response.goto_section);
            checkout.reloadProgressBlock();
            return;
        }

        checkout.setBilling();
    }
}

Both these classes code is written in the excellencecheckout.js file.
Step3: Checkout and Step Block
Next we need to create blocks for our phtml file. The 2 blocks to create are Excellence_Custom_Block_Checkout_Onepage
Excellence_Custom_Block_Checkout_Onepage_Excellence
The block Excellence_Custom_Block_Checkout_Onepage will override the default Onepage.php block of magento.
So to override open your config.xml file and add

<blocks>
        	<checkout>
        		<rewrite>
        			<onepage>Excellence_Custom_Block_Checkout_Onepage</onepage>
        		</rewrite>
        	</checkout>
        </blocks>

and the code in class Onepage.php would be

<?php
class Excellence_Custom_Block_Checkout_Onepage extends Mage_Checkout_Block_Onepage{

	public function getSteps()
	{
		$steps = array();

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

		//New Code Adding step excellence here
		$stepCodes = array('excellence','billing', 'shipping', 'shipping_method', 'payment', 'review');

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

	public function getActiveStep()
	{
		//New Code, make step excellence active when user is already logged in
		return $this->isCustomerLoggedIn() ? 'excellence' : 'login';
	}

}

Next the code in our step block Excellence_Custom_Block_Checkout_Onepage_Excellence would be

<?php
class Excellence_Custom_Block_Checkout_Onepage_Excellence extends Mage_Checkout_Block_Onepage_Abstract{
	protected function _construct()
	{
		$this->getCheckout()->setStepData('excellence', array(
            'label'     => Mage::helper('checkout')->__('Excelence Blog Review'),
            'is_show'   => $this->isShow()
		));
		if ($this->isCustomerLoggedIn()) {
			$this->getCheckout()->setStepData('excellence', 'allow', true);
			$this->getCheckout()->setStepData('billing', 'allow', false);
		}

		parent::_construct();
	}
}

Step4: Creating the Controller
Now we need to create the controller for our ajax url. The ajax url which is being called is custom/onepage/saveExcellence. So the controller file for this is created at location app\code\local\Excellence\Custom\controllers\OnepageController.php

<?php
require_once 'Mage/Checkout/controllers/OnepageController.php';
class Excellence_Custom_OnepageController extends  Mage_Checkout_OnepageController{
	public function saveExcellenceAction(){
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
			$data = $this->getRequest()->getPost('excellence', array());

			$result = $this->getOnepage()->saveExcellence($data);

			if (!isset($result['error'])) {
				$result['goto_section'] = 'billing';
			}

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

The code is very simple and the main part to focus on is $this->getOnepage()->saveExcellence($data) and also $result[‘goto_section’] = ‘billing’;
The goto_section specified which section to show after the data for current section is successfully saved.
Step4: Model
Now need to create the saveExcellence() function in the Onepage Model. So we will override the Onepage.php model class, for this in the modules config.xml put in this code

<models>
        	<checkout>
	            <rewrite>
             <type_onepage>Excellence_Custom_Model_Checkout_Type_Onepage</type_onepage>
	            </rewrite>
	        </checkout>
</models>

and code in our new Onepage.php file is

<?php
class Excellence_Custom_Model_Checkout_Type_Onepage extends Mage_Checkout_Model_Type_Onepage{
	public function saveExcellence($data){
		if (empty($data)) {
			return array('error' => -1, 'message' => $this->_helper->__('Invalid data.'));
		}
		$this->getQuote()->setExcellenceLike($data['like']);
		$this->getQuote()->collectTotals();
		$this->getQuote()->save();

		$this->getCheckout()
		->setStepData('excellence', 'allow', true)
		->setStepData('excellence', 'complete', true)
		->setStepData('billing', 'allow', true);

		return array();
	}
}

Here we are putting doing setExcellenceLike on the quote object, and as in my previous blog using Observer we will save this value to database. The detail code for this is found the module attached.
After this the new step would be fully functional.

Adding Step In Between

Now we will see how to add a step in between Shipping Step and Shipping Method Step. The title of the new step will be “Excelence2 Post Review” and its id will be “excellence2”.

Magento Checkout - New Step Added Between Shipping and Shipping Method

Magento Checkout - New Step Added Between Shipping and Shipping Method


Step1: Adding HTML
Now again we need to create the phtml file for our step. The name of file will be excellence2.phtml and location would be app\design\frontend\default\default\template\custom\checkout\onepage\excellence2.phtml

<form id="co-excellence2-form" action="">
<fieldset>
    <ul class="form-list">
    <li id="excellence2-form">
        <fieldset>
            <ul>
                <li class="wide">
                    <label for="excellence2:like" class="required"><em>*</em><?php echo $this->__('Do you like this post?') ?></label>
                    <div class="input-box">
                        <input type='text' name='excellence2[like]'  class="required-entry input-text" id="excellence2:like" value='<?php echo $this->getQuote()->getExcellenceLike2()?>'/>
                    </div>
                </li>
            </ul>
        </fieldset>
     </li>
    </ul>
    <div class="buttons-set" id="excellence2-buttons-container">
        <p class="required"><?php echo $this->__('* Required Fields') ?></p>
        <button type="button" title="<?php echo $this->__('Continue') ?>" class="button" onclick="excellence2.save()"><span><span><?php echo $this->__('Continue') ?></span></span></button>
        <span class="please-wait" id="excellence2-please-wait" style="display:none;">
            <img src="<?php echo $this->getSkinUrl('images/opc-ajax-loader.gif') ?>" alt="<?php echo $this->__('Loading next step...') ?>" title="<?php echo $this->__('Loading next step...') ?>" class="v-middle" /> <?php echo $this->__('Loading next step...') ?>
        </span>
    </div>
</fieldset>
</form>
<script type="text/javascript">
//<![CDATA[
    var excellence2 = new ExcellenceMethod2('co-excellence2-form','<?php echo $this->getUrl('custom/onepage/saveExcellence2') ?>');
    var excellenceForm2 = new VarienForm('co-excellence2-form');
//]]>
</script>

This time we have added a text field instead of drop down and used the javascript class ExcellenceMethod2. Since we have already create progress.phtml and onepage.phtml we don’t need to create it again, but we need to add our new step’s progress html to the progress.phtml. So between the two steps i will put this code

<?php if ($this->getCheckout()->getStepData('excellence2', 'is_show')): ?>
        <?php if($this->getCheckout()->getStepData('excellence2', 'complete')): ?>
            <dt class="complete">
                <?php echo $this->__('Excellence2 Review') ?> <span class="separator">|</span> <a href="#excellence2" onclick="checkout.accordion.openSection('opc-excellence2'); return false;"><?php echo $this->__('Change') ?></a>
            </dt>
            <dd class="complete">
                <div>
                	<?php echo $this->__('Excellence2 Review Is: ');?>
                	<?php 
                		$like = $this->getQuote()->getExcellenceLike2();
                		echo $like;
                	?>
                </div>
            </dd>
        <?php else: ?>
            <dt>
                <?php echo $this->__('Excellence2 Review') ?>
            </dt>
        <?php endif; ?>
        <?php endif; ?>

Next we need to add the excellence2.phtml to onepage block through the layout file. So we will add this code in the custom.xml layout file

<block type="custom/checkout_onepage_excellence2" name="checkout.onepage.excellence2" as="excellence2" template="custom/checkout/onepage/excellence2.phtml"/>

This block is added inside the “checkout.onepage” block.
Step2: Javascript
Now we will create the javascript code for our new step. We will create the class ExcellenceMethod2 in the previously created javascript file excellencecheckout.js

var ExcellenceMethod2 = Class.create();
ExcellenceMethod2.prototype = {
    initialize: function(form, saveUrl){
        this.form = form;
        if ($(this.form)) {
            $(this.form).observe('submit', function(event){this.save();Event.stop(event);}.bind(this));
        }
        this.saveUrl = saveUrl;
        this.validator = new Validation(this.form);
        this.onSave = this.nextStep.bindAsEventListener(this);
        this.onComplete = this.resetLoadWaiting.bindAsEventListener(this);
    },

    validate: function() {
        if(!this.validator.validate()) {
            return false;
        }
        return true;
    },

    save: function(){

        if (checkout.loadWaiting!=false) return;
        if (this.validate()) {
            checkout.setLoadWaiting('excellence2');
            var request = new Ajax.Request(
                this.saveUrl,
                {
                    method:'post',
                    onComplete: this.onComplete,
                    onSuccess: this.onSave,
                    onFailure: checkout.ajaxFailure.bind(checkout),
                    parameters: Form.serialize(this.form)
                }
            );
        }
    },

    resetLoadWaiting: function(transport){
        checkout.setLoadWaiting(false);
    },

    nextStep: function(transport){
        if (transport && transport.responseText){
            try{
                response = eval('(' + transport.responseText + ')');
            }
            catch (e) {
                response = {};
            }
        }

        if (response.error) {
            alert(response.message);
            return false;
        }

        if (response.update_section) {
            $('checkout-'+response.update_section.name+'-load').update(response.update_section.html);
        }


        if (response.goto_section) {
            checkout.gotoSection(response.goto_section);
            checkout.reloadProgressBlock();
            return;
        }

        checkout.setPayment();
    }
}

Rest other javascript changes are same as before.
Step3: Checkout and Step Block
The checkout block we have already created previously, we just need to modify it. We only need to change the $stepCodes array to

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

Next need to create a block for our new step. The block class is Excellence_Custom_Block_Checkout_Onepage_Excellence2

<?php
class Excellence_Custom_Block_Checkout_Onepage_Excellence2 extends Mage_Checkout_Block_Onepage_Abstract{
	protected function _construct()
	{
		$this->getCheckout()->setStepData('excellence2', array(
            'label'     => Mage::helper('checkout')->__('Excelence2 Post Review'),
            'is_show'   => $this->isShow()
		));
		parent::_construct();
	}
}

Here we are setting the title of our step.
Step4: Controller
In the onepage controller created previously, we need to add a new action i.e saveExcellence2Action. This is the action that is called in our phtml file through ajax.

public function saveExcellence2Action(){
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
			$data = $this->getRequest()->getPost('excellence2', array());

			$result = $this->getOnepage()->saveExcellence2($data);

			if (!isset($result['error'])) {
				$result['goto_section'] = 'shipping_method';
				$result['update_section'] = array(
                    'name' => 'shipping-method',
                    'html' => $this->_getShippingMethodsHtml()
				);
			}

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

In this action, we also set what would next step would be and its html.
Now, since this block is being placed in the middle of the site, we need to change the code of other steps as well, so that when other steps are successful our new step shows. Specifically, in our case we need to change the billing and shipping step. So when billing step is competed and user has chosen shipping address same as billing our step should open, and also when shipping step is completed our step should open. So for this we need to change the saveBillingAction and saveShippingAction function. So we will no override the default OnepageController with our OnepageController. To do this in the config.xml file put this code

<global>
    	<rewrite>
	        <test_cart> <!--This can be any unique id -->
	            <from><![CDATA[#^/checkout/onepage/#]]></from>  <!-- the URL which u want to override-->
	            <to>/custom/onepage/</to>  <!-- destination url -->
	        </test_cart>
	    </rewrite>
</global>

and in our OnepageController we will add these two actions

public function saveBillingAction()
	{
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
			//            $postData = $this->getRequest()->getPost('billing', array());
			//            $data = $this->_filterPostData($postData);
			$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'] = 'excellence2';  //Goes to our step
					$result['allow_sections'] = array('shipping');
					$result['duplicateBillingInfo'] = 'true';
				} else {
					$result['goto_section'] = 'shipping';
				}
			}

			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
		}
	}
	public function saveShippingAction()
	{
		if ($this->_expireAjax()) {
			return;
		}
		if ($this->getRequest()->isPost()) {
			$data = $this->getRequest()->getPost('shipping', array());
			$customerAddressId = $this->getRequest()->getPost('shipping_address_id', false);
			$result = $this->getOnepage()->saveShipping($data, $customerAddressId);

			if (!isset($result['error'])) {
				$result['goto_section'] = 'excellence2'; //Go to our step
			}
			$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
		}
	}

Step5: Model
Now like before, we need to create the saveExcellence2 function in Onepage model. This would be same as before, in our Onepage.php file add

public function saveExcellence2($data){
		if (empty($data)) {
			return array('error' => -1, 'message' => $this->_helper->__('Invalid data.'));
		}
		$this->getQuote()->setExcellenceLike2($data['like']);
		$this->getQuote()->collectTotals();
		$this->getQuote()->save();

		$this->getCheckout()
		->setStepData('excellence2', 'allow', true)
		->setStepData('excellence2', 'complete', true)
		->setStepData('billing', 'allow', true);

		return array();
	}

Now, this step is fully working.

Adding A Dynamic Step

Here we will add a new step after the Payment Step. This step will be dynamic, meaning the content would depend on our previous steps data. So in our step, i will show two different texts based on the shipping method. So if shipping method is Flate Rate then text shown will be ‘We are shipping your products though Express Delivery, the product will reach you in 3days’, or else text shown will be ‘We are shipping your products though Normal Delivery, the product will reach you in 1weeks time’.

Magento Onestep Checkout - Dynamic Step

Magento Onestep Checkout - Dynamic Step


Step1: Adding HTML
So again we need to create the phtml for our step, but this our step html is divided into two parts: 1. Static Part (buttons, divs) 2. Dynamic Part (The text message). Accordingly, we need to divide our html. We will create a phtml file called excellence3.phtml at location app\design\frontend\default\default\template\custom\checkout\onepage\excellence3.phtml

<form id="co-excellence3-form" action="">
<fieldset>
    <ul class="form-list">
    <li id="checkout-excellence3-load">  <!-- This id format is very important -->
    	<?php echo $this->getChildHtml('info');?>
     </li>
    </ul>
    <div class="buttons-set" id="excellence3-buttons-container">
        <p class="required"><?php echo $this->__('* Required Fields') ?></p>
        <button type="button" title="<?php echo $this->__('Continue') ?>" class="button" onclick="excellence3.save()"><span><span><?php echo $this->__('Continue') ?></span></span></button>
        <span class="please-wait" id="excellence3-please-wait" style="display:none;">
            <img src="<?php echo $this->getSkinUrl('images/opc-ajax-loader.gif') ?>" alt="<?php echo $this->__('Loading next step...') ?>" title="<?php echo $this->__('Loading next step...') ?>" class="v-middle" /> <?php echo $this->__('Loading next step...') ?>
        </span>
    </div>
</fieldset>
</form>
<script type="text/javascript">
//<![CDATA[
    var excellence3 = new ExcellenceMethod3('<?php echo $this->getUrl('custom/onepage/saveExcellence3') ?>');
//]]>
</script>

The main difference here is

<li id="checkout-excellence3-load">  <!-- This id format is very important -->
    	<?php echo $this->getChildHtml('info');?>
     </li>

We have added the dynamic part as a child block and the id of the element which has the child block is “checkout-stepid-load”This id format is important or else the checkout won’t work.
Next we will define our child block, filename info.phtml at location app\design\frontend\default\default\template\custom\checkout\onepage\excellence3\info.phtml

<fieldset>
        <?php
        $shippingMethod = $this->getQuote()->getShippingAddress()->getShippingMethod();
        if($shippingMethod == 'flatrate_flatrate'){
        echo 'We are shipping your products though Express Delivery, the product will reach you in 3days';
        }else{
        echo 'We are shipping your products though Normal Delivery, the product will reach you in 1weeks time';
        }
        ?>
            
</fieldset>

Now, we will add these phtml files to out layout. The below code is inside the checkout.onepage block as usual

<block type="custom/checkout_onepage_excellence3" name="checkout.onepage.excellence3" as="excellence3" template="custom/checkout/onepage/excellence3.phtml">
    		 		<block type="custom/checkout_onepage_excellence3" name="checkout.onepage.excellence3.info" as="excellence3.info" template="custom/checkout/onepage/excellence3/info.phtml" />
    		 </block>

the new code for our dynamic block is

<checkout_onepage_excellence3>
        <!-- Mage_Checkout -->
        <remove name="right"/>
        <remove name="left"/>

        <block type="custom/checkout_onepage_excellence3" name="root" output="toHtml" template="custom/checkout/onepage/excellence3/info.phtml"/>
    </checkout_onepage_excellence3>

Step2: Javascript
The javascript here is very simple, since we don’t have any form so we don’t save any data. So our ajax will simple get the html for the next review step and display it. The code written in our step phtml file, excellence.phtml is

var excellence3 = new ExcellenceMethod3('<?php echo $this->getUrl('custom/onepage/saveExcellence3') ?>');

and the ExcellenceMethod3 is

var ExcellenceMethod3 = Class.create();
ExcellenceMethod3.prototype = {
    initialize: function(saveUrl){
        this.saveUrl = saveUrl;
        this.onSave = this.nextStep.bindAsEventListener(this);
        this.onComplete = this.resetLoadWaiting.bindAsEventListener(this);
    },
    save: function(){

        if (checkout.loadWaiting!=false) return;
            checkout.setLoadWaiting('excellence3');
            var request = new Ajax.Request(
                this.saveUrl,
                {
                    method:'post',
                    onComplete: this.onComplete,
                    onSuccess: this.onSave,
                    onFailure: checkout.ajaxFailure.bind(checkout)
                }
            );
    },

    resetLoadWaiting: function(transport){
        checkout.setLoadWaiting(false);
    },

    nextStep: function(transport){
        if (transport && transport.responseText){
            try{
                response = eval('(' + transport.responseText + ')');
            }
            catch (e) {
                response = {};
            }
        }

        if (response.error) {
            alert(response.message);
            return false;
        }

        if (response.update_section) {
            $('checkout-'+response.update_section.name+'-load').update(response.update_section.html);
        }


        if (response.goto_section) {
            checkout.gotoSection(response.goto_section);
            checkout.reloadProgressBlock();
            return;
        }

        checkout.setReview();
    }
}

Step3: Controller
Now in our OnepageController, first we need to change the implementation of savePaymentAction(), so that it shows our new tab instead of review tab.

public function savePaymentAction()
	{
		if ($this->_expireAjax()) {
			return;
		}
		try {
			if (!$this->getRequest()->isPost()) {
				$this->_ajaxRedirectResponse();
				return;
			}

			// set payment to quote
			$result = array();
			$data = $this->getRequest()->getPost('payment', array());
			$result = $this->getOnepage()->savePayment($data);

			if (empty($result['error'])) {
				$result['goto_section'] = 'excellence3';
				$result['update_section'] = array(
                    'name' => 'excellence3',
                    'html' => $this->_getExcellence3Html()
				);
			}

		} catch (Mage_Payment_Exception $e) {
			if ($e->getFields()) {
				$result['fields'] = $e->getFields();
			}
			$result['error'] = $e->getMessage();
		} catch (Mage_Core_Exception $e) {
			$result['error'] = $e->getMessage();
		} catch (Exception $e) {
			Mage::logException($e);
			$result['error'] = $this->__('Unable to set Payment Method.');
		}
		$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
	}
protected function _getExcellence3Html()
	{
		$layout = $this->getLayout();
		$update = $layout->getUpdate();
		$update->load('checkout_onepage_excellence3');
		$layout->generateXml();
		$layout->generateBlocks();
		$output = $layout->getOutput();
		return $output;
	}

As you can see in the code, dynamic html is called here using the _getExcellence3Html() function. Next we need to add the function for saveExcellence3Action as well, this will simple get the review step html and show it.

public function saveExcellence3Action(){
		if ($this->_expireAjax()) {
			return;
		}

		// get section and redirect data
		$redirectUrl = $this->getOnepage()->getQuote()->getPayment()->getCheckoutRedirectUrl();

		if (!isset($result['error'])) {
			$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));
	}

Step4: Block
Next we need to create the Excellence3.php block as before.

<?php
class Excellence_Custom_Block_Checkout_Onepage_Excellence3 extends Mage_Checkout_Block_Onepage_Abstract{
	protected function _construct()
	{
		$this->getCheckout()->setStepData('excellence3', array(
            'label'     => Mage::helper('checkout')->__('Excellence  Review'),
            'is_show'   => $this->isShow()
		));
		parent::_construct();
	}
}

This is all is required to make this step functional.

In this blog, we have seen in very detail how to add steps to existing magento onepage checkout. We have seen 3 different examples, hopefully all code was covered in the blog. This module has been tested in magento version 1.6, but i see now reason why it shouldn’t work in other magento version. No core files have been changed, not even javascript files. Please comment on the blog if anything is missing or not working.