This document deals with the pricing of products. Its main focus is the definition of product prices in various ways, which are then used by the storefront for display purposes and as a base for selling them. It does explicitly not describe promotions, cart calculation etc, as these topics are covered by other, dedicated documents. Also, prices for product configurations and warranties are explicitly excluded from this document as they follow their own approach.
From a customer perspective, prices come into play as soon as product information is viewed, e.g., on the product detail page.
This snippet shows two prices:
This is a typical marketing scenario, where you want to show a price at which the product is sold (in big bold font) if you put it in your cart (not considering promotions and the like yet) next to a price that has just some informational purpose (in small crossed-out font) trying to convince the storefront user to buy the product right now as it seems to be cheaper at the moment.
In order to realize such sections, you need different types of prices and a definition of which type of price shows up where and under which condition. Most of the time, a store will at least have:
So the business use case would be something like "show the regular retail price as the sale price and the manufacturer suggested retail price as informational price, but the latter one only if it is higher than the regular retail price". In more fine-grained scenarios, there might even be multiple informational prices. Thus the section of informational prices would show multiple values. Think of a scenario like "product A's price was $50, then $45, now only $40", where $50 and $45 are the informational prices and $40 the sale price.
The price at which the product is offered, i.e., the base value for a line item when putting it into the cart. Promotions etc are calculated on top of that.
Just for informational purposes, this is a price that is most likely higher than the sale price, like a manufacturer suggested retail price, which is normally higher than the regular retail price.
Each retrieved price has also a type like SalePrice, ListPrice or CostPrice, representing the business meaning.
product price provider
Containing logic that reads a price from a specific source like a price list.
product price selection strategy
Equates the price type considering product price providers to price type assignment.
It is like a sheet of paper with two columns, one for the product and one for its price.
price list type
Each price list of the price list storage has a type, e.g., ES_SalePrice.
product price change event
A discrete point in time when the price of a product changes.
Import/export format definitions:
Since there are different purposes for prices, different types of prices are required. The following price types currently exist:
regular retail price
manufacturer suggested retail price
There is one price type registry on which all eligible price type instances are registered with an external name (SalePrice, ListPrice, CostPrice). Clients could easily add their own additional price types to the price type registry with their own named instance of a PriceType implementation. The PriceType implementations are very simple and just provide an internal name for itself. When talking about a "price type", the external name is meant.
Conversely, this also means that a retrieved price value should always be typed. That is, there is no "give me the price of product [A]" request, instead it is a "give me the price of type [X] for product [A]". In this request, the codomain of [X] contains all external names registered in the price type registry, which currently are SalePrice, ListPrice, and CostPrice.
The price type for which you request a product's price value drives the look-up (as already stated above). You do not really store a product's price for a given type. It is more a usage of some storage, and the look-up configuration then defines which storage is involved to determine the price of the desired price type. Such a look-up configuration could be different per price type. But this is discussed in the later sections; for now, you just need to know that you always request prices for a given price type but do not store them for a given type.
As stated before, prices are not stored for the defined price types. Instead, Enfinity currently provides 3 different storages where you can save your product prices:
You might ask why there is no direct coupling between a PriceType and such a storage, e.g., the ListPrice price type and the ProductListPrice storage. The reason is that the retrieval of a price is subject to a configuration that can contain fallbacks. So a storage can be multi-purpose (polymorphic), e.g., the ProductListPrice storage might be (typically) involved for retrieving the ListPrice and serve as such, but could also be involved as a fallback when retrieving the SalePrice (and serve as such).
The ProductListPrice is just a flat price for a currency, not time- or customer/customer segment-driven.
Since the prices are part of the ORM product import, you can provide product list prices as a part of the <product>...</product> section. You can also use the back office to manage such product list prices (the price list section on the Price tab of the product detail view).
<product-list-prices> <product-list-price currency="EUR">100</product-list-price> <product-list-price currency="USD">140</product-list-price> </product-list-prices>
This storage is coupled very closely to the product. It can be stored at the master repository and is subject to product sharing, or it can be set up directly in the channel.
You would typically use this storage for manufacturer suggested retail prices.
The ProductCostPrice is a flat price for a currency, not time- or customer/customer segment-driven.
As part of the ORM product import you can provide product cost prices as a part of the <product>...</product> section. You can also use the back office to manage such product cost prices (the cost price section on the Price tab of the product detail view).
<product-cost-prices> <product-cost-price currency="EUR">50</product-cost-price> <product-cost-price currency="USD">70</product-cost-price> </product-cost-prices>
This storage is coupled very closely to the product. It can be stored at the master repository and is subject to product sharing, or you can set it up directly in the channel.
You would typically use this storage for prices that your purchasing department had to pay in order to provide this product in the shop. As this is not a very common use case, especially for B2C, this storage may be often not used/needed.
A Price List is not part of the product import; instead and due to its greater complexity, it is a separate ORM import. Besides the simple import, there is also a scheduled import, which looks for price list import files in a specific folder at regular intervals. You just need to drop your files there and they are imported automatically.
You can also use the back office to manage such price lists. The option is found in the special price section on the Price tab of the product detail view, where you can add entries to price lists for your product. There are several other routes for price list management:
<?xml version="1.0" encoding="UTF-8"?> <enfinity xsi:schemaLocation="http://www.intershop.com/xml/ns/enfinity/6.3/bc_pricing/impex bc_pricing.xsd" xmlns="http://www.intershop.com/xml/ns/enfinity/6.3/bc_pricing/impex" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="http://www.intershop.com/xml/ns/enfinity/6.5/core/impex-dt"> <product-price-list id="pl1" priceType="ES_SalePrice"> <display-name xml:lang="en-US">pl1</display-name> <description xml:lang="en-US">Price List</description> <enabled>true</enabled> <priority>1.0</priority> <valid-from>2013-10-01T00:00:00+03:00</valid-from> <valid-to>2013-10-31T00:00:00+02:00</valid-to> <user-groups> <user-group id="IG_RegisteredUsers" domain="PrimeTech-PrimeTechBusiness-Anonymous" /> <user-group id="IG_SMBCustomers" domain="PrimeTech-PrimeTechBusiness-Anonymous" /> <user-group id="IG_UnregisteredUsers" domain="PrimeTech-PrimeTechBusiness-Anonymous" /> </user-groups> <customers> <customer id="AgroNet" /> <customer id="BioTech" /> <customer id="CarPort" /> <customer id="OilCorp" /> </customers> <product-price-list-entry sku="6946438"> <price-scale-table currency="USD" type-code="1"> <price-scale-entries> <relative-price-entry quantity="1.0" unit=""> <value>25.0</value> </relative-price-entry> </price-scale-entries> </price-scale-table> </product-price-list-entry> <product-price-list-entry sku="7041208"> <price-scale-table currency="USD" type-code="1"> <price-scale-entries> <fixed-price-entry quantity="1.0" unit=""> <value>100.0</value> </fixed-price-entry> </price-scale-entries> </price-scale-table> </product-price-list-entry> </product-price-list> </enfinity>
This storage is coupled very loosely to the product. Price lists are only stored in the channel and therefore are not subject to product sharing. That is, if you have two B2C channels which share products with the master repository, each of them has to set up their own price lists, they do not derive them from the master repository.
Let us outline some of the core features of price lists:
The typical usage of the PriceList storage cannot be outlined as easily as for the ProductListPrice and ProductCostPrice. The reason is the ability to state of which type a price list is. Currently, there is one type available for price lists: ES_SalePrice.
Do not mix up the price list type ES-SalePrice with the price type! Of course it is no accident that this ES_SalePrice price list type equates the internal name of the SalePrice price type. You will see later that this equation is used when retrieving prices.
You can add your own price list type definitions to the system, which will automatically provide the ability to manage this type of price list in the back office. As this type is related to the price retrieval, your rule of thumb should be that there is a price type counterpart whose internal name equates this price list type. The section about price retrieval will make this more comprehensible.
As stated in the price type section, "there is no 'give me the price of product [A]' request, instead it is a 'give me the price of type [X] for product [A]'. In this request, the codomain of [X] contains all external names registered in the price type registry, which currently are SalePrice, ListPrice, and CostPrice".
Product price providers are registered using the Enfinity provider framework. It can register up to 6 providers (in an ordered manner) per price type. This facilitates registering your own client-specific product price providers for existing price types. In case you defined your own price types, the approach is similar - just define which providers are registered for your price type following the corresponding notation.
The product or its business object just delegates the request for a price to the ProductPriceMgr.
When asking for a product's price by a given price type, let the ProductPriceMgr perform the following steps:
As the registration of the providers happens in an ordered manner, the iteration over all of them by the ProductPriceMgr follows this order. Each asked provider must react to the fact that a previous one in the iteration might already have obtained a price, e.g., just let it pass and continue with the next iteration step or maybe modify it (calculate on it, replace it or whatever you might think of).
The ProductPriceMgr also incorporates the logic of how the price for different types of products is obtained from a structural perspective. What does that mean? Assume you have a simple product like a TV - it has a price, that's it. Now assume you sell a jacket with different colors and sizes (i.e., variations), or a retail set as a combination of various products. You cannot just state one price, as the price mainly depends on the involved parameters - a jacket in size XXL may be more expensive than one in size S. This leads to price ranges (min - max) for master products and retail sets. In case min and max are the same, the price range collapses to a single value again.
A master product with n variations means there might be n prices involved. The price range for the master product is built using the lowest price across all variations and the highest price across all variations.
Jacket: $60 - $70
A retail set with n parts means there might be n prices involved. The price range for the retail set is built using the lowest price across all parts and the sum of all parts' prices.
PC: $100 - $1050
As you can see, the really interesting part is now the behavior of these providers. At the end, each of them will use a price storage to retrieve a price from. In Enfinity, there are providers available for each of the three storages:
Now the picture gets clearer why a storage is not tied to a price type. It solely depends on which providers are registered for a price type to determine a product's price. It might be confusing that the storages are named pretty similar to the price types, especially the ProductListPrice and ProductCostPrice storage, but this is just since price types have been introduced after these storages - so do not let yourself be confused by this naming.
Using the ProductListPrice storage, this one is pretty straightforward and returns the corresponding value found.
Using the ProductCostPrice storage, this one is pretty straightforward and returns the corresponding value found.
This one uses the PriceList storage. It is the Swiss army-knife for a price storage due to the extensible price list types. Now there are 2 very basic facts you need to know to understand how a product's price is retrieved from the price lists:
An example will illustrate this: "PriceListPriceProviderImpl ... give me the price of product A for the price type SalePrice".
As outlined above, the provider first needs to determine which price lists must be involved. Therefore, it resolves the price type's internal name (SalePrice resolves to ES_SalePrice as we learned before) and fetches all price lists whose price list type equates to this internal price type name (which is ES_SalePrice). Of course only price lists are relevant where the validity period and user group assignments match, too.
Now step 2, following the defined strategy, is applied on this set of price lists, which retrieves the desired price. As there might be multiple entries for one product on a price list, the applicable entry is chosen by the newest valid-from date (in case of multiple identical
lastmodified dates). As price lists and their entries are time-driven (i.e., have a valid-from and a valid-to date, BTW not defining them is interpreted as non-restricted), a validity period of the returned price value can be calculated.
The following table shows which providers are registered for which price type in Enfinity (such an assignment is also called "product price selection strategy"). The order of the providers is to be read top-down per price type.
Especially the ListPriceProviderImpl is an example of how a storage can be polymorphic. Even though it looks into the ProductListPrice storage, its value is once interpreted as ListPrice (it is the only provider registered for the ListPrice price type), and the other time as SalePrice (in case the PriceListPriceProviderImpl, which comes first in the look-up order of the SalePrice selection strategy, did not find a value within a price list).
As provider definitions are defined per cartridge, the definition of selection strategies is valid for the entire application server (because provider definitions are loaded per cartridge in the order they are defined in the cartridgelist.properties).
This section lists the price display-relevant CMS components.
display sale price
The product's SalePrice price is shown. It can be configured such that it is highlighted in case it is lower than the price types selected for this component. So for instance, it can be set up that the SalePrice price is highlighted if it is lower than the ListPrice price.
display informational price
This component defines which informational price should be displayed, e.g., the ListPrice price. You can also define against which price it must be compared in order to even show up. This allows to define, for instance, that the ListPrice price should be shown, but only if it is lower than the SalePrice price. So this serves as a counterpart (in terms of marketing) to the component that shows the SalePrice price.
display of savings between sale and informational price
This one allows you to present the savings between the SalePrice price and a set of other informational prices. For example, you select ListPrice and CostPrice, so the savings ListPrice-SalePrice and CostPrice-SalePrice will be calculated, the lowest price will win and be displayed. So the maximum saving is finally shown to the user.
display of validity period of the sale price
The date range of valid-from until valid-to of the currently applying SalePrice price is shown. Especially during special marketing periods (like seasonal sales), this will allow the user to see how long he still is eligible to get a product for the featured price.
Intershop supports price display formatting setup, which is not part of the pricing, but part of the Configuration framework.
Each localization described in localization.properties could have its own localization file placed in <ISServer>/share/system/config/cluster folder. The file format is <LanguageCode>_<CountryCode>.properties (e.g., en_US.properties).
The properties which are used for price formatting are :
Since the properties are loaded on server startup, any changes will not take effect until localization.lastupdate file is deleted and the server is restarted.
In case you use a search engine, the search index uses data providers to index attributes. There are currently two corresponding providers available:
You can add the ListPrice and SalePrice to the index attributes of the search index. The user group- and time-driven parameters are ignored, i.e., the corresponding price is fetched for everyone, and the current time as a search index is not specific per user group and/or time.
As product prices can be time-driven (due to the usage of the PriceList storage), there are discrete points in time when a product's price can change, namely "product price change events".
This can be realized by a price list containing entries for this product with the according valid-from and valid-to dates. Assume the import of this price list already happened in summer 2011. When reaching the desired marketing time frame before Christmas (i.e., the price event on 01 Dec 2011), the system should automatically realize that a price change happens. Consequently, the system triggers the required logic to make the storefront show this new price. This includes:
The established mechanism (product price change event handler) allows to wire an arbitrary number of listeners that react on such events. This is, a client may add their own implementations that do whatever they need to do once the price changes (e.g., sending out notifications if a price drops below a threshold). Such configuration happens in an ordered manner. As a price change event should always be per product (hence the naming), there are also collectors attached that resolve the related product out of the involved object. That means, if there is a change event for a price list (e.g., because the whole price list is only valid until a specific date), there is a collector that fetches all products of this price list for which the processing will happen subsequently.
As the PriceList storage is currently the only one that provides that ability to manage time-driven prices, it is also the only element (including its sub-elements) that is subject to tracking the events. Every time somebody changes a PriceList price, via Commerce Management application or via import, which leads to a new product price change event, this event is stored persistently. The ORM layer is used to notice changes in PriceList prices.
The evaluation of the events happens on a regular basis. All these events that need to apply since the last run of this evaluation are processed in the next run. This means that a product price change does not really apply in real time but at least "near" real time. The job that performs the event evaluation can, of course, be configured to run more often to come closer to "real time" (but keep the performance in mind). The job and possibilities of configuration is described in Job - ProcessProductPriceRefresh.
This document is about handling the product price based on date/time & user groups. Currently, it is used in the storefront when previewed from the back office. The product price is calculated upon a given date/time (current or fixed) and on selected user groups. The site preview can be configured with different parameters. The product price handling in preview mode is modified to respect this configuration. The technical configuration used for determining the correct price is not directly dependent on the date configured by the user in the preview configuration panel. Instead, an extension point is provided as the necessary connection between the configurations.
The actual ISH 7.1 version determines the current product price depending on the system or selected date/time & selected user groups. If the storefront is called from the back office, the price list is calculated upon the given configuration.
The date/time configuration determines whether the current date or a fixed date, specified by the user, is used for the preview. Below you can see the configuration panel used for storefront preview for date/time configuration.
The user group configuration determines user groups, specified by the user, used for the preview. Below you can see the configuration panel used for storefront preview for user groups selection.
PriceCalculationConfigurationProvider- provides the price calculation configuration values.
DefaultPriceCalculationConfigurationProvider- class implementing
PriceCalculationConfigurationProvider. Defines an extension point for the
priceCalculationOnlineDateconfiguration parameter. The
getPriceCalculationOnlineDatemethod goes through the extensions and returns the value from the extension with highest priority and whose
isPriceCalculationOnlineDateActivemethod returns true. Otherwise the current date is returned. The
isPriceCalculationOnlineDateParameterActivemethod returns true if an extension with an active
PriceCalculationOnlineDatewas found, otherwise it returns false.
PriceCalculationOnlineDateConfiguration- The extension point interface.
UserGroupProvider- provides methods to find out the assigned user groups of a user.
UserGroupProviderImpl- class implementing
getCurrentUserUserGroupsmethod reads user groups stored in the HTTP session object. Returns an empty collection if no user group items exist.
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.