Problem

Taxes are being calculated on orders BEFORE order discounts have been applied. This is not the standard way the tax calculation is done, and it some countries could potentially be illegal. By default, taxes should be calculated AFTER order discounts have been applied, with the option of not doing this, rather than just having no tax option whatsoever.

Temporary Solution

I developed a custom module to fix it.
The module use hook_commerce_order_presave and update the order ($order) with good VAT amount and good total order amount.

This module is pretty simple but has to be customized, eventually, to works with your site configuration....
I'm using Commerce_tax 7.x-1.10.

If you don't know how to make a custom module you can read this post.

Post in French / Article en français : coming soon / bientôt !

We call this module : Custom Discounts VAT

custom_discounts_VAT.info

name = Discounts and VAT
description = Make discounts calculated before VAT
package = Other
core = 7.x
version = 7.x-1.0
dependencies[] = commerce
dependencies[] = commerce_tax
dependencies[] = commerce_discount

custom_discounts_VAT.module

In this file you have to modify the string tva_20, line 16, by the id of your VAT. You can find it in the taxes settings, this is the system name.
If you're using a multilingual website you probably have to modify this key : 'und' by another like LANGUAGE_SOMETHING... I don't know exactly !

function custom_discounts_VAT_commerce_order_presave($order){
		
	//si la commande a des remises
	if(isset($order->commerce_discounts)){

        $pdiscount = 0;
				
		//on parcourt les composants de prix à la recherche de ces remises
		foreach($order->commerce_order_total['und'][0]['data']['components'] as $key=>$valeur){
			
			
			// on en profite pour récup le montant du base_price ... :)
			if($valeur['name'] == 'base_price'){
				$BP = $valeur['price']['amount'];
			
			// ainsi que la clé du composant TVA ainsi que son taux et son montant
			}elseif($valeur['name'] == 'tax|tva_20'){
				$keyTVA = $key;
				$tauxTVA = $valeur['price']['data']['tax_rate']['rate'];
				$tva = $valeur['price']['amount'];
			}
			
			//si le composant est une remise
			if(strpos($valeur['name'], 'discount|') !== false){
				
				//on garde et cumule (si autres remises) le montant
				$pdiscount = $pdiscount + abs($valeur['price']['amount']);
			}
		}
		
		//si on a bien trouvé nos remises dans les composants
		if($pdiscount){
			//on calcule la tva sans remise(s)
			$tvasansremise = $BP*$tauxTVA;
			
			//on calcule la TVA de(s) remise(s)
			$tvaremise = $pdiscount*$tauxTVA;
			
			//pour pouvoir calculer la TVA avec remise
			$goodtva = $tvasansremise - $tvaremise;
			
			//Si la TVA n'est effectivement pas bonne
			if($tva != $goodtva){
								
				//on met à jour la TVA
				$order->commerce_order_total['und'][0]['data']['components'][$keyTVA]['price']['amount'] = $goodtva;
				
				//et on met à jour la montant total
				$order->commerce_order_total['und'][0]['amount'] = $order->commerce_order_total['und'][0]['amount'] - $tvaremise;
			}
		}
		//on retourne notre commande modifiée !
		return $order;
		
	}
}

This code probably should be optimized but, for the moment, it works ! And sorry, the comments are in french but I haven't time to translate it.

If you have already discounts apply to orders, after enabling the module, you will have to remove discounts from order and apply pricing rules to update VAT and Order total !

I hope this helps you !

Best regards.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Mantalo Conseil’s picture

Issue summary: View changes
Mantalo Conseil’s picture

Issue summary: View changes
Mantalo Conseil’s picture

Issue summary: View changes
Mantalo Conseil’s picture

Issue summary: View changes
danny1997’s picture

Thanks for your solution. I just installed it and got an error message:

Notice : Undefined variable: pdiscount dans custom_discounts_VAT_commerce_order_presave()

I'm using Commerce_tax 7.x-1.10 and Commerce 7.x-1.10.

Mantalo Conseil’s picture

