Commit 03b463630f7 for woocommerce
commit 03b463630f79b48e38bc7af4fa922234e55b15bb
Author: Seghir Nadir <nadir.seghir@gmail.com>
Date: Thu Oct 22 15:35:40 2026 +0200
Fix cart and checkout price tax setting mismatch (#69368)
* Fix effective price tax setting on checkout orders
* Test cart and checkout price tax agreement
* Clarify cart and checkout changelog entry
* Fix effective price tax setting for new orders
* Keep tax admin and rounding settings unchanged
* Fix tax test setup for effective price settings
diff --git a/plugins/woocommerce/changelog/fix-effective-checkout-price-tax-setting b/plugins/woocommerce/changelog/fix-effective-checkout-price-tax-setting
new file mode 100644
index 00000000000..63b7e1d44af
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-effective-checkout-price-tax-setting
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep tax-inclusive pricing consistent between carts and new orders.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index aa99e70e986..218b96fc200 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -188,7 +188,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->data['cogs_total_value'] = 0;
}
- $this->data['prices_include_tax'] = 'yes' === get_option( 'woocommerce_prices_include_tax' );
+ $this->data['prices_include_tax'] = wc_prices_include_tax();
parent::__construct( $order );
diff --git a/plugins/woocommerce/includes/class-wc-checkout.php b/plugins/woocommerce/includes/class-wc-checkout.php
index d13acbb38e7..095c26c01a4 100644
--- a/plugins/woocommerce/includes/class-wc-checkout.php
+++ b/plugins/woocommerce/includes/class-wc-checkout.php
@@ -468,7 +468,7 @@ class WC_Checkout {
*/
$order->set_customer_id( apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() ) );
$order->set_currency( get_woocommerce_currency() );
- $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $order->set_prices_include_tax( wc_prices_include_tax() );
$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
$order->set_customer_user_agent( wc_get_user_agent() );
$order->set_customer_note( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
index f5cc3f37cd2..fe7296e2b50 100644
--- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
@@ -463,7 +463,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
'cart_tax' => $meta_data['_order_tax'][0] ?? '',
'total' => $meta_data['_order_total'][0] ?? '',
'version' => $meta_data['_order_version'][0] ?? '',
- 'prices_include_tax' => metadata_exists( 'post', $id, '_prices_include_tax' ) ? 'yes' === $prices_include_tax : 'yes' === get_option( 'woocommerce_prices_include_tax' ),
+ 'prices_include_tax' => metadata_exists( 'post', $id, '_prices_include_tax' ) ? 'yes' === $prices_include_tax : wc_prices_include_tax(),
)
);
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php
index ab69f7529ae..53c482f73bf 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-orders-v1-controller.php
@@ -552,7 +552,7 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller {
try {
$order = $this->prepare_item_for_database( $request );
$order->set_created_via( 'rest-api' );
- $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $order->set_prices_include_tax( wc_prices_include_tax() );
$order->calculate_totals();
$order->save();
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
index 1fecb63bb49..6b33cee4dae 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
@@ -823,7 +823,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
if ( $creating ) {
$object->set_created_via( 'rest-api' );
- $object->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $object->set_prices_include_tax( wc_prices_include_tax() );
$object->save();
$object->calculate_totals();
} else {
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php
index 4387452878a..6e8f49172ce 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php
@@ -276,7 +276,7 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
if ( $creating ) {
$object->set_created_via( ! empty( $request['created_via'] ) ? sanitize_text_field( wp_unslash( $request['created_via'] ) ) : 'rest-api' );
- $object->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $object->set_prices_include_tax( wc_prices_include_tax() );
$object->save();
$object->calculate_totals();
} else {
diff --git a/plugins/woocommerce/includes/wc-core-functions.php b/plugins/woocommerce/includes/wc-core-functions.php
index 0b5401f8041..05fab8992cb 100644
--- a/plugins/woocommerce/includes/wc-core-functions.php
+++ b/plugins/woocommerce/includes/wc-core-functions.php
@@ -127,7 +127,7 @@ function wc_create_order( $args = array() ) {
// Set these fields when creating a new order but not when updating an existing order.
if ( ! $args['order_id'] ) {
$order->set_currency( get_woocommerce_currency() );
- $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $order->set_prices_include_tax( wc_prices_include_tax() );
$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
$order->set_customer_user_agent( wc_get_user_agent() );
}
diff --git a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Controller.php b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Controller.php
index c17428e2724..775e8cecdfb 100644
--- a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Controller.php
+++ b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Controller.php
@@ -332,7 +332,7 @@ class Controller extends AbstractController {
try {
$order = new WC_Order();
$order->set_created_via( ! empty( $request['created_via'] ) ? sanitize_text_field( wp_unslash( $request['created_via'] ) ) : 'rest-api' );
- $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $order->set_prices_include_tax( wc_prices_include_tax() );
$this->update_utils->update_order_from_request( $order, $request );
$this->update_additional_fields_for_object( $order, $request );
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
index acd82dd422c..79b7023b62d 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
@@ -83,7 +83,7 @@ class OrderController {
$this->update_line_items_from_cart( $order );
$this->update_addresses_from_cart( $order );
$order->set_currency( get_woocommerce_currency() );
- $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
+ $order->set_prices_include_tax( wc_prices_include_tax() );
$order->set_customer_id( get_current_user_id() );
$order->set_customer_ip_address( \WC_Geolocation::get_ip_address() );
$order->set_customer_user_agent( wc_get_user_agent() );
diff --git a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
index 5b771721dbc..40e7fb88a2b 100644
--- a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
+++ b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php
@@ -121,6 +121,7 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$previous_tax_mode = get_option( 'woocommerce_prices_include_tax' );
try {
+ update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_prices_include_tax', $tax_mode );
$sut = new $order_class();
@@ -161,6 +162,7 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$sut = null;
try {
+ update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_prices_include_tax', $tax_mode );
$sut = new WC_Order();
if ( null !== $override ) {
@@ -211,6 +213,7 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
add_filter( 'woocommerce_email_log_enabled', '__return_false' );
try {
+ update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_prices_include_tax', $tax_mode );
$order = new WC_Order();
if ( null !== $override ) {
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-effective-price-tax-setting-test.php b/plugins/woocommerce/tests/php/includes/class-wc-effective-price-tax-setting-test.php
new file mode 100644
index 00000000000..2f2686f1a23
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/class-wc-effective-price-tax-setting-test.php
@@ -0,0 +1,84 @@
+<?php
+declare( strict_types = 1 );
+
+use Automattic\WooCommerce\StoreApi\Utilities\OrderController;
+
+/**
+ * Tests that checkout keeps the cart's price tax setting.
+ */
+class WC_Effective_Price_Tax_Setting_Test extends WC_Unit_Test_Case {
+ /**
+ * @testdox New orders use the same effective price tax setting as the cart.
+ * @testWith ["no", true, "yes", true]
+ * ["yes", false, "yes", false]
+ * ["yes", true, "no", false]
+ * @param string $option Stored price tax setting.
+ * @param bool $filtered_setting Price tax filter result.
+ * @param string $tax_enabled Whether tax calculation is enabled.
+ * @param bool $expected Expected price tax setting.
+ */
+ public function test_new_orders_use_effective_price_tax_setting( string $option, bool $filtered_setting, string $tax_enabled, bool $expected ): void {
+ update_option( 'woocommerce_calc_taxes', $tax_enabled );
+ update_option( 'woocommerce_prices_include_tax', $option );
+ add_filter( 'woocommerce_prices_include_tax', $filtered_setting ? '__return_true' : '__return_false' );
+
+ $this->assertSame( $expected, ( new WC_Order() )->get_prices_include_tax(), 'New orders must use the effective setting.' );
+ $order = wc_create_order();
+ $this->assertInstanceOf( WC_Order::class, $order );
+ $this->assertSame( $expected, $order->get_prices_include_tax(), 'Orders created through wc_create_order() must use the effective setting.' );
+ }
+
+ /**
+ * @testdox Both checkout paths keep the price tax setting used by the cart.
+ * @testWith ["classic", "no", true, "109.09", "10.91", "120.00"]
+ * ["store-api", "no", true, "109.09", "10.91", "120.00"]
+ * ["classic", "yes", false, "120.00", "12.00", "132.00"]
+ * ["store-api", "yes", false, "120.00", "12.00", "132.00"]
+ * @param string $path Checkout path.
+ * @param string $option Stored price tax setting.
+ * @param bool $effective_setting Price tax setting after filtering.
+ * @param string $expected_subtotal Cart line subtotal.
+ * @param string $expected_tax Cart line tax.
+ * @param string $expected_total Cart and order total.
+ */
+ public function test_checkout_keeps_cart_price_tax_setting( string $path, string $option, bool $effective_setting, string $expected_subtotal, string $expected_tax, string $expected_total ): void {
+ update_option( 'woocommerce_calc_taxes', 'yes' );
+ update_option( 'woocommerce_default_country', 'DE' );
+ update_option( 'woocommerce_prices_include_tax', $option );
+ update_option( 'woocommerce_price_num_decimals', 2 );
+ add_filter( 'woocommerce_prices_include_tax', $effective_setting ? '__return_true' : '__return_false' );
+ WC_Tax::_insert_tax_rate(
+ array(
+ 'tax_rate_country' => 'DE',
+ 'tax_rate' => '10.0000',
+ 'tax_rate_name' => 'VAT',
+ 'tax_rate_priority' => 1,
+ )
+ );
+ WC()->customer->set_billing_country( 'DE' );
+ WC()->customer->set_shipping_country( 'DE' );
+ WC()->customer->set_is_vat_exempt( false );
+ WC()->cart->empty_cart();
+ $product = WC_Helper_Product::create_simple_product( true, array( 'regular_price' => 120 ) );
+ $product->set_virtual( true );
+ $product->save();
+ WC()->cart->add_to_cart( $product->get_id() );
+ WC()->cart->calculate_totals();
+ $cart_items = WC()->cart->get_cart();
+ $cart_item = current( $cart_items );
+
+ $this->assertSame( $effective_setting, wc_prices_include_tax(), 'The cart must use the filtered setting.' );
+ $this->assertSame( $expected_subtotal, wc_format_decimal( $cart_item['line_subtotal'], 2 ), 'The cart must price the item using that setting.' );
+ $this->assertSame( $expected_tax, wc_format_decimal( $cart_item['line_subtotal_tax'], 2 ), 'The cart must calculate tax using that setting.' );
+ $this->assertSame( $expected_total, wc_format_decimal( WC()->cart->get_total( 'edit' ), 2 ), 'The cart total must include the expected tax.' );
+
+ if ( 'store-api' === $path ) {
+ $order = ( new OrderController() )->create_order_from_cart();
+ } else {
+ $order = wc_get_order( WC()->checkout()->create_order( array( 'payment_method' => '' ) ) );
+ }
+
+ $this->assertSame( $effective_setting, $order->get_prices_include_tax(), 'The saved order must use the cart price tax setting.' );
+ $this->assertSame( $expected_total, wc_format_decimal( $order->get_total(), 2 ), 'Checkout must keep the cart total.' );
+ }
+}
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
index 3ac20437fea..2a9bc941b14 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
@@ -829,14 +829,17 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
}
/**
- * @testdox An order with missing tax-mode metadata uses the store setting without writing during the read.
+ * @testdox An order with missing tax-mode metadata uses the filtered setting without writing during the read.
*/
- public function test_reading_order_without_prices_include_tax_metadata_uses_store_setting(): void {
- $previous_tax_mode = get_option( 'woocommerce_prices_include_tax' );
- $order = null;
+ public function test_reading_order_without_prices_include_tax_metadata_uses_effective_setting(): void {
+ $previous_tax_mode = get_option( 'woocommerce_prices_include_tax' );
+ $previous_tax_enabled = get_option( 'woocommerce_calc_taxes' );
+ $order = null;
try {
- update_option( 'woocommerce_prices_include_tax', 'yes' );
+ update_option( 'woocommerce_calc_taxes', 'yes' );
+ update_option( 'woocommerce_prices_include_tax', 'no' );
+ add_filter( 'woocommerce_prices_include_tax', '__return_true' );
$order = WC_Helper_Order::create_order();
delete_post_meta( $order->get_id(), '_prices_include_tax' );
@@ -846,14 +849,16 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
$this->assertTrue(
$read_order->get_prices_include_tax(),
- 'Missing metadata should use the existing CPT fallback to the store setting.'
+ 'Missing metadata should use the effective price tax setting.'
);
$this->assertFalse( metadata_exists( 'post', $order->get_id(), '_prices_include_tax' ), 'Reading must not persist the fallback.' );
} finally {
+ remove_filter( 'woocommerce_prices_include_tax', '__return_true' );
if ( $order ) {
$order->delete( true );
}
update_option( 'woocommerce_prices_include_tax', $previous_tax_mode );
+ update_option( 'woocommerce_calc_taxes', $previous_tax_enabled );
}
}
@@ -871,6 +876,7 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
$order_id = 0;
try {
+ update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_prices_include_tax', $tax_mode );
update_option( 'woocommerce_currency', 'EUR' );
$post = get_default_post_to_edit( 'shop_order', true );