Sitecore Commerce 8.2.1: Setting up Multi-Currency Support

Andrei Paliakou on November 18, 2017

The Sitecore Commerce 8.2.1 doesn’t support multi currency functionality in Braintree Payment Gateway, though Braintree supports multi currency itself . In my previous post I’ve described how to set up multi currency in Sitecore and now I want to describe how to enable multiple currencies featrue in the Plugin.Sample.Payments.Braintree plugin.

First of all, we need to define Merchant Accounts for each currency that you want to have. Go to your sandbox account or production account in Braintree, then Account -> Merchant Account Info -> New Sandbox Merchant Account.

braintree merchant account

After that, we need additional configuration in all CommerceAuthoring configs under CommerceAuthoring\wwwroot\data\Environments\

We need to update BraintreeClientPolicy and add Merchants property:

   
{
     "$type":  "Plugin.Sample.Payments.Braintree.BraintreeClientPolicy, Plugin.Sample.Payments.Braintree",
     "Environment":  "sandbox",
     "MerchantId":  "xxxxxxxx",
     "PublicKey":  "xxxxxxxxxxxxx",
     "PrivateKey":  "xxxxxxxxxxxxxxx",
     "Merchants": [
                      {
                          "CurrencyCode": "BRL",
                          "MerchantId": "{Merchant Account ID}"
                      },
                      {
                          "CurrencyCode": "EUR",
                          "MerchantId":  "{Merchant Account ID}"                                                              
                      },
                      {
                          "CurrencyCode": "GBP",
                          "MerchantId":  "{Merchant Account ID}"
                      }
                  ],
     "ConnectTimeout":  120000,
     "PolicyId":  "0af6253f42184fd8ae0a61085980c03c",
     "Models":  {
                    "$type":  "System.Collections.Generic.List`1[[Sitecore.Commerce.Core.Model, Sitecore.Commerce.Core]], mscorlib",
                    "$values":  [

                                ]
                }
 },

In the code above you should replace {Merchant Account ID} on your Merchant Account ID for each currency. Now CommersAuthoring knows about available Merchants Accounts.

Now we need to modify CreateFederatedPaymentBlock class in Plugin.Sample.Payments.Braintree project and add logic for Merchant Accounts:

    
[PipelineDisplayName(PaymentsBraintreeConstants.Pipelines.Blocks.CreateFederatedPaymentBlock)]
public class CreateFederatedPaymentBlock : PipelineBlock<CartEmailArgument, CartEmailArgument, CommercePipelineExecutionContext>
{
    /// <summary>
    /// Runs the specified argument.
    /// </summary>
    /// <param name="arg">The argument.</param>
    /// <param name="context">The context.</param>
    /// <returns>
    /// A cart with federate payment component
    /// </returns>
    public override Task<CartEmailArgument> Run(CartEmailArgument arg, CommercePipelineExecutionContext context)
    {
        Condition.Requires(arg).IsNotNull($"{this.Name}: The cart can not be null");

        var cart = arg.Cart;
        if (!cart.HasComponent<FederatedPaymentComponent>())
        {
            return Task.FromResult(arg);
        }

        var payment = cart.GetComponent<FederatedPaymentComponent>();

        if (string.IsNullOrEmpty(payment.PaymentMethodNonce))
        {
            context.Abort(context.CommerceContext.AddMessage(
                context.GetPolicy<KnownResultCodes>().Error,
                "InvalidOrMissingPropertyValue",
                new object[] { "PaymentMethodNonce" },
                $"Invalid or missing value for property 'PaymentMethodNonce'."), context);

            return Task.FromResult(arg);
        }

        var braintreeClientPolicy = context.GetPolicy<BraintreeClientPolicy>();
        if (string.IsNullOrEmpty(braintreeClientPolicy?.Environment) || string.IsNullOrEmpty(braintreeClientPolicy?.MerchantId)
            || string.IsNullOrEmpty(braintreeClientPolicy?.PublicKey) || string.IsNullOrEmpty(braintreeClientPolicy?.PrivateKey) || braintreeClientPolicy.Merchants == null || !braintreeClientPolicy.Merchants.Any())
        {
            context.CommerceContext.AddMessage(
               context.GetPolicy<KnownResultCodes>().Error,
               "InvalidClientPolicy",
               new object[] { "BraintreeClientPolicy" },
                $"{this.Name}. Invalid BraintreeClientPolicy");
            return Task.FromResult(arg);
        }

        try
        {
            var merchant = braintreeClientPolicy.Merchants.FirstOrDefault(x => x.CurrencyCode == cart.Totals.PaymentsTotal.CurrencyCode);
            var gateway = new BraintreeGateway(braintreeClientPolicy?.Environment, braintreeClientPolicy?.MerchantId, braintreeClientPolicy?.PublicKey, braintreeClientPolicy?.PrivateKey);

            var request = new TransactionRequest
            {
                Amount = payment.Amount.Amount,
                //fix
                MerchantAccountId = merchant?.MerchantId,
                PaymentMethodNonce = payment.PaymentMethodNonce,
                BillingAddress = ComponentsHelper.TranslatePartyToAddressRequest(payment.BillingParty, context),
                Options = new TransactionOptionsRequest
                {
                    SubmitForSettlement = true
                }
            };

            Result<Transaction> result = gateway.Transaction.Sale(request);

            if (result.IsSuccess())
            {
                Transaction transaction = result.Target;
                payment.TransactionId = transaction?.Id;
                payment.TransactionStatus = transaction?.Status?.ToString();
                payment.PaymentInstrumentType = transaction?.PaymentInstrumentType?.ToString();
                //fix
                payment.Amount.CurrencyCode = transaction?.CurrencyIsoCode;

                var cc = transaction?.CreditCard;
                payment.MaskedNumber = cc?.MaskedNumber;
                payment.CardType = cc?.CardType?.ToString();
                if (cc?.ExpirationMonth != null)
                {
                    payment.ExpiresMonth = int.Parse(cc.ExpirationMonth);
                }

                if (cc?.ExpirationYear != null)
                {
                    payment.ExpiresYear = int.Parse(cc.ExpirationYear);
                }
            }
            else
            {
                string errorMessages = result.Errors.DeepAll().Aggregate(string.Empty, (current, error) => current + ("Error: " + (int)error.Code + " - " + error.Message + "\n"));

                context.Abort(context.CommerceContext.AddMessage(
                   context.GetPolicy<KnownResultCodes>().Error,
                   "CreatePaymentFailed",
                   new object[] { "PaymentMethodNonce" },
                   $"{this.Name}. Create payment failed :{ errorMessages }"), context);
            }

            return Task.FromResult(arg);
        }
        catch (BraintreeException ex)
        {
            context.Abort(context.CommerceContext.AddMessage(
               context.GetPolicy<KnownResultCodes>().Error,
               "CreatePaymentFailed",
                new object[] { "PaymentMethodNonce", ex },
                $"{this.Name}. Create payment failed."), context);
            return Task.FromResult(arg);
        }
    }
}

After all these small changes you have a complete Braintree integration with multi currency support working.

Find the plugin file on GitHub

Fly high with Sitecore Commerce!