Question

I think I may have found a bug in Magento CE 1.9.3 which was released last week?

I upgraded our Staging environment to it and have found that the pricing of Configurable products is not updating correctly when a customer selects a configurable attribute (e.g. size) for a product. Specifically, it is the price within the following html element which is not updating.

<span class="regular-price" id="product-price-161_clone">

We display this price in our theme and it was updating correctly before the update. It's also not updating the price here in the default RWD theme. It only updates the price in <span class="regular-price" id="product-price-161">, not the cloned price. The below screen shot illustrates the problem.

enter image description here

As you can see the cloned price highligted in the buy box has not changed eventhough Large has been selected. Whereas the price at the top has changed.

After a lot of debugging (See Below) I discovered it is due to a new file which was added in Magento 1.9.3 that removes the _clone value on the idSuffix key from the Product.OptionsPrice JSON object e.g."idSuffix":"_clone". The new file in question is: app/code/core/Mage/Catalog/Helper/Product/Type/Composite.php

I'm not sure why Magento would have removed this value as it breaks the pricing, is it a bug in this Magento version?

I could try to update the JSON object before adding it to the Product.OptionsPrice object. Alternatively, I could stop using the cloned price in my template but I'm not sure if this will break something else?


Debugging

I checked the JavaScript stack and found that the price was not being updated, as this.duplicateIdSuffix was not set on line 218 of js/varien/product_options.js

$(pair.value+this.duplicateIdSuffix).select('.price')[0].innerHTML = formattedPrice;

this.duplicateIdSuffix should of been set earlier in js/varien/product_options.js at line 44:

