Sitecore for Developers
Sitecore Commerce Connect: Product Pricing
In this post I’ll be demonstrating how to retrieve product pricing using Sitecore Commerce Connect.
As with other Commerce Connect features the details of determining pricing is left up to the implementer. The preferred way to determine product pricing is by patching a processor into the appropriate pipeline. This processor will then take request inputs and either query a 3rd party commerce system or perform some other type of lookup. From here, you'll then be able to invoke this pipeline by creating a request and calling the a method on the PricingServiceProvider. By default, commerce connect ships with two pricing related pipelines: “commerce.prices.getProductPrices” and “commerce.prices.getProductBulkPrices”.
To begin let’s add some code to a page to request the price of a product with an ID of “myProductID”.
[sourcecode language="csharp"] var pricingServiceProvider = new PricingServiceProvider(); var request = new GetProductPricesRequest("myProductID"); var result = pricingServiceProvider.GetProductPrices(request); if (!result.Success) { Log.Error("Nooooo! " + result.SystemMessages, this); return; }
var price = result.Prices["Basic Price"].Amount;[/sourcecode]
If you've worked with Commerce Connect at all this should look fairly familiar to you and is a pattern you'll use time and again while working with Commerce Connect.
First, we’re creating a new instance of a PricingServiceProvider. These providers wrap the pipeline calls and generally make the handling of inputting the request and retrieving the response easier. Once we have a provider we’ll create a request and use this to call a method that will kick off the pipeline. The request is provided to each pipeline processor in turn. For now, we’ll just be providing a product ID but Commerce Connect’s developer guide lists a number of optional parameters that can be used, but you already read the manuals, right?
Once we have the above code added we could run it but without adding our own processor nothing would happen. To add our own processor we’ll create a new configuration file.
[sourcecode language="xml"]<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <commerce.prices.getProductPrices> <processor type="LaunchSitecore.DemoStuff.Pricing, LaunchSitecore" /> </commerce.prices.getProductPrices> </pipelines> </sitecore></configuration>
[/sourcecode]
And of course we’ll need to create our pricing class. For processors that are part of commerce connect pipelines inheriting from the abstract class PipelineProcessor is generally recommended.
[sourcecode language="csharp"]using Sitecore.Commerce.Pipelines;using System;using System.Collections.Generic;using System.Linq;using System.Web;
namespace LaunchSitecore.DemoStuff{ public class Pricing : PipelineProcessor<ServicePipelineArgs> { public override void Process(ServicePipelineArgs args) { throw new NotImplementedException(); } }}[/sourcecode]
So far we’re calling the “getProductPrices” pipeline using the PricingServiceProvider and have created a simple processor which has been patched into this pipeline. We’ll now want to take a look at the args passed into our pipeline to determine the context of the pricing request. ServicePipelineArgs are broken down into two main parts, the Request provided when the pipeline was started and a result. It’s important to remember while implementing your processor that other processors may have run before yours and added their own results.
We’ll update our pricing class to validate the request and result.
[sourcecode language="csharp"] public override void Process(ServicePipelineArgs args) { var request = args.Request as GetProductPricesRequest; var results = args.Result as GetProductPricesResult;
if (request == null || results == null) { Log.Error("LaunchSitecore.DemoStuff.Pricing unexpected requests or results", this); } }
[/sourcecode]
Once we have our GetProductPricesRequest we can use the parameters provided there to look up a products pricing. We’ll finish our test implementation by simply hard code the price. If the product ID matches our ID the basic price is 9.99 otherwise its one million dollars.
[sourcecode language="csharp"]public override void Process(ServicePipelineArgs args) { var request = args.Request as GetProductPricesRequest; var results = args.Result as GetProductPricesResult;
if (request == null || results == null) { Log.Error("LaunchSitecore.DemoStuff.Pricing unexpected requests or results", this); }
Price price; if(request.ProductId == "myProductID") { price = new Price { Amount = 9.99m }; } else { price = new Price { Amount = 1000000.00m }; }
if (results.Prices.ContainsKey("Basic Price")) { //If the basic price was already set we're overriding it. results.Prices.Remove("Basic Price"); }
results.Prices.Add("Basic Price", price); }
[/sourcecode]
I’d like to point out a few details in the code above.
First, we haven’t’ specified what currency our price is in. In a real world scenario you’d probably end up defining the currency in the request and using that to tailor the results. I’d imagine this is probably something you don’t want to forget while creating a multinational solution.
The second item of interest is that the results contain a list of prices. We’re simply adding one price to this list by key, then using this key to output the price once the pipeline completes.
Third, we're specifically checking the results to see if a basic price has already been defined. There's a chance that another processor is part of this pipeline and has already been executed.
At this point you should be able to request and determine the price for any product under a number of different conditions based on the parameters of the request. When implementing pricing in the real world there’s a chance that what’s provided out of the box doesn't fulfill your requirements. In that case there’s really only one thing to do, panic! extend the base functionality.
This is fairly straightforward and to demonstrate this I’m going to be modifying our example so that pricing for returning customers is $1.00 less than for new customers on our test product. Just a quick note, this is for demonstrating modifying the request, there are far better ways to handle this requierment in the real world (TBH, I just couldn't think of a better example :/).
I’ll start by creating a new class. This class will extend the GetProductPriceRequest and add a Boolean to indicate if the current customer is new or returning. I’ll add a constructor to require this Boolean to be set while instantiating the class.
[sourcecode language="csharp"]public class MyPricingRequest : GetProductPricesRequest { public bool ReturningCustomer { get; protected set; }
public MyPricingRequest(bool returningCustomer, string productId, params string[] priceTypeIds) : base(productId, priceTypeIds) { ReturningCustomer = returningCustomer; } }
[/sourcecode]
We’ll then update our processor as well as our call to the pricing service provider to account for this.
[sourcecode language="csharp"]public override void Process(ServicePipelineArgs args) { var request = args.Request as MyPricingRequest; var results = args.Result as GetProductPricesResult;
if (request == null || results == null) { Log.Error("LaunchSitecore.DemoStuff.Pricing unexpected requests or results", this); }
Price price; if (request.ProductId == "myProductID") { if (request.ReturningCustomer) { price = new Price { Amount = 8.99m }; } else { price = new Price { Amount = 9.99m }; } } else { price = new Price { Amount = 1000000.00m }; }
if (results.Prices.ContainsKey("Basic Price")) { //If the basic price was already set we're overriding it. results.Prices.Remove("Basic Price"); }
results.Prices.Add("Basic Price", price); }
[/sourcecode]
and
[sourcecode language="csharp"]private void GetPrice() { … var request = new LaunchSitecore.DemoStuff.Pricing.MyPricingRequest(true, "myProductID"); var result = pricingServiceProvider.GetProductPrices(request); … }
[/sourcecode]
Well there you have it. You should now be able to make the request price for a product, provide details on the context of the price needed, have the ability to inject your own code into the pricing pipelines and have idea on how to start extending the core functionality to suit your needs.
[sourcecode language="csharp"]//Thanks![/sourcecode]