1

I am indexing Sitecore Commerce 9.0.2 sellable items through a regular SitecoreItemCrawler. The HabitatHome demo sellable items are in my content tree at /sitecore/content/Habitat Sites/Habitat Home/home/Catalogs/Habitat_Master. Items synchronized there from Sitecore Commerce do not have a any price field, not even a list price. Thus, I am trying to build a computed index field to get the list price from Sitecore Commerce.

I know Sitecore Commerce APIs have a GetBulkPrices() method that returns the information I want. I can call it using Postman with the "CatalogAPISamples/Catalog - Pricing/Get bulk prices" request:

  • URL: https://localhost:5000/api/GetBulkPrices()
  • Method: PUT
  • HTTP Headers
    • ShopName: CommerceEngineDefaultStorefront
    • ShopperId: ShopperId
    • Language: en-US
    • Currency: USD
    • Environment: HabitatAuthoring
    • GeoLocation: IpAddress=1.0.0.0
    • Authorization: Bearer + Access_Token
  • Body: { "itemIds": ["habitat_master|productSku|"] }

However, I cannot find the way to call this method from Sitecore code without crafting my own HTTP request. I have looked in Sitecore.Commerce.Connect and other places without success. I have tried the following approaches:

  1. PricingServiceProvider + GetProductPricesRequest

    PricingServiceProvider pricingServiceProvider = new PricingServiceProvider();
    GetProductPricesRequest getProductPricesRequest = new GetProductPricesRequest(productSku);
    getProductPricesRequest.SetShopName("CommerceEngineDefaultStorefront");
    GetProductPricesResult productPricesResult = pricingServiceProvider.GetProductPrices(getProductPricesRequest);
    return productPricesResult.Prices.ContainsKey("List") ? productPricesResult.Prices["List"].Amount.ToString(CultureInfo.InvariantCulture) : null;
    

    It ends with this exception that does not provide much insight:

    ManagedPoolThread #4 16:16:43 ERROR An exception occurred while trying to process the item {E1E4CDD3-01C1-0854-7EBC-6F64C1A01F1F}: Default Control Panel Storefront
    Exception: System.InvalidOperationException
    Message: Default Control Panel Storefront
    Source: Sitecore.Commerce.XA.Foundation.Common
       at Sitecore.Commerce.XA.Foundation.Common.Models.CommerceStorefront.get_ControlPanel()
       at Sitecore.Commerce.XA.Foundation.Common.Models.CommerceStorefront.get_CurrencyConfiguration()
       at Sitecore.Commerce.XA.Foundation.Common.Models.CommerceStorefront.get_CurrencySet()
       at Sitecore.Commerce.XA.Foundation.Common.Models.CommerceStorefront.get_DefaultCurrency()
       at Sitecore.Commerce.XA.Foundation.Common.Providers.CurrencyProvider.GetSelectedCurrency()
       at Sitecore.Commerce.Services.ServiceProvider.RunPipeline[TRequest,TResult](String pipelineName, TRequest request)
       at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.GetPriceFromApi(IItem p_Item, String productSku)
       at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.GetListPrice(IItem p_Item)
       at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.ComputeFieldValue(IIndexable p_Indexable)
       at Coveo.SearchProvider.Documents.AbstractDocumentBuilder.AddComputedFields()
       at Coveo.SearchProvider.Documents.DocumentFactory.<>c__DisplayClass3_0.<CreateIndexableItem>b__3()
       at Coveo.SearchProvider.Documents.AbstractDocumentFactory.TryBuildDocument(Action p_Action, IIndexable p_Indexable)
    
  2. PricingServiceProvider + GetProductBulkPricesRequest

    PricingServiceProvider pricingServiceProvider = new PricingServiceProvider();
    GetProductBulkPricesRequest getProductBulkPricesRequest = new GetProductBulkPricesRequest(new List<string> { productSku });
    getProductBulkPricesRequest.SetShopName("CommerceEngineDefaultStorefront");
    GetProductBulkPricesResult productBulkPricesResult = pricingServiceProvider.GetProductBulkPrices(getProductBulkPricesRequest);
    Price listPrice = productBulkPricesResult.Prices["List"];
    if (listPrice != null) {
        return listPrice.Amount.ToString(CultureInfo.InvariantCulture);
    }
    

    It ends with a very similar exception than the first approach.

  3. PricingManager.GetProductPrices()

    IConnectServiceProvider connectServiceProvider = new ConnectServiceProvider();
    IItemTypeProvider itemTypeProvider = new ItemTypeProvider();
    ISiteContext siteContext = new SiteContext(itemTypeProvider) {
        CurrentCatalogItem = p_Item.SitecoreItem
    };
    PricingServiceProvider pricingServiceProvider = new PricingServiceProvider();
    IPricingManager pricingManager = new PricingManager(connectServiceProvider) {
        PricingServiceProvider = pricingServiceProvider
    };
    IModelProvider modelProvider = new ModelProvider();
    IStorefrontUrlManager storefrontUrlManager = new StorefrontUrlManager(siteContext);
    IContext defaultContext = new DefaultContext {
        Item = p_Item.SitecoreItem,
        Database = p_Item.SitecoreItem.Database
    };
    IStorefrontContext storefrontContext = new StorefrontContext(modelProvider, storefrontUrlManager, defaultContext);
    ICurrencyProvider currencyProvider = new CurrencyProvider(storefrontContext, defaultContext);
    CommerceStorefront commerceStorefront = new CommerceStorefront(currencyProvider, defaultContext);
    ICartManager cartManager = new CartManager(connectServiceProvider);
    IAccountManager accountManager = new AccountManager(connectServiceProvider, cartManager, storefrontContext, modelProvider);
    IVisitorContext visitorContext = new VisitorContext(accountManager);
    ManagerResponse<GetProductPricesResult, IDictionary<string, Price>> managerResponse = pricingManager.GetProductPrices(commerceStorefront, visitorContext, HABITAT_CATALOG_NAME, productSku, false, "List");
    return managerResponse.Result.ContainsKey("List") ? managerResponse.Result["List"].Amount.ToString(CultureInfo.InvariantCulture) : null;
    

    It ends with this exception because there is no HttpContext:

    Exception: System.NullReferenceException
    Message: Object reference not set to an instance of an object.
    Source: Sitecore.Commerce.XA.Foundation.Common
      at Sitecore.Commerce.XA.Foundation.Common.Context.SiteContext.set_CurrentCatalogItem(Item value)
      at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.GetPriceFromApi(IItem p_Item, String productSku)
      at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.GetListPrice(IItem p_Item)
      at Sitecore.HabitatHome.Foundation.CoveoIndexing.ComputedFields.ListPriceComputedField.ComputeFieldValue(IIndexable p_Indexable)
      at Coveo.SearchProvider.Documents.AbstractDocumentBuilder.AddComputedFields()
      at Coveo.SearchProvider.Documents.DocumentFactory.<>c__DisplayClass3_0.<CreateIndexableItem>b__3()
      at Coveo.SearchProvider.Documents.AbstractDocumentFactory.TryBuildDocument(Action p_Action, IIndexable p_Indexable)
    