Product.OptionsPrice.prototype = {
    initialize: function(config) {
        this.productId          = config.productId;
        this.priceFormat        = config.priceFormat;
        this.includeTax         = config.includeTax;
        this.defaultTax         = config.defaultTax;
        this.currentTax         = config.currentTax;
        this.productPrice       = config.productPrice;
        this.showIncludeTax     = config.showIncludeTax;
        this.showBothPrices     = config.showBothPrices;
        this.productOldPrice    = config.productOldPrice;
        this.priceInclTax       = config.priceInclTax;
        this.priceExclTax       = config.priceExclTax;
        this.skipCalculate      = config.skipCalculate; /** @deprecated after 1.5.1.0 */
        this.duplicateIdSuffix  = config.idSuffix;
        this.specialTaxPrice    = config.specialTaxPrice;
        this.tierPrices         = config.tierPrices;
        this.tierPricesInclTax  = config.tierPricesInclTax;

        this.oldPlusDisposition = config.oldPlusDisposition;
        this.plusDisposition    = config.plusDisposition;
        this.plusDispositionTax = config.plusDispositionTax;

        this.oldMinusDisposition = config.oldMinusDisposition;
        this.minusDisposition    = config.minusDisposition;

        this.exclDisposition     = config.exclDisposition;

        this.optionPrices   = {};
        this.customPrices   = {};
        this.containers     = {};

        this.displayZeroPrice   = true;

        this.initPrices();
    },

The Product.OptionsPrice object was created near the beginning of catalog/product/view.phtml with

<script type="text/javascript">
    var optionsPrice = new Product.OptionsPrice(<?php echo $this->getJsonConfig() ?>);
</script>

This is were it got interesting, in versions prior to Magento 1.9.3 this object was created in app/code/core/Mage/Catalog/Block/Product/View.php with:

/**
 * Get JSON encoded configuration array which can be used for JS dynamic
 * price calculation depending on product options
 *
 * @return string
 */
public function getJsonConfig()
{
    $config = array();
    if (!$this->hasOptions()) {
        return Mage::helper('core')->jsonEncode($config);
    }

    $_request = Mage::getSingleton('tax/calculation')->getDefaultRateRequest();
    /* @var $product Mage_Catalog_Model_Product */
    $product = $this->getProduct();
    $_request->setProductClassId($product->getTaxClassId());
    $defaultTax = Mage::getSingleton('tax/calculation')->getRate($_request);

    $_request = Mage::getSingleton('tax/calculation')->getRateRequest();
    $_request->setProductClassId($product->getTaxClassId());
    $currentTax = Mage::getSingleton('tax/calculation')->getRate($_request);

    $_regularPrice = $product->getPrice();
    $_finalPrice = $product->getFinalPrice();
    if ($product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
        $_priceInclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, true,
            null, null, null, null, null, false);
        $_priceExclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, false,
            null, null, null, null, null, false);
    } else {
        $_priceInclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, true);
        $_priceExclTax = Mage::helper('tax')->getPrice($product, $_finalPrice);
    }
    $_tierPrices = array();
    $_tierPricesInclTax = array();
    foreach ($product->getTierPrice() as $tierPrice) {
        $_tierPrices[] = Mage::helper('core')->currency(
            Mage::helper('tax')->getPrice($product, (float)$tierPrice['website_price'], false) - $_priceExclTax
                , false, false);
        $_tierPricesInclTax[] = Mage::helper('core')->currency(
            Mage::helper('tax')->getPrice($product, (float)$tierPrice['website_price'], true) - $_priceInclTax
                , false, false);
    }
    $config = array(
        'productId'           => $product->getId(),
        'priceFormat'         => Mage::app()->getLocale()->getJsPriceFormat(),
        'includeTax'          => Mage::helper('tax')->priceIncludesTax() ? 'true' : 'false',
        'showIncludeTax'      => Mage::helper('tax')->displayPriceIncludingTax(),
        'showBothPrices'      => Mage::helper('tax')->displayBothPrices(),
        'productPrice'        => Mage::helper('core')->currency($_finalPrice, false, false),
        'productOldPrice'     => Mage::helper('core')->currency($_regularPrice, false, false),
        'priceInclTax'        => Mage::helper('core')->currency($_priceInclTax, false, false),
        'priceExclTax'        => Mage::helper('core')->currency($_priceExclTax, false, false),
        /**
         * @var skipCalculate
         * @deprecated after 1.5.1.0
         */
        'skipCalculate'       => ($_priceExclTax != $_priceInclTax ? 0 : 1),
        'defaultTax'          => $defaultTax,
        'currentTax'          => $currentTax,
        'idSuffix'            => '_clone',
        'oldPlusDisposition'  => 0,
        'plusDisposition'     => 0,
        'plusDispositionTax'  => 0,
        'oldMinusDisposition' => 0,
        'minusDisposition'    => 0,
        'tierPrices'          => $_tierPrices,
        'tierPricesInclTax'   => $_tierPricesInclTax,
    );

    $responseObject = new Varien_Object();
    Mage::dispatchEvent('catalog_product_view_config', array('response_object' => $responseObject));
    if (is_array($responseObject->getAdditionalOptions())) {
        foreach ($responseObject->getAdditionalOptions() as $option => $value) {
            $config[$option] = $value;
        }
    }

    return Mage::helper('core')->jsonEncode($config);
}

