Achieve uniform pricing with Channels
Explore how Channels can be used to organize a complex pricing strategy.
After completing this page, you should be able to:
- Implement a pricing strategy that ensures uniform pricing for a Product Variant across multiple sales Channels and markets.
At Electronics High Tech, streamlined pricing during promotional events is a critical business requirement. The company achieves this through a carefully structured system of Channels and price lists.
As we mentioned on the previous page, with over 200 physical stores and an online store to manage, the company must ensure that every outlet offers the same Prices for products during sales events to maintain a coherent brand experience and customer satisfaction.
While there is no one perfect solution to meet these requirements, we have instead several paths to take to meet them. On this page, we will explore a few options that could allow Electronics High Tech to support their pricing requirements in Composable Commerce.
Total number of Prices
Before we jump into exploring the possible solutions, let’s consider how many Prices are involved in this Project.
We have 46,000 Products in the Project, with each Product averaging three Product Variants. We can take that as our starting point for estimating total Prices in our Project.
Item | Formula | Result |
---|---|---|
Total Products | - | 46,000 Products |
Average Product Variants per Product | 46,000 Products x 3 Variants | 138,000 Product Variants |
Number of Price Channels | 200 Physical Stores + 1 Online Store | 201 Channels |
Total Prices | 138,000 Product Variants x 201 Channels | 27,738,000 Prices |
We also have the following considerations to keep in mind as we work our way through:
- On average, the retail stores will elect their Prices for 20% of the Product Variants.
- On average, promotions impact 10% of Product Variants. These cases can overlap with the same Product Variants.
Ok, great, let’s now start working our way through three possible solutions.
Solution A: dedicated Distribution Channels for each sales Channel
Solution A involves creating the following in Composable Commerce:
- 201 Distribution Channels in Composable Commerce, which is one for each sales Channel (200 physical stores + 1 online store).
- Each physical store will also have its own Store entity associated with its corresponding Distribution Channel. This means we will also need to create 201 Stores in Composable Commerce.
Another way to look at this solution is that for each physical store or online store, we create one Distribution Channel and one Store. In Composable Commerce, each Distribution Channels will be assigned to their respective Store.
By combining Standalone Prices and scoped Pricing, we can meet each of the pricing requirements, let's see how.
Requirement | Solution |
---|---|
Use one of the eight standardized Price lists. | Use Currency + storeChannel scoping to set the specific Price list. |
Each retail store has the right to override Prices on an ad-hoc basis | Use Currency + storeChannel + Country. |
Global promotion Prices must override any other Price | Use Currency + storeChannel + Country + time bound. |
The solution above is relying on the fallback logic of Price selection, from least specific to most specific. This will ensure that the correct Price will apply.
To ensure this works correctly, when we add an item to a Cart, we need to set the correct distributionChannel
on the LineItemDraft.
Let's look at an example of how these Prices will look like in a table for a single Product Variant:
Row # | Currency | Country | Channel (name) | Time-bound | Price |
---|---|---|---|---|---|
1 | AUD | - | store-thomastown | False | 100.00 |
2 | AUD | Australia | store-thomastown | False | 105.00 |
3 | AUD | Australia | store-thomastown | True | 95.99 |
4 | AUD | - | store-darlinghurst | False | 100.00 |
5 | AUD | Australia | store-darlinghurst | True | 97.95 |
- Row 1: the Price represents one of the ‘Channel List Price’ for the Thomastown store Channel.
- Row 2: This Price represents one of the ‘Store Specific Override’ for thomastown Channel Store.
- Row 3: the Price represents one of the global Promotion Prices. Note that it is not of type Discounted.
- Rows 1 to 3: all Prices are scoped to one of the 201 Channels.
- Rows 4 and 5: the Prices are for the Darlinghurst store Channel. The Store has not set a ‘Store Specific Override’.
Solution summary
The minimum number of Prices per Product Variant is 201 (because we have 201 Channels, each with their own Price), whilst 20% of Product Variants will have 402 Prices (due to a Store-specific override Price) and 10% of Product Variants will have 402 Prices (due to global promotions Prices). Because of this, we will need to use Standalone Prices (Embedded Prices have a soft limit of 100 Prices per Product Variant)
The calculation for the total number of Price records in Solution A is:
Price Type | Formula | Number of Price Records |
---|---|---|
Product Variant Prices | 138,000 Product Variants x 201 Distribution Channels | 27,738,000 Prices |
Store-specific override Prices | 20% of 27,738,000 Prices | 5,547,600 Prices |
Global promotion Prices | 10% of 27,738,000 Prices | 2,773,800 Prices |
Total | Sum of all Price records | 36,059,400 Prices |
Let’s look at the strengths and weaknesses of this solution.
Strengths
- Granular control: the solution provides individual control over pricing for each sales Channel.
- Scalability: the solution supports the current number of Stores and can accommodate future expansion.
- Full feature utilization: the solution lets you use other Store features.
- Product Search: the solution leverages the Product Search API that supports the Standalone Prices.
Weaknesses
- High volume of Price records: the solution can lead to a large number of Price records, potentially impacting the duration to import and update Prices.
- Standalone Prices not compatible with Product Projection Search: with this solution, we can’t use Embedded Prices because we have more than 100 Prices per Variant. This is a weakness as Standalone Prices are not compatible with Product Projection Search.
- Scoped Price search limitations: Scoped Price search for Product Search will return only the single matching Price, not both the original and sellable Price. However, you can resolve this by having a Custom Price field that flags if the Price is of type Discount and a field for the original Price. You should keep in mind that this can impact how Price information is displayed in certain scenarios.
Solution B-1: combining Price selection logic with scoped pricing
Solution B1 utilizes Composable Commerce’s Price Selection fallback logic and scoped Prices to significantly reduce the number of Price records while maintaining pricing flexibility. We will use Embedded Prices for this solution. In particular, Solution B-1 involves creating the following in Composable Commerce:
- Eight Distribution Channels, one for each price list. Because, as we mentioned in Solution A, Zen Electron maintains eight different Price lists.
- Further 201 Distribution Channels, one for each retail store and one for the online store.
- One Store for each selling Channel (again, one for each retail store and one for the online store). Each Store will thus have two Distribution Channels associated with it. One Distribution Channel will be the Price list Channel and the other will be the Store-specific Distribution Channel.
Let’s again look at each requirement and how our solution addresses each in turn:
Requirement | Solution |
---|---|
Use one of the eight standardized price lists. | Use Currency + listChannel (the Distribution Channel here being one of the eight price list Channels). |
Each retail store has the right to override Prices on an ad-hoc basis | Use Currency + storeChannel (the Distribution Channel here being the one created for that specific store). |
Global promotion Prices must override any other Price | Use Currency + listChannel + Country (the Distribution Channel here being one of the eight Price list Channels). |
Let's look at an example of how these Prices will look like in a table for a single Variant:
Row # | Currency | Country | Channel (name) | Price |
---|---|---|---|---|
1 | AUD | - | list-A | 100.00 |
2 | AUD | - | store-thomastown | 95.00 |
3 | AUD | Australia | list-A | 90.00 |
4 | AUD | - | list-B | 99.95 |
5 | AUD | - | list-C | 97.95 |
- Row 1: Is an example of the ‘Channel List Price’. Channel
list-A
represents one of the eight Price Channels. - Row 2: Is an example of the ‘Store-specific override’.
- Row 3: Is an example of the global promotion Price’.
- Row 4 - 5: Is an example of the ‘Channel List Price’. Channel
list-B
andlist-C
represent two of the eight price lists.
A retail store in Thomastown
Let's consider this scenario in more detail and look at the store in Thomastown. That Store entity has the following assigned Distribution Channels: list-A
and store-thomastown
. This means that only Price row one and two can be applied to a Cart for that Store. Both of these Prices also have equal Price scope specificity. Because both Prices use a Distribution Channel scope, we need to use setLineItemDistributionChannel
on the Cart for one of the Prices to be applied. How then do we know which of the two Distribution Channels should be used for each setLineItemDistributionChannel
?
When using Product Projection Search or Product Search, we pass the storeProjection
and the response will give us Prices that match either of the two Distribution Channels for the Store.
Let’s look at an example of the response for the Prices object. We can see two Prices with different Supply Channels, but we don’t automatically know which Channel is of type list or store. Following is an example of what the Prices JSON from Product Projection Search or Products Search would look like. The Prices are based on the Thomastown store and are in the same order as the above table.
{"prices": [{"Channel": {"id": "96e1204a-db76-4e34-a139-540c4a9cc315","typeId": "Channel"},"id": "3286ed11-99f9-4192-a86c-31cc4c24ef3e","value": {"centAmount": 9500,"fractionDigits": 2,"type": "centPrecision"}},{"Channel": {"id": "534fce7f-45e8-45d0-a9df-822dac87686b","typeId": "Channel"},"id": "5e972a60-013a-4b97-9222-80a8bdb22b4d","value": {"centAmount": 10000,"currencyCode": "AUD","fractionDigits": 2,"type": "centPrecision"}},{"Channel": {"id": "534fce7f-45e8-45d0-a9df-822dac87686b","typeId": "Channel"},"country": "AU","id": "5e972a60-013a-4b97-9222-80a8bdb22b4d","value": {"centAmount": 9000,"currencyCode": "AUD","fractionDigits": 2,"type": "centPrecision"}}]}
Now, we need to know which Channel is a Price list or store Channel, knowing this will be important for setting the correct Distribution Channel for each Line Item. We can use different strategies to identify the Distribution Channel type:
- We can use a Custom Field to identify the type.
- We can set a key on the Store using a naming convention that identifies the type. For example, key {ChannelType}-{identifier}.
Once you have decided on the identification type, you need a way to understand how each Channel ID maps to each type. Again, we have a number of options to do it:
- Use GraphQL with the Product Projections Search API or Products Search API to query the Channel
key
or Custom field. You may want to cache these responses, so that you don’t need to request thekey
or Custom field each time. This will allow the response to be more performant. - Make a successive request to the Channels API to get the fields. Again, we recommend caching the values that you need.
Once we know each Channel type per Channel ID, we can figure out which Channel to use for Distribution Channel on the lineItemDraft when adding each item to Cart:
- If one Price is returned, use that Channel to
setLineItemDistributionChannel
as it would mean that we have a list-Channel Price. - If a Price with a Channel and Country scope is returned, use that Channel to
setLineItemDistributionChannel
as it would mean that we have a list-Channel Price. - If two Prices are returned and they both have Channel scopes and not Country, use the store-Channel.
Solution summary
In this solution, each Product Variant will have a minimum of eight Prices associated with it, corresponding to the eight base Price lists (and therefore eight respective Channels). Let’s look in more detail as the expected number of Prices per Product Variant:
- Channel list Price: it is the base Price for each Product Variant, determined by the Channel (and therefore the Price list) assigned to a specific Store. This accounts for eight Prices per Product Variant.
- Global promotion Price: during promotions, a ninth Price will be added to 10% of the Product Variants to reflect the promotional Price, which overrides the Channel List Price.
- Store-specific override: predicting the number of store-specific override Prices per Product Variant is more complex. Each store has the autonomy to adjust Prices for any Product Variant based on their individual needs. Our estimation suggests that, on average, a store will modify Prices for approximately 20% of the products. This means that a significant portion of Product Variants could potentially have more than nine Prices, depending on how many stores choose to override the base Price for a particular Product Variant.
While we can guarantee a minimum of eight Prices per Product Variant (and nine for 10% of Product Variants during promotions), the actual number will vary depending on the frequency of store-specific Price overrides. This flexibility allows stores to tailor pricing to local market conditions and customer preferences, but it also adds complexity to the overall pricing structure.
Let's estimate how many Price records will persist in Composable Commerce for this structure:
Price Type | Formula | Number of Price Records |
---|---|---|
Channel list Prices | 138,000 Variants x 8 Channels | 1,104,000 Prices |
Store-specific override Prices | 20% of 138,000 Variants = 27,600 x 201 Stores | 5,547,600 Prices |
Global promotion Prices | 10% of 138,000 Variants = 13,800 x 8 Channels | 110,400 Prices |
Total | Sum of all Price records | 6,789,600 Prices |
In comparison, Solution A had 27,738,000 total Price records, while now here with Solution B1 we have approximately 6,789,600 Price records.
This means that Solution B1 has 75% less Price records, which is great as this will mean that the Product Projection is smaller and that updating Prices is faster.
Strengths
- Reduced Price records: significantly reduces the number of Price records compared to Solution A, leading to improved performance.
- Flexibility and control: maintains the ability to manage Store-specific overrides and global promotions.
- Scalability: adding new Stores requires minimal effort as it involves assigning them to existing Channels and managing overrides.
- Search compatibility: will work with Products Projection Search and Product Search.
Weaknesses
- Discounted Price handling: using scoped Prices for global promotions means they are not recognized as discounted Prices in the API. This can be addressed by using Custom Fields to indicate if a Price is of type Discount.
- Scoped Price search limitations: Scoped Price search for Product Projections and Product Search will only return the single matching Price, not both the original and sale Price. This can impact how Price information is displayed in certain scenarios.
Solution B-2: combining Price selection logic with Product Discounts
This solution builds upon Solution B-1 but uses Product Discounts to manage global promotion Price instead of scoped Prices.
We would implement it in the following way:
- Channel mapping and scoped Prices: similar to solution B-1, we map base Price lists to Channels and use Scoped Prices for store-specific overrides and Channel list Prices.
- Product Discounts: we create Product Discounts with
ProductDiscountValueExternal
to define global promotions. - Discounted Price application: For each Product Variant included in the promotion, use the
setDiscountedPrice
update action on all Prices in the eight Price list Distribution Channels to apply the sale Price.
Let's look at an example of how these Prices will look in a table for a single Product Variant:
Row # | Currency | Channel (name) | Price | Discounted |
---|---|---|---|---|
1 | AUD | list-A | 100.00 | 90.00 |
2 | AUD | store-thomastown | 95.00 | - |
3 | AUD | list-B | 99.95 | - |
4 | AUD | list-C | 97.95 | - |
- Row 1: Is an example of the ‘Channel List Price’. Channel
list-A
represents one of the eight Price Channels. The Discounted value comes from Product Price External Discount and represents the global promotion Price. This same record also has a Discounted value, that represents the global promotion Price - Row 2: Is an example of the ‘Store-specific override’.
- Row 3 - 4: Is an example of the ‘Channel List Price’. Channel ‘list-B’ and ‘list-C’ represent two of the eight price lists.
Line Item Distribution Channel
The logic that we use to determine the correct LineItemDistributionChannel
, will be slightly different than Solution B-1.
Once we know each Channel type per Channel ID, we can figure out which Channel to use for Distribution Channel on the lineItemDraft when adding each item to Cart:
- If one Price is returned, then use that Channel to
setLineItemDistributionChannel
(this would mean that we have a Price List Distribution Channel Price). - If two Prices are returned and the Price List Distribution Channel has a Discount, the
distributionChannel
should use the Price List Distribution Channel.
Solution summary
Solution B1 had approximately 6,789,600 Price records. By using Product Discounts to manage global promotion Price instead of Scoped Prices, we managed to further lower the total number of Prices persisted in Composable Commerce to 6,651,600.
Finally, let's take a quick look at the strengths and weaknesses of this approach.
Strengths
- Improved scoped Price search: using Product Discounts for promotions allows scoped Price search to return both the original and sale Price, addressing a limitation of Solution B-1.
- Reduced Price records: slightly reduces the number of Price records compared to Solution B-1 by eliminating the need for scoped Prices for promotions.
- Retains Strengths of Solution B-1: maintains the benefits of reduced Price records, flexibility, and scalability offered by Solution B-1.
Weaknesses
This solution has no weaknesses.
Comparison of solutions
That was an intense bit of learning. Well done! Let's compare the three solutions:
Solution | Price Records | Flexibility | Scalability | Discounted Price type | scoped Price Search |
---|---|---|---|---|---|
A | High | High | High | - | Partially compatible |
B-1 | Low | High | High | - | Partially compatible |
B-2 | Lowest | Highest | Highest | Standard | Fully compatible |
Each solution offers a viable approach to managing Zen Electron's complex pricing requirements within Composable Commerce. Solution B-2 would be our recommended solution.
Remember, the choice of the best solution always depends on specific business priorities and tradeoffs.