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.
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.