But in Magento 1.9.3 it was changed to with:

   /**
     * Prepare general params for product to be used in getJsonConfig()
     * @see Mage_Catalog_Block_Product_View::getJsonConfig()
     * @see Mage_ConfigurableSwatches_Block_Catalog_Product_List_Price::getJsonConfig()
     *
     * @return array
     */
    public function prepareJsonGeneralConfig()
    {
        return array(
            'priceFormat'       => Mage::app()->getLocale()->getJsPriceFormat(),
            'includeTax'        => Mage::helper('tax')->priceIncludesTax() ? 'true' : 'false',
            'showIncludeTax'    => Mage::helper('tax')->displayPriceIncludingTax(),
            'showBothPrices'    => Mage::helper('tax')->displayBothPrices(),
            'idSuffix'            => '',
            'oldPlusDisposition'  => 0,
            'plusDisposition'     => 0,
            'plusDispositionTax'  => 0,
            'oldMinusDisposition' => 0,
            'minusDisposition'    => 0,
        );
    }



    /**
     * Prepare product specific params to be used in getJsonConfig()
     * @see Mage_Catalog_Block_Product_View::getJsonConfig()
     * @see Mage_ConfigurableSwatches_Block_Catalog_Product_List_Price::getJsonConfig()
     *
     * @param Mage_Catalog_Model_Product $product
     * @return array
     */
    public function prepareJsonProductConfig($product)
    {
        $_request = Mage::getSingleton('tax/calculation')->getDefaultRateRequest();
        $_request->setProductClassId($product->getTaxClassId());
        $defaultTax = Mage::getSingleton('tax/calculation')->getRate($_request);

        $_request = Mage::getSingleton('tax/calculation')->getRateRequest();
        $_request->setProductClassId($product->getTaxClassId());
        $currentTax = Mage::getSingleton('tax/calculation')->getRate($_request);

        $_regularPrice = $product->getPrice();
        $_finalPrice = $product->getFinalPrice();
        if ($product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
            $_priceInclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, true,
                null, null, null, null, null, false);
            $_priceExclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, false,
                null, null, null, null, null, false);
        } else {
            $_priceInclTax = Mage::helper('tax')->getPrice($product, $_finalPrice, true);
            $_priceExclTax = Mage::helper('tax')->getPrice($product, $_finalPrice);
        }
        $_tierPrices = array();
        $_tierPricesInclTax = array();
        foreach ($product->getTierPrice() as $tierPrice) {
            $_tierPrices[] = Mage::helper('core')->currency(
                Mage::helper('tax')->getPrice($product, (float)$tierPrice['website_price'], false) - $_priceExclTax
                , false, false);
            $_tierPricesInclTax[] = Mage::helper('core')->currency(
                Mage::helper('tax')->getPrice($product, (float)$tierPrice['website_price'], true) - $_priceInclTax
                , false, false);
        }

        return array(
            'productId'           => $product->getId(),
            'productPrice'        => Mage::helper('core')->currency($_finalPrice, false, false),
            'productOldPrice'     => Mage::helper('core')->currency($_regularPrice, false, false),
            'priceInclTax'        => Mage::helper('core')->currency($_priceInclTax, false, false),
            'priceExclTax'        => Mage::helper('core')->currency($_priceExclTax, false, false),
            'skipCalculate'       => ($_priceExclTax != $_priceInclTax ? 0 : 1),
            'defaultTax'          => $defaultTax,
            'currentTax'          => $currentTax,
            'tierPrices'          => $_tierPrices,
            'tierPricesInclTax'   => $_tierPricesInclTax,
            'swatchPrices'        => $product->getSwatchPrices(),
        );
    }

As you can see 'idSuffix' is given an empty string which is causing the problem.

Was it helpful?

Solution

In the end this was the solution I used in our theme's template/catalog/product/view.phtml

<script type="text/javascript">
    var productOptions = <?php echo $this->getJsonConfig() ?>;
    <?php if($this->hasOptions()): ?>
        productOptions.idSuffix = "_clone";
    <?php endif ?>
    var optionsPrice = new Product.OptionsPrice(productOptions);
</script>

I went with this solution as it required less code changes in layout xml & phtml etc.

I tried reporting this issue to Magento at: https://www.magentocommerce.com/bug-tracking/report

But it's down at the moment with just this message...

Magento Issue Tracking

The Bug Tracking System is currently down for maintenance. We apologize for the inconvenience. Please check back later to submit your issue. Thank you for your interest in helping us improve our product by letting us know about issues you have found.

UPDATE

There is a module available which fixes this bug: https://github.com/rossmc/JsonPriceFix

OTHER TIPS

I had similar issue and my browser console was showing

TypeError: Product.OptionsPrice is not a constructor

I figured out the my theme was not adding product_options.js file to catalog category default page and catalog product view page.

So I made two changes in my-template/default/layout/catalog.xml

1- Injected product_options.js inside catalog_category_default handle

<reference name="head">
    <action method="addJs"><script>varien/product_options.js</script></action>
</reference>

2- Injected product_options.js inside catalog_product_view handle

<action method="addJs"><script>varien/product_options.js</script>

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top