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.