Products that cover the topic Magento 2 Price Per Customer

Fixing incorrect tier price with special price in Magento 2

 

There is a little nasty bug in Magento 2 which somehow survived till latest Magento 2 version (2.4.8 at the time of writing). The bug is that if you have set a regular price, then lower special price and then tier prices, the tier prices get calculated based on the regular price and not the special price. While this may not seem like a bug to everyone, in reality it affects the calculation of the lowest price possible.

 

THE BUG

Consider the following scenario (see figure below)

A product with:

  1. Regular price of $200
  2. Special price of $100
  3. All groups 20% discount if quantity is equal or larger than 3

The expected price if customer adds quantity of 3 in the cart would be 20% out of $100, however in reality the price is calculated 20% out of $200 (the regular price), thus resulting in $160 instead of expected $80 and this is wrong.

This is caused in Magento\Catalog\Model\Product\Attribute\Backend\Tierprice::modifyPriceData method (see below)

{ } php protected  function  modifyPriceData($object,  $data) {         /**           *  @var  \Magento\Catalog\Model\Product  $object           **/         $data  =  parent::modifyPriceData($object,  $data);         $price  =  $object->getPrice();         foreach  ($data  as  $key  =>  $tierPrice)           {                 $percentageValue  =  $this->getPercentage($tierPrice);                 if  ($percentageValue)                   {                         $data[$key]['price']  =  $price  *  (1  -  $percentageValue  /  100);                         $data[$key]['website_price']  =  $data[$key]['price'];                 }         }         return  $data; }

In here $price = $object->getPrice(); gets the regular product price and does not take into consideration that product might have a special price defined as well.

 

FIXING THE BUG

Unfortunately, modifyPriceData() method is a protected method and we can't fix this by defining an around() or after() plugin. The only possible solution would be to use a preference for Magento\Catalog\Model\Product\Attribute\Backend\Tierprice, this is not ideal because it may result in conflict with another extensions that could be doing the same thing but it's the only possible approach.

The steps to fix the bug include (the example here is from our Customer pricing extension for Magento 2 but you can do it in your own module as well):

1) Define a DI preference in vendor\anowave\price\etc\di.xml

{ } vendor\anowave\price\etc\di.xml <?xml  version="1.0"?> <config  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">         <preference  for="Magento\Catalog\Model\Product\Attribute\Backend\Tierprice"  type="Anowave\Price\Preference\Tierprice"  /> </config>

With this we define a preference to override the modifyPriceData() method in our own class. It is important to use the etc/di.xml and not etc/frontend/di.xml because totals are calculated in the webapi_rest AREA and if you define this in the frontend area, the prefetence won't work at checkout where totals are calculated using API call.

2) Define a class that will override \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice in Preference\Tierprice

{ } vendor\anowave\price\Preference\Tierprice.php namespace  Anowave\Price\Preference; class  Tierprice  extends  \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice {         /**           *  @inheritdoc           */         protected  function  modifyPriceData($object,  $data)         {                 /**                     *  @var  \Magento\Catalog\Model\Product  $object                     **/                                  $data  =  parent::modifyPriceData($object,  $data);                 $price  =  $this->getPrice($object);                 foreach  ($data  as  $key  =>  $tierPrice)                   {                         $percentageValue  =  $this->getPercentage($tierPrice);                         if  ($percentageValue)                           {                                 $data[$key]['price']  =  $price  *  (1  -  $percentageValue  /  100);                                 $data[$key]['website_price']  =  $data[$key]['price'];                         }                 }                 return  $data;         }           /**           *  Check  whether  price  has  percentage  value.           *           *  @param  array  $priceRow           *  @return  null           */         private  function  getPercentage($priceRow)         {                 return  isset($priceRow['percentage_value'])  &&  is_numeric($priceRow['percentage_value'])  ?  $priceRow['percentage_value']  :  null;         }         /**           *  Get  price           *             *  @fix  Fix  for  special  price           */         private  function  getPrice(\Magento\Catalog\Model\Product  $product)  :  mixed         {                 $price  =  $product->getPrice();                 if  ($product->getSpecialPrice())                 {                         if  ($product->getSpecialFromDate()  ||  $product->getSpecialToDate()  ||  ($product->getSpecialFromDate()  &&  $product->getSpecialToDate()))                         {                                 $f  =  strtotime((string)  $product->getSpecialFromDate());                                 $t  =  strtotime((string)  $product->getSpecialToDate());                                 if  ($f  &&  $t)                                 {                                         if  ($f  <=  time()  &&  time()  <=  $t)                                         {                                                 $price  =  $product->getSpecialPrice();                                         }                                         else  if($f)                                         {                                                 if  (time()  >=  $f)                                                 {                                                         $price  =  $product->getSpecialPrice();                                                 }                                         }                                         elseif  ($t)                                         {                                                 if  (time()  <=  $t)                                                 {                                                         $price  =  $product->getSpecialPrice();                                                 }                                         }                                 }                                 else                                   {                                         $price  =  $product->getSpecialPrice();                                 }                         }                         else                         {                                 $price  =  $product->getSpecialPrice();                         }                 }                 return  $price;         } }

This is a simple and quick way to fix the bug discussed in this quick article. If you don't feel like developing this yourself, you can check out our robust Price per Customer extension for Magento 2 which will do this fix for you. You can also benefit from a flexible customer pricing features for Magento 2 as well.

Extensions for Magento

Anowave is an extension developer for Magento 1.x and Magento 2.x platforms. We provide a wide range of premium extensions for our in-house and public clients. The extensions we offer extend the capabilities of Magento and provide bespoke functionality. They also fill some gaps in the functionality provided by the base platform and help customers choose Magento as their preferred eCommerce platform.

The extensions we offer are part of our full-range Magento service, which also includes a Premium Helpdesk where customers can speak with actual software engineers and have their issues resolved without hassle. Premium-labeled modules also include free installation, configuration, testing, etc.