That Software Guy! eCommerce Starts Here!

Notes on Taxes in Zen Cart


Special Tax Situations

If you don't use embedded taxes, and don't have a mix of taxable and tax-free products, and don't have a different rate of tax for shipping, please skip this section.

osCommerce users: although this text was written for Zen Cart, the same issues apply to osCommerce 2.x. An osCommerce page for this issue is provided in osCommerce tax issues.

Mixing taxable and non-taxable products

The way taxes are handled internally in Zen Cart 1.x is that the entire tax amount is stored in the cart; the tax is not broken down on an item by item basis. For this reason, tax recalculation after discounting may not work perfectly if items which attract different levels of tax are in the cart - for example, taxable and non-taxable items. This issue is discussed in this forum post, and we are hopeful that Zen Cart 2.x will solve this problem.

Included Taxes and My Mods

This section is only relevant to Zen Carts which use "Display Prices with Tax" = true (in Admin->Configuration->My Store).

If your shop displays prices with tax included, you should configure my discounting mods to use Include Tax = true and set Re-Calculate tax to either "Standard" or "VAT" (depending on how taxes are handled in your jurisdiction).

There was a time when my software would gross up percentage discounts for tax but not currency based discounts. I decided to change this and handle both uniformly, so now (post April 2010) if you configure one of my discount mods to use include taxes = true, your discount will be grossed up whether it is a currency value or a percentage.

Tax Descriptions

Due to the way gross-up and gross-down calculations are done for shops using embedded taxes, your tax descriptions (as shown in Admin->Locations/Taxes->Tax Rates) must be unique. The customer using the setup shown below had problems because the recalculation logic couldn't tell which version of Varav moms (Swedish for "Including VAT") should be used.
Tax Rates in Zen Admin

Alternatively you could use the SQL query:
mysql> select tax_rates_id, tax_rate, tax_description from tax_rates;
This example would have a problem, since rates 4 and 5 have the same description.
+--------------+----------+-----------------+
| tax_rates_id | tax_rate | tax_description |
+--------------+----------+-----------------+
|            2 |     0.00 | View            |
|            7 |    45.00 | M├ędia           |
|            4 |    30.00 | Baixa           |
|            5 |    35.00 | Baixa           |

Once you've fixed things, you can check your work with
mysql> select count(*) from tax_rates;
then
mysql> select count(*) from (select distinct tax_description from tax_rates) as T;
These numbers should be the same.

Different rates of tax for shipping

If you have taxable shipping but the tax rate is different than it is for products, you'll need to make some code changes. Firstly, be sure your descriptions for shipping taxes include an easily identifiable string such as "on shipping"
Tax Rates in Zen Admin

Then, in the discount module you use, skip over this tax when computing the updated amount of tax.

