Commit 0eab1bff0a6 for woocommerce
commit 0eab1bff0a60d4cb5e188772498043530421ff93
Author: Néstor Soriano <konamiman@konamiman.com>
Date: Tue Jun 16 17:24:42 2026 +0200
Add the woocommerce_variable_product_taxes_influence_price filter (#65394)
Add woocommerce_variable_product_taxes_influence_price filter
Lets extensions override whether displayed variable-product prices need
separate cache entries for tax-inclusive and tax-exclusive lookups in
WC_Product_Variable_Data_Store_CPT::read_price_data().
By default the method returns true only when the product is taxable
and tax rates are configured for the customer's location; otherwise
it returns false and the opposite-hash optimization stores the same
computed prices under both direction hashes in one pass. That's the
right default for stock WooCommerce, but it breaks setups where
another extension produces direction-dependent prices on top - for
example, dynamic page caching keyed by country cookies for locations
without configured tax rates. The previous workaround was to register
a 0% rate for those locations just to keep the cache from collapsing.
diff --git a/plugins/woocommerce/changelog/add-filter-for-variable-product-taxes-influence-price b/plugins/woocommerce/changelog/add-filter-for-variable-product-taxes-influence-price
new file mode 100644
index 00000000000..49a7aade1bd
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-filter-for-variable-product-taxes-influence-price
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add the `woocommerce_variable_product_taxes_influence_price` filter.
\ No newline at end of file
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 bd271a223b4..780790a085d 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
@@ -569,21 +569,33 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
*/
protected function taxes_influence_price( $product ): bool {
if ( ! $product->is_taxable() ) {
- return false;
- }
-
- if ( empty( WC_Tax::get_rates( $product->get_tax_class() ) ) ) {
- return false;
+ $taxes_influence_price = false;
+ } elseif ( empty( WC_Tax::get_rates( $product->get_tax_class() ) ) ) {
+ $taxes_influence_price = false;
+ } else {
+ // 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.
+ $taxes_influence_price = true;
}
- // 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;
+ /**
+ * Filters whether taxes influence the displayed price of a variable product.
+ *
+ * Return `true` from this filter to force separate cache entries for the two
+ * variants. This is needed when an extension produces displayed prices that
+ * differ from raw prices independently of the standard tax calculation, for
+ * example when computing per-country prices for locations that have no
+ * configured tax rates.
+ *
+ * @param bool $taxes_influence_price Default decision based on product taxability and configured tax rates.
+ * @param WC_Product $product The variable product being evaluated.
+ *
+ * @since 10.9.0
+ */
+ return (bool) apply_filters( 'woocommerce_variable_product_taxes_influence_price', $taxes_influence_price, $product );
}
/**
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 12eff53c3f4..ca578e846ac 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
@@ -540,6 +540,49 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
remove_filter( 'woocommerce_matched_rates', array( $this, '__return_rates' ) );
}
+ /**
+ * @testdox The woocommerce_variable_product_taxes_influence_price filter can override the default decision.
+ *
+ * @testWith [true, true, true, true, true]
+ * [true, true, true, false, false]
+ * [false, true, false, true, true]
+ * [true, false, false, true, true]
+ * [false, false, false, true, true]
+ * [true, false, false, false, false]
+ *
+ * @param bool $taxable_product Product is taxable or not.
+ * @param bool $tax_has_rates Product tax has defined rates or not.
+ * @param bool $expected_default Value the filter callback should receive as the default decision.
+ * @param bool $filter_returns Value the filter callback will return.
+ * @param bool $expected Expected return value from taxes_influence_price.
+ */
+ public function test_taxes_influence_price_filter_overrides_default( bool $taxable_product, bool $tax_has_rates, bool $expected_default, bool $filter_returns, bool $expected ) {
+ add_filter( 'wc_tax_enabled', '__return_true' );
+ add_filter( 'woocommerce_product_is_taxable', $taxable_product ? '__return_true' : '__return_false' );
+ add_filter( 'woocommerce_matched_rates', $tax_has_rates ? array( $this, '__return_rates' ) : '__return_empty_array' );
+
+ $received_default = null;
+ $received_product = null;
+ $filter_callback = function ( $default_value, $product ) use ( $filter_returns, &$received_default, &$received_product ) {
+ $received_default = $default_value;
+ $received_product = $product;
+ return $filter_returns;
+ };
+ add_filter( 'woocommerce_variable_product_taxes_influence_price', $filter_callback, 10, 2 );
+
+ $product = WC_Helper_Product::create_variation_product();
+ $extended_data_store = $this->get_data_store_with_public_taxes_influence_price();
+
+ $this->assertSame( $expected, $extended_data_store->taxes_influence_price( $product ) );
+ $this->assertSame( $expected_default, $received_default, 'Filter callback should receive the default decision.' );
+ $this->assertSame( $product->get_id(), $received_product->get_id(), 'Filter callback should receive the product being evaluated.' );
+
+ remove_filter( 'woocommerce_variable_product_taxes_influence_price', $filter_callback, 10 );
+ remove_filter( 'wc_tax_enabled', '__return_true' );
+ 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 read_prices does separate caching for prices for display and not for display when they are different.
*