Sitecore Commerce 8.2.1 Multi Currency Support Setup

Recently we’ve successfully launched a project with Sitecore Commerce 8.2.1 Initial Release and I want to share several issues that we've met and resolved.

In this post, I’ll cover one issue that was related to currency. The out of the box Reference Storefront site doesn’t support multi currency functionality. But you can notice that there’s an option to set multiple prices in Merchandiser Manager and see them on frontend side. This seems to work only until you click the “Add to cart” button. After you click “Add to cart” and check the cart content, you will see that all calculations are made in default catalog currency (by default it’s USD), regardless of what currency has been displayed before.

Sitecore commerce currency settings

There is a solution which helped us to resolve the issue. The requirement was to detect user by IP, identify his country, show all product prices in currency related to his region and give a possibility to the end user to buy products with correct price and currency.

After investigating Sitecore Commerce Engine we discovered that Sitecore Commerce supports multi currency:

Sitecore.Commerce.Engine.Connect.Pipelines.Carts.CartProcessor

      protected virtual Sitecore.Commerce.Plugin.Carts.Cart GetCart(string userId, string shopName, string cartId, string currency, string customerId = "")
        {
            return Proxy.GetValue<Sitecore.Commerce.Plugin.Carts.Cart>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).Carts.ByKey(cartId).Expand("Lines($expand=CartLineComponents),Components"));
        }

        protected virtual CommerceCommand AddCartLine(string userId, string shopName, string cartId, string currency, string itemId, Decimal quantity, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).AddCartLine(cartId, itemId, quantity));
        }

        protected virtual CommerceCommand UpdateCartLine(string userId, string shopName, string cartId, string currency, string lineId, Decimal quantity, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).UpdateCartLine(cartId, lineId, quantity));
        }

        protected virtual CommerceCommand RemoveCartLine(string userId, string shopName, string cartId, string currency, string lineId, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).RemoveCartLine(cartId, lineId));
        } 	
 

As we can see here we use GetContainer method from Sitecore.Commerce.Engine.Connect.Pipelines.PipelineProcessor namespase and this method has currency parameter:

      protected virtual Container GetContainer(string shopName, string userId, string customerId = "", string language = "", string currency = "", DateTime? effectiveDate = null)
    {
      return EngineConnectUtility.GetContainer(shopName, userId, customerId, language, currency, effectiveDate);
    }	   

We need to overwrite few processors:

  • Sitecore.Commerce.Engine.Connect.Pipelines.Carts. AddCartLines
  • Sitecore.Commerce.Engine.Connect.Pipelines.Carts.GetCarts
  • Sitecore.Commerce.Engine.Connect.Pipelines.Carts.LoadCart
  • Sitecore.Commerce.Engine.Connect.Pipelines.Carts.MergeCart
  • Sitecore.Commerce.Engine.Connect.Pipelines.Carts.RemoveCartLines
  • Sitecore.Reference.Storefront.CommercePiplines.Orders.SubmitVisitorOrder