Here is a block of logic from Quantity Discounts 1.11. (Newer versions may look different.)
       switch ($this->calculate_tax) {
       case 'Standard':
          reset($order->info['tax_groups']);
          while (list($key, $value) = each($order->info['tax_groups']))
          {

             if (strpos($key,"on shipping") !== false) {
                continue;
             }

             $tax_rate = zen_get_tax_rate_from_desc($key);
             if ($tax_rate > 0) {
                $od_amount[$key] = $tod_amount = round((($od_amount['total'] * $tax_rate)) /100, 2) ;
                $od_amount['tax'] += $tod_amount;
             }
          }
          break;

Here is a block of logic from Big Chooser 1.2.16. (Newer versions may look different.)
       switch ($this->calculate_tax) {
       case 'Standard':
          reset($order->info['tax_groups']);
          $taxGroups = array_keys($order->info['tax_groups']);
          foreach ($taxGroups as $key )
          {

             if (strpos($key,"on shipping") !== false) {
                continue;
             }

             $tax_rate = zen_get_tax_rate_from_desc($key);
             if ($tax_rate > 0) {
                $od_amount[$key] = $tod_amount = round((($taxable_amount  * $tax_rate)) /100, 2) ;
                $od_amount['tax'] += $tod_amount;
             }
          }
          break;

Example: Better Together Tax Handling

Normal Tax Handling

The most common way of configuring Better Together (under Admin->Modules->Order Total->Better Together) with Include Tax = False and Recalculate Tax = Standard.

For the next group of examples, we will consider a discount where you buy one item for $35.99 and get a $39.99 item for free.

The discount is coded as
$this->add_prod_to_prod(8, 12, "%", 100);
where product 8 is $35.99 and product 12 is $39.99.

With the default settings (Include Tax = False and Recalculate Tax = Standard), and a 17.5% tax rate, these are the results:
Sub-Total:  $75.98
Store Pickup (Walk In): $0.00
Better Together Discount: -$39.99
Tax (7%):  $2.52
Total: $38.51


The tax at 7% is calculated against the subtotal less the discount (($75.98 - $39.99) * 0.07 = $2.52), and the total is $75.98 - $39.99 + $2.52 = $38.51.

Another common settings is Include Tax = False and Recalculate Tax = None. This means the customer is responsible for the taxes for the discounted product. In this case, the numbers would be

Sub-Total:  $75.98
Store Pickup (Walk In): $0.00
Better Together Discount: -$39.99
Tax (7%):  $5.32
Total: $41.31


The taxes in this case are 7% of the subtotal ($75.98 * 0.07 = $5.32) rather than the subtotal less the discount.

A less common settings is "Include Tax = True." This really means "Gross up the discount for taxes." This makes more sense in an environment where taxes are included in the prices (Admin->Configuration->Display Prices with Tax = true). If the tax rate is 17.5%, and the Better Together tax settings are Include Tax = True and Recalculate Tax = None, the numbers above become

Sub-Total:  $89.28
Store Pickup (Walk In): $0.00
Better Together Discount: -$46.99
Tax (17.5%):  $13.30
Total: $42.29


The two items are both $35.99 and $39.99, so their total price is $75.98. Grossed up to include taxes, this is $75.98 * 1.175 = $89.28. The Better Together item is $39.99, grossed up for taxes is $39.99 * 1.175 = $46.99. The total is $89.28 - $46.99 = $42.29. The tax is calculated on the subtotal, so $89.28 - $89.28/1.175 = $13.30.

Taxes in the United Kingdom

Tax behavior in the UK is so different that it could not easily be accommodated without breaking other things, so I added a flag to the file which triggers this behavior.

In the UK version of Better Together, at the top of the file includes/modules/order_total/ot_better_together.php there is a setting called BT_TAX_MODEL_UK_VAT. When it is set to "1" here are the results you'll get for various discounts.

Note: In this mode, the settings in Admin->Modules->Order Total->Better Together are ignored. Instead, recalculate tax is always standard, and include tax depends on whether the discount is currency or percentage. A percentage discount is always grossed up for taxes; a currency discount is not.

It is assumed that shopowners who use this setting will have Admin->Configuration->My Store->Display Prices with Tax set to true.

Let's use the product example from the last example above. Remember, in Better Together UK, Recalculate Tax is Standard, and include tax is Yes for percentage discount (and this is a % discount).

Sub-Total:  $89.28
Store Pickup (Walk In): $0.00
Better Together Discount: -$46.99
Tax (17.5%):  $6.30
Total: $42.29


All the numbers are the same as the previous example except the tax component - which is expected. The tax component is calculated as $42.29 - $42.29/1.175 = $6.30.

Now let's look at some more complicated discounts. The semantics of "currency off" discounts are different in Better Together UK - they are NOT grossed up for taxes (whereas % discounts are).

The next example discount involves two products, 3 and 25, where product 3 is £188.32 and product 25 is £74.24. This is a total of £262.56, which grossed up for taxes is £308.51.

If you have a Better Together discount that yields £5 off,
$this->add_prod_to_prod(3, 25, "$", 5);
your numbers would look like this:
Sub-Total:  £308.51
Store Pickup (Walk In): £0.00
Better Together Discount: -£5.00
Includes VAT component 17.5%: £45.21
Total: £303.51


The discount is deducted off the subtotal without being grossed up for VAT. The tax component is recomputed against the new subtotal (£303.51 - £303.51/1.175 = £45.21).

If you have a Better Together discount that yields 10% off the second item, and the items are £221.28 and £87.23 (VAT inc), your numbers would look like this:
Sub-Total:  £308.51
Store Pickup (Walk In): £0.00
Better Together Discount: -£8.72
Includes VAT component 17.5%: £44.65
Total: £299.79


The discount is taken off the second item's VAT inc price, (10% of £87.23), and this amount is deducted from the Sub-Total giving the new total (£308.51 - £8.72 = £299.79). The tax component is recomputed against the new subtotal (£299.79 - £299.79/1.175 = £44.65).