Prices

  • To get the price actually set on a product, use $product->getPrice(), this returns the price without any discounts applied.
    -To get the price with discounts applied, use $product->getFinalPrice(). This takes the lowest of the product’s price, special price and and customer group price.

    • To calculate the product’s price with tax, use the tax helper:
      <?php
      Mage::helper('tax')->getPrice($product, $product->getFinalPrice()));
      
    • This takes all tax settings into account in the config.
    • Product prices can be set to include tax with their price, or not. This is set in Config > sales tax > product prices
    • Make sure to set the Shipping Country’s origin (Config > Sales > Shipping Settings) to the correct country. If left on the United States, for some reason Magento will ignore whether product prices are saved including tax, and will apply it again.
    • To format the output, use
       <?php Mage::helper('core')->currency($value, $format = true, $includeContainer = true);

Overriding the product’s final price

  • Use
     <?php $product->setFinalPrice($price)

    to override the getFinalPriceCalculation:

    <?php
    public function getFinalPrice($qty=null)
        {
            $price = $this->_getData('final_price');
            if ($price !== null) {
                return $price;
            }
            return $this->getPriceModel()->getFinalPrice($qty, $this);
        }
    
    • So, to modify a product’s price before adding it to the cart, an observer could be set up to modify the product’s final price beforehand.

Getting an item’s tiered price

  • Use the product’s price model to get the price with the tiered price applied.
  • A quantity must be supplied in order to get a product’s tiered price.
<?php
$product->getPriceModel()->getFinalPrice($qty, $product);
  • This executes the following method, which takes the minimum of the tiered price, grouped price, and special price:
<?php
 public function getBasePrice($product, $qty = null)
    {
        $price = (float)$product->getPrice();
        return min($this->_applyGroupPrice($product, $price), $this->_applyTierPrice($product, $qty, $price),
            $this->_applySpecialPrice($product, $price)
        );
    }

The Quote

  • When orders are created, the base price for the product is stored on the cart object (base_grand_total). This is the order’s grand price in the base currency for the country the store is set in.
  • The grand_total on the order is the price in the currency of the store the user is in.
  • The base currency code (E.g. GBP) is stored alongside the order, the store’s currency code and the quote’s currency code. The quote’s currency code is used in determining the grand total from the base grand total.
  • Order items also have a base_price and price set on them.

Javascript prices

On the product view page, product prices are pushed into the an instance of the Product.Config object.

