Creating an Address Validation Plugin UPS or FEDEX Sitecore Commerce Engine

  • Twitter
  • LinkedIn

Creating an Address Validation Plugin UPS or FEDEX - Sitecore Commerce Engine


Address validation on E-commerce web applications is very useful because it can help enhance the customer purchasing experience. There is nothing like hassle free delivery for the seller and customer.
To make this happen, it is important to validate shipping the address during the checkout process and ensure the customer is presented with suggestions and allowed to ascertain that the shipping address is indeed valid.
UPS provides a web service that one can use to make this happen. In this blog, we will be developing a plugin for UPS address validation. You can extend the same approach to FEDEX address validation.

Step 1
You need to register with UPS to get your API credentials. Visit https://www.ups.com/ to do that.

Step 2
Under the Commerce Engine Solution for your site, create a new .NET Core project and name it as you wish. I named mine Sitecore.Commerce.Plugin.UpsAddressValidation.
I am using Visual Studio 2017 and my .Net framework is 4.6.2

Step 3
Add Sitecore Nugget Sitecore.Commerce.Core 2.2.29

Step 4
Add folders for your Commands, Controllers, Models and Policies.

Step 5
Create a CommandsController under your controller folder.
Ensure the controller inherits from CommerceController
Add constructor to the controller as shown in figure 6 below .

Step 6
Add the address Validation endpoint to the controller and name it as you wish.
I have names mine UpsValidateAddress I have also set the protocol to HttpPost and the route to UpsValidateAddress()

You will also need to add some validation on the data being sent to the endpoint so that you do not have to initiate unnecessary validation with UPS or FEDEX.

I am validating the Address first line, city, state and zip. If one of these is missing, I simply reject the request as bad. You may want to include more details in your rejection response.
Step 7
Create a DTO class to hold the address input under the Models folder. I named mine InputAddress I set the address input properties as UPS wants them in the UPS documentation on their API site

    public class InputAddress
    {
        public string ConsigneeName { get; set; }
        public string BuildingName { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
        public string CountryCode { get; set; }
    }

Step 8
Create a class for your policy under the policies folder and inherit from Policy. You may follow the naming convention and end the class name with Policy. I named mine UpsAddressValidationPolicy
This class will be holding your credentials you need for authenticating with UPS or FEDEX
Add properties for the credentials. You may also initialize them in a constructor to save you from having to bootstrap during development, so that you can test quick.

    public class UpsAddressValidationPolicy : Policy
    {

        public UpsAddressValidationPolicy()
        {
            UserName = "xxxxx";
            Password = "xxxxxx";
            LicenseKey = "xxxxxxxx";
            ProdUrl = "https://onlinetools.ups.com/rest/XAV";
            TestUrl = "https://wwwcie.ups.com/rest/XAV";
            IsLive=true;
        }

        public string UserName { get; set; }
        public string Password { get; set; }
        public string LicenseKey { get; set; }
        public string ProdUrl { get; set; }
        public string TestUrl { get; set; }
        public bool IsLive { get; set; }
    }


Step 9
Create a class under your Commands folder and inherit from CommerceCommand. You may follow the naming convention and end the class name with Command. I named mine UpsValidateAddressCommand
Add a constructor as shown below and is typical for Commands classes.
Add the code that will make validation request to UPS or FEDEX to the commands class so that when it is called from the Controller action method, it will return the result from UPS or FEDEX service to the controller.

I composed my request as JSON string and use HTTPClient to post it to UPS. For FEDEX, you may be using webServices.

    public class UpsValidateAddressCommand : CommerceCommand
    {
        public UpsValidateAddressCommand(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public virtual string Process(CommerceContext commerceContext, InputAddress inputAddress)
        {

            var upsClientPolicy = commerceContext.GetPolicy();

            var url = upsClientPolicy.IsLive ? upsClientPolicy.ProdUrl : upsClientPolicy.TestUrl;

            var requestString = $@"{{
            ""UPSSecurity"": {{
            ""UsernameToken"": {{
            ""Username"": ""{upsClientPolicy.UserName}"",
            ""Password"": ""{upsClientPolicy.Password}""
            }},
            ""ServiceAccessToken"": {{
            ""AccessLicenseNumber"": ""{upsClientPolicy.LicenseKey}""
            }}
            }},
            ""XAVRequest"": {{
            ""Request"": {{
            ""RequestOption"": ""1"",
            ""TransactionReference"": {{
            ""CustomerContext"": ""Your Customer Context""
            }}
            }},
            ""MaximumListSize"": ""10"",
            ""AddressKeyFormat"": {{
            ""ConsigneeName"": ""{inputAddress.ConsigneeName}"",
            ""BuildingName"": ""{inputAddress.BuildingName}"",
            ""AddressLine"": ""{inputAddress.AddressLine1}"",
            ""PoliticalDivision2"": ""{inputAddress.City}"",
            ""PoliticalDivision1"": ""{inputAddress.State}"",
            ""PostcodePrimaryLow"": ""{inputAddress.ZipCode}"",
            ""CountryCode"": ""{inputAddress.CountryCode}""
            }}
            }}
            }}";




            var client = new HttpClient();
            var content = new StringContent(requestString, Encoding.UTF8, Constants.Headers.ContentType);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(Constants.Headers.ContentType));
            content.Headers.ContentType = new MediaTypeHeaderValue(Constants.Headers.ContentType);
            client.DefaultRequestHeaders.Add(Constants.Headers.AcessControlHeaders, Constants.Headers.AllowHeaders);
            client.DefaultRequestHeaders.Add(Constants.Headers.AcessControlMethods, Constants.Headers.Methods);
            client.DefaultRequestHeaders.Add(Constants.Headers.AcessControlOrigin, Constants.Headers.Origin);

            try
            {
                var output = client.PostAsync(url, content).Result;
                output.EnsureSuccessStatusCode();

                var outputData = output.Content.ReadAsStringAsync().Result;

                return outputData;

            }
            catch (Exception ex)
            {
                Log.Error(ex.Message);

            }

            return string.Empty;



        }
    }


