Creating a Tax Provider
Anatomy of a Tax Provider
Tax providers are utilized by Broadleaf Commerce to interface with an external provider (or some custom algorithm) for tax calculation. The cost of tax can be based on a number of factors including region and dollar total. If you encounter a situation where there is no existing Broadleaf Commerce tax module for your desired tax calculation method, you may find it necessary to develop your own custom tax provider. Let's review the TaxProvider interface from the framework:
public Order calculateTaxForOrder(Order order, ModuleConfiguration config) throws TaxException;
public Order commitTaxForOrder(Order order, ModuleConfiguration config) throws TaxException;
public void cancelTax(Order order, ModuleConfiguration config) throws TaxException;
calculateTaxForOrder- invoked during theblPricingWorkflowto associateTaxDetailobjects to theFulfillmentGroups andFulfillmentGroupItems within the given ordercommitTaxForOrder- invoked during theblCheckoutWorkflowin theCommitTaxActivityto communicate with an external tax provider that might need to manage tax documentscancelTax- if an order has already gone through theCommitTaxActivitybut did not complete the entireblCheckoutWorkflowand taxes that have might have been committed to the external provider need to be rolled back
Some providers (or your custom implementation) may not use the
commitTaxForOrderorcancelTaxmethods. At a minimum, yourTaxProvidershould createTaxDetailobjects for theFulfillmentGroups in an order via thecalculateTaxForOrdermethod.
Selecting a provider
Multiple TaxProviders can exist in the framework at once. In order to decide which provider to use, blTaxService looks up ModuleConfigurations (a database entity) to get a list of the configured modules in the system. The logic works like this:
- Look up all the Module Configurations that have a type of
ModuleConfigurationType.TAX\_CALCULATION, sorted bypriority - Loop through each configuration
- If a configuration is set to the default, use that configuration
- If there is not a default configuration, use the first one in resulting list (the one with the highest priority)
- Take the determined configuration and try to invoke the delegate method by checking
canRespondfor each provider - If no provider could respond to the determined configuration and there is a requirement to calculate tax (via the
mustCalculateproperty onblTaxService) then throw a TaxException
This pattern means that each provider has a unique Module Configuration instance to go along with it.
Hooking up your provider
Add your provider to the blTaxProviders bean:
<bean id="myTaxProviders" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<bean class="com.mycompany.core.tax.provider.MyCustomTaxProvider" />
</list>
</property>
</bean>
<bean class="org.broadleafcommerce.common.extensibility.context.merge.LateStageMergeBeanPostProcessor">
<property name="collectionRef" value="myTaxProviders"/>
<property name="targetRef" value="blTaxProviders"/>
</bean>
You will also need a database table subclass of AbstractModuleConiguration. See the docs for Extending Entities for more information on how to do this.
If you want to bypass the database step for
ModuleConfigurationyou can simply override theblTaxServicebean with a subclass ofTaxServiceImpland implement those methods yourself. Rather than a customTaxProvideryou would then have a customTaxService
Calculating tax
When implementing a TaxProvider, the key to communicating the tax cost to Broadleaf Commerce is the Order instance passed into the calculateTaxForOrder method. While the specific informational needs for each tax calculation method will differ, you should have access here to the building blocks necessary to calculate any form of tax. Here are some hints:
- Call
order.getFulfillmentGroups()to get all the fulfillment groups for the order. Multiple fulfillment groups mean that parts of the order are shipping to different locations. - Call
fulfillmentGroup.getAddress()to get the shipping address. - Call
fulfillmentGroup.getItems()to get all theFulfillmentGroupItems in aFulfillmentGroup - Call
fulfillmentGroupItem.getOrderItem()to get an OrderItem - Call
orderItem.getTaxablePrice()to get the taxable amount for the item
You'll want to iterate through all the FulfillmentGroupItem and FulfillmentGroupFee objects in every FulfillmentGroup of the Order, creating and setting TaxDetail objects when appropriate. You may also have additional taxes that reside on a FulfillmentGroup, such as a shipping tax.
Responding with tax
Broadleaf Commerce expects that the Order instance returned from the calculateTaxForOrder method of your tax module has the pertinent tax cost totals assigned. Your tax module is on the hook for setting the list of TaxDetail objects on:
- Every
FulfillmentGroup - Every
FulfillmentGroupItem - Every
FulfillmentGroupFee
The totalTax fields will be set in the pricing workflow during the TotalActivity.