{
  {
	"attributes": {
		"92": { // The ID of the attribute
			"id": "92",
			"code": "color",
			"label": "Color",
			"options": [{
				"id": "20",
				"label": "Black",
				"price": "10", // How much to modify the base price by
				"oldPrice": "0",
				"products": ["253", "254", "483", "484", "488"] // Simple product IDs
			}, {
				"id": "22",
				"label": "White",
				"price": "20",
				"oldPrice": "0",
				"products": ["251", "252"]
			}]
		},
		"180": {
			"id": "180",
			"code": "size",
			"label": "Size",
			"options": [{
				"id": "80",
				"label": "S",
				"price": "0",
				"oldPrice": "0",
				"products": ["253", "249"]
			}, {
				"id": "78",
				"label": "L",
				"price": "0",
				"oldPrice": "0",
				"products": ["254", "252"]
			}, {
				"id": "77",
				"label": "XL",
				"price": "85", // How much the base price is modified by
				"oldPrice": "85",
				"products": ["488"]
			}]
		}
	},
	"template": "£#{price}",
	"basePrice": "75", // The base price with any customer discounts. When recalculating the actual product price, it's not this value that's used - it's the productPrice value on the optionsPrice object.
	"oldPrice": "95", // The old price without discounts
	"productId": "410", 
	"chooseText": "Choose an Option...", // For select boxes in configurables
	"taxConfig": {
		"includeTax": true,
		"showIncludeTax": true,
		"showBothPrices": false,
		"defaultTax": 0,
		"currentTax": 0,
		"inclTaxTitle": "Incl. Tax"
	}
}

An instance of Product.OptionPrices is also instantiated with its own config. This is the object which will actually perform price changes on the page. Here’s an example of the config object:

var optionsPrice = new Product.OptionsPrice({
	"productId": "410",
	"priceFormat": {
		"pattern": "£%s",
		"precision": 2,
		"requiredPrecision": 2,
		"decimalSymbol": ".",
		"groupSymbol": ",",
		"groupLength": 3,
		"integerRequired": 1
	},
	"includeTax": "true",
	"showIncludeTax": true,
	"showBothPrices": false,
	"productPrice": 65, // The base price of the product with any customer discounts or special prices applied. This price can be modified to change the output on the page
	"productOldPrice": 75, // The old price on the product if we've got customer discounts or a special price applied
	"priceInclTax": 78
	"priceExclTax": 65,
	"skipCalculate": 1,
	"defaultTax": 0,
	"currentTax": 0,
	"idSuffix": "_clone",
	"oldPlusDisposition": 0,
	"plusDisposition": 0,
	"plusDispositionTax": 0,
	"oldMinusDisposition": 0,
	"minusDisposition": 0,
	"tierPrices": [],
	"tierPricesInclTax": []
})

When a configurable option is changed, the Product.Config calls configueElement, which refills the select boxes for all other attributes. This calls fillSelect() which updates the next select with available options. Finally, reloadPrice() is called:

 reloadPrice: function(){
        if (this.config.disablePriceReload) {
            return;
        }
        var price    = 0;
        var oldPrice = 0;

		
        for(var i=this.settings.length-1;i>=0;i--){
            var selected = this.settings[i].options[this.settings[i].selectedIndex];
            if(selected.config){
                price    += parseFloat(selected.config.price);
                oldPrice += parseFloat(selected.config.oldPrice);
            }
        }

        optionsPrice.changePrice('config', {'price': price, 'oldPrice': oldPrice});
        optionsPrice.reload();

        return price;
  • This loops over all selected attributes, and using the config object which is set upon the option element (in fillSelect()), adds up each configurable prices, and sends them to an instance of the Product.OptionsPrice model’s changePrice method. key tends to be config for configurables (guess), and price is an object containing the oldPrice of the product option and the price modifier.
 changePrice: function(key, price) {
        this.optionPrices[key] = price;
    },

– Change price is an important function. By default, the key that’s passed into it is config. E.g

optionsPrice.changePrice('config', {'oldPrice':0, 'price':300});

– Calling the above will cause the configurable modifier to be 300, which will be added on to the base price when optionsPrice.reload() is called. This value would be replaced the next time a product option is changed (as it would replace the values set on the ‘config’ key.
– **Adding more keys into the optionsPrice.changePrice method will also cause the price to be changed by each. Negative numbers can also be passed into this method. E.g.

optionsPrice.changePrice('alternative-options', {'oldPrice':0, 'price':10});
  • Calling the reload method then also applies customPrices which have been set on the product. These are stored in the Product.OptionsPrice’s customPrice array. All of the custom options are stored in an instance of the Product.Options object (opConfig), and looks like this:
{
	"3": {
		"price": 20,
		"oldPrice": 20,
		"priceValue": "20.0000",
		"type": "fixed", // type of discount, fixed or percent
		"excludeTax": 20,
		"includeTax": 24
	},
	"2": {
		"1": {
			"price": 32.5, // Even though this is a percent modifier, the price is already calculated in the base currency.
			"oldPrice": 50,
			"priceValue": "50.0000",
			"type": "percent",
			"excludeTax": 32.5,
			"includeTax": 39
		},
		"2": {
			"price": 60,
			"oldPrice": 60,
			"priceValue": "60.0000",
			"type": "fixed",
			"excludeTax": 60,
			"includeTax": 72
		}
	}
}

Modifying the prices

  • We can set
     window.optionsPrice.productPrice

    to a value, and then call

     optionsPrice.reload()

    Calculations will then be applied to this new base price – custom options and configurable options will be applied on top of this price

  • We can call
     window.optionsPrice.addCustomPrices('customcode', priceData)

    , and pass an object such as:

{
			"price": 60,
			"oldPrice": 60,
			"priceValue": "60.0000",
			"type": "fixed",
			"excludeTax": 60,
			"includeTax": 72
		}

However, this may prove awkward as we have to calculate our own tax values for our object.

  • We can call window.optionsPrice.changePrice, and pass our own key and code with the object, so if we wanted to take 50% off the base price, we could do something like the following:
var halfPrice  = window.optionsPrice.productPrice / 2;

window.optionsPrice.changePrice('custom-price-change', {
  price: -halfPrice, // This needs to be negative, prices are added to the base price.
  oldPrice: -halfPrice
});

window.optionsPrice.reload();

– Bear in mind that this will affect the base price — which may or may not already include tax depending on the config settings.
Note: If there are no custom options or configurable options on the product then the javascript will not refresh the price with the above method. This is because the productOptions object is not populated with any data. TODO: Investigate this to see how possible it is – might involve making our own data array like magento’s to initiate things.
– The logic will need duplicating when adding the product to the cart in PHP 🙂 Fuuuun.

TODO: Applying tax to shipping

Allowing prices to be set per website

By default, Magento prices are set on a global level. To allow them to be set on a website level, the System > Configuration > Catalog > Price > Catalog Price Scope must be changed.