Complete the controller endpoint code that will invoke the Command and get response back from UPS or FEDEX

    public class CommandsController : CommerceController
    {
        /// 
        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The service provider.
        /// The global environment.
        public CommandsController(IServiceProvider serviceProvider, CommerceEnvironment globalEnvironment)
            : base(serviceProvider, globalEnvironment)
        {
        }

        /// 
        /// 
        /// 
        /// 
        /// 
        [HttpPost]
        [Route("UpsValidateAddress()")]
        public IActionResult UpsValidateAddress([FromBody] ODataActionParameters value)
        {

            if (!value.ContainsKey(Constants.Address.AddressLine1)) return (IActionResult)new BadRequestObjectResult((object)value);
            if (!value.ContainsKey(Constants.Address.City)) return (IActionResult)new BadRequestObjectResult((object)value);
            if (!value.ContainsKey(Constants.Address.State)) return (IActionResult)new BadRequestObjectResult((object)value);
            if (!value.ContainsKey(Constants.Address.ZipCode)) return (IActionResult)new BadRequestObjectResult((object)value);

            var inputAddress = new InputAddress
            {
                AddressLine1 = value[Constants.Address.AddressLine1].ToString(),
                City = value[Constants.Address.City].ToString(),
                State = value[Constants.Address.State].ToString(),
                ZipCode = value[Constants.Address.ZipCode].ToString(),
                CountryCode = "US", //value[Constants.Address.CountryCode].ToString(),
                AddressLine2 = value.ContainsKey(Constants.Address.AddressLine2) ? value[Constants.Address.AddressLine2].ToString() : string.Empty,
                ConsigneeName =
                    value.ContainsKey(Constants.Address.ConsigneeName) ? value[Constants.Address.ConsigneeName].ToString() : Constants.Address.DefaultConsigneeName,
                BuildingName = value.ContainsKey(Constants.Address.BuildingName) ? value[Constants.Address.BuildingName].ToString() : Constants.Address.DefaultBuildingName
            };

            var command = this.Command();
            var result = command.Process(CurrentContext, inputAddress);

            return new ObjectResult(result);
        }

    }


Fig 6
We now need to wire up the controller to Sitecore commerce engine so that it is reachable by requests. To do that we will be adding two configuration classes.
Step 10

Add a class named ConfigureServiceApiBlock to the root of your project and inherit from PipelineBlock
Add the bound actions and parameters for the UpsValidateAddress endpoint as shown below.
Step 11

    public class ConfigureServiceApiBlock : PipelineBlock
    {
        /// 
        /// The execute.
        /// 
        /// 
        /// The argument.
        /// 
        /// 
        /// The context.
        /// 
        /// 
        /// The .
        /// 
        public override Task Run(ODataConventionModelBuilder modelBuilder, CommercePipelineExecutionContext context)
        {
            Condition.Requires(modelBuilder).IsNotNull($"{this.Name}: The argument cannot be null.");

            // Add unbound actions
            var configuration = modelBuilder.Action("UpsValidateAddress");
            configuration.Parameter("AddressLine1");
            configuration.Parameter("AddressLine2");
            configuration.Parameter("BuildingName");
            configuration.Parameter("ConsigneeName");
            configuration.Parameter("City");
            configuration.Parameter("State");
            configuration.Parameter("ZipCode");
            configuration.Parameter("CountryCode");
            configuration.ReturnsFromEntitySet("Commands");




            return Task.FromResult(modelBuilder);
        }
    }

Add another configuration class named ConfigureSitecore to the root of your project and inherit from IConfigureSitecore implement the interface and add the line below as shown on line 34 of figure 7 below.
.ConfigurePipeline(configure => configure.Add()));

    public class ConfigureSitecore : IConfigureSitecore
    {
        /// 
        /// The configure services.
        /// 
        /// 
        /// The services.
        /// 
        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(config => config

               .ConfigurePipeline(configure => configure.Add()));

            services.RegisterAllCommands(assembly);
        }
    }

The line ensures that the Controller endpoints will be reachable when a request comes to the commerce engine.
You may now test your endpoint using postman.
As shown in figure 8 below.

You may down load the sample plugin from Github here: Github Link

Related Blogs

Latest Blogs