This document describes the pricing provider feature in EPiServer Commerce. The pricing logic in the system is provider based so that it can call an ERP system or other external system directly to get price data for a product. It will allow a custom pricing module to be developed in the system, and for the price data to be used in all display and calculation locations in the platform. The existing pricing data is isolated into a replaceable module, and upgrades will always use this provider.
Classes referred to here are available in the following namespaces:
The pricing providers are the access points for accessing and editing price data. Various pieces of data in the price values determine when the price is applicable:
- The market associated with the purchase
- The currency for the price
- The quantity being purchased
- The customer making the purchase
- The date and time that the purchase is placed
All price values are specific to a single market, specified in the MarketId property of IPriceValue. In implementations that do not use multi-market functionality, all prices will be specific to the default market.
All prices are specific to a single currency, specified in the Money.Currency property within IPriceValue.UnitPrice.
Automatic currency conversion is not supported directly within the pricing system. If automatic conversions between currencies are needed, a scheduled task should be created to convert prices from a base currency with prices managed by a user or ERP.
Prices may be limited to purchases of a minimum quantity with the IPriceValue.MinQuantity property. Since the commerce system permits fractional quantities, the minimum quantity for a default price should be set to 0, and not 1. Prices that do not vary by quantity should have a minimum quantity of 0.
Purchase date and time
Each price is limited by date and time, to allow prices changes to be set to take effect in the future, and to ensure that past orders can be recalculate based on the price at the time of the purchase for processing returns.
Each price value may be available to all customers, individual customers, or all customers in a specific customer group. The customer specific pricing is set in the CustomerPricing property of IPriceValue, which is of type CustomerPricing.
For a price to be available to all customers, the CustomerPricing.AllCustomers constant should be used. This is the default value.
To make a price available only to a specific customer group, create a CustomerPricing object with a PriceTypeId value of PriceType.PriceGroup and a PriceCode value matching the desired customer group.
The price group for a customer is set either in an organization associated with the customer, or directly in the customer contact. If a customer is associated with an organization, and that organization has a non-empty customer group value for Organization.OrgCustomerGroup value, then the value of that property is the customer's effective customer group. If the customer does not have an organization-level customer group, then the customer group is defined by CustomerContact.CustomerGroup.
The CustomerContact.EffectiveCustomerGroup property encapsulates the logic of determining the customer's price group. Additional price types can be defined in ecf.catalog.config. Refer to PriceType examples for more information on how to work with price typ features.
Multiple matching price values
Multiple price values will frequently be available for a particular product, market, currency, quantity, date, and customer. For example, a purchase of a dozen items may match prices with:
- Minimum quantity 0.0, available to all customers
- Minimum quantity 0.0, available only to the current customer's customer group
- Minimum quantity 12.0, available to all customers
- Minimum quantity 12.0, available only to the current customer's customer group
When multiple prices are available for a purchase, the lowest available price is always used.
Interacting with price data
Two services are available for interacting with price data. The IPriceService API is typically used by back-end order processing to fetch prices for actual use, while the IPriceDetailService is typically used in user interface elements to display and edit prices.
The difference between these APIs is that the IPriceService works with optimized sets of price data, while the IPriceDetailService works with exact user input. The optimization in the IPriceService removes prices that will never be used, and trims or splits prices that overlap.
For example, if a price manager sets three prices that are identical except:
- All Customers, Minimum Quantity 0.0, Unit Price ¤100.00
- All Customers, Minumum Quantity 10.0, Unit Price ¤200.00
- Specific Customer Group, Minimum Quantity 0.0, Unit Price ¤200.00
Then the second and third prices are entirely redundant. Any order that matches either the second or third price will also match the first, and the first price will be used because it is the lower price. If these prices are saved and then fetched via the IPriceDetailService, then all three prices will be returned. If the prices are fetched via the IPriceService, then only the first price will be returned, since it will never be used for processing.
The optimized service may also make some modifications to prices. For example, if a price manager sets two prices that are identical except:
- Available for an entire year, Unit Price ¤200.00
- Available only for the month of February, Unit price ¤100.00
Then the optimized price service will split the first price into one value that is only for January, and one value that is for March through December; and leave the price for February intact.
Using the IPriceDetailService
The price detail service is the recommended way to access price data for user interfaces.
To fetch prices, a List method is used with a content reference and optional filter. The PriceFilter class is discussed below. The content reference has contextual behavior:
- If a content reference for a node is used, then prices for all products and variants in that node will be returned.
- If a content reference for a product is used, then prices for the product and all of its variants will be returned (typically, a system will only have prices on products or variants, but not both).
- If a content reference for a variant is used, then prices for the variant will be returned.
The Save and Delete methods insert/update or delete the specified values. Values are identified by the IPriceDetailValue.PriceValueId property, which should be left as 0 when creating a new value.
All values edited via the detail service are immediately incorporated into the optimized service. If a custom detail service is implemented, and it does not have its own mechanism for synchronizing with a custom optimized service, then it must call IPriceService.ReplicatePriceDetailChanges on all edits to update the optimized data store. Calls to ReplicatePriceDetailChanges must include a collection of all the catalog entries affected by the changes, and all price values associated with those entries. If one price value is added to one price entry, then ReplicatePriceDetailChanges must be called with a key to the associated entry, and ALL price values associated with that entry after the change.
Using the IPriceService
The optimized price service is the recommended way to access price data when using price values for business processing.
The optimized prices are fetched by catalog entry. There is no contextual behavior for the optimized service based on the type of the entry; only prices directly associated with the target entry are returned, regardless of if it is product, variant, or other entry type.
The GetCatalogEntryPrices methods return full sets of price data for the specified catalog entries.
The GetPrices methods return filtered data for the specified catalog entries. Returned price values will match all specified The CatalogKeyAndQuantity class may be used to get prices for a multiple entries, with different quantities for each, in a single request.
When saving prices via the optimized price service, all price values for the specified catalog entries must be included. The SetCatalogEntryPrices methods replace all price values for the specified catalog entries; if no price values are specified, then the pricing data is cleared for the entry.
All values edited via the optimized service are immediately incorporated into the detail service. If a custom optimized service is implemented, and it does not have its own mechanism for synchronizing with a custom detail service, then it must call IPriceDetailService.ReplicatePriceServiceChanges on all edits to update the optimized data store. When optimized prices are replicated into the detail service, all values for the edited catalog entries are replaced in the detail service, which may eliminate redundant values or create less intuitive data when price values are split as in the date example above. For this reason, the price detail service is recommended as the service to use for editing, unless this behavior is specifically desired.
The PriceFilter class
The PriceFilter class is used to describe a filter for a pricing request.
- Currencies describes the currencies that may be returned in the results. If the value of this property is null or empty, value with any currency may be returned. The default value of this property will return all currencies.
- Quantity describes the quantity of the entry being purchased. If the value of this property is null, then prices for all quantities will be returned. The initial value will return prices for all quantities. When used in a method that also accepts a parameter with type CatalogKeyAndQuantity, then this value will be ignored in favor of that parameter value.
- CustomerPricing describes the customer pricing values to return. The values in this enumeration must be an exact match; if this property only describes a specific user, then prices that are available to all customers will not be included in the result.
- ReturnCustomerPricing describes how price data will be returned from the optimized price service. If this value is false, and multiple values could be returned that differ only on customer pricing, then only the lowest-priced value in the group will be returned. If the value is true, then the individual values will be returned.
The ReturnCustomerPricing property is typically false when fetching price data for business processing, since only the final price value is usually needed. It is not typically useful to set this value to true with doing business processing; if additional data about varying prices by customer is needed, consider using the price detail service.