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 );