craftcms / commerce

Fully integrated ecommerce for Craft CMS.
https://craftcms.com/commerce
Other
217 stars 170 forks source link

[4.x]: Commerce discounts applying before tax? #3100

Closed robzor closed 1 year ago

robzor commented 1 year ago

What happened?

Description

Hi there,

We have a Commerce store with products that either have 20% tax or 0% tax so we have a VAT tax rule of 20%:

CleanShot 2023-02-28 at 2 57 12

If we create a discount, for example, 30% off:

CleanShot 2023-02-28 at 2 58 00

If a customer adds products to their cart (note: the majority of our products do have VAT tax applied), then the discount gets applied to the cart price before tax is added. So with a £50 (inc. VAT) product, a customer who applies the 30% off voucher expects to get £15 off, but instead only gets £13 off. Here is an example of the cart with both VAT and non-VAT products in:

CleanShot 2023-02-28 at 2 59 57

Is it possible that we can set it that Commerce discounts apply after tax?

All prices on the site are displayed including tax (this is the norm in the UK) so the client (and customers) are struggling to understand why the discount is not 'what is expected'.

Steps to reproduce

  1. Create tax category of 20%
  2. Create discount of 30% off (Per Item Percentage Off)
  3. Add items to basket
  4. Add discount code

Expected behavior

Discount applies after VAT is added

Actual behavior

Discount applies before VAT is added

Craft CMS version

4.3.10

Craft Commerce version

Pro 4.2.5.1

PHP version

No response

Operating system and version

No response

Database type and version

No response

Image driver and version

No response

Installed plugins and versions

-

cherrykoda commented 1 year ago

Surprised there isn't an available option in the discount setup... seems like Line Item + Line Item Tax should be an option here.

image
pdaleramirez commented 1 year ago

@robzor You can re-order the adjustment by doing this. E.g.

        Event::on(
            OrderAdjustments::class,
            OrderAdjustments::EVENT_REGISTER_ORDER_ADJUSTERS,
            function (RegisterComponentTypesEvent $event) {

                $event->types = [Tax::class, Discount::class, Shipping::class];
            }
        );

The screen you’ve shown seems to be not an included tax setup.

Luke: Please note we do not recommend doing this as we expect tax to run last.

robzor commented 1 year ago

@pdaleramirez Hi there, thanks for getting back to me.

I'm not sure what you mean by "not an included tax setup".

Where would I put the code you've provided?

Thanks!

robzor commented 1 year ago

Hi @pdaleramirez , I've worked out how to make a custom module and used your code snippet but while this has reordered the tax to come before the discount, the discount total is still being calculated from the original price. 

This is actually worse than before, as it is saying "you get 30 off the non-VAT price, but you still have to pay the original VAT amount":

CleanShot 2023-03-02 at 2 20 54

What we need is for the discount amount to be calculated including tax. So in this screenshot, on the second item, the discount total would be £43.20 (30% off (£120 + £24)).

Is there a way to tell the discount adjuster to use that line item + tax value?

lukeholder commented 1 year ago

@robzor I am seeing the issue with the opening post.

30% off 120 is 84, and 20% tax on 84 is 16.80?

lukeholder commented 1 year ago

If you say:

What we need is for the discount amount to be calculated including tax.

Then you need the tax amount included in the price you enter for the product:

CleanShot 2023-03-03 at 12 24 49@2x

robzor commented 1 year ago

Hi @lukeholder thanks for getting back to me.

Unfortunately we have over 4000 variants on the site including bulk discounted prices so there is no easy way to change them all at this point.

It's a shame that while the tax adjuster takes the running subtotal, e.g. after discount it applies to the new subtotal but the discount adjuster doesnt.

As shown above you can see that when I reordered the adjusters as per @pdaleramirez 's code snippet, the discount adjuster still used the original item price.

I'm really not sure how to proceed here, have you got any other suggestions?

