Magento OnePage Checkout – Fixing Wrong Totals After Product Save

onepage-checkoutproductsave

This has proved to be quite the challenge to solve! I am working on a Magento site with a multi-store setup with custom shipping and totals calculations using extensions and a OnePage checkout. I have encountered an issue that googling around for has yielded no helpful hints. Everything is working great except for two scenarios occur when a product is modified in the backend and I strongly feel they are related:

Scenario 1:

  • Customer has a product in the cart in the front end
  • The same product is modified in the backend in the store view the customer is in
  • When the customer goes to the OnePage checkout, the product shows in the cart review but the Product Total and Grand total are zero
  • Refreshing the checkout page recalculates the totals correctly; the zero totals only appear the first time the customer reaches the checkout page after the product is saved in the backend

Scenario2:

  • Customer has a product in the cart in the front end and is on the checkout page
  • The same product is modified in the backend in the store view the customer is in
  • When the customer removes the product from the cart, the product is removed from the cart summary but the Product Total and Grand total remain at what they were when the product was in the cart. Even a now empty cart will still show the old total before the product was removed.
  • Refreshing the checkout page recalculates the totals down to zero; the product totals only appear the first time the checkout page is rendered after the customer removes the cart item

Unfortunately, I cannot post code as this is a heavily customized system but I hope someone might give me a clue as to what may be the issue so I know at least where to dig.

My investigation so far shows that, after a product save, Magento loads the core OnePage checkout controller rather than the custom checkout controller. It appears to be related to a preDispatch event that is fired by dispatch() in app/code/core/Mage/Core/Controller/Varien/Action.php only after the product is modified in the backend since the backtrace for the checkout starts with:

First time after product save:

app/code/core/Mage/Checkout/controllers/OnepageController.php:62
app/code/core/Mage/Core/Controller/Varien/Action.php:407
app/code/core/Mage/Core/Controller/Varien/Router/Standard.php:251
app/code/core/Mage/Core/Controller/Varien/Front.php:172
app/code/core/Mage/Core/Model/App.php:354
app/Mage.php:684
index.php:87

Any other time the checkout page is loaded:

app/code/local/path/to/custom/checkout/controller/...
app/code/core/Mage/Core/Controller/Varien/Action.php:418
app/code/core/Mage/Core/Controller/Varien/Router/Standard.php:251
app/code/core/Mage/Core/Controller/Varien/Front.php:172
app/code/core/Mage/Core/Model/App.php:354
app/Mage.php:684
index.php:87

Any help with this would be greatly appreciated… In the very least a clue on where I should start looking 😉

Thanks!

Best Answer

Okay, no responses so hopefully the following may help some poor soul who finds themselves in the same boat I was in. Credit for helping me track down the issue goes to this post on TechyTalk.info. The solution outlined there didn't apply in my case as I wasn't dealing with price rules or creating orders in the backend. However, it did help me pinpoint what I need to be concerned about, so big thanks for that! To summarize:

When using a custom totals collector, Magento's _afterLoad() method in Mage_Sales_Model_Quote can cause an excessive call to collectTotals() after a product has been modified. The call to collectTotals() is managed by the trigger_recollect flag which, is set to 1 when the product has been modified. Fine in most cases but not when certain events are triggered!

The first thing collectTotals() in Mage_Sales_Model_Quote does is to wipe out (set to zero) all of the quote totals and then call collectTotals() on all address objects it can get its hands on (Mage_Sales_Model_Quote_Address). As it turns out, however, when _afterLoad() is called on certain events after a product is modified, there are no address objects available and the quote total remains at zero when collectTotals() completes.

The second scenario I described was just a result of the way our code worked where, when a product was removed from the cart (and Magento reported that it's total price is zero), the Ajax method did not update the checkout page totals since, according to it's logic, the cart total had not changed (a zero-price product was removed).

I inspected the quote object and, unfortunately, could not find a property with which I could detect that the trigger_recollect flag has been set at product save. In addition, my custom totals collector had already re-calculated the totals in each instance that I could test for so I simply temporarily turned off the trigger_recollect while calling the parent::_afterload() in a descendant class to at least allow all other processing and events dispatched to proceed as expected. This fixed the issue in both scenarios.

Mock override:

class Some_Other_Model_Sales_Quote extends Mage_Sales_Model_Quote
{
    //... some other stuff here

    /**
     * Bypass the collectTotals() call in Mage_Sales_Model_Quote
     *
     * This still triggers all the _afterLoad() events and processing
     *
     * @return Mage_Core_Model_Abstract
     */
    protected function _afterLoad()
    {
        $triggerRecollect = $this->getTriggerRecollect();
        $this->setTriggerRecollect(false);
        $return = parent::_afterLoad();
        $this->setTriggerRecollect($triggerRecollect);
        return $return;
    }
}