All of my approaches seems to require an HttpContext that is a request to the storefront site matching a Sitecore site element.

Is there a way to call the Sitecore Commerce Engine to get the price of a product without a Sitecore site context?

1
  • Ah, the good old ServiceLocator pattern lives on
    – Mark Cassidy
    Commented Mar 21, 2019 at 21:01

2 Answers 2

1

Option 1.

Just wrap your code in SiteContextSwitcher:

using (new SiteContextSwitcher(Factory.GetSite("YOUR_COMMERCE_SITE_NAME")))
{
    // get price here
}

Option 2.

Review Sitecore.Commerce.Engine.Connect.Search.Crawlers.SellableItemsCrawler, not SitecoreItemCrawler. It does request to CE to get list of Sellable Items with all required price information, you need.

For example, you could extend GetItem method in SellableItemsCrawler to add custom field to index:

protected override CommerceCatalogItemIndexableItem GetItem(SellableItem catalogEntity, string artifactStoreId)
{
    var indexableItem = base.GetItem(catalogEntity, );
    // here we get sellable item price:
    var pricesPolicy = sellableItem.Policies.OfType<ListPricingPolicy>();
    var prices = pricesPolicy.SelectMany(pricePolicy => pricePolicy.Prices).Select(money => money.Amount)
                .ToList();
    // here we add custom field to index:
    indexableItem.InternalFields.Add(new CommerceDataField("listprice", prices.FirstOrDefault());
    return indexableItem;
}

Note. Of course, don't forget to add new field to index configuration.

1

Why not go all the way, lets borrow the code from the previous answer and than put in in the Commerce Engine.

  1. Create a custom index handler
  2. Update the json config - PlugIn.Search.Solr.PolicySet-1.0.0.json
  3. Do a bootstrap, and don't forget to stop start your Minions service. So the the index minions are updated.

Custom Index handler:

public class PriceHandler : AbstractIndexFieldHandler
    {


        public ComposerValueSource ComposerSource { get; set; } = new ComposerValueSource(string.Empty, string.Empty);


        public override object ComposeValue(object source, ConcurrentDictionary<string, object> context)
        {
            if (context == null || !(source is CatalogItemBase catalogItemBase))
                return null;


            if (!(catalogItemBase is SellableItem))
                return null;


            SellableItem sellabelItem = catalogItemBase as SellableItem;

            if (ComposerSource.PropertyName.Equals("ListPrice"))
            {
                var pricesPolicy = sellabelItem.GetPolicies<ListPricingPolicy>();
                var prices = pricesPolicy.SelectMany(pricePolicy => pricePolicy.Prices).Select(money => money.Amount)
                            .ToList();

                return prices.FirstOrDefault();
            }


            return null;

        }
    }

PlugIn.Search.Solr.PolicySet-1.0.0.json:

{
            "$type": "Sitecore.Commerce.Plugin.Search.Solr.SolrIndexFieldConfiguration, Sitecore.Commerce.Plugin.Search.Solr",
            "Name": "ListPrice",
            "Type": "System.Decimal",
            "Handler": {
              "$type": "MyCompany.Commerce.Plugin.Pricing.IndexFields.PriceHandler, MyCompany.Commerce.Plugin.Pricing",
              "ComposerSource": {
                "PropertyName": "ListPrice"
              }
            }
          }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.