How do I get quote totals before saving payment method?

Orhan Saglam picture Orhan Saglam · Feb 8, 2012 · Viewed 7.7k times · Source

I am trying to create a one step checkout, but in the checkout page I have problem with order review part . When I select a payment method for example "cash on deliver" has 5$ extra, or "checkorder" has %4 discount or "credit card payment" adds extra to the order total. I need a way to calculate the discounts before saving the payment method. Any suggestion?

Answer

Vinai picture Vinai · Feb 9, 2012

Because we are talking about Magento, there are several ways you could do this. The best way to implement that functionality would be to create your own total models for the discounts or additional charges.

How to create custom Magento total models

To create your own total model you first need to create a module, and add the total models to the configuration.

<global>
  <sales>
    <quote>
      <totals>
        <your_total>
          <class>your_module/quote_address_total_yourTotal</class>
          <after>shipping</after><!-- calculate after these total models -->
          <before>grand_total,tax</before><!-- calculate before these total models -->
        </your_total>
      </totals>
    </quote>
    <order_invoice>
      <totals>
        <your_total>
          <class>your_module/order_invoice_total_yourTotal</class>
          <after>shipping</after>
          <before>grand_total,tax</before>
        </your_total>
      </totals>
    </order_invoice>
    <order_creditmemo>
      <totals>
        <your_total>
          <class>your_module/order_creditmemo_total_yourTotal</class>
          <after>shipping</after>
          <before>grand_total,tax</before>
        </your_total>
      </totals>
    </order_creditmemo>
  </sales>
  <pdf>
    <totals>
      <your_total translate="title">
        <title>Your Total</title>
        <source_field>your_total</source_field>
        <font_size>7</font_size>
        <display_zero>0</display_zero>
        <sort_order>450</sort_order>
      </your_total>
    </totals>
  </pdf>
</global>

Then implement the three classes specified in the XML.
The quote address total needs to extend sales/quote_address_total_abstract and implement the two methods collect() and fetch().

class Your_Module_Model_Quote_Address_Total_YourTotal
    extends Mage_Sales_Model_Quote_Address_Total_Abstract
{
    // Calculate your total value
    public function collect(Mage_Sales_Model_Quote_Address $address)
    {
        parent::collect($address);

        // Calculate the totals based on the information in the $address
        // and the $address->getQuote()
        // To get the items in the cart use $address->getAllItems()
        // To get the payment method use getPayment()->getMethodInstance()
        // etc

        // When your totals are known..
        $this->_addAmount($total); // store view currency amount
        $this->_addBaseAmount($baseTotal); // base currency amount

        // Also store in address for later reference in fetch()
        $address->setMyTotal($total);
        $address->setBaseMyTotal($baseTotal);

        return $this;
    }

    // If the total should be displayed in the cart and the checkout
    // add them to the address model here, otherwise just return
    // (it will still be calculated and added to the grand total)
    public function fetch(Mage_Sales_Model_Quote_Address $address)
    {
        if ($address->getMyTotal() > 0)
        {
            $address->addTotal(array(
                'code'  => $this->getCode(),
                'title' => Mage::helper('your_module')->__('Your Total'),
                'value' => $address->getMyTotal()
            ));
        }
        return $this;
    }
}

The next class specified in the config XML is the invoice total model your_module/order_invoice_total_yourTotal.

class Your_Module_Model_Order_Invoice_Total_YourTotal
    extends Mage_Sales_Model_Order_Invoice_Total_Abstract
{
    // Collect the totals for the invoice
    public function collect(Mage_Sales_Model_Order_Invoice $invoice)
    {
        $order = $invoice->getOrder();
        $myTotal = $order->getMyTotal();
        $baseMyTotal = $order->getBaseMyTotal();

        $invoice->setGrandTotal($invoice->getGrandTotal() + $myTotal);
        $invoice->setBaseGrandTotal($invoice->getBaseGrandTotal() + $basemyTotal);

        return $this;
    }
}

The final class you need to implement in the creditmemo total model, which is just like the invoice total model, only it extends the abstract class Mage_Sales_Model_Order_Creditmemo_Total_Abstract.

You will also need to add the attributes using a setup script:

/**
 * @var Mage_Sales_Model_Resource_Setup $installer
 */
$installer = Mage::getResourceModel('sales/setup', 'default_setup');

$installer->startSetup();

$installer->addAttribute('order', 'base_your_total', array(
    'label' => 'Base Your Total',
    'type'  => 'decimal',
));

$installer->addAttribute('order', 'your_total', array(
    'label' => 'Your Total',
    'type'  => 'decimal',
));

$installer->addAttribute('invoice', 'base_your_total', array(
    'label' => 'Base Your Total',
    'type'  => 'decimal',
));

$installer->addAttribute('invoice', 'your_total', array(
    'label' => 'Your Total',
    'type'  => 'decimal',
));

$installer->addAttribute('creditmemo', 'base_your_total', array(
    'label' => 'Base Your Total',
    'type'  => 'decimal',
));

$installer->addAttribute('creditmemo', 'your_total', array(
    'label' => 'Your Total',
    'type'  => 'decimal',
));

$installer->endSetup();

In order to display the new total in the admin area, you need to add a totals block for it using layout XML. Register a layout update file for the adminhtml area in your module. Here is a sample content:

<layout version="0.1.0">

  <adminhtml_sales_order_view>
    <reference name="order_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_view>

  <adminhtml_sales_order_invoice_new>
    <reference name="invoice_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_invoice_new>

  <adminhtml_sales_order_invoice_updateqty>
    <reference name="invoice_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_invoice_updateqty>

  <adminhtml_sales_order_invoice_view>
    <reference name="invoice_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_invoice_view>

  <adminhtml_sales_order_creditmemo_new>
    <reference name="creditmemo_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_creditmemo_new>

  <adminhtml_sales_order_creditmemo_view>
    <reference name="creditmemo_totals">
      <block type="your_module/sales_total_yourTotal" name="total_your_total" as="your_total"/>
    </reference>
  </adminhtml_sales_order_creditmemo_view>

</layout>

If you don't want to display your total somewhere, just leave it out. It will still be calculated. Okay, almost there. Finally, the admin area total block class implementation:

// Many ways to implement this, here is one option
class Your_Module_Block_Sales_Total_YourTotal
    extends Mage_Core_Block_Abstract
{
    public function initTotals()
    {
        $parent = $this->getParentBlock();

        $value = $parent->getSource()->getMyTotal();

        if ($value > 0)
        {
            $total = new Varien_Object(array(
            'code'  => 'my_total',
            'value' => $parent->getSource()->getMyTotal(),
            'base_value' => $parent->getSource()->getBaseMyTotal(),
            'label' => $this->__('My Total'),
            'field' => 'my_total'
            ));
            $parent->addTotal($total, 'my_total');
        }
        return $this;
    }
}

Now the only thing missing is the fieldset to copy the total amount from the quote address to the order, and from the order to the invoice and creditmemo. Add the following XML to the config.xml:

<fieldsets>
    <sales_convert_quote_address>
        <shipping_surcharge><to_order>*</to_order></shipping_surcharge>
        <base_shipping_surcharge><to_order>*</to_order></base_shipping_surcharge>
    </sales_convert_quote_address>
    <sales_convert_order>
        <shipping_surcharge><to_invoice>*</to_invoice><to_cm>*</to_cm></shipping_surcharge>
    </sales_convert_order>
</fieldsets>

And thats it. The total will be displayed everywhere (including the generated PDF's).
As I said, there are many other ways to simply update the values of the total models already present in the core, but this is the full blown way to implement it.