Issue summary: View changes
Mantalo Conseil’s picture

Hello,

This is just a "notice", the script still works ....
I update the script to remove this notice.

cferthorney’s picture

Worked for me Mantalo - thanks.

tadesign’s picture

Hi Mantalo,

Thanks for the module, It works great except the discount amount being removed appears to be wrong, my cart shows:

Subtotal exc VAT: £20.85
VAT: £3.42
Discount 15%: -£3.75
Order Total: £20.52

The above would be correct but 15% off 20.85 is not £3.75 it is £3.13 so the checkout should read:

Subtotal exc VAT: £20.85
VAT: £3.54
Discount 15%: -£3.13
Order Total: £21.26

Have I missed something or is there something I need to adjust, or do you think the error is caused by the discounts?
Thanks again for the work so far

tadesign’s picture

Hi,

Looks like it's taking 18% off rather than 15% in the above post so I'm not sure what's going on here.

I also tried a 20% discount and instead of taking 20% off it is taking 24% off:

Shows:
Total inc VAT £20.99
Subtotal exc VAT: £17.49
VAT: £2.66
Discount 20%: -£4.20
Order Total: £15.95

should be:
Total inc VAT £20.99
Subtotal exc VAT: £17.49
VAT: £2.80
Discount 20%: -£3.50
Order Total: £16.79

Hope you can help.

Mantalo Conseil’s picture

Hello tadesign,

It seems that your discount is applied on your total (Total inc VAT)...
Are you sure the module is enabled ?
Do you have price with or without VAT in your database ,

tadesign’s picture

Hi Mantalo,

The module was definitely enabled, the prices for each product have been imported excluding VAT with the product display showing the calculated price including VAT and the cart adding VAT.

Cheers

mandus.cz’s picture

