This topic describes the pricing provider feature involved in the calculation of various types of prices in Optimizely Commerce.
In this topic
How it works
The pricing logic is provider-based, so it can call an ERP system or another external system directly to get a product's price data.
It lets you develop a custom pricing module in the system, and lets you use price data in all display and calculation locations in the platform. The existing pricing data is isolated into a replaceable module, and upgrades always use this provider.
Classes in this topic are available in the following namespaces:
- Mediachase.Commerce.Pricing
- Mediachase.Commerce.Catalog
Price data
The pricing providers are the access points for accessing and editing price data. The following pieces of data in the price values determine when a price is applicable:
- The market associated with the purchase.
- The currency for the price.
- The quantity being purchased.
- The date and time that the purchase is placed.
- The customer making the purchase.
Market
Price values are specific to a single market, specified in the MarketId property of IPriceValue. In implementations that do not use multi-market functionality, prices are specific to the default market.
Currency
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.
Quantity
You can limit prices to purchases of a minimum quantity with the IPriceValue.MinQuantity property. Because the commerce system permits fractional quantities, set the minimum quantity for a default price to 0, and not 1. Set prices that do not vary by quantity to a minimum quantity of 0.
Purchase date and time
Each price is limited by date and time, to allow prices changes to take effect in the future, and to ensure that past orders can be recalculated based on price at purchase time for processing returns.
Customer pricing
Each price value may be available to all customers, individual customers, or all customers in a specific customer group. You set customer-specific pricing in the CustomerPricing property of IPriceValue, which is of type CustomerPricing.
- All customers. Use the CustomerPricing.AllCustomers constant to set a price for all customers; this is the default value.
- Individual customer. Create a CustomerPricing object with a PriceTypeId value of PriceType.UserName and a PriceCode value matching the desired username.
- Customer group. Create a CustomerPricing object with a PriceTypeId value of PriceType.PriceGroup and a PriceCode value matching the desired customer group.
You set the price group for a customer in an organization associated with the customer, or directly in the customer contact. If you associate a customer with an organization, and that organization has a non-empty customer group value for Organization.OrgCustomerGroup value, the value of that property is the customer's effective customer group. If the customer does not have an organization-level customer group, the customer group is defined by CustomerContact.CustomerGroup.
[New in Commerce 14] The CustomerContact.EffectiveCustomerGroup property encapsulates the logic of determining the customer's price group. You can define additional price types through CatalogOptions. See PriceType examples for information about working with price type features.
Multiple matching price values
Multiple price values are frequently 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 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, Minimum Quantity 10.0, Unit Price ¤200.00
- Specific Customer Group, Minimum Quantity 0.0, Unit Price ¤200.00
The second and third prices are redundant. Any order that matches either the second or third price also matches the first, and the first price is used because it is lower. If these prices are saved and then fetched via the IPriceDetailService, all three prices are returned. If the prices are fetched via the IPriceService, only the first price is returned, because it will never be used for processing.
The optimized service also may 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
The optimized price service splits the first price into one value for January only, one value for March through December, and leaves the February price intact.
To change the way a price is selected to be saved (for example, you want to save the highest overlapped price), implement IPriceOptimizer and register it in one of your IConfigurableModules.
public class CustomPriceOptimizer : IPriceOptimizer
{
public IEnumerable<IOptimizedPriceValue> OptimizePrices(IEnumerable<IPriceValue> prices)
{
return prices.GroupBy(p => new { p.CatalogKey, p.MinQuantity, p.MarketId, p.ValidFrom, p.CustomerPricing, p.UnitPrice.Currency })
.Select(g => g.OrderByDescending(c => c.UnitPrice.Amount).First()).Select(p => new OptimizedPriceValue(p, null));
}
}
Using the IPriceDetailService
[New in Commerce 14] Use the price detail service to access price data in edit view. An admin UI component in edit mode might need price information, and the IPriceDetailService should be used in that case.
To fetch prices, a List method is used with a content reference and optional filter. The content reference has contextual behavior:
- If a content reference for a node is used, prices for all products and variants in that node are returned.
- If a content reference for a product is used, prices for the product and all of its variants are returned (typically, a system only has prices on products or variants, but not both).
- If a content reference for a variant is used, prices for the variant are 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.
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, the detail service must call IPriceService.ReplicatePriceDetailChanges on edits to update the optimized data store. Calls to ReplicatePriceDetailChanges must include a collection of the catalog entries affected by the changes, and price values associated with those entries. If you add one price value to one price entry, then you must call ReplicatePriceDetailChanges with a key to the associated entry, and each price value associated with that entry after the change.
Using the IPriceService
Use the optimized price service to access price data when you use price values for business processing. The default implementation of IPriceService uses ISynchronizedObjectInstanceCache to prevent unnecessary database calls.
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 whether it is a 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. You can use the CatalogKeyAndQuantity class to get prices for a multiple entries, with different quantities for each, in a single request.
When you save prices via the optimized price service, include price values for the specified catalog entries. The SetCatalogEntryPrices methods replace price values for the specified catalog entries; if no price values are specified, the pricing data is cleared for the entry.
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, the optimized service must call IPriceDetailService.ReplicatePriceServiceChanges on edits to update the optimized data store. When you replicate optimized prices into the detail service, 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. For this reason, use the price detail service as the service to use for editing, unless this behavior is specifically desired.
The PriceFilter class
Use the PriceFilter class to describe a filter for a pricing request.
- Currencies describe 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 returns all currencies.
- Quantity describes the quantity of the entry being purchased. If the value of this property is null, prices for all quantities are returned. The initial value returns prices for all quantities. When used in a method that also accepts a parameter with type CatalogKeyAndQuantity, this value is 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 available to all customers are not included in the result.
- ReturnCustomerPricing describes how price data is returned from the optimized price service. If this value is false, and multiple values could be returned that differ only on customer pricing, only the lowest-priced value in the group is returned. If the value is true, individual values are returned.
The ReturnCustomerPricing property is typically false when fetching price data for business processing, because only the final price value is usually needed. It is typically not useful to set this value to true with doing business processing; if you need additional data about varying prices by customer, consider using the price detail service.
Last updated: Jul 02, 2021