Painfully debugged Magento core code (log all queries and reviewed step-by-step), the issue is in:
vendor/magento/module-configurable-product/Model/ResourceModel/Product/Indexer/Price/Configurable.php
line 192
$priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 0, null, true);
The fourth parameter to Magento\Catalog\Api\Data\ProductInterface\AbstractIndexer::_addAttributeToSelect()
is supposed to the the store_id for the (non-default) price to join from the attribute "decimal" value table, instead Magento passes the hard-coded "0", which is causing default store (website) prices to be put in product price index for configurable products, for any website_id.
Quick&Dirty Solution:
Replace in vendor/magento/module-configurable-product/Model/ResourceModel/Product/Indexer/Price/Configurable.php
line 192
:
$priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 0, null, true);
with:
$select->join(
['sg' => $this->getTable('store_group')],
'sg.website_id = i.website_id',
[]
);
$priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 'sg.default_store_id', null, true);
This way, Magento will select the price for non-default websites from the correct associated value for the default store in those websites, instead of statically assigning the default website price to each result.
Don't forget to reindex:
php bin/magento indexer:reindex catalog_product_price
Proper solution:
Create a custom module and overload the
Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable
class with your own modified version (see Quick&Dirty Solution) using DI preference ({your module}/etc/di.xml).
Don't forget to specify that your module depends on Magento_ConfigurableProduct module in your etc/module.xml and composer.json files.
You can use getFinalPrice()
coming from the below product model.
It also allows a $qty
variable in case of tier prices.
i.e.:
$myProduct->getFinalPrice(3);
Reference:
\Magento\Catalog\Model\Product::getFinalPrice($qty = null);
NOTE:
It is not defined in ProductInterface
, so it will not be part of service contract. But it will work anyway.
UPDATE::
The best Magento2 way to preserve service contracts model is to create a new custom ProductInteface
in a custom module and reference getFinalPrice
as interface method.
You will have to use the new ProductInterface
in your project. This is the way to create a sort of service contract inside your project.
If getFinalPrice will change, you will change only your custom model.
Best Answer
Here's how to do it with proper DI:
Please note that the product repository returns an object that complies to the
\Magento\Catalog\Api\Data\ProductInterface
, which does not have agetFinalPrice()
-method. So this code may work now, but it could potentially break ik future updates that are not backward compatible. For example, if Magento decides in the future that Product objects become Product Data objects (like with customers), this code will break.The most safe approach then would be to implement your own
getFinalPrice()
-method, for example:The safest way to write future-proof Magento code is to depend on abstractions (interfaces), not concretions, even though this sometimes means that you have to look for different solutions.