In this article I will show how to add a new Total row e.g. "Insurance" to Magento Shopping cart / Checkout.
Example:
If you need to add an additional fee to your total price, you need to add your own total item to the collection of totals.
To do this you need to add the following code to your main config.xml file:
<global> <sales> <quote> <totals> <turnkeye_insurance> <class>turnkeye_insurance/total_screen_quote</class> <after>subtotal</after> <before>tax</before> </turnkeye_insurance> </totals> </quote> <order_invoice> <totals> <turnkeye_insurance> <class>turnkeye_insurance/total_screen_invoice</class> <after>subtotal</after> <before>tax</before> </turnkeye_insurance> </totals> </order_invoice> <order_creditmemo> <totals> <turnkeye_insurance> <class>turnkeye_insurance/total_screen_creditmemo</class> <after>subtotal</after> <before>tax</before> </turnkeye_insurance> </totals> </order_creditmemo> </sales> </global>
Here we declare "total items" in 3 collections in Magento system: 1 - quote, 2 - invoice and 3 - creditmemo.
So the total price of the quote, invoice and creditmemo will have additional fee (which you will add in the corresponded classes).
In this config we can inform Magento in which order our total will be calculated, so you can calculate something after shipping or tax calculation stage. E.g. if you will need shipping or tax cost in your further calculations. However, it does not affect the order of totals in customer front-end. To change the position of the total in the totals table, you need to insert this code:
<default> <sales> <totals_sort> <turnkeye_insurance>15</turnkeye_insurance> </totals_sort> </sales> </default>
First class is 'turnkeye_insurance/total_insurance_quote':
class Turnkeye_Insurance_Model_Total_Insurance_Quote extends Mage_Sales_Model_Quote_Address_Total_Abstract { public function __construct() { $this->setCode('turnkeye_insurance'); } /** * Get label * * @return string */ public function getLabel() { return Mage::helper('turnkeye_insurance')->__('Insurance'); } /** * Collect totals information about insurance * * @param Mage_Sales_Model_Quote_Address $address */ public function collect(Mage_Sales_Model_Quote_Address $address) { parent::collect($address); if (($address->getAddressType() == 'billing')) { return $this; } $amount = INSURANCE_FEE; if ($amount) { $this->_addAmount($amount); $this->_addBaseAmount($amount); } return $this; } /** * Add giftcard totals information to address object * * @param Mage_Sales_Model_Quote_Address $address */ public function fetch(Mage_Sales_Model_Quote_Address $address) { if (($address->getAddressType() == 'billing')) { $amount = INSURANCE_FEE; if ($amount != 0) { $address->addTotal(array( 'code' => $this->getCode(), 'title' => $this->getLabel(), 'value' => $amount )); } } return $this; } }
Total collection is calculated for each address in the quote, that is why we need to add our fee only if we calculate total for billing address, in other case the fee will be added twice. Also if you have multi-currency store you will need to calculate base amount with rate and after that add this amount to base total.
Invoice and creditmemo total classes are very similar:
class Turnkeye_Insurance_Model_Total_Insurance_Creditmemo extends Mage_Sales_Model_Order_Creditmemo_Total_Abstract { public function collect(Mage_Sales_Model_Order_Creditmemo $creditmemo) { $order = $creditmemo->getOrder(); $amount = INSURANCE_FEE; if ($amount) { $creditmemo->setGrandTotal($creditmemo->getGrandTotal() + $amount); $creditmemo->setBaseGrandTotal($creditmemo->getBaseGrandTotal() + $amount); } return $this; } }
class Turnkeye_Insurance_Model_Total_Screen_Invoice extends Mage_Sales_Model_Order_Invoice_Total_Abstract { public function collect(Mage_Sales_Model_Order_Invoice $invoice) { $order = $invoice->getOrder(); $amount = INSURANCE_FEE; if ($amount) { $invoice->setGrandTotal($invoice->getGrandTotal() + $amount); $invoice->setBaseGrandTotal($invoice->getBaseGrandTotal() + $amount); } return $this; } }
Here we need to add a fee, so the total value will be the same as in the order. There is one thing which you should keep in mind: invoice and creditmemo can be partial, in this case if your total fee linked with items price you will need to calculate you fee based on invoiced/refunded items.
After that the total price will be calculated with your fee, but you will not see your total row anywhere except cart/checkout page. So there are 3 blocks (order, invoice, creditmemo) where you need to add your total, we need to rewrite it:
<blocks> <adminhtml> <rewrite> <sales_order_totals>Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Totals</sales_order_totals> <sales_order_invoice_totals>Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Invoice_Totals</sales_order_invoice_totals> <sales_order_creditmemo_totals>Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Creditmemo_Totals</sales_order_creditmemo_totals> </rewrite> </adminhtml> </blocks>
These classes are very similar:
class Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Totals extends Mage_Adminhtml_Block_Sales_Order_Totals { /** * Initialize order totals array * * @return Mage_Sales_Block_Order_Totals */ protected function _initTotals() { parent::_initTotals(); $amount = INSURANCE_FEE; if ($amount) { $this->addTotalBefore(new Varien_Object(array( 'code' => 'turnkeye_insurance', 'value' => $amount, 'base_value'=> $amount, 'label' => $this->helper('turnkeye_insurance')->__('Insurance'), ), array('shipping', 'tax'))); } return $this; } }
class Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Creditmemo_Totals extends Mage_Adminhtml_Block_Sales_Order_Creditmemo_Totals { /** * Initialize order totals array * * @return Mage_Sales_Block_Order_Totals */ protected function _initTotals() { parent::_initTotals(); $amount = INSURANCE_FEE; if ($amount) { $this->addTotalBefore(new Varien_Object(array( 'code' => 'turnkeye_insurance', 'value' => $amount, 'base_value'=> $amount, 'label' => $this->helper('turnkeye_insurance')->__('Insurance'), ), array('shipping', 'tax'))); } return $this; } }
class Turnkeye_Insurance_RW_Adminhtml_Block_Sales_Order_Invoice_Totals extends Mage_Adminhtml_Block_Sales_Order_Invoice_Totals { /** * Initialize order totals array * * @return Mage_Sales_Block_Order_Totals */ protected function _initTotals() { parent::_initTotals(); $amount = INSURANCE_FEE; if ($amount) { $this->addTotalBefore(new Varien_Object(array( 'code' => 'turnkeye_insurance', 'value' => $amount, 'base_value'=> $amount, 'label' => $this->helper('turnkeye_insurance')->__('Insurance'), ), array('shipping', 'tax'))); } return $this; } }
After that you will see new row in totals with "Insurance" value in the admin area. You need to do the same for blocks in frontend: 'sales/order_totals', 'sales/order_invoice_totals' and 'sales/order_creditmemo_totals'. After that you will see your row with a fee on frontend and backend.
If you use PayPal payment method you need to add your fee to PayPal request, in other case you will see error that total amount is not sum of items price and you will not be able to place order. To solve this just add your observer for 'paypal_prepare_line_items' event:
<events> <paypal_prepare_line_items> <observers> <turnkeye_insurance> <type>singleton</type> <class>turnkeye_insurance/observer</class> <method>addScreenToPayPal</method> </turnkeye_insurance> </observers> </paypal_prepare_line_items> </events>
The method which add your fee to PayPal request is:
public function addScreenToPayPal($observer) { $paypal_cart = $observer->getPaypalCart(); if ($paypal_cart && $paypal_cart->getSalesEntity()) { $amount = INSURANCE_FEE; if ($amount) { $paypal_cart->addItem(Mage::helper('turnkeye_insurance')->__('Insurance'), 1, $amount, 'insurance'); } } }
Finally, you can change INSURANCE_FEE in our example to any amount you need or better to change it to helper method with quote/order parameter. Also we recommend to store the fee value you want to apply to the orders in the database and use this value for creditmemo/invoices/order.
I have a few things to add:
1. in order to have the total transferred from the quote to order you need to add fieldset nodes:
<global>
...
<fieldsets>
<sales_convert_quote_address>
<your_total_amount>
<to_order>*</to_order>
</your_total_amount>
</sales_convert_quote_address>
</fieldsets>
</global>
2. A great article :)
Thanks!
Thanks for this great tutorial. There are smal issue in config.xml we have to change:
<class>turnkeye_insurance/total_screen_creditmemo</class> AND <class>turnkeye_insurance/total_screen_quote</class>
INTO
<class>turnkeye_insurance/total_insurance_creditmemo</class> AND <class>turnkeye_insurance/total_insurance_quote</class>
Now everything works :)
Total model should be extended from Mage_Sales_Model_Order_Total_Abstract
Please guide.
Instead of fixed amount how to add percentage to the order total?
Its clear to understand and very help full to all magento developer.
Thanks for sharing such a great knowledge with us.