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.

    ItemFormulaResult
    Total Products-46,000 Products
    Average Product Variants per Product46,000 Products x 3 Variants138,000 Product Variants
    Number of Price Channels200 Physical Stores + 1 Online Store201 Channels
    Total Prices138,000 Product Variants x 201 Channels27,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.

    Diagram of solutions A's use of Channels and Stores.

    By combining Standalone Prices and scoped Pricing, we can meet each of the pricing requirements, let's see how.

    RequirementSolution
    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 basisUse Currency + storeChannel + Country.
    Global promotion Prices must override any other PriceUse 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 #CurrencyCountryChannel (name)Time-boundPrice
    1AUD-store-thomastownFalse100.00
    2AUDAustraliastore-thomastownFalse105.00
    3AUDAustraliastore-thomastownTrue95.99
    4AUD-store-darlinghurstFalse100.00
    5AUDAustraliastore-darlinghurstTrue97.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 TypeFormulaNumber of Price Records
    Product Variant Prices138,000 Product Variants x 201 Distribution Channels27,738,000 Prices
    Store-specific override Prices20% of 27,738,000 Prices5,547,600 Prices
    Global promotion Prices10% of 27,738,000 Prices2,773,800 Prices
    TotalSum of all Price records36,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.

    Diagram of solution B1's use of Channels and Stores.

    Let’s again look at each requirement and how our solution addresses each in turn:

    RequirementSolution
    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 basisUse Currency + storeChannel (the Distribution Channel here being the one created for that specific store).
    Global promotion Prices must override any other PriceUse 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 #CurrencyCountryChannel (name)Price
    1AUD-list-A100.00
    2AUD-store-thomastown95.00
    3AUDAustralialist-A90.00
    4AUD-list-B99.95
    5AUD-list-C97.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 and list-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 the key 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 TypeFormulaNumber of Price Records
    Channel list Prices138,000 Variants x 8 Channels1,104,000 Prices
    Store-specific override Prices20% of 138,000 Variants = 27,600 x 201 Stores5,547,600 Prices
    Global promotion Prices10% of 138,000 Variants = 13,800 x 8 Channels110,400 Prices
    TotalSum of all Price records6,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 #CurrencyChannel (name)PriceDiscounted
    1AUDlist-A100.0090.00
    2AUDstore-thomastown95.00-
    3AUDlist-B99.95-
    4AUDlist-C97.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:

    SolutionPrice RecordsFlexibilityScalabilityDiscounted Price typescoped Price Search
    AHighHighHigh-Partially compatible
    B-1LowHighHigh-Partially compatible
    B-2LowestHighestHighestStandardFully 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.

    Test your knowledge