I have 7.x-1.11 - probably not working :-(

maxplus’s picture

Hi,

My VAT is also calculated on the original price without the order discount applied...

(Using Drupal core 7.34, Commerce 7.x-1.11, Commerce Discount 7.x-1.x-dev (2015-May-21), Inline Conditions 7.x-1.0-alpha5)

My example:

* product price: 99 euro including VAT
* order discount: 20 euro
* VAT: 17,18 euro (wrong, is VAT amount on original product price)
* order total: 79 euro (is correct)

I have both tested products that have prices including VAT and products that have prices excluding VAT
=> same results

I'm sure the module is enabled, because I get this error during my checkout process, when displaying the cart summary:

Warning: call_user_func() expects parameter 1 to be a valid callback, function 'rules_events_entity_unchanged' not found or invalid function name in RulesState->get() (regel 148 van /var/www/vhosts/tvee.be/httpdocs/sites/all/modules/rules/includes/rules.state.inc).
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'rules_events_entity_unchanged' not found or invalid function name in RulesState->get() (regel 148 van /var/www/vhosts/tvee.be/httpdocs/sites/all/modules/rules/includes/rules.state.inc).

It would be great to have a working solution with commerce_tax module: if not really necessary, I don't want to use an extra module for VAT like commerce_vat

mandus.cz’s picture

It works on "Product discount", but stop calculating shipping VAT and fee VAT.

(Using Drupal core 7.38, Commerce 7.x-1.11, Commerce Discount 7.x-1.0-alpha5, Inline Conditions 7.x-1.0-alpha5)

joelpittet’s picture

presave is an interesting approach to re-apply vat but it may be better to call the apply/calculate tax rules on the line items again instead of doing them manually...

Just a thought.

IckZ’s picture

Hey! Thanks for your input! This code snippet was the only chance for me to get this working after I've tried several patches from the other issues.

I've modified the code a little bit to get more accuracy during the tax calulation. During my testings I did not noticed any variance to the "right" tax ammount. So imho the calulations are ok. However this should be reviewed. I've also removed all french comments and renamed almost every variable for getting an international standard. edit:// I also added an condition that shipping costs are added if they are present.

  • I use this custom module with a vanilla install of the latest dev verison of commerce_discount.
  • My Products are all imported with the price including vat
  • I only use one disount to offer the customers 10% (order based) by entering a coupon code


Notices:

  • If you have also a custom discount (outside from the module) which is added by rules (e.g. commerce discount vat) you should install https://www.drupal.org/project/commerce_discount_fields and add a condition that a use of both discount types is not possible at the same time. Otherwise you have to check the code and calculate both discount types in an other way or you will get wrong calculations.
  • I did not test other discounts (e.g. fixed) or the usage of multiple discount types in one order. Test this!
  • In the foreach I'm looking for a component which name contains "shipping". Should be working for most use cases but check twice if your shipping costs are added correctly.
  • For me this is a little wokrarround/ temporary solution till one of the other (one of the rules based -> look into the other issues) solution is stable enough.
function custom_discounts_VAT_commerce_order_presave($order){
    if(isset($order->commerce_discounts)){
        $pdiscount = 0;
		$shipping_costs = 0;
	
        foreach($order->commerce_order_total['und'][0]['data']['components'] as $key=>$value){
			if($value['name'] == 'base_price'){
					$netto_price = $value['price']['amount'];
			} elseif(substr($value['name'], 0, 3) == 'tax') {
					$taxkey = $key;
					$taxrate = $value['price']['data']['tax_rate']['rate'];
					$tax_amount = $value['price']['amount'];
			}
			if(strpos($value['name'], 'discount|') !== false){
                $pdiscount = $pdiscount + abs($value['price']['amount']);
            }			
			if(strpos($value['name'], 'shipping') !== false){
                $shipping_costs = $value['price']['amount'];
            }
        }
		
        if($pdiscount > 0 && isset($taxkey) && isset($taxrate)){
			$net_discount = $pdiscount/(($taxrate*100)+100)*100;
			$net_amount = $netto_price - $net_discount;
			$tax_amount = $net_amount*$taxrate;
			$new_amount = ($net_amount + $tax_amount);
	

			//dpr($net_discount); 	//1
			//dpr($net_amount);		//2
			//dpr($tax_amount);		//3
			//dpr($netto_price); 	//4
			//dpr($new_amount);		//5
			
            if($netto_price != $new_amount){
				$order->commerce_order_total['und'][0]['data']['components']['0']['price']['amount'] = $net_amount;
				$order->commerce_order_total['und'][0]['data']['components'][$taxkey]['price']['amount'] = $tax_amount;
				$order->commerce_order_total['und'][0]['amount'] = $new_amount + $shipping_costs;
				//kpr($order);
			}
        }
        return $order;
    }
}

Scott Robertson’s picture

Scott Robertson’s picture

Scott Robertson’s picture

Title: How To use Order Discount with VAT » Order discounts are not taken into account during tax calculation
Issue summary: View changes
Issue tags: -VAT

Going to use this issue as the main for all discussion around taxes and order discounts. I believe I've come up with a solution, which I'll be rolling a patch for shortly.

Scott Robertson’s picture

Version: 7.x-1.0-alpha3 » 7.x-1.x-dev
Assigned: Mantalo Conseil » Scott Robertson
Category: Feature request » Bug report
Scott Robertson’s picture

Status: Active » Needs review
FileSize
6.17 KB

First attempt at a patch to address this. A rule by itself wasn't enough to avoid potential issues when discounts are disabled, so the patch consists of the following:

  1. A change to commerce_discount_commerce_cart_order_refresh() to remove all order discount line items before they're recalculated. Previously, the price component would just be removed from them, causing problems if a tax component was also applied to the line item.
  2. A new rule event that fires when order discount line items are added or updated
  3. A new default rule that fires on the above event, calculating taxes for all fixed amount discount line items
  4. A new test to check for the presence of the tax component on the order discount line item (and order total). It also disables the discounts and makes sure that the line item is completely removed and the correct order total is calculated.


Now to see how many other tests fail...

kfitz’s picture

FileSize
20.46 KB
19.75 KB

Patch addresses and solves the issue. +1 for providing new test with well commented code. Patch tested on fresh install. Screen shots are attached to below to show the checkout calculations post and pre patch.

kfitz’s picture

Status: Needs review » Reviewed & tested by the community
Scott Robertson’s picture

Status: Reviewed & tested by the community » Needs work

After thinking on this more, while I believe this solution DOES work, my change to commerce_discount_commerce_cart_order_refresh() will cause discount line items to be deleted every time commerce_cart_order_refresh() is called. This is going to rapidly increment the line item IDs for no good reason, especially as there's already functions for modifying existing line items.

I'm thinking that commerce_discount_remove_discount_components may need to be modified so that you can optionally also remove tax components or something.

Scott Robertson’s picture

Status: Needs work » Reviewed & tested by the community

Going to set this back to RTBC because I don't believe my patch adds any additional issues relating to line items being deleted and re-added over and over. This will already happen on the current DEV release for any order discounts, as their amounts are always set back to zero in commerce_discount_commerce_cart_order_refresh

joelpittet’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/commerce_discount.module
@@ -28,7 +28,7 @@ function commerce_discount_commerce_cart_order_refresh(EntityDrupalWrapper $orde
-    if ($wrapper_line_item->getBundle() == 'product_discount') {
+    if (in_array($wrapper_line_item->getBundle(), array('product_discount', 'commerce_discount'))) {

This is the only change in the patch. The rest is all additions. I'm wrapping my head around what is changing here.

This change does look right but it's doing this again on lines 56-58 but slightly different with a condition extra && empty($unit_price['amount']). Maybe that could be moved up so they are treated the same but $unit_price['amount'] needs to be sorted out why it's empty check is needed, right?

Also could we post the tests only to prove that they fail without the changes?

Scott Robertson’s picture

Status: Needs work » Needs review
FileSize
2.64 KB

Here's a patch with the tests only.

I think lines 56-58 can be removed entirely as they're now redundant. In the current code, commerce_discount_remove_discount_components get's called on order discounts on lines 42 and 43. This works until there's taxes involved, at which point the tax components will be left on the order discount's prices and you end up with a weird order discount line item that only has a negative tax component on it. The empty check on line 56 will then fail and the line item won't get removed properly.

Status: Needs review » Needs work

The last submitted patch, 28: 2429595-order-discounts-no-taxes-tests-only.patch, failed testing.

tewdin’s picture

I hope Commerce Guys are fixing this soon, but in the mean time you can use this temporary fix. Add it to Commerce Cart Summary's footer and Commerce Emails. This code works with or without tax module(s). If you use it without tax module, remove "* 1.24" because then you already have a right price.

Values are without decimals, so that's why I divide that by 100. Then taxes 24% (1.24). Subtotal before 1.24 is a number without taxes.

<?php
$q = explode('/', $_GET['q']);
$order = commerce_order_load($q[1]);
$subtotal = $order->commerce_order_total['und'][0]['data']['components'][0]['price']['amount'];
$subtotal = ($subtotal / 100) * 1.24;
$discount = $order->commerce_order_total['und'][0]['data']['components'][2]['price']['amount'];
$discount = ($discount / 100) * -1;
$total = $subtotal - $discount;
$without_vat = $total / 1.24;
$vat = $total - $without_vat;
?>

Subtotal <?php echo $subtotal; ?> €.
Discount <?php echo $discount; ?> €.
VAT 24% <?php echo $vat; ?> €
Order total <?php echo $total;?> €

Read more about this here.

Scott Robertson’s picture

Unfortunately my patch causes all kinds of issues when the total of your taxable line items is less than the fixed amount of an order discount.

For example, given an order discount of $10 and an order with the following line items:

  • A taxed product: $5
  • Non-taxed shipping: $5

Assuming we have a tax rate of 10%, WITHOUT the discount total breakdown would look like:

Subtotal: $5.00
Shipping: $5.00
Taxes: $0.50
Total: $10.50

With the $10 discount applied, the expected total breakdown is:

Subtotal: $5.00
Shipping: $5.00
Discount: $-10.00
Taxes: $0.00
Total: $0.00

Unfortunately with the patch, the code will attempt to calculate negative tax on the whole $10 fixed amount, resulting in a negative order total:

Subtotal: $5.00
Shipping: $5.00
Discount: $-10.00
Taxes: -$0.50 (This is the result of subtracting -$1.00 in discount "taxes" from $0.50 in product taxes
Total: -$0.50

Clearly, this is not correct, so I'm still working on a way to account for this scenario.

czigor’s picture

Status: Needs work » Needs review
FileSize
1.35 KB

After many days of trying I think I got this finally working using https://www.drupal.org/project/commerce_vat_proportional. I will update the issue in #2276227: [META] Use order discount with VAT. I use this patch to make vat calculation work for coupons that have larger amount than the order total.

vincentdemers’s picture

I've been able to fix this issue by implementing postprocess hooks in commerce_discount as done here in #24:

Then in my custom module I forced the calculation of tax rates on discount price components:

function my_module_commerce_discount_line_item($line_item_wrapper, $discount_name) {
  $line_item=$line_item_wrapper->value();
  $tax_types=commerce_tax_types();
  foreach($tax_types as $tax_type){
    commerce_tax_type_calculate_rates($tax_type,$line_item);
  }
  commerce_line_item_save($line_item); 
  entity_get_controller('commerce_line_item')->resetCache(array($line_item->line_item_id));
}
Ronino’s picture

Patch #22 doesn't fix the problem for me which might be related to the fact that I use commerce_vat (and commerce_eu_vat) instead of commerce_tax.

czigor’s picture

@Ronino Have you tried the patch and description in #32? There are a couple more patches in the description of #2276227: [META] Use order discount with VAT.

Alex Bukach’s picture

Re-rolled #22 against current dev.

Status: Needs review » Needs work

The last submitted patch, 36: commerce_discount-tax-2429595-36.patch, failed testing. View results

pontus.froden’s picture

Patch in #32 works for me with VAT, but it doesn't work with latest beta2, updated patch attached.

czigor’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 38: commerce_discount-2429595-38-vat.patch, failed testing. View results

pontus.froden’s picture

new attempt, that should work with latest dev.

pontus.froden’s picture

Status: Needs work » Needs review
jsacksick’s picture

Status: Needs review » Needs work

The attached patch will cause the commerce_discount line items to be recreated on each refresh (which is really bad for performance).

@pontus.froding: Could you upgrade to the latest beta5 release and tell me if the issue is still occurring?

pontus.froden’s picture

The development is fast with this one and I just tested with beta5.

Beta 5 together with Commerce VAT and using reverse tax calculation. This issue is gone, the taxes are being calculated correct.

jsacksick’s picture

@pontus.froding: so no need to apply any additional patch? If you confirm, I think we mark this issue as fixed.

pontus.froden’s picture

Yes I deleted everything on this module and updated to beta5 and the tax calculation was correct.

My only issue was that I had to update the page before the discount was applied on the order, but I don't think that's relevant in this issue. It belongs more to the rest of this comment ... https://www.drupal.org/project/commerce_discount/issues/2276227#comment-...

czigor’s picture

czigor’s picture

Update: Our commerce_discount module is still on alpha8 in production. We have (some) tests for tax amounts even with coupons and giftcards and they still pass after upgrading the module to beta5 and using the rerolled patch in #47 in a local site instance. I will be able to tell more once we deploy this.

Alex Bukach’s picture

Re-rolled #22 against latest dev.

Status: Needs review » Needs work

The last submitted patch, 49: commerce_discount-tax-2429595-49.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

shi99’s picture

Patch in #49 worked for me.
Thanks

joelpittet’s picture

Status: Needs work » Needs review

Sorry, rerunning testbot

ahmad09x’s picture

Patch #49 worked for me ONLY in Order Discount case, and not worked on Product Discount.

Patch applied on "7.x-1.0-beta5+3-dev".

Any chance for a rule that calculate tax after Product Discount type ?

ahmad09x’s picture

@shi99 Does it work on Product Discount type?

traudong’s picture

Patch #49 works for me, on both Order discount & Product discount

CHill_Samurai’s picture

I've updated our Commerce Discount to 7.x-1.0-beta5 and applied the patch in #49, but we've got a complicated scenario where not all of our products are taxable, and while the patch seems to work in many cases, it fails when an order-wide discount with mixed taxable and exempt product is applied. The complications of the Canadian sometimes 2, others harmonized, federal/provincial tax systems don't help, I'm sure. Case 4 kinda works, Case 6 appears to fail.

Case 1: Just taxable product with a % off Product-Discount
Pass = Tax calculates on NET, which is now shown in the line-item price, as well, instead of Gross price.

Case 2: Taxable and Exempt product with a % off Product-Discount
Pass = Tax again calculates on NET for just taxable product, and is the same as in Case 1, again displaying discounted price in line-item.

Case 3: Just Taxable with a Fixed Amount Discount on Order
Pass = calculates on NET, and although the full price is listed in line item, it's taken full price minus fixed discount to apply tax.

Case 4: Taxable and Exempt with a Fixed Amount Discount on Order
??? = It applies the discount in its entirety to just the taxable line item to calculate taxes, so that the tax is the same as in Case 3. Great for consumers, but not sure that the taxman will like it - to look into...

Case 5: Just Taxable with a per cent Discount on Order
Pass = Calculates tax correctly on NET, discounted subtotal

Case 6: Taxable and Exempt with Order-wide %age discount:
Fail = Not sure WHAT is going on in this tax calculation, as I've tried a few iterations of weightings and ratios to try and arrive at the numbers the patch does. Line item prices are not given as discounted, although I've added them here for clarity:

Product Price Quantity Total Discounted
Taxable $3.75 10 $37.50 $30.00 % of order 10.42%
Exempt $64.50 5 $322.50 $258.00

Subtotal $360.00
Ontario Shipping HST 13% $1.45
20 Percent Off -$72.00 Full Tax Discounted tax
Ontario HST 13% -$4.49 -$4.88 -$3.90
Shipping: Canada Post Regular Parcel $11.15
Order total $296.11

Product Price Quantity Total Discounted
Taxable $3.75 10 $37.50 $30.00
Exepmt $64.50 5 $322.50 $258.00
Taxable2 $4.50 10 $45.00 $36.00
Exempt2 $28.75 5 $143.75 $115.00
$548.75
Subtotal $548.75
Ontario Shipping HST 13% $1.63 Discount Tax
20 Percent Off -$109.75 Full Tax -$8.58
Ontario HST 13% -$3.54 -$10.73
Shipping: Canada Post Xpresspost $12.51
Order total $449.59

Any guidance here? I'm reticent to roll out a beta patch into our production environment, but this IS a big problem affecting our business, as without a remedy, overall prices appear higher even with discounts due to default tax on Gross behaviour.

dtamajon’s picture

It didn't work for me, applying the patch #49 on the last dev version. The "Calculate taxes on order discounts" rule looks like is never invoked (no trace on log). I have cleaned and rebuild rules and cache, but not working.

My conclusion is event "Adding or updating an order discount line item" is never raised. Am I missing anything in the configuration?

danharper’s picture

I have updated to latest dev and patched with 49 and although the VAT looks correct the discount is calculated based on the order total after the VAT has been applied and I can't workout from this thread how to resolve that.

Do I need to use https://www.drupal.org/project/commerce_vat_proportional ?

Dadaisme’s picture

Applying the patch #49 on the last dev version (beta5) didn't work for me.

I used the code (customized for my region) on top of this issue to resolve the problem.

Tx.

apaderno’s picture

Issue tags: -tax, -discount, -pre-tax
aurelianzaha’s picture

Patch #49 works for me as well

aurelianzaha’s picture

Status: Needs review » Reviewed & tested by the community