An Example Product Level Pricing Provider
Introduction
For some e-Commerce projects out there it might be more logical to store the prices on a product level than to store it on the variants. One example is clothing stores. A T-Shirt product might have four different sizes but they all cost the same. Then it makes sense to store the price on the product level instead of on the variant level.
Out of the box EPiServer Commerce UI only supports storing the prices on the variant level by using the default IPriceDetailService and IPriceService implementations. The good news is that the default implementation actually supports pricing on products and it’s very easy to create your own pricing implementation! The former part means we can keep our implementation of the two interfaces to a minimum and rely on the default implementations as much as possible.
Ready for the bad news? The Catalog UI does not support prices directly on products; all operations (CRUD) will be on variants or packages. For editors, this means that any price change must be performed on a variant. Our provider then needs to intercept any price changes coming from the UI and make them into a product price change. How this is done will be explained briefly later in the post.
Product Level Pricing Provider
Installation
Installing the Product Level Pricing Provider (PLPP) is easy. The PLPP consists of 4 files. Two are the implementation of the IPriceService and IPriceDetailService. The project also comes with a IConfigurableModule class which will configure EPiServer to use our providers instead of the default ones. The last file contains helper extension methods that will help us find the product parent of a variant or translate the prices from a variant price to a product price. You install it by either adding the project to your EPiServer solution or you build the project and place the assembly in the bin directory.
Configuration
The PLPP adds one new configuration setting and that is if you want to store the prices on the variants or the products. I’ll explain the difference between the two values below.
<appSettings>
...
<add key="ProductLevelPricing" value="true|false" />
<appSettings>
If no “ProductLevelPricing” setting is found, the default pricing implementations will be used instead.
Store the product prices on the variants (ProductLevelPricing == false)
Storing the prices on the variants means that each variant for a product will have the same set of prices stored on all of them. If you have a product with two variants linked to it and three product prices, those three prices will be stored for each variant resulting in a total of six prices in the database. This works by assuming that all the variants start out with the same price (or no prices) and then synchronizing the variants when a price change is done to either of them. The read performance of this mode is the same as the default implementation as the price is still stored on the variants. The write performance however will take a toll as it needs to update the variant, get the new prices, find the sibling variants, delete the siblings’ prices and update each sibling with the new prices. This is to ensure they are synchronized. As you can imagine, this will result in a performance hit and the hit will scale with the number of children/variants a parent product has. However, writing prices is generally an infrequent action so the performance hit will not matter on most sites.
Store the product prices on the product (ProductLevelPricing == true)
The other option is to store the prices on the product. If you have three prices on a product, then three prices will be stored in the database. This results in less redundant data but it also means a small incurred cost when fetching prices for a variant as we always need to find the product parent of the variant first. In your site you can avoid this cost by being sure only to query for product prices directly and by a sensible caching policy. This incurred cost cannot be avoided in the Catalog UI/Commerce Manager because the UI only deals with variant prices, but those are internal functions and the delay should be small.
Disclamer
This is only an example implementation and is offered as is. This is not a supported pricing provider by EPiServer and is not garanteed with upgrade/updates. If you find bugs or want to suggest improvements, feel free to contact me at the email below.
Limitations
- A variant can only be linked to one product.
- The implementation assumes that all API calls are for one type of entry. It’s either variants or products never a mix of the two.
- For the API call IList<IPriceDetailValue> Save(IEnumerable<IPriceDetailValue> priceValues) priceValues is an array of prices that belongs to one variant or product
- This implementation was only tested on latest Commerce.Core and Commerce.UI Nuget package (8.11.1).
- If product level pricing is not used (store prices on variants) and a price change is for a product, the price will not be saved on the product’s children and will be saved on the product.
Source
You can find the source on Expert Services Bitbucket account: https://bitbucket.org/episerver-es/product-level-pricing-provider/.
If you need to solve this problem, I hope this helps. Please feel free to contact me with any questions/suggestions/bugs in EPiServer Expert Services at Tobias.Nilsson@episerver.com.
Can we avoid GetCatalogEntryDto used in public static Boolean IsVariant(this ContentReference catalogContentReference)?
https://bitbucket.org/episerver-es/product-level-pricing-provider/src/4674190c0dbdb0a6db1f839510bdd10f256d6342/ProductPriceDetailService/Extensions.cs?at=master
Regards
/K
Hi Khurram,
(ContentReference), but that caused a deadlock as I was trying to read the same entry twice in the same thread. And apparently there's a read lock on the database table/row. So I had to resort to using the old method of getting the entry.
At first I used the Get