Beyond the Basics, Part 2: Dynamic Tax Logic in Portal Connector Checkout

Aug 21, 2025, 22:13 by Omar Shah

If you read part 1 of this blog, Beyond the Basics: Transforming Portal Connector into an Ecommerce Power Tool, you will have a custom dropdown for selecting different states in the Portal Connector Checkout widget. Next, we’ll focus on server-side enhancements using the Portal Connector ecommerce interface for the Checkout widget. Our goal is to make taxes dynamic based on the selected state. To do this, we’ll extend the ITpcTaxProvider. Follow the steps below.

Extending Ecommerce Interface for the Checkout Widget

  1. Create a new class library project in your solution. I called mine pavliks.PortalConnector.MvcEcommerceExtensions to match the other pavliks-prefixed DLLs.
  2. In the project, install the following packages and ensure they match the Sitefinity and Portal Connector versions used in your web app:
    1. Telerik.Sitefinity.core (same version as your Sitefinity web app)
    2. PortalConnector.Mvc.Ecommerce (same version as your Portal Connector)
  3. To extend the ITpcTaxProvider, we must create a Ninject Module. Start by creating an InterfaceMappings class in the root of your Ecommerce project. This class will inherit from NinjectModule, and we’ll override its Load method.

    Inside Load, bind the ITpcTaxProviderConfiguration to a new class called DynamicTaxProviderConfiguration, and bind ITpcTaxProvider to DynamicTaxProvider.

    Finally, bind ITpcAdminAppSitefinityInitializer to TpcEcommerceAdminAppInitializer. In that initializer, we’ll implement type mappings for ecommerce configuration elements, making it possible to apply our custom code to the ecommerce widget in the Portal Connector site.

    
    using Ninject.Modules;
    using pavliks.PortalConnector.AdminApp.Models.Initialization.Sitefinity;
    using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
    using pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic;
    using Telerik.Sitefinity.Configuration;
    using Telerik.Sitefinity.Services;
    
    namespace pavliks.PortalConnector.Mvc.EcommerceExtensions
    {
        /// <summary>
        /// This class is used to describe the bindings which will be used by the Ninject container when resolving classes
        /// </summary>
        public class InterfaceMappings : NinjectModule
        {
            /// <summary>        
            /// Loads the module into the kernel.
            /// </summary>
            public override void Load()
            {
                //Get System Configuration
                ConfigManager manager = ConfigManager.GetManager();
                SystemConfig systemConfig = manager.GetSection
  4. In the Ecommerce project, create the following three directories:
    1. Extensions – Add a Helpers class containing a static method called GetTaxRateForState. This method will return hardcoded values for the US states included in your UI.

      Note: This demo uses hardcoded tax rates for simplicity and clarity. In a production environment, consider retrieving tax rates from a configuration file (e.g., JSON or XML) or an external tax API for accuracy and maintainability.

      
      using System;
      
      namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Extensions
      {
          public static class Helpers
          {
              public static decimal GetTaxRateForState(string state)
              {
                  if (state == null) { throw new ArgumentNullException(nameof(state)); }
                  switch (state)
                  {
                      case "FL": return 7.0M;
                      case "NY": return 8.5M;
                      case "CA": return 8.9M;
                      case "HI": return 4.5M;
                      default: return 13;
                  }
              }
          }
      }
              
    2. Initialization > AdminApp – Add a TpcEcommerceAdminAppInitializer class. This makes your provider accessible in Sitefinity’s Advanced Settings.
      
      using pavliks.PortalConnector.AdminApp.Models.Initialization.Sitefinity;
      using pavliks.PortalConnector.Mvc.Ecommerce;
      using pavliks.PortalConnector.Mvc.Ecommerce.Configuration;
      using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
      using pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic;
      using System;
      using Telerik.Sitefinity.Configuration;
      
      namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Initialization.AdminApp
      {
          public class TpcEcommerceAdminAppInitializer : TpcAdminAppSitefinityInitializer
          {
              public TpcEcommerceAdminAppInitializer()
              {
                  ModuleName = PortalConnectorMvcEcommerceModule.ModuleName;
                  Order = 3;
              }
      
              /// <summary>
              /// This method is used to add the implementation type mappings for Ecomm Configuration Elements
              /// </summary>
              /// <returns>Type Mappings</returns>
              public override Tuple<Type, Type>[] ImplementationTypeMappings()
              {
                  return new Tuple<Type, Type>[]
                  {
                      //Taxes
                      Tuple.Create (typeof(DynamicTaxProviderConfiguration), typeof(ITpcTaxProviderConfiguration)),
                  };
              }
      
              /// <summary>
              /// This method is used to initialize configuration logic for Ecomm Configuration Elements.
              /// </summary>
              public override void InitializeConfigurations()
              {
                  try
                  {
                      bool saveChanges = false;
      
                      //Get Configurations Manager
                      ConfigManager configManager = ConfigManager.GetManager();
                      PortalConnectorMvcEcommerceConfig config = configManager.GetSection<PortalConnectorMvcEcommerceConfig>();
      
                      //Add Default Tax Configuration(s)
                      AddDefaultToConfigElementDictionary<TpcTaxProviderConfiguration, DynamicTaxProviderConfiguration, DynamicTaxProvider>(config.TaxProviders, ref saveChanges);
      
                      //Save Changes
                      if (saveChanges) configManager.SaveSection(config);
                  }
                  catch (Exception ex)
                  {
                      ExceptionManager.HandleError("Initialize Ecommerce Configurations", ex);
                  }
              }
          }
      }
      
    3. Providers > Tax > Dynamic – Create the following two classes in this subdirectory:
      1. DynamicTaxProviderConfiguration:
        
        using pavliks.PortalConnector.Mvc.Ecommerce.Configuration;
        using Telerik.Sitefinity.Configuration;
        
        namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic
        {
            // This class defines the configuration for the Dynamic Tax Provider
            public class DynamicTaxProviderConfiguration : TpcTaxProviderConfiguration
            {
                public DynamicTaxProviderConfiguration(ConfigElement parent) : base(parent) { }
            }
        }
                    
      2. DynamicTaxProvider:
        
        using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Payment;
        using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        
        namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic
        {
            // Implements the TPC tax provider interface using a dynamic configuration
            public class DynamicTaxProvider : ITpcTaxProvider<DynamicTaxProviderConfiguration>
            {
                // Holds the configuration used to calculate tax
                private DynamicTaxProviderConfiguration _configuration { get; set; }
        
                // Default constructor
                public DynamicTaxProvider() { }
        
                // Constructor that accepts a configuration object
                public DynamicTaxProvider(DynamicTaxProviderConfiguration configuration)
                {
                    _configuration = configuration;
                }
        
                // Public accessor for the configuration
                public DynamicTaxProviderConfiguration Configuration
                {
                    get => _configuration;
                    set => _configuration = value;
                }
        
                // Name of the provider shown in UI or logs
                public string Name => "Dynamic Tax";
        
                // Core method that calculates tax based on the transaction details
                public IList<ITpcTaxInformation> GetTaxes(ITpcPaymentTransaction transaction)
                {
                    // Ensure configuration is set before proceeding
                    if (Configuration == null)
                        throw new InvalidOperationException($"A tax configuration is required in order to calculate the taxes for {Name} Provider.");
        
                    List<ITpcTaxInformation> taxInformation = new List<ITpcTaxInformation>();
                    string state = null;
        
                    // Attempt to extract the state from shipping details
                    if (transaction?.ShippingDetails?.ShippingDetails != null)
                    {
                        state = transaction.ShippingDetails.ShippingDetails
                            .FirstOrDefault(x => x.Key == "address2_stateorprovince").Value?.ToString();
        
                        // Fallback to address1 if address2 is missing
                        if (string.IsNullOrWhiteSpace(state))
                        {
                            state = transaction.ShippingDetails.ShippingDetails
                                .FirstOrDefault(x => x.Key == "address1_stateorprovince").Value?.ToString();
                        }
                    }
        
                    // If no state is found, throw an error
                    if (string.IsNullOrWhiteSpace(state))
                    {
                        throw new InvalidOperationException("State information is missing from the transaction.");
                    }
        
                    // Loop through each cart item and calculate tax
                    if (transaction.CartDetails != null)
                    {
                        foreach (ITpcCartItem cartItem in transaction.CartDetails.Items())
                        {
                            // Get the tax rate based on the state
                            decimal taxRate = Extensions.Helpers.GetTaxRateForState(state);
        
                            // Calculate tax amount using price, rate, and quantity
                            decimal taxAmount = Math.Round(cartItem.Price * (taxRate / 100) * cartItem.Quantity, 2);
        
                            // Add tax info to the result list
                            taxInformation.Add(new TpcTaxInformation
                            {
                                Item = cartItem,
                                Price = taxAmount,
                                Percent = taxRate
                            });
                        }
                    }
        
                    return taxInformation;
                }
            }
        }
        

Adding the Dynamic Tax Provider to Your Site

  1. Reference the project in your Sitefinity web app. Open your web application, right-click References, and under Projects, select pavliks.PortalConnector.MvcEcommerceExtensions (or whatever name you gave the project).  Visual Studio IDE displaying custom ecommerce project to as a reference
  2. To configure dynamic taxes, run your web app and navigate to the Sitefinity backend. Go to Advanced Settings, scroll down to PortalConnectorMvcEcommerce, expand Tax Providers, and create a new DynamicTaxProviderConfiguration. Sitefinity backend advanced settings showing numbered steps on adding a custom tax provider to PortalConnectorMvcEcommerce
  3. Give the Tax Provider a meaningful name that will appear in the Designer, then click Save Changes. Sitefinity backend displaying the input for naming the Tax Provider along with the Save changes button
  4. Navigate to the page where your Checkout widget is placed. Click the Payment tab and edit the Tax Provider section to use the Dynamic Tax provider. image displaying Portal Connector Checkout widget Payment tab where users can set a custom Tax Provider
  5. Visit the frontend and test to ensure that dynamic taxes are being applied correctly to your products.

Next Steps: From Dynamic Taxes to Dataverse Integration

Now that you've implemented dynamic tax logic in your Portal Connector Checkout widget, you're ready to take your ecommerce experience to the next level. In the next part of this series, we’ll explore how to connect Portal Connector 7.0 to Dynamics 365 Business Central using Dataverse integration. This allows you to surface product data from Business Central directly onto your portal site—enabling real-time updates, centralized inventory management, and seamless customer experiences. Portal Connector 7.0 makes this easier than ever with Accelerator-generated pages, which let you build themed content quickly and consistently.

Stay tuned: We’ve created a dedicated video walkthrough series that guides you through this integration step-by-step—from syncing Business Central with Dynamics CRM, to exposing product data on your Portal Connector site. We’ll introduce the series in the next blog post and walk through the setup in detail.

Load more comments

Beyond the Basics, Part 2: Dynamic Tax Logic in Portal Connector Checkout

Aug 21, 2025, 22:13 by Omar Shah

If you read part 1 of this blog, Beyond the Basics: Transforming Portal Connector into an Ecommerce Power Tool, you will have a custom dropdown for selecting different states in the Portal Connector Checkout widget. Next, we’ll focus on server-side enhancements using the Portal Connector ecommerce interface for the Checkout widget. Our goal is to make taxes dynamic based on the selected state. To do this, we’ll extend the ITpcTaxProvider. Follow the steps below.

Extending Ecommerce Interface for the Checkout Widget

  1. Create a new class library project in your solution. I called mine pavliks.PortalConnector.MvcEcommerceExtensions to match the other pavliks-prefixed DLLs.
  2. In the project, install the following packages and ensure they match the Sitefinity and Portal Connector versions used in your web app:
    1. Telerik.Sitefinity.core (same version as your Sitefinity web app)
    2. PortalConnector.Mvc.Ecommerce (same version as your Portal Connector)
  3. To extend the ITpcTaxProvider, we must create a Ninject Module. Start by creating an InterfaceMappings class in the root of your Ecommerce project. This class will inherit from NinjectModule, and we’ll override its Load method.

    Inside Load, bind the ITpcTaxProviderConfiguration to a new class called DynamicTaxProviderConfiguration, and bind ITpcTaxProvider to DynamicTaxProvider.

    Finally, bind ITpcAdminAppSitefinityInitializer to TpcEcommerceAdminAppInitializer. In that initializer, we’ll implement type mappings for ecommerce configuration elements, making it possible to apply our custom code to the ecommerce widget in the Portal Connector site.

    
    using Ninject.Modules;
    using pavliks.PortalConnector.AdminApp.Models.Initialization.Sitefinity;
    using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
    using pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic;
    using Telerik.Sitefinity.Configuration;
    using Telerik.Sitefinity.Services;
    
    namespace pavliks.PortalConnector.Mvc.EcommerceExtensions
    {
        /// <summary>
        /// This class is used to describe the bindings which will be used by the Ninject container when resolving classes
        /// </summary>
        public class InterfaceMappings : NinjectModule
        {
            /// <summary>        
            /// Loads the module into the kernel.
            /// </summary>
            public override void Load()
            {
                //Get System Configuration
                ConfigManager manager = ConfigManager.GetManager();
                SystemConfig systemConfig = manager.GetSection
  4. In the Ecommerce project, create the following three directories:
    1. Extensions – Add a Helpers class containing a static method called GetTaxRateForState. This method will return hardcoded values for the US states included in your UI.

      Note: This demo uses hardcoded tax rates for simplicity and clarity. In a production environment, consider retrieving tax rates from a configuration file (e.g., JSON or XML) or an external tax API for accuracy and maintainability.

      
      using System;
      
      namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Extensions
      {
          public static class Helpers
          {
              public static decimal GetTaxRateForState(string state)
              {
                  if (state == null) { throw new ArgumentNullException(nameof(state)); }
                  switch (state)
                  {
                      case "FL": return 7.0M;
                      case "NY": return 8.5M;
                      case "CA": return 8.9M;
                      case "HI": return 4.5M;
                      default: return 13;
                  }
              }
          }
      }
              
    2. Initialization > AdminApp – Add a TpcEcommerceAdminAppInitializer class. This makes your provider accessible in Sitefinity’s Advanced Settings.
      
      using pavliks.PortalConnector.AdminApp.Models.Initialization.Sitefinity;
      using pavliks.PortalConnector.Mvc.Ecommerce;
      using pavliks.PortalConnector.Mvc.Ecommerce.Configuration;
      using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
      using pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic;
      using System;
      using Telerik.Sitefinity.Configuration;
      
      namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Initialization.AdminApp
      {
          public class TpcEcommerceAdminAppInitializer : TpcAdminAppSitefinityInitializer
          {
              public TpcEcommerceAdminAppInitializer()
              {
                  ModuleName = PortalConnectorMvcEcommerceModule.ModuleName;
                  Order = 3;
              }
      
              /// <summary>
              /// This method is used to add the implementation type mappings for Ecomm Configuration Elements
              /// </summary>
              /// <returns>Type Mappings</returns>
              public override Tuple<Type, Type>[] ImplementationTypeMappings()
              {
                  return new Tuple<Type, Type>[]
                  {
                      //Taxes
                      Tuple.Create (typeof(DynamicTaxProviderConfiguration), typeof(ITpcTaxProviderConfiguration)),
                  };
              }
      
              /// <summary>
              /// This method is used to initialize configuration logic for Ecomm Configuration Elements.
              /// </summary>
              public override void InitializeConfigurations()
              {
                  try
                  {
                      bool saveChanges = false;
      
                      //Get Configurations Manager
                      ConfigManager configManager = ConfigManager.GetManager();
                      PortalConnectorMvcEcommerceConfig config = configManager.GetSection<PortalConnectorMvcEcommerceConfig>();
      
                      //Add Default Tax Configuration(s)
                      AddDefaultToConfigElementDictionary<TpcTaxProviderConfiguration, DynamicTaxProviderConfiguration, DynamicTaxProvider>(config.TaxProviders, ref saveChanges);
      
                      //Save Changes
                      if (saveChanges) configManager.SaveSection(config);
                  }
                  catch (Exception ex)
                  {
                      ExceptionManager.HandleError("Initialize Ecommerce Configurations", ex);
                  }
              }
          }
      }
      
    3. Providers > Tax > Dynamic – Create the following two classes in this subdirectory:
      1. DynamicTaxProviderConfiguration:
        
        using pavliks.PortalConnector.Mvc.Ecommerce.Configuration;
        using Telerik.Sitefinity.Configuration;
        
        namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic
        {
            // This class defines the configuration for the Dynamic Tax Provider
            public class DynamicTaxProviderConfiguration : TpcTaxProviderConfiguration
            {
                public DynamicTaxProviderConfiguration(ConfigElement parent) : base(parent) { }
            }
        }
                    
      2. DynamicTaxProvider:
        
        using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Payment;
        using pavliks.PortalConnector.Mvc.Ecommerce.Engine.Data.Taxes;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        
        namespace pavliks.PortalConnector.Mvc.EcommerceExtensions.Engine.Providers.Tax.Dynamic
        {
            // Implements the TPC tax provider interface using a dynamic configuration
            public class DynamicTaxProvider : ITpcTaxProvider<DynamicTaxProviderConfiguration>
            {
                // Holds the configuration used to calculate tax
                private DynamicTaxProviderConfiguration _configuration { get; set; }
        
                // Default constructor
                public DynamicTaxProvider() { }
        
                // Constructor that accepts a configuration object
                public DynamicTaxProvider(DynamicTaxProviderConfiguration configuration)
                {
                    _configuration = configuration;
                }
        
                // Public accessor for the configuration
                public DynamicTaxProviderConfiguration Configuration
                {
                    get => _configuration;
                    set => _configuration = value;
                }
        
                // Name of the provider shown in UI or logs
                public string Name => "Dynamic Tax";
        
                // Core method that calculates tax based on the transaction details
                public IList<ITpcTaxInformation> GetTaxes(ITpcPaymentTransaction transaction)
                {
                    // Ensure configuration is set before proceeding
                    if (Configuration == null)
                        throw new InvalidOperationException($"A tax configuration is required in order to calculate the taxes for {Name} Provider.");
        
                    List<ITpcTaxInformation> taxInformation = new List<ITpcTaxInformation>();
                    string state = null;
        
                    // Attempt to extract the state from shipping details
                    if (transaction?.ShippingDetails?.ShippingDetails != null)
                    {
                        state = transaction.ShippingDetails.ShippingDetails
                            .FirstOrDefault(x => x.Key == "address2_stateorprovince").Value?.ToString();
        
                        // Fallback to address1 if address2 is missing
                        if (string.IsNullOrWhiteSpace(state))
                        {
                            state = transaction.ShippingDetails.ShippingDetails
                                .FirstOrDefault(x => x.Key == "address1_stateorprovince").Value?.ToString();
                        }
                    }
        
                    // If no state is found, throw an error
                    if (string.IsNullOrWhiteSpace(state))
                    {
                        throw new InvalidOperationException("State information is missing from the transaction.");
                    }
        
                    // Loop through each cart item and calculate tax
                    if (transaction.CartDetails != null)
                    {
                        foreach (ITpcCartItem cartItem in transaction.CartDetails.Items())
                        {
                            // Get the tax rate based on the state
                            decimal taxRate = Extensions.Helpers.GetTaxRateForState(state);
        
                            // Calculate tax amount using price, rate, and quantity
                            decimal taxAmount = Math.Round(cartItem.Price * (taxRate / 100) * cartItem.Quantity, 2);
        
                            // Add tax info to the result list
                            taxInformation.Add(new TpcTaxInformation
                            {
                                Item = cartItem,
                                Price = taxAmount,
                                Percent = taxRate
                            });
                        }
                    }
        
                    return taxInformation;
                }
            }
        }
        

Adding the Dynamic Tax Provider to Your Site

  1. Reference the project in your Sitefinity web app. Open your web application, right-click References, and under Projects, select pavliks.PortalConnector.MvcEcommerceExtensions (or whatever name you gave the project).  Visual Studio IDE displaying custom ecommerce project to as a reference
  2. To configure dynamic taxes, run your web app and navigate to the Sitefinity backend. Go to Advanced Settings, scroll down to PortalConnectorMvcEcommerce, expand Tax Providers, and create a new DynamicTaxProviderConfiguration. Sitefinity backend advanced settings showing numbered steps on adding a custom tax provider to PortalConnectorMvcEcommerce
  3. Give the Tax Provider a meaningful name that will appear in the Designer, then click Save Changes. Sitefinity backend displaying the input for naming the Tax Provider along with the Save changes button
  4. Navigate to the page where your Checkout widget is placed. Click the Payment tab and edit the Tax Provider section to use the Dynamic Tax provider. image displaying Portal Connector Checkout widget Payment tab where users can set a custom Tax Provider
  5. Visit the frontend and test to ensure that dynamic taxes are being applied correctly to your products.

Next Steps: From Dynamic Taxes to Dataverse Integration

Now that you've implemented dynamic tax logic in your Portal Connector Checkout widget, you're ready to take your ecommerce experience to the next level. In the next part of this series, we’ll explore how to connect Portal Connector 7.0 to Dynamics 365 Business Central using Dataverse integration. This allows you to surface product data from Business Central directly onto your portal site—enabling real-time updates, centralized inventory management, and seamless customer experiences. Portal Connector 7.0 makes this easier than ever with Accelerator-generated pages, which let you build themed content quickly and consistently.

Stay tuned: We’ve created a dedicated video walkthrough series that guides you through this integration step-by-step—from syncing Business Central with Dynamics CRM, to exposing product data on your Portal Connector site. We’ll introduce the series in the next blog post and walk through the setup in detail.

Load more comments
Get connected with us on social!