Commit 0b703a574f5 for woocommerce
commit 0b703a574f5801dd93b9360fdd9af9ce41555353
Author: SH Sajal Chowdhury <72102985+shsajalchowdhury@users.noreply.github.com>
Date: Fri May 1 20:55:55 2026 +0600
Fix VAT exemption not applying to variable product prices (since 10.4.0) (#64406)
* Fix VAT exemption not applying to variable product prices since 10.4.0
The taxes_influence_price() method, introduced in 10.4.0, returns false
when the customer is VAT exempt. This causes the opposite_price_hash
optimization in read_price_data() to be used instead of direct cache
key differentiation.
The problem: when taxes_influence_price() returns false for VAT exempt
customers, the cached prices from a non-exempt page load are served
to exempt customers because the opposite hash check logic doesn't
properly regenerate the exempt-specific cache entry.
Even though the customer is VAT exempt, taxes DO influence the price
(the displayed price changes when tax is removed). The method should
return true so that separate cache entries are maintained for exempt
and non-exempt customers via the price_hash (which already includes
the is_vat_exempt status).
Remove the VAT exempt check from taxes_influence_price() and update
the test data provider accordingly. Add a dedicated test verifying
that taxes_influence_price() returns true for both exempt and
non-exempt customers.
Closes #63716
* Add docstring to test helper for docstring coverage threshold
Add PHPDoc block to get_data_store_with_public_taxes_influence_price()
to meet the 80% docstring coverage threshold.
* Reset VAT-exempt state in test to prevent leaking into subsequent tests
CodeRabbit flagged that test_taxes_influence_price_returns_true_for_vat_exempt
leaves is_vat_exempt(true) set, which could cause order-dependent flakiness
in downstream tests.
* Add VAT-exempt state reset to test_read_prices_cache_when_taxes_dont_influence_price
Addresses reviewer observation: the existing test was missing the
set_is_vat_exempt(false) restoration that the new VAT-exempt test
already includes. While currently harmless (all @testWith rows pass
false), adding the reset prevents future data-provider additions
from accidentally polluting later tests.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index 0bf991771b1..6a40709a906 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -574,9 +574,12 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
return false;
}
- if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) {
- return false;
- }
+ // Taxes influence the price regardless of VAT exempt status. Even when a
+ // customer is VAT exempt, the displayed prices differ from non-exempt
+ // prices, so they need separate cache entries and the opposite_price_hash
+ // optimization should not apply. Returning false here was causing cached
+ // non-exempt prices to be served to VAT exempt customers.
+ // See: https://github.com/woocommerce/woocommerce/issues/63716
return true;
}
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
index b397c6af2c7..33ce8ff95ee 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
@@ -468,7 +468,6 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
* @testWith [false, true, true, false]
* [false, false, true, false]
* [true, true, false, false]
- * [true, true, true, true]
*
* @param bool $tax_enabled Taxes enabled shop-wide or not.
* @param bool $taxable_product Product is taxable or not.
@@ -504,11 +503,43 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
sort( $actual_hashes );
$this->assertEquals( $expected_hashes, $actual_hashes );
+ // Restore default state to avoid leaking into subsequent tests.
+ WC()->customer->set_is_vat_exempt( false );
+
remove_filter( 'wc_tax_enabled', $tax_enabled ? '__return_true' : '__return_false' );
remove_filter( 'woocommerce_product_is_taxable', $taxable_product ? '__return_true' : '__return_false' );
remove_filter( 'woocommerce_matched_rates', $tax_has_rates ? array( $this, '__return_rates' ) : '__return_empty_array' );
}
+ /**
+ * @testdox taxes_influence_price returns true even when customer is VAT exempt, because exempt prices differ from non-exempt.
+ */
+ public function test_taxes_influence_price_returns_true_for_vat_exempt() {
+ add_filter( 'wc_tax_enabled', '__return_true' );
+ add_filter( 'woocommerce_product_is_taxable', '__return_true' );
+ add_filter( 'woocommerce_matched_rates', array( $this, '__return_rates' ) );
+
+ $product = WC_Helper_Product::create_variation_product();
+
+ $extended_data_store = $this->get_data_store_with_public_taxes_influence_price();
+
+ // Non-exempt: taxes should influence price.
+ WC()->customer->set_is_vat_exempt( false );
+ $this->assertTrue( $extended_data_store->taxes_influence_price( $product ) );
+
+ // VAT exempt: taxes should STILL influence price because the displayed
+ // prices are different (tax removed), requiring separate cache entries.
+ WC()->customer->set_is_vat_exempt( true );
+ $this->assertTrue( $extended_data_store->taxes_influence_price( $product ) );
+
+ // Restore default state to avoid leaking into other tests.
+ WC()->customer->set_is_vat_exempt( false );
+
+ remove_filter( 'wc_tax_enabled', '__return_true' );
+ remove_filter( 'woocommerce_product_is_taxable', '__return_true' );
+ remove_filter( 'woocommerce_matched_rates', array( $this, '__return_rates' ) );
+ }
+
/**
* @testdox read_prices does separate caching for prices for display and not for display when they are different.
*
@@ -604,6 +635,21 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
// phpcs:enable Generic.CodeAnalysis, Squiz.Commenting
}
+ /**
+ * Get a data store instance with taxes_influence_price() exposed as public.
+ *
+ * @return object Data store with public taxes_influence_price method.
+ */
+ private function get_data_store_with_public_taxes_influence_price(): object {
+ // phpcs:disable Generic.CodeAnalysis, Squiz.Commenting
+ return new class() extends WC_Product_Variable_Data_Store_CPT {
+ public function taxes_influence_price( $product ): bool {
+ return parent::taxes_influence_price( $product );
+ }
+ };
+ // phpcs:enable Generic.CodeAnalysis, Squiz.Commenting
+ }
+
/**
* Parse a variable product prices transient and return the hashes only.
*