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:
A price in small crossed-out font ($ 799)
A price in big bold font ($ 719.10)
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:
A regular retail price at which products are sold and
A manufacturer suggested retail price, which is usually higher.
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.
Name | Meaning |
---|---|
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. |
informational price | 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. |
price type | 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. |
price list | 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:
for the product list price see .../share/system/impex/schema/catalog.xsd
for the product cost price see .../share/system/impex/schema/catalog.xsd
for the price list see .../share/system/impex/schema/bc_pricing.xsd
Have a look at Cookbook - Pricing for related common questions.
Further readings:
Since there are different purposes for prices, different types of prices are required. The following price types currently exist:
external name | internal name | business meaning |
---|---|---|
SalePrice | ES_SalePrice | regular retail price |
ListPrice | ES_ListPrice | manufacturer suggested retail price |
CostPrice | ES_CostPrice | purchase 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, Intershop Commerce Management currently provides three different storages where you can save your product prices:
ProductListPrice
ProductCostPrice
PriceList
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 Commerce Management application 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 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 Commerce Management application 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 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 not be used/needed in many cases.
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. Drop your files there and they will be imported automatically.
You can also use the Commerce Management application 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:
Catalogs|Price Lists: manage all aspects of all price lists for the current channel
Customers|Specific Customer|Price Lists: manage price lists to which this customer or his customer segments are assigned.
Customers|Customer Segments|Specific Customer Segment|Price Lists: manage price lists to which this customer segment is assigned.
The following is a sample xml, which might be used for import. For more information of the available fields, have a look at bc_pricing.xsd.
<enfinity xsi:schemaLocation="http://www.intershop.com/xml/ns/enfinity/7.0/bc_pricing/impex bc_pricing.xsd" xmlns="http://www.intershop.com/xml/ns/enfinity/7.0/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> <target-groups> <customer-segments> <customer-segment id="IG_RegisteredUsers" repository-id="PrimeTech-PrimeTechBusiness-Anonymous" /> <customer-segment id="IG_SMBCustomers" repository-id="PrimeTech-PrimeTechBusiness-Anonymous" /> <customer-segment id="IG_UnregisteredUsers" repository-id="PrimeTech-PrimeTechBusiness-Anonymous" /> </customer-segments> <customers> <customer id="AgroNet" /> <customer id="BioTech" /> <customer id="CarPort" /> <customer id="OilCorp" /> </customers> </target-groups> <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>
Customer Segments Export
Customer segment assignments coming from currently disabled Customer Segmentation Service will not be exported.
The following file is a sample CSV, which might be used for import. It contains the same data as the XML above.
PriceList_Name;PriceList_ID;PriceList_Description;PriceList_PriceType;PriceList_Enabled;PriceList_Priority;PriceList_ValidFrom;PriceList_ValidTo;PriceList_CustomerSegment_ID1;PriceList_CustomerSegment_Repository_ID1;PriceList_CustomerSegment_ID2;PriceList_CustomerSegment_Repository_ID2;PriceList_CustomerSegment_ID3;PriceList_CustomerSegment_Repository_ID3;PriceList_Customer_ID1;PriceList_Customer_ID2;PriceList_Customer_ID3;PriceList_Customer_ID4;Product_SKU;PriceScale_Type;PriceScale_Currency;FixedPriceScale_Price1;FixedPriceScale_Quantity1;RelativePriceScale_Price1;RelativePriceScale_Quantity1 pl1;pl1;Price List;ES_SalePrice;true;1;2013-10-01T00:00:00+03:00;2013-10-31T00:00:00+02:00;IG_RegisteredUsers;PrimeTech-PrimeTechBusiness-Anonymous;IG_SMBCustomers;PrimeTech-PrimeTechBusiness-Anonymous;IG_UnregisteredUsers;PrimeTech-PrimeTechBusiness-Anonymous;AgroNet;BioTech;CarPort;OilCorp;6946438;1;USD;;;25;1 pl1;pl1;Price List;ES_SalePrice;true;1;2013-10-01T00:00:00+03:00;2013-10-31T00:00:00+02:00;IG_RegisteredUsers;PrimeTech-PrimeTechBusiness-Anonymous;IG_SMBCustomers;PrimeTech-PrimeTechBusiness-Anonymous;IG_UnregisteredUsers;PrimeTech-PrimeTechBusiness-Anonymous;AgroNet;BioTech;CarPort;OilCorp;7041208;1;USD;100;1;;
CSV Format description:
The CSV columns should be separated with the ; symbol. If the symbol in the CSV is different, there is an option in the Commerce Management application to specify delimiter to be used.
The order of the CSV headers is not strict, but the data in the columns must match the header. If column data is missing, just add the delimiter symbol and continue with the next column.
The CSV import supports up to 10 customer segment assignments, up to 10 customer assignments, up to 10 fixed price values for different quantities and up to 10 relative price values for different quantities.
CSV Headers
Header | Mandatory | Description |
---|---|---|
PriceList_Name | yes | the name of the price list |
PriceList_ID | yes | the id of the price list |
PriceList_Description | no | description of the price list |
PriceList_PriceType | yes | price type of the price list (see Price Types) |
PriceList_Enabled | yes | indicates if the price list is enabled (prices from it are taken into account in the storefront) |
PriceList_Priority | yes | priority which is taken into account when the price lookup is custom |
PriceList_ValidFrom | no | from when the price list is valid (taken into account) |
PriceList_ValidTo | no | to when the price list is valid (taken into account) |
PriceList_NetPrice | no | indicates if the prices being imported should be considered net or gross |
PriceList_Customer_ID1 | no | customer to be assigned to the price list |
... | ... | ... |
PriceList_Customer_ID10 | no | customer to be assigned to the price list |
PriceList_CustomerSegment_ID1 | no | customer segment, to be assigned to the price list |
PriceList_CustomerSegment_Repository_ID1 | no (yes if PriceList_CustomerSegment_ID1 is present) | repository id of the customer segment |
... | ... | ... |
PriceList_CustomerSegment_ID10 | no | customer segment, to be assigned to the price list |
PriceList_CustomerSegment_Repository_ID10 | no (yes if PriceList_CustomerSegment_ID10 is present) | repository id of the customer segment |
Product_SKU | yes | the product SKU for which the price is imported |
PriceScale_Type | yes | type of the price scale (see Price Scale Type Codes) |
PriceScale_ValidFrom | no | from when the price list scale is valid (taken into account) |
PriceScale_ValidTo | no | to when the price list scale is valid (taken into account) |
PriceScale_Currency | yes | the currency of the price list scale |
FixedPriceScale_Price1 | no | value of the fixed price to be imported |
FixedPriceScale_Quantity1 | no (yes if FixedPriceScale_Price1 is present) | quantity for which the price is available |
... | ... | ... |
FixedPriceScale_Price10 | no | value of the fixed price to be imported |
FixedPriceScale_Quantity10 | no (yes if FixedPriceScale_Price10 is present) | quantity for which the price is available |
RelativePriceScale_Price1 | no | value of the relative price to be imported (this is used to import % off the base price) |
RelativePriceScale_Quantity10 | no (yes if RelativePriceScale_Price1 is present) | quantity for which the price is available |
... | ... | ... |
RelativePriceScale_Price10 | no | value of the relative price to be imported (this is used to import % off the base price) |
RelativePriceScale_Quantity10 | no (yes if RelativePriceScale_Price10 is present) | quantity for which the price is available |
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.
The JDBC/Batch import for price lists addresses the performance issues of the ORM-based import.
It uses SQL statements to store the data directly into the database instead of storing data via the ORM layer.
From ICM 11.0.14 all import modes (INITIAL, UPDATE, REPLACE, DELETE) are available for the JDBC/Batch import, which is also set up as default price list import strategy.
During the UPDATE mode existing price entries must be read from the database to check which entries are new, or which already exist and have to be updated. In order to reduce the number of database accesses, multiple price entries are fetched from the database.
The exact number of entries can be configured with the following property:
PriceList.UpdatePriceListEntiesChunkSize=100
The default number is 20. A higher number will improve the performance of the UPDATE mode but will also result in a higher memory load.
The JDBC/Batch import does not allow customizations as the ORM import does. No customization means: no extension points are available, no ORMObject listeners are available, etc.
The only supported custom attribute types of the price list are Integer, Double, String, MultipleInteger, MultipleDouble and MultipleString.
To switch back to ORM import, see Cookbook - Pricing | Recipe - Change Price List Import Strategy (JDBC/Batch vs. ORM) for further details.
Core features of price lists are:
Scale prices: manage scaled prices, i.e., price values depending on the related quantity.
Prioritization: prioritize price lists.This is relevant when looking up prices across multiple price lists (to be discussed later when talking about price retrieval).
Customer / customer segment driven: specify for which customer and/or customer segments a price list is eligible.
Time-driven: define the validity period for the whole price list and even for each of its entries.
Relative or absolute: provide a price as absolute value ($ 100) or as relative value (10% off from the list price).
Type: specify of which type a price list is.
The ability to assign price lists to customer segments depends on the currently configured customer segmentation services. There should be at least one such service enabled for the channel and storefront application in order to have customer segments available which can be assigned. Then the effect can be seen (as received price list-specific prices) in the storefront depending on the logged on customer. There is one Standard Customer Segmentation Service which provides access to the customer segments managed internally and stored in the Intershop system.
CustomerSegmentBO instances for both Commerce Management application and storefront implementations are provided by CustomerSegmentProvider which traverses all enabled customer segmentation services to retrieve the available customer segments.
If price list-customer segment assignments are created and the customer segmentation service the segments originate from is disabled then, the effect of these assignments will not be applied (as if they were absent). This means that in the Price List details | Target Group tab the assigned customer segments will not be present, and also in the storefront the customer segment-specific prices will not be seen. This does not mean, however that the assignments themselves are removed - they are still stored in Intershop database (no matter if the customer segments are managed internally or from external systems). So if the same segmentation service is later re-enabled, the customer segments (together with their effect) will be visible again.
For more details regarding customer segmentation please refer to: Concept - Customer Segmentation Managed Service.
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.
Heed the difference between the price list type ES-SalePrice and the price type! It is intended that this ES_SalePrice price list type equals the internal name of the SalePrice price type. 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 Commerce Management application. As this type is related to the price retrieval, there should be a price type counterpart whose internal name equates this price list type. See Price Retrieval section.
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 co-domain 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 ICM provider framework. It can register up to six 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 ProductBOPricingExtension is the starting point for price retrieval. Besides delegating the price retrieval to the ProductPriceMgr, tax calculation is handled here as well.
For more information on tax calculation see the Concept - Taxation Calculation and Display.
When asking for a product's price by a given price type, let the ProductPriceMgr perform the following steps:
Determine all ProductPriceProvider instances registered for the given price type
For each of them ask for the product's price (this includes price type, date, time, user groups, etc)
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 is 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.
Master ProductA 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.
Size S: $ 60
Size M: $ 65
Size L: $ 70
Retail SetA 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.
Hard disk: $ 100
Graphics card: $ 200
Display: $ 200
Main board: $ 200
CPU: $ 200
RAM: $ 150
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 | Intershop, there are providers available for each of the three storages:
Storage | Provider |
---|---|
ProductListPrice | ListPriceProviderImpl |
ProductCostPrice | CostPriceProviderImpl |
PriceList | PriceListPriceProviderImpl |
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 two very basic facts you need to know to understand how a product's price is retrieved from the price lists:
Multiple price lists can be managed per price list type.For example, you have four price lists set up for the ES_SalePrice price list type:
one containing the all-year regular retail prices for everyone
one containing the all-year regular retail prices for premium users
one containing the current-season regular retail prices for everyone
one containing the current-season regular retail prices for premium users
A strategy can be defined for the look-up across multiple price lists of the same price list type.
Determining a product's price from multiple price lists is basically an iteration across these multiple price lists. As multiple price lists might imply multiple hits (but we just want one price returned), a configurable strategy has to be followed. Currently, two strategies are defined (and selectable via the Commerce Management application):
Priority-based: when iterating the price lists, follow the order defined through the priority attribute. As soon as a price is found, use it as result (break the iteration).
Best price-based: determine the product price for each price list and choose the lowest one as result.
The following table shows which providers are registered for which price type in Enfinity | Intershop (such an assignment is also called "product price selection strategy"). The order of the providers is to be read top-down per price type.
PriceType | ProductPriceProviders |
---|---|
ListPrice | ListPriceProviderImpl |
CostPrice | CoststPriceProviderImpl |
SalePrice | PriceListPriceProviderImpl |
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:
shortCurrencyPositivePattern (and shortCurrencyNegativePattern)
longCurrencyPositivePattern (and longCurrencyNegativePattern)
inputCurrencyPositivePattern (and inputCurrencyNegativePattern)
currencyGroupingCharacter
currencyDecimalSeparator
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:
ProductListPriceDataProvider
Provides the ListPrice price as value for the index attribute.
ProductSalePriceDataProvider
Provides the SalePrice price as value for the index attribute.
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:
Clear caches of ProductPriceProvider implementations
Update the search index
Clear relevant page cache entries
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.