Add new parameter Currency like for AddCartLines:

      public override void Process(ServicePipelineArgs args)
        {
            AddCartLinesRequest request;
            CartResult result;
            PipelineUtility.ValidateArguments<AddCartLinesRequest, CartResult>(args, out request, out result);
            try
            {
                Assert.IsNotNull((object)request.Cart, "request.Cart");
                Assert.IsNotNullOrEmpty(request.Cart.UserId, "request.Cart.UserId");
                Assert.IsNotNull((object)request.Lines, "request.Lines");
                List<CartLine> list = request.Lines.ToList<CartLine>();
                list.RemoveAll((Predicate<CartLine>)(l =>
                {
                    if (l != null)
                        return l.Product == null;
                    return true;
                }));
                request.Lines = (IEnumerable<CartLine>)list;
                var currency = CookieManager.GetCookie("site_currency");
                foreach (CartLine line in request.Lines)
                {
                    string lineItemId = this.GenerateLineItemId(line as CommerceCartLine);
                    if (!string.IsNullOrEmpty(lineItemId))
                    {
                        CommerceCommand command = this.AddCartLine(request.Cart.UserId, request.Cart.ShopName, request.Cart.ExternalId, currency, lineItemId, (Decimal)line.Quantity, request.Cart.CustomerId);
                        result.HandleCommandMessages(command);
                        if (!result.Success)
                            break;
                    }
                }
                Sitecore.Commerce.Plugin.Carts.Cart cart = this.GetCart(request.Cart.UserId, request.Cart.ShopName, request.Cart.ExternalId, currency, "");
                if (cart != null)
                    result.Cart = (Sitecore.Commerce.Entities.Carts.Cart)this.TranslateCartToEntity(cart, (ServiceProviderResult)result);
            }
            catch (ArgumentException ex)
            {
                result.Success = false;
                result.SystemMessages.Add(PipelineUtility.CreateSystemMessage((Exception)ex));
            }
            catch (AggregateException ex)
            {
                result.Success = false;
                result.SystemMessages.Add(PipelineUtility.CreateSystemMessage((Exception)ex));
            }
            base.Process(args);
        }

         protected override CommerceCommand AddCartLine(string userId, string shopName, string cartId, string currency, string itemId, Decimal quantity, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).AddCartLine(cartId, itemId, quantity));
        }	

We added new processor on httpRequestBegin after Sitecore.Pipelines.HttpRequest.LanguageResolver and there we set site_currency cookie. Value for this cookie we use currency code (USD, CAD and etc.) which we resolved based on our custom logic.

Also we need to overwrite Sitecore.Commerce.Engine.Connect.Pipelines.Carts.CartProcessor:

      protected override Sitecore.Commerce.Plugin.Carts.Cart GetCart(string userId, string shopName, string cartId, string currency, string customerId = "")
        {
            return Proxy.GetValue<Sitecore.Commerce.Plugin.Carts.Cart>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).Carts.ByKey(cartId).Expand("Lines($expand=CartLineComponents),Components"));
        }

        protected override CommerceCommand AddCartLine(string userId, string shopName, string cartId, string currency, string itemId, Decimal quantity, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).AddCartLine(cartId, itemId, quantity));
        }

        protected override CommerceCommand UpdateCartLine(string userId, string shopName, string cartId, string currency, string lineId, Decimal quantity, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).UpdateCartLine(cartId, lineId, quantity));
        }

        protected override CommerceCommand RemoveCartLine(string userId, string shopName, string cartId, string currency, string lineId, string customerId = "")
        {
            return Proxy.DoCommand<CommerceCommand>(this.GetContainer(shopName, userId, customerId, "", currency, new DateTime?()).RemoveCartLine(cartId, lineId));
        }	    

Create new config App_Config/Include/ Z.SwitchCurrency.config

      
	<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <commerce.carts.addCartLines>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Carts.AddCartLines, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Carts.AddCartLines, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.carts.addCartLines>

      <commerce.carts.getCarts>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Carts.GetCarts, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Carts.GetCarts, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.carts.getCarts>

      <commerce.carts.loadCart>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Carts.LoadCart, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Carts.LoadCart, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.carts.loadCart>

      <commerce.carts.mergeCart>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Carts.MergeCart, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Carts.MergeCart, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.carts.mergeCart>

      <commerce.carts.removeCartLines>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Carts.RemoveCartLines, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Carts.RemoveCartLines, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.carts.removeCartLines>

      <commerce.orders.submitVisitorOrder>
        <processor type="Sitecore.Commerce.Engine.Connect.Pipelines.Orders.SubmitVisitorOrder, Sitecore.Commerce.Engine.Connect">
          <patch:attribute name="type">Sitecore.Reference.Storefront.CommercePiplines.Orders.SubmitVisitorOrder, Sitecore.Reference.Storefront.Powered.by.SitecoreCommerce</patch:attribute>
        </processor>
      </commerce.orders.submitVisitorOrder>
    </pipelines>
  </sitecore>
</configuration>  

After all these changes, you can add products to cart and in the cart all the calculations will occur based on client’s currency. Next step is to Submit Order with selected currency to payment gateway. This step is described in the next post. Tell me if you find it helpful or if you have any questions.