How to Set Up Customer-Based Price Calculation in Shopware

Custom developmentshopware
How to Set Up Customer-Based Price Calculation in Shopware

Introduction

Currently, in Shopware it is not possible to provide customer-based prices or discounts for products using tags/categories. In this article, we will discuss how to provide a price based on the customer.

To do customer-based price calculations that can’t be met with Shopware’s default options, we have to decorate the service ProductPriceCalculator. It comes with a “calculate” method, which you can decorate and customise. So you should have a basic understanding of service decoration in Shopware.

We will be creating an admin section to map products, and customers with price or discount. Based on this mapping price calculation will be done in the frontend.

But before we proceed, our guide involves using Shopware 6.5, So we assume you have intermediate knowledge of Shopware. If not, Jumping right into a Framework as a 1st step is not such a good idea, so if you are a newcomer we suggest you get some basic knowledge before proceeding here.

Step 1: Create an Admin section for mapping price

Create a custom entity and admin CRUD section to insert the following details.

Quantity range.

For infinite quantities, we can make the “To Quantity” field Null. 

Text box to enter price or discount(%).

Either price or discount has to be selected.

Product/tag

Either select a product or product tag. You can also extend functionality to add a category select to apply the rule to all products in that category.

Customer

Select a customer for the rules to be applied, you can also add a customer group, so that all customers within that group applied. These fields can be changed or updated depending on your requirements.

Step 2: Decorating the price calculator

In this section, we have been using the “adjust a service” method to decorate the price calculator function. If you are unaware of this, we suggest you get some basic knowledge before proceeding here.

We must add the ProductPriceCalculator decorator in the services.xml file with the attribute decorates pointing to the service we want to decorate. We can also add our custom entity repository as an argument.

<service id="CustomerSpecificPrices\Core\Content\Product\SalesChannel\Price\ProductPriceCalculator"                decorates="Shopware\Core\Content\Product\SalesChannel\Price\ProductPriceCalculator">
           <argument type="service" id="CustomerSpecificPrices\Core\Content\Product\SalesChannel\Price\ProductPriceCalculator.inner"/>
           <argument type="service" id="Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator" />
           <argument id="custom_customer_price.repository" type="service"/>
</service>

In the ProductPriceCalculator class, we could accept arguments in the construct.

class ProductPriceCalculator extends AbstractProductPriceCalculator implements ResetInterface
{
   protected EventDispatcherInterface $eventDispatcher;
   private AbstractProductPriceCalculator $coreService;
   private QuantityPriceCalculator $quantityPriceCalculator;
   private EntityRepository $customerPricesRepository;


