3. Taxes¶
As a general rule, the unit price of a product, shall always contain the net price. When our
products show up in the catalog, their method get_price(request)
is consulted by the framework.
It is here where you add tax, depending on the tax model to apply. See below.
3.1. Use Cart Modifiers to handle Value Added Tax¶
Django-SHOP is not shipped with any kind of built-in tax handling code. This is because tax models vary from product to product and region to region. Therefore the tax computation shall be pluggable and easily exchangeable.
3.1.1. American tax model¶
The American tax model presumes that all prices are shown as net prices, hence the subtotal is the sum of all net prices. On top of the subtotal we add the taxes and hence compute the total.
A simple tax cart modifier which adds the tax on top of the subtotal:
from shop.serializers.cart import ExtraCartRow
from shop.modifiers.base import BaseCartModifier
VALUE_ADDED_TAX = 9.0
class CartIncludeTaxModifier(BaseCartModifier):
taxes = VALUE_ADDED_TAX / 100
def add_extra_cart_row(self, cart, request):
amount = cart.subtotal * self.taxes
instance = {
'label': "plus {}% VAT".format(VALUE_ADDED_TAX),
'amount': amount,
}
cart.extra_rows[self.identifier] = ExtraCartRow(instance)
cart.total += amount
3.1.2. European tax model¶
The European tax model presumes that all prices are shown as gross prices, hence the subtotal already contains the taxes. However, we must report the contained taxes on the invoice.
A simple tax cart modifier which reports the tax already included in the subtotal:
from shop.serializers.cart import ExtraCartRow
from shop.modifiers.base import BaseCartModifier
VALUE_ADDED_TAX = 19.0
class CartExcludedTaxModifier(BaseCartModifier):
taxes = 1 - 1 / (1 + VALUE_ADDED_TAX / 100)
def add_extra_cart_row(self, cart, request):
amount = cart.subtotal * self.taxes
instance = {
'label': "{}% VAT incl.".format(VALUE_ADDED_TAX),
'amount': amount,
}
cart.extra_rows[self.identifier] = ExtraCartRow(instance)
Note that here we do not change the current total.
3.1.3. Mixed tax models¶
When doing business to business, then in Europe the American tax model is used. Sites handling both
private customers as well as business customers must provide a mixture of both tax models.
Since business customers can be identified through the Customer
objects provided by request
object, we can determine which tax model to apply in each situation.
3.2. Varying Taxes per Item¶
For certain kind of products, different tax rates must be applied. If your e-commerce site must handle these kinds of products, then we add a tag to our product model. This could be an enum field, with one value per tax rate or a decimal field containing the rate directly.
In this example we use the latter, where each product contains a field named vat
, containing the
tax rate in percent.
from shop.serializers.cart import ExtraCartRow
from shop.modifiers.base import BaseCartModifier
from myshop.models.product import Product
class TaxModifier(BaseCartModifier):
def __init__(self, identifier=None):
super(TaxModifier, self).__init__(identifier)
self.tax_rates = Product.objects.order_by('vat').values('vat').annotate(count=Count('vat'))
def pre_process_cart(self, cart, request):
for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
setattr(cart, tax_attr, Money(0))
def add_extra_cart_item_row(self, cart_item, request):
vat = cart_item.product.vat
tax_attr = '_{0}_vat_{1}'.format(self.identifier, vat)
amount = cart_item.line_total * Decimal(vat) / 100
setattr(cart_item, tax_attr, amount)
def post_process_cart_item(self, cart, cart_item, request):
tax_attr = '_{0}_vat_{1}'.format(self.identifier, cart_item.product.vat)
setattr(cart, tax_attr, getattr(cart, tax_attr) + getattr(cart_item, tax_attr))
def add_extra_cart_row(self, cart, request):
for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
instance = {
'label': "plus {vat}% VAT".format(**rate),
'amount': getattr(cart, tax_attr),
}
cart.extra_rows['{}:vat_{vat}'.format(self.identifier, **rate)] = ExtraCartRow(instance)
def process_cart(self, cart, request):
super(TaxModifier, self).process_cart(cart, request)
for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
cart.total += getattr(cart, tax_attr)
First, in method pre_process_cart
we add additional attributes to the cart object, in order to
have a placeholder where to sum up the taxes for each tax rate.
In method add_extra_cart_item_row
we compute the tax amount for each item individually and store
it as additional attribute in each cart item.
In method post_process_cart_item
we sum up the tax amount over all cart items.
In method add_extra_cart_row
we report the sum of all tax rates individually. They will show up
on the invoice using one line per tax rate.
Finally, in method process_cart
we sum up all tax amounts for all rates and add them to the
cart’s total.