Item Price without VAT = retailPrice / (taxRate + 1) VAT Amount = retailPrice - itemPriceWithoutVAT
VAT stands for "Value-Added Tax" and is a method of calculating and collecting taxes when an item is sold.
Under a VAT system, a customer pays the retail price for merchandise and the taxes are collected from that amount.
In contrast, under a Sales Tax system, the taxes are added to the retail price that the customer pays.
For a $10 item with a 5% VAT:
the customer pays $10
there is $0.48 tax
the merchant keeps $9.52.
To calculate a VAT amount, we need to apply the tax rate to the pre-tax price of the item. Since the retail price includes VAT, we need to work backwards.
Item Price without VAT = retailPrice / (taxRate + 1) VAT Amount = retailPrice - itemPriceWithoutVAT
With our $10 item with 5% VAT example, the math would be:
itemPriceWithoutVAT = $10 / (0.05 +1) = $10 / 1.05 = $9.52 VAT Amount = $10 - $9.52 = $0.48
We can check our math by applying the tax rate to the pre-tax amount:
$9.52 * 0.05 = $0.48 $9.52 + $0.48 = $10.00
Note
|
com.broadleafcommerce.tax.simple.SimpleTaxRecord |
A SimpleTaxRecord
is the core piece of tax configuration. A SimpleTaxRecord
will have the tax rate as well as fields for the taxing jurisdiction (e.g. country, state, postal code). SimpleTaxRecord
also has a vat
field to indicate if the tax is a VAT.
SimpleTaxRecords
are configured with SimpleTaxConfig
and used in SimpleTaxProvider
.
Note
|
com.broadleafcommerce.tax.simple.SimpleTaxConfig |
The SimpleTaxProvider is configured with properties, by default in "simple-tax-example.json".
{
"defaultRate": {"rate": "0.05"},
"taxTables" : {
"US": [{
"countryDefault": true,
"rate": "0"
},
{
"stateProvinceRegion": "TX",
"rate": "0.06375"
},
{
"stateProvinceRegion": "TX",
"city": "Celina",
"postalCode": "75009",
"rate": "0.0625"
}],
"GB": [{
"countryDefault": true,
"rate": "0.2",
"vat": true
}],
"VAT20": [{
"countryDefault": true,
"rate": "0.20",
"vat": true
}],
"VAT5": [{
"countryDefault": true,
"rate": "0.05",
"vat": true
}]
}
}
In the example above, the taxTables
map has 4 keys: US
, GB
, VAT20
, and VAT5
. You can see that the GB
, VAT20
, and VAT5
keys have "vat":true
. You can also see in the US
example that tax records can be configured by country, region, city, and postal code.
SimpleTaxProvider
will first check these keys for an item’s tax code. If no record is found by tax code, then it checks the country of the destination address (see SimpleTaxProvider#getTaxRecord
).
This means that to configure VAT taxes, you can set a specific tax code on a product, or you can configure the tax settings geographically.
Taxes will be calculated against the retail price of an item (less any applicable discounts). With our example, the retail price of the product in the admin is $10 for both tax systems. If your tax configuration uses Tax Codes, then the appropriate tax code should be added to the product. If your tax configuration is geographically based, then no further updates are needed to the product in the admin.
To configure VAT taxes, in simple-tax-example.json
, configure a tax record with "vat":true
. The tax record can be based on tax code or geography. If based on tax code, set that tax code on the appropriate products.
The CartOperationsServices documentation also has a section about VAT, see CartOps VAT configuration.
During tax calculation, the SimpleTaxProvider
looks up the appropriate SimpleTaxRecord
and adds TaxDetail
objects to the tax response. TaxDetail
has an isVat
flag that is set from the SimpleTaxRecord
. Related classes FulfillmentItemTaxDetail
and OrderFulfillmentItemTaxDetail
also have the isVat
flag.
Note
|
com.broadleafcommerce.tax.domain.TaxDetail com.broadleafcommerce.tax.simple.SimpleTaxProvider |
A CartPricing
object is used to track the pricing data for the cart, such as subtotal, tax amounts, and fulfillment amounts. CartPricing also has fields taxIncludedType
and includedTaxAmount
to indicate if and how much tax is included in the subtotal. The subtotal is the sum of the retail prices of a cart, so in a VAT system, tax is included in the subtotal. taxIncludedType
can be YES
, NO
, or PARTIAL
. The amount of tax included in the subtotal is tracked in includedTaxAmount
.
Note
|
com.broadleafcommerce.cart.client.domain.CartPricing |
The method responsible for determining the includedTaxAmount
is: com.broadleafcommerce.cartoperation.service.pricing.DefaultDelegatingTaxService#calculateIncludedTaxes
. That method will check TaxDetails#isVat
from all items in the cart to determine what amount of tax is included in the subtotal.
CartPricing#totalTax
will have the total amount of tax for the Cart, including VAT and Sales Taxes.
The formula for pricing a cart is subtotal + fulfillment + fees - adjustments + (totalTax - includedTax)
.
With our example from earlier, the $10 item with 5% tax, the CartPricing object would have the following values:
CartPricing Field | Value when isVat=true | Value when isVat=false |
---|---|---|
subtotal |
$10 |
$10 |
taxTotal |
$0.48 |
$0.50 |
taxIncludedType |
YES |
NO |
includedTaxAmount |
$0.48 |
$0 |
total |
$10 |
$10.50 |
The formula for pricing a cart is subtotal + fulfillment + fees - adjustments + (totalTax - includedTax)
.
The logic is in the DefaultCartTotalsCalculator
:
@Override
public MonetaryAmount calculateCartTotalBeforeTax(@lombok.NonNull CartPricing cartPricing) {
return cartPricing.getSubtotal()
.add(cartPricing.getFulfillmentTotal())
.add(cartPricing.getFeesTotal())
.subtract(cartPricing.getAdjustmentsTotal());
}
@Override
public MonetaryAmount calculateCartTotalAfterTax(@lombok.NonNull CartPricing cartPricing) {
return calculateCartTotalBeforeTax(cartPricing)
.add(getNonIncludedTax(cartPricing));
}
/**
* Determine the amount of tax that was not included in the subtotal and therefore should be
* added to the subtotal to get the grand total.
*
* @param cartPricing The pricing fields of a Cart to total.
* @return The amount of tax that should be added to the subtotal to get the grand total.
*/
protected MonetaryAmount getNonIncludedTax(CartPricing cartPricing) {
return cartPricing.getTotalTax().subtract(cartPricing.getIncludedTaxAmount());
}
After checkout, in OrderOperationServices, the CheckoutCompletionListener
creates an Order
from a Cart
. There is a setting available to control how VAT taxes are handled
Note
|
com.broadleafcommerce.orderoperation.service.messaging.checkout.CheckoutCompletionListener |
The property broadleaf.orderoperation.ordergeneration.removeVatFromItemPrice
controls if the item’s price on the Order should include the VAT. With our previous example, our cart with a $10 item would get converted to an Order
with an OrderItem
, which would have a corresponding OrderFulfillmentItem
, which has OrderFulfillmentTaxItemDetails
, which has the isVat
flag with the value from the SimpleTaxRecord
.
In DefaultCartOrderGenerationService#updateOrderItemTotalsForVat
and DefaultCartOrderFulfillmentGenerationService#buildOrderFulfillmentItem
, the removeVatFromItemPrice
flag is checked. If true, the VAT amount will be subtracted from the item’s price.
Below is a table that shows the difference when removeVatFromItemPrice
is enabled.
Field | removeVatFromItemPrice=true | removeVatFromItemPrice=false |
---|---|---|
item price |
$9.52 |
$10 |
item tax |
$0.48 |
$0.48 |
item total |
$10 |
$10 |
The removeVatFromItemPrice
flag controls how order data is saved in the database. If instead, you would like to alter how order data is displayed, that can be accomplished by customizing the front-end admin application. The order endpoint response includes all tax info about the order, including the isVAT
and includedAmount
fields.