Real-time pricing

The Real Time pricing feature provides a standardized way to implement on-demand pricing calls to an external system rather than using the internal price matrix or basic list pricing. Real Time pricing is most valuable when there are complicated pricing rules or pricing changes frequently throughout the day.

When using Real Time pricing, page loads are not dependent on the pricing data being immediately available. This allows the pages to build up quickly and display product information without waiting for pricing to be obtained. The price will show a loading ellipsis and the prices will be "AJAXed" onto the page as they are available. The reference site's client-side code has been built to enable this behavior.

It is expected that the external call will be made to an exposed API and the web servers will need to communicate directly over the network with that exposed resource. Optimizely can provide IP addresses to whitelist if running in Optimizely B2B Commerce.

Prices are cached server side by product, customer, ship-to, currency, warehouse, and unit of measure based on a system setting to improve performance and to minimize load on the external resource.

Prices are cached on a per-price/customer/warehouse combination basis.

What is required to implement Real-time pricing:

  1. Create a plug-in which implements IExternalPricingService in your project and calls an external pricing service.
  2. Use the Pricing Service setting to enable the RealTime plug-in.

Locations on the site

The following angular pages and components have built-in Real Time pricing display:

  • Product List
  • Product Detail
  • Product Comparison
  • Cross sells
  • Quick Order
  • Wishlist
  • Cart

Set up

  1. Admin Console > Administration > System > Settings.
  2. Search for "Pricing Service".
  3. Select RealTime from the menu.
    • When Real Time is selected, the following settings appear:
      • Connection: Optional connection information to the API Endpoint.
      • Real Time Pricing Service: The real time pricing service name. The default value of the Insite simply implements the internal Generic pricing service- the correct service should be selected.
      • Real Time Service Timeout Seconds: Time to wait for real time pricing service to return before timing out. Default value: 10
      • Real Time Service Cache Minutes: Number of minutes that prices from a real time service will be cached. Default value: 60
      • Real-time Service Unavailable Retry Minutes: Number of minutes to wait until we try to send another request to the ERP if the real time service timed out. The intent is to stop communicating with the remote server if it is not responding to give it time to reset/reboot or otherwise catch up to traffic. Default value: 5
      • Real-time Service Delay Seconds: Number of seconds to delay the real time pricing. This is used for testing and should be set to 0 in a production environment. Default value: 0.

API

POST a list of products to /api/v1/realtimepricing and get back a list of prices.

Insite.catalog.ProductService.getProductRealTimePrices does this, and returns an object of type RealTimePricingModel

public class RealTimePricingModel : BaseModel

{
  /// <summary>The real time pricing results.</summary>
      public virtual Collection<ProductPriceDto> RealTimePricingResults { get; set; }
}

Server side

  • Module: RealtimePricing
  • API controller: RealTimePricingV1Controller
  • Handler: GetRealTimePricingHandler
  • Mapper: GetRealTimePricingMapper

Calls RealtimePricingEngine

Pipeline: IPricingPipeline - this interface is used by all server side code to get prices, from which every service is being used real-time or otherwise.

Plugin / extensibility

The RealtimePricingEngine will call a class which implements IExternalPricingService and is configured in the settings Real-time Pricing Service.

public interface IExternalPricingService : IMultiInstanceDependency

{
  IDictionary<Guid, PricingServiceResult> GetPrice(PricingServiceParameter productPriceParameter);
  IDictionary<Guid, PricingServiceResult> GetPrice(ICollection<PricingServiceParameter> productPriceParameter);
}		

Sample Real-time pricing plug-in

A sample pricing plug-in is built into the platform, called InsiteExternalPricingService. This plug-in simply calls the generic pricing service .

Use the following steps to enable the sample RealTime plug-in:

  1. Admin Console > Administration > System > Settings.
  2. Search for "Pricing Service".
  3. Select RealTime from the menu.
  4. The Real Time Pricing Service setting should be set to Insite.
  5. Use the Real-Time Service Delay Seconds setting to demonstrate the client side spinners, by setting the "Real-Time Service Delay Seconds" to a non-zero number.

Use the site's Theme and its insite.module.ts file to manage change events for spinners, which are part of the ISpinnerService interface and SpinnerService class (insite.spinner.service.ts file) and SpinnerController class (insite.spinner.controller.ts file).

The source code for the sample real-time pricing plug-in is shown below:

Code Sample

namespace Insite.RealTimePricing.Services
{
  using System;
  using System.Collections.Generic;
  using System.Threading;
  using Insite.Common.Logging;
  using Insite.Core.Interfaces.Data;
  using Insite.Core.Interfaces.Dependency;
  using Insite.Core.Interfaces.Plugins.Pricing;
  using Insite.Core.Plugins.EntityUtilities;
  using Insite.Core.Plugins.ExternalPricingService;
  using Insite.Core.Plugins.Utilities;
  using Insite.Core.SystemSetting.Groups.Catalog;
  using Insite.Plugins.Pricing;
  using Insite.RealTimePricing.Engines;
  using Insite.RealTimePricing.SystemSettings;
  [DependencyName("Insite")]
  public class InsiteExternalPricingService : IExternalPricingService
  {
    protected readonly PricingServiceGeneric PricingServiceGeneric;
    protected readonly RealTimePricingSettings RealTimePricingSettings;
	
	public InsiteExternalPricingService(
      IUnitOfWorkFactory unitOfWorkFactory,
      ICurrencyFormatProvider currencyFormatProvider,
      IOrderLineUtilities orderLineUtilities,
      IPricingServiceFactory pricingServiceFactory,
      PricingSettings pricingSettings,
      RealTimePricingSettings realTimePricingSettings)
      {
        this.RealTimePricingSettings = realTimePricingSettings;
        this.PricingServiceGeneric = new PricingServiceGeneric(unitOfWorkFactory, currencyFormatProvider, orderLineUtilities, pricingServiceFactory, pricingSettings);
      }
    public IDictionary<Guid, PricingServiceResult> GetPrice(PricingServiceParameter pricingServiceParameter)
      {
        var prices = new Dictionary<Guid, PricingServiceResult>();
        try
          {
			var price = this.PricingServiceGeneric.CalculatePrice(pricingServiceParameter);
			prices.Add(pricingServiceParameter.ProductId, price);
		  }
		catch (Exception exception)
  		  {
			// Ignore records we cannot price.
			LogHelper.For(this).Info("Unable to calculate price for product " + pricingServiceParameter.ProductId, exception);
          }
	
		return prices;
	  }
	
	public IDictionary<Guid, PricingServiceResult> GetPrice(ICollection pricingServiceParameters)
	  {
		// This is used for testing purposes only.  This setting should be 0 in a production environment.
		var timeoutDuration = Math.Abs(this.RealTimePricingSettings.ServiceDelay) * 1000;
		Thread.Sleep(timeoutDuration);
		var prices = new Dictionary<Guid, PricingServiceResult>();
		foreach (var pricingServiceParameter in pricingServiceParameters)
	  {
		try
	  	{
	      var price = this.PricingServiceGeneric.CalculatePrice(pricingServiceParameter);
		  prices.Add(pricingServiceParameter.ProductId, price);
		}
		catch (Exception exception)
		{
  		  // Ignore records we cannot price.
		  LogHelper.For(this).Info("Unable to calculate price for product " + pricingServiceParameter.ProductId, exception);
		}
	  }
	return prices;
    }
  }
}

The plug-in contains two methods which need to be implemented:

  • one to get the price for a single product
  • one to get prices for a list of products

It does not need to be responsible for timeouts or caching - these concerns are handled in the engine. The DependencyName must changed to something other than "Insite".