robzor commented 1 year ago

To add to this, I think what would be the ideal scenario is if the 'Taxable Subject' rule in the Tax rule setup has another option, "Line Item Price" as well as "Line Item Price (Minus Discounts)".

CleanShot 2023-03-03 at 11 51 37

Can you think of a way I could 'hack' this in?

pdaleramirez commented 1 year ago

@robzor To update all variants with the new taxCategoryId, you can create a new Content Migration. In your content migration script up can follow this implementation

        $oldTaxCategoryId = 1;
        $newTaxCategoryId = 2;
        $this->update(\craft\commerce\db\Table::PRODUCTS, [
            'taxCategoryId' => $newTaxCategoryId
        ], ['taxCategoryId' => $oldTaxCategoryId]);

I would suggest trying using the included tax.

robzor commented 1 year ago

Hi @pdaleramirez , the issue isn't updating the category but the actual variant price data itself. As I mentioned previously, there are over 4000 purchasables on the site and at least 50% of them have further volume discount pricing data using the bulk pricing plugin.

We also are dealing with the fact that, in the UK, fruit trees (the site sells trees) are tax free, but everything else has tax applied to it.

I'm really not sure how to proceed here, any other suggestions would be massively appreciated.

robzor commented 1 year ago

Hi there, we've had a meeting with the client and I wanted to give a clear update on this issue.

Our client sells trees and is based in the UK. They have a combination of retail and trade custom. They store all their price data without tax because of various reasons including the fact that their stock management system stores all prices ex-VAT and some of their products (fruit trees) are not taxed.

Because of these reasons, we cannot use the option 'Included in price' in our Tax Setup.

As for the order of adjustments, MattWilcox put it best on the craft Discord:

In the UK, we don't do any crazy maths stuff with sales taxes. The price you see is the price you pay. If there's a 20% off voucher, that's 20% off the complete price including taxes. As a consumer, you never have to think about "taxes" of any sort.

I am aware that this is different in America and other countries in the world but I do not think our case is completely unique. I am also curious as to what would happen if, for instance, VAT in the UK changed (It has in the last 15 years). If we used 'included in price' we would then be presented with having to update 4500 prices rather than a single input field in the Tax settings in Commerce.

My understanding, and please do correct me if I'm wrong because I am no high level coder, is that the adjustments are run in this order (I shall leave out shipping as this doesn't apply to our situation):

  1. Discount
  2. Tax

On a line item of £100, with 10% off, you get a value of £90. The Tax adjuster then takes that 'sub-total' and applies the tax to that £90, in this situation giving you a line item total of £118 (20% of £90 = £18)

With the previous suggestion from @pdaleramirez , what currently happens if you reorder the Adjusters to:

  1. Tax
  2. Discount

On an order of £100, with 10% off. The tax adjuster takes that and applies 20% Tax (£20) and the order sub-total is now £120. Then the discount adjuster runs and takes the original line item price instead of the running subtotal and takes off £10 to give you a total of £110.

A UK customer would assume that an order of £120 (because they always assume tax is included) with 10% off would give you a total of £108.

Sorry for any repetition in this message, I just want to make sure I get the problem described in a single message rather than the thread.

My proposed solution would be that the Discount adjuster had the ability to take the 'running total' from the line item rather than taking the line item base price, and then @pdaleramirez 's reordering snippet would work.

As previously stated, any suggestions on this would be massively appreciated.

pdaleramirez commented 1 year ago

@robzor If the existing included price calculation does not work for you. It would be good to create your discount adjuster. You can choose not to use the existing discount feature. In your adjust function, you must get all available tax adjustments and add the amount to the line items’ base price. Check out the implementation here: https://github.com/craftcms/commerce/blob/c9cdda166dcbaecaaee6e0ac13e9a0aa7f0f8b6c/src/adjusters/Discount.php#L142

This is a tricky solution, but I hope you can achieve it.