Customize price calculation

Overview

There are cases where you globally want to adjust the calculation of product prices. This can be achieved in Shopware by decorating a single service.
This guide will cover this subject with a short example.

Prerequisites

As most guides, this guide is also built upon our plugin base guide, but it's not mandatory to use exactly that plugin as a foundation. The examples in this guide use the namespace however.
Furthermore, you'll have to understand service decoration for this guide, so if you're not familiar with that, head over to our guide regarding adjusting a service.

Decorating the calculator

In order to customize the price calculation for products as a whole, you'll have to decorate the service ProductPriceCalculator. It comes with a calculate method, which you can decorate and therefore customize.
So let's do that real quick. If you're looking for an in-depth explanation, head over to our guide about adjusting a service.
Here's an example decorated calculator:
<plugin root>/src/Service/CustomProductPriceCalculator.php
1
<?php declare(strict_types=1);
2
3
namespace Swag\BasicExample\Service;
4
5
use Shopware\Core\Content\Product\SalesChannel\Price\AbstractProductPriceCalculator;
6
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
7
use Shopware\Core\System\SalesChannel\SalesChannelContext;
8
9
class CustomProductPriceCalculator extends AbstractProductPriceCalculator
10
{
11
/**
12
* @var AbstractProductPriceCalculator
13
*/
14
private AbstractProductPriceCalculator $productPriceCalculator;
15
16
public function __construct(AbstractProductPriceCalculator $productPriceCalculator)
17
{
18
$this->productPriceCalculator = $productPriceCalculator;
19
}
20
21
public function getDecorated(): AbstractProductPriceCalculator
22
{
23
return $this->productPriceCalculator;
24
}
25
26
public function calculate(iterable $products, SalesChannelContext $context): void
27
{
28
/** @var SalesChannelProductEntity $product */
29
foreach ($products as $product) {
30
$price = $product->getPrice();
31
// Just an example!
32
// A product can have more than one price, which you also have to consider.
33
// Also you might have to change the value of "getCheapestPrice"!
34
$price->first()->setGross(100);
35
$price->first()->setNet(50);
36
}
37
38
$this->getDecorated()->calculate($products, $context);
39
}
40
}
Copied!
So what is done here? The constructor gets passed the inner instance of AbstractProductPriceCalculator, most likely the ProductPriceCalculator itself. This will be used to call the original calculate method later on. You also have to return that instance in your getDecorated method.
Inside the overridden calculate method, we're iterating over each product and we straight forward set new prices. Of course this is just an example to show how you can now manipulate a product's prices.
Most likely you also want to narrow down which product's prices you want to edit, as in this example we're adjusting every single product and setting them all to the same price. You might want to have a look at the original calculate method to see how calculating a price is done in the core code.

Registering the decorator

Do not forget to actually register your decoration to the service container, otherwise it will not have any effect.
<plugin root>/src/Resources/config/services.xml
1
<?xml version="1.0" ?>
2
<container xmlns="http://symfony.com/schema/dic/services"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5
6
<services>
7
<service id="Swag\BasicExample\Service\CustomProductPriceCalculator" decorates="Shopware\Core\Content\Product\SalesChannel\Price\ProductPriceCalculator">
8
<argument type="service" id="Swag\BasicExample\Service\CustomProductPriceCalculator.inner" />
9
</service>
10
</services>
11
</container>
Copied!

Next steps

Instead of manipulating a product's price, you can also try to add a discount or a surcharge to the cart. This is explained in our guide about adding cart discounts.