   public function __construct(
       AbstractProductPriceCalculator $coreService,
       QuantityPriceCalculator $quantityPriceCalculator,
       EntityRepository $customerPricesRepository
   ) {
       $this->coreService = $coreService;
       $this->quantityPriceCalculator = $quantityPriceCalculator;
       $this->customerPricesRepository = $customerPricesRepository;
   }

Recreate calculate function inside the decorator class and trigger core calculate function, then fetch customer information from saleschannel context. 

Then loop through the products and fetch the calculated price and list price. 

Also, fetch customer-specific prices from the custom entity.

public function calculate(iterable $products, SalesChannelContext $context): void
   {
       $this->coreService->calculate($products, $context);
       $customer = $context->getCustomer();
	foreach ($products as $product) {
		$originalCalculatedPrices =  $product->getCalculatedPrices();
		$originalListPrice = $originalCalculatedPrice->getListPrice();
		 /* Fetch custom price form entity */
		$customerPricesCollection = $this->fetchCustomerPrices($product, $customer);

From our custom entity, we could fetch the price or discount based on product/tag and customer. Here you can customize it based on your requirements.

   public function fetchCustomerPrices($productId, $customerId)
   {
       $customerPrice = $this->customerPricesRepository->search(
           (new Criteria())
               ->addFilter(
                   new EqualsFilter('productId', $productId),
                   new EqualsFilter('customerId', $customerId),
                   new EqualsFilter('active', true)
               )
               ->addSorting(new FieldSorting('fromQuantity', 'ASC')),
           Context::createDefaultContext()
       )->getEntities();


       return $customerPrice;
   }

If a Custom entity has only one price for all quantities of a product. Set the calculated price and cheapest price and skip the rest of the part.

if($customerPricesCollection->count() === 1 && $customerPricesCollection->first()->getFromQuantity() === 1 && $customerPricesCollection->first()->getToQuantity() === null)            
{
    $discountPrice = $customerPricesCollection->first();
    /* Apply custom price on all calculated prices */
    foreach ($originalCalculatedPrices as $price) {
         $this->setPriceOnCalculatedPrice($price, $discountPrice);
     }
      /* Apply custom price on cheapest price */
if($product->getCalculatedCheapestPrice()) {                  
          $this->setPriceOnCalculatedPrice($product->getCalculatedCheapestPrice(),  $discountPrice);
      }
      continue;
}

You can set custom prices according to conditions in the custom entity, you can modify it according to the functionality in the backend.

 /* Set price */
protected function setPriceOnCalculatedPrice(CalculatedPrice $price, CustomerPriceEntity $discountPrice): void
   {
       $reducedUnitPrice = $this->calcCustomerPrice($discountPrice, $price->getUnitPrice());
       $totalPrice = $this->calcCustomerPrice($discountPrice, $price->getTotalPrice());
       $price->assign([
           'unitPrice' => $reducedUnitPrice,
           'totalPrice' => $totalPrice
       ]);
   }


  /*Find price according with backend conditions */	
   private function calcCustomerPrice(CustomerPriceEntity $customerPrice, float $originalPrice): float
   {
       if($customerPrice->getPrice() > 0) {
           return $customerPrice->getPrice();
       }
       if($customerPrice->getDiscount() !== null) {
           return $originalPrice * (1 - ($customerPrice->getDiscount() / 100));
       }
      
       return $originalPrice;
   }

If more than different prices for different quantities, then we need to loop through each quantity price and set the price accordingly.

$quantityCustomerPrice = $customerPricesCollection->first();
$cheapestPrice = null;
$taxRules = $context->buildTaxRules($product->getTaxId());
$prices = new PriceCollection();


/* Loop through price for different quantities and set price for each quantity */
foreach ($customerPricesCollection as $customerPrice) {
$curQuantity = $customerPrice['toQuantity'];
       $price = new QuantityPriceDefinition(   $this->calcCustomerPrice($customerPrice,  $originalCalculatedPrice->getUnitPrice()),$taxRules,$curQuantity);
   	$price->setIsCalculated(true);
$price->setReferencePriceDefinition($this->buildReferencePriceDefinition($product));


   	$prices->add($this->quantityPriceCalculator->calculate($price, $context));
   	/* To find cheapest price */
   	if($cheapestPrice === null || 
($price->getPrice() < $cheapestPrice->getPrice())) {
       	$cheapestPrice = $price;
   	}
}
/* Set product calculatedPrices with quantity */
$product->setCalculatedPrices($prices);


/* Set product calculatedPrice */
$quantityPrice = new QuantityPriceDefinition(   $this->calcCustomerPrice($quantityCustomerPrice,$originalCalculatedPrice->getUnitPrice()),$taxRules);


$quantityPrice->setIsCalculated(true);
$quantityPrice->setReferencePriceDefinition($this->buildReferencePriceDefinition($product));
if($originalListPrice !== null) {
   $quantityPrice->setListPrice($originalListPrice);
}
$product->setCalculatedPrice(
   $this->quantityPriceCalculator->calculate($quantityPrice, $context)
);
/* Set product calculatedPrice end */


/** Set product $calculatedCheapestPrice */
$calculatedCheapestPrice = CalculatedCheapestPrice::createFrom(
   $this->quantityPriceCalculator->calculate($cheapestPrice, $context)
);
$product->setCalculatedCheapestPrice($calculatedCheapestPrice);
/** Set product $calculatedCheapestPrice end*/

 

Since here we create custom prices based on the customer, the Customer has to always log in to the store to reflect this price update.

Conclusion

With Shopware 6.5, you can now implement customer-based price calculations. It’s super easy! Just follow these steps and make sure to tweak the code to fit your business needs. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

17 − 16 =

2hats Logic HelpBot