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.
pavliks.PortalConnector.MvcEcommerceExtensions
to match the other pavliks-prefixed DLLs.
Telerik.Sitefinity.core
(same version as your Sitefinity web app)PortalConnector.Mvc.Ecommerce
(same version as your Portal Connector)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
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;
}
}
}
}
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);
}
}
}
}
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) { }
}
}
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;
}
}
}
pavliks.PortalConnector.MvcEcommerceExtensions
(or whatever name you gave the project). PortalConnectorMvcEcommerce
, expand Tax Providers, and create a new DynamicTaxProviderConfiguration
. Dynamic Tax
provider. 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.
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.
pavliks.PortalConnector.MvcEcommerceExtensions
to match the other pavliks-prefixed DLLs.
Telerik.Sitefinity.core
(same version as your Sitefinity web app)PortalConnector.Mvc.Ecommerce
(same version as your Portal Connector)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
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;
}
}
}
}
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);
}
}
}
}
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) { }
}
}
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;
}
}
}
pavliks.PortalConnector.MvcEcommerceExtensions
(or whatever name you gave the project). PortalConnectorMvcEcommerce
, expand Tax Providers, and create a new DynamicTaxProviderConfiguration
. Dynamic Tax
provider. 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.
Portal Connector by Sylogist is developed and supported by Sylogist © 2024 All Rights Reserved.
Order by
Newest on top Oldest on top