Totals in Magento are rather basic and on several projects it is required to change their design, sometimes even their data. Here is how to do that.


What's happening

Totals generation happens in the the Mage_Checkout_Block_Cart_Totals class. At first, Magento calls the renderTotals() method. This method gathers all totals to be diplayed then, for each of them, calls the renderTotal() method. At last, in order to check which renderer to use for each total, the _getTotalRenderer() method is called. This is the one that catches our interest, particularly those lines :

    // ...
    $blockName = $code.'_total_renderer';
    $block = $this->getLayout()->getBlock($blockName);
    // ...

This is where Magento attaches a renderer block to the total based on its code. So if total code is shipping, Magento will use the block which name is shipping_total_renderer.

Then, in order to use a renderer that is not the native one, we just have to:

  • Update the layout to use a block with a name that fits to the one we want to change
  • Create a new block class extending the original total block class
  • Update the code of this new class depending on our needs

Here is, for each native Magento total, its original name and its attached class which will then be extended:

"name" of block Class
grand_total_total_renderer Mage_Tax_Block_Checkout_Grandtotal
subtotal_total_renderer Mage_Tax_Block_Checkout_Subtotal
discount_total_renderer Mage_Tax_Block_Checkout_Discount
shipping_total_renderer Mage_Tax_Block_Checkout_Shipping
tax_total_renderer Mage_Tax_Block_Checkout_Tax

Example : change the shipping total line

In this example, we will:

  • Change the "Shipping" title to "Shipping (Price from)"
  • Add a template displaying this text: "Free shipping from 100€"

Requirements

  • A declared module (Herve_TotalRenderer in this example)
  • In its declaration (app/etc/modules), dependencies to Mage_Checkout and Mage_Tax must exist.
  • This module must also have blocks, helpers and some layout and translate declared for the frontend

Layout update

We are now updating the layout for the checkout_cart_index handle by declaring a specific block for shipping_total_renderer and by adding a child block named shipping.free.

<?xml version="1.0"?>  
<layout>  
    <checkout_cart_index>
        <reference name="checkout.cart">
            <block type="totalrenderer/tax_checkout_shipping" name="shipping_total_renderer" as="shipping_total_renderer">
                <block type="core/template" name="shipping.free" as="shipping_free" template="totalrenderer/checkout/cart/shipping/free.phtml"/>
            </block>
        </reference>
    </checkout_cart_index>
</layout>  

Create the new renderer class

We can see in the former XML code that a block of type totalrenderer/tax_checkout_shipping is called. We will then create its class. Please not that this new class extends the Mage_Tax_Block_Checkout_Shipping class.

class Herve_TotalRenderer_Block_Tax_Checkout_Shipping extends Mage_Tax_Block_Checkout_Shipping {  
}

And as we want to update this total title, we will then (see template file /app/design/frontend/base/default/template/tax/checkout/shipping.phtml):

  • Overload the two methods used to get the title when taxes are fully displayed: getIncludeTaxLabel() and getExcludeTaxLabel()
  • Add / overload the _beforeToHtml() method in order to change the total title when taxes are not fully displayed
class Herve_TotalRenderer_Block_Tax_Checkout_Shipping extends Mage_Tax_Block_Checkout_Shipping {

    /**
     * Get label for shipping include tax
     *
     * @return string
     */
    public function getIncludeTaxLabel()
    {
        $originalLabel = parent::getIncludeTaxLabel();
        return $originalLabel . $this->getAdditionalLabel();
    }

    /**
     * Get label for shipping exclude tax
     *
     * @return string
     */
    public function getExcludeTaxLabel()
    {
        $originalLabel = parent::getExcludeTaxLabel();
        return $originalLabel . $this->getAdditionalLabel();
    }

    /**
     * Get additional label string
     *
     * @return string
     */
    public function getAdditionalLabel()
    {
        return ' ' . $this->__('(Price from)');
    }

    /**
     * Update total title with our additional string
     *
     * @return void
     */
    protected function _beforeToHtml()
    {
        $originalLabel = $this->getTotal()->getTitle();
        $this->getTotal()->setTitle($originalLabel . $this->getAdditionalLabel());
    }
}

We then update the CSV translation file (in French here):

"(Price from)","(A partir de)"

Adding the "Free shipping from 100€" text

As declared is the layout file above, we have created a block with the shipping_free alias. We must then inject it in the HTML rendering. There are several ways to do that. The one I chose uses the _toHtml() method. The idea is to get the original HTML code generated by the Mage_Tax_Block_Checkout_Shipping parent class and add the HTML code of our shipping_free child block.
In the Herve_TotalRenderer_Block_Tax_Checkout_Shipping class, we will then add the code below:

/**
 * Add our additional "shipping_free" block
 *
 * @return string
 */
protected function _toHtml()  
{
    $html = parent::_toHtml();
    $html .= $this->getChildHtml('shipping_free');
    return $html;
}

We also must manage our template file (/app/design/frontend/base/default/template/totalrenderer/checkout/cart/shipping/free.phtml):

<tr>  
    <td colspan="100">
        <?php echo $this->__('Free shipping from %s', Mage::helper('core')->currency(100, true, false));?>
    </td>
</tr>  

And update the CSV translation file (in French):

"Free shipping from %s","Livraison offerte à partir de %s"

Result (with the French translation)

Total renderer

In the checkout

We do it the same way. Files used are the same. There is then no change to do in the code. We just have to update our layout file in order to declare the renderer. Here is the updated layout file:

<?xml version="1.0"?>

<layout version="0.1.0">

    <checkout_cart_index>
        <reference name="checkout.cart">
            <block type="totalrenderer/tax_checkout_shipping" name="shipping_total_renderer" as="shipping_total_renderer">
                <block type="core/template" name="shipping.free" as="shipping_free" template="totalrenderer/checkout/cart/shipping/free.phtml"/>
            </block>
        </reference>
    </checkout_cart_index>

    <checkout_onepage_review>
        <reference name="checkout.onepage.review.info.totals">
            <block type="totalrenderer/tax_checkout_shipping" name="shipping_total_renderer" as="shipping_total_renderer">
                <block type="core/template" name="shipping.free" as="shipping_free" template="totalrenderer/checkout/cart/shipping/free.phtml"/>
            </block>
        </reference>
    </checkout_onepage_review>
</layout>  

Example module

An example module is available here: https://github.com/herveguetin/Herve_TotalRenderer.