Commit bc45a18da1b for woocommerce

commit bc45a18da1b6ccdb83b2a971b118a18317b619e9
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Fri Jun 12 13:08:26 2026 +0200

    [Performance] Reduce the number of SQL queries required to fetch tax rates in the cart and during checkout (#65558)

    Reduces the number of SQL queries on the checkout hot path (by bulk-fetching tax rates), giving the Database more headroom for HVM/BFCM use cases.

diff --git a/plugins/woocommerce/changelog/performance-tax-rates-sqls-number b/plugins/woocommerce/changelog/performance-tax-rates-sqls-number
new file mode 100644
index 00000000000..f097d61a3d2
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-tax-rates-sqls-number
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Reduced the number of SQL queries required to fetch tax rates in the cart and during checkout.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index 051a9eae379..8dd3e01c12b 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -18,6 +18,7 @@ use Automattic\WooCommerce\Enums\TaxBasedOn;
 use Automattic\WooCommerce\Internal\CostOfGoodsSold\CogsAwareTrait;
 use Automattic\WooCommerce\Internal\Customers\SearchService as CustomersSearchService;
 use Automattic\WooCommerce\Internal\Orders\PaymentInfo;
+use Automattic\WooCommerce\Internal\Tax\TaxRateDataStore;
 use Automattic\WooCommerce\Proxies\LegacyProxy;
 use Automattic\WooCommerce\Utilities\ArrayUtil;
 use Automattic\WooCommerce\Utilities\NumberUtil;
@@ -2070,17 +2071,27 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 			}
 		}

+		$tax_rate_objects = wc_get_container()->get( TaxRateDataStore::class )->get_rate_objects_for_ids( array_keys( $cart_taxes + $shipping_taxes ) );
+
 		foreach ( $existing_taxes as $tax ) {
+			$tax_rate_id = $tax->get_rate_id();
 			// Remove taxes which no longer exist for cart/shipping.
-			if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
+			if ( ( ! array_key_exists( $tax_rate_id, $cart_taxes ) && ! array_key_exists( $tax_rate_id, $shipping_taxes ) ) || in_array( $tax_rate_id, $saved_rate_ids, true ) ) {
 				$this->remove_item( $tax->get_id() );
 				continue;
 			}
-			$saved_rate_ids[] = $tax->get_rate_id();
-			$tax->set_rate( $tax->get_rate_id() );
-			$tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
-			$tax->set_label( WC_Tax::get_rate_label( $tax->get_rate_id() ) );
-			$tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
+
+			$tax_rate_object_or_id = $tax_rate_objects[ $tax_rate_id ] ?? $tax_rate_id;
+
+			$tax->set_rate_id( $tax_rate_id );
+			$tax->set_rate_code( WC_Tax::get_rate_code( $tax_rate_object_or_id ) );
+			$tax->set_label( WC_Tax::get_rate_label( $tax_rate_object_or_id ) );
+			$tax->set_compound( WC_Tax::is_compound( $tax_rate_object_or_id ) );
+			$tax->set_rate_percent( WC_Tax::get_rate_percent_value( $tax_rate_object_or_id ) );
+			$tax->set_tax_total( $cart_taxes[ $tax_rate_id ] ?? 0 );
+			$tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
+
+			$saved_rate_ids[] = $tax_rate_id;
 			$tax->save();
 		}

@@ -2088,10 +2099,17 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {

 		// New taxes.
 		foreach ( $new_rate_ids as $tax_rate_id ) {
+			$tax_rate_object_or_id = $tax_rate_objects[ $tax_rate_id ] ?? $tax_rate_id;
+
 			$item = new WC_Order_Item_Tax();
-			$item->set_rate( $tax_rate_id );
-			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
+			$item->set_rate_id( $tax_rate_id );
+			$item->set_rate_code( WC_Tax::get_rate_code( $tax_rate_object_or_id ) );
+			$item->set_label( WC_Tax::get_rate_label( $tax_rate_object_or_id ) );
+			$item->set_compound( WC_Tax::is_compound( $tax_rate_object_or_id ) );
+			$item->set_rate_percent( WC_Tax::get_rate_percent_value( $tax_rate_object_or_id ) );
+			$item->set_tax_total( $cart_taxes[ $tax_rate_id ] ?? 0 );
 			$item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
+
 			$this->add_item( $item );
 		}

diff --git a/plugins/woocommerce/includes/class-wc-cart.php b/plugins/woocommerce/includes/class-wc-cart.php
index b6e976e832d..4e29045e352 100644
--- a/plugins/woocommerce/includes/class-wc-cart.php
+++ b/plugins/woocommerce/includes/class-wc-cart.php
@@ -12,10 +12,11 @@
 use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
 use Automattic\WooCommerce\Enums\ProductStatus;
 use Automattic\WooCommerce\Enums\ProductType;
+use Automattic\WooCommerce\Internal\Tax\TaxRateDataStore;
+use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
 use Automattic\WooCommerce\Utilities\DiscountsUtil;
 use Automattic\WooCommerce\Utilities\NumberUtil;
 use Automattic\WooCommerce\Utilities\ShippingUtil;
-use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;

 defined( 'ABSPATH' ) || exit;

@@ -979,12 +980,14 @@ class WC_Cart extends WC_Legacy_Cart {
 	 * @return array
 	 */
 	public function get_tax_totals() {
-		$shipping_taxes = $this->get_shipping_taxes(); // Shipping taxes are rounded differently, so we will subtract from all taxes, then round and then add them back.
-		$taxes          = $this->get_taxes();
-		$tax_totals     = array();
+		$shipping_taxes   = $this->get_shipping_taxes();
+		$taxes            = $this->get_taxes();
+		$tax_rate_objects = wc_get_container()->get( TaxRateDataStore::class )->get_rate_objects_for_ids( array_keys( $taxes ) );
+		$tax_totals       = array();

 		foreach ( $taxes as $key => $tax ) {
-			$code = WC_Tax::get_rate_code( $key );
+			$tax_rate_object_or_id = $tax_rate_objects[ $key ] ?? $key;
+			$code                  = WC_Tax::get_rate_code( $tax_rate_object_or_id );

 			if ( $code || apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) === $key ) {
 				if ( ! isset( $tax_totals[ $code ] ) ) {
@@ -993,9 +996,10 @@ class WC_Cart extends WC_Legacy_Cart {
 				}

 				$tax_totals[ $code ]->tax_rate_id = $key;
-				$tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key );
-				$tax_totals[ $code ]->label       = WC_Tax::get_rate_label( $key );
+				$tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $tax_rate_object_or_id );
+				$tax_totals[ $code ]->label       = WC_Tax::get_rate_label( $tax_rate_object_or_id );

+				// Shipping taxes are rounded differently, so we will subtract from all taxes, then round and then add them back.
 				if ( isset( $shipping_taxes[ $key ] ) ) {
 					$tax -= $shipping_taxes[ $key ];
 					$tax  = wc_round_tax_total( $tax );
@@ -2377,7 +2381,7 @@ class WC_Cart extends WC_Legacy_Cart {
 	 */
 	public function get_tax_amount( $tax_rate_id ) {
 		$taxes = wc_array_merge_recursive_numeric( $this->get_cart_contents_taxes(), $this->get_fee_taxes() );
-		return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
+		return $taxes[ $tax_rate_id ] ?? 0;
 	}

 	/**
@@ -2388,7 +2392,7 @@ class WC_Cart extends WC_Legacy_Cart {
 	 */
 	public function get_shipping_tax_amount( $tax_rate_id ) {
 		$taxes = $this->get_shipping_taxes();
-		return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
+		return $taxes[ $tax_rate_id ] ?? 0;
 	}

 	/**
@@ -2399,17 +2403,21 @@ class WC_Cart extends WC_Legacy_Cart {
 	 * @return float price
 	 */
 	public function get_taxes_total( $compound = true, $display = true ) {
-		$total = 0;
 		$taxes = $this->get_taxes();
-		foreach ( $taxes as $key => $tax ) {
-			if ( ! $compound && WC_Tax::is_compound( $key ) ) {
-				continue;
+
+		// Skip compounding taxes if requested.
+		if ( ! $compound ) {
+			$tax_rate_objects = wc_get_container()->get( TaxRateDataStore::class )->get_rate_objects_for_ids( array_keys( $taxes ) );
+			foreach ( $taxes as $key => $tax ) {
+				if ( WC_Tax::is_compound( $tax_rate_objects[ $key ] ?? $key ) ) {
+					unset( $taxes[ $key ] );
+				}
 			}
-			$total += $tax;
-		}
-		if ( $display ) {
-			$total = wc_format_decimal( $total, wc_get_price_decimals() );
 		}
+
+		$total = array_sum( $taxes );
+		$total = $display ? wc_format_decimal( $total, wc_get_price_decimals() ) : $total;
+
 		return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this );
 	}

diff --git a/plugins/woocommerce/includes/class-wc-checkout.php b/plugins/woocommerce/includes/class-wc-checkout.php
index 4ba18230ac8..520c1ef8036 100644
--- a/plugins/woocommerce/includes/class-wc-checkout.php
+++ b/plugins/woocommerce/includes/class-wc-checkout.php
@@ -11,6 +11,7 @@
 use Automattic\WooCommerce\Enums\OrderStatus;
 use Automattic\WooCommerce\Enums\ProductType;
 use Automattic\WooCommerce\Internal\CostOfGoodsSold\CogsAwareTrait;
+use Automattic\WooCommerce\Internal\Tax\TaxRateDataStore;

 defined( 'ABSPATH' ) || exit;

@@ -680,7 +681,10 @@ class WC_Checkout {
 	 * @param WC_Cart  $cart  Cart instance.
 	 */
 	public function create_order_tax_lines( &$order, $cart ) {
-		foreach ( array_keys( $cart->get_cart_contents_taxes() + $cart->get_shipping_taxes() + $cart->get_fee_taxes() ) as $tax_rate_id ) {
+		$tax_rate_ids     = array_keys( $cart->get_cart_contents_taxes() + $cart->get_shipping_taxes() + $cart->get_fee_taxes() );
+		$tax_rate_objects = wc_get_container()->get( TaxRateDataStore::class )->get_rate_objects_for_ids( $tax_rate_ids );
+
+		foreach ( $tax_rate_ids as $tax_rate_id ) {
 			/**
 			 * Controls the zero rate tax ID.
 			 *
@@ -691,16 +695,17 @@ class WC_Checkout {
 			 * @param string $tax_rate_id The ID of the zero rate tax.
 			 */
 			if ( $tax_rate_id && apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) !== $tax_rate_id ) {
-				$item = new WC_Order_Item_Tax();
+				$tax_rate_object_or_id = $tax_rate_objects[ $tax_rate_id ] ?? $tax_rate_id;
+				$item                  = new WC_Order_Item_Tax();
 				$item->set_props(
 					array(
 						'rate_id'            => $tax_rate_id,
 						'tax_total'          => $cart->get_tax_amount( $tax_rate_id ),
 						'shipping_tax_total' => $cart->get_shipping_tax_amount( $tax_rate_id ),
-						'rate_code'          => WC_Tax::get_rate_code( $tax_rate_id ),
-						'label'              => WC_Tax::get_rate_label( $tax_rate_id ),
-						'compound'           => WC_Tax::is_compound( $tax_rate_id ),
-						'rate_percent'       => WC_Tax::get_rate_percent_value( $tax_rate_id ),
+						'rate_code'          => WC_Tax::get_rate_code( $tax_rate_object_or_id ),
+						'label'              => WC_Tax::get_rate_label( $tax_rate_object_or_id ),
+						'compound'           => WC_Tax::is_compound( $tax_rate_object_or_id ),
+						'rate_percent'       => WC_Tax::get_rate_percent_value( $tax_rate_object_or_id ),
 					)
 				);

diff --git a/plugins/woocommerce/src/Internal/Tax/TaxRateDataStore.php b/plugins/woocommerce/src/Internal/Tax/TaxRateDataStore.php
new file mode 100644
index 00000000000..9d48c9c15e1
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Tax/TaxRateDataStore.php
@@ -0,0 +1,37 @@
+<?php
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Tax;
+
+/**
+ * Data store for tax rates.
+ */
+class TaxRateDataStore {
+
+	/**
+	 * Fetch multiple tax rate rows in a single query, keyed by tax_rate_id.
+	 *
+	 * @since 11.0.0
+	 *
+	 * @param int[] $ids Tax rate IDs to fetch.
+	 * @return array<int,object>
+	 */
+	public function get_rate_objects_for_ids( array $ids ): array {
+		$tax_rate_objects = array();
+		$ids              = array_values( array_filter( array_map( 'absint', array_unique( $ids ) ) ) );
+
+		if ( ! empty( $ids ) ) {
+			global $wpdb;
+
+			$list = implode( ', ', $ids );
+			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+			$rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id IN ( $list )" );
+			foreach ( $rows as $row ) {
+				$tax_rate_objects[ (int) $row->tax_rate_id ] = $row;
+			}
+		}
+
+		return $tax_rate_objects;
+	}
+}
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
index 55b2a3cf52c..21a2c257bdc 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
@@ -1,11 +1,11 @@
 <?php
 namespace Automattic\WooCommerce\StoreApi\Schemas\V1;

+use Automattic\WooCommerce\Internal\Tax\TaxRateDataStore;
 use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
 use Automattic\WooCommerce\StoreApi\SchemaController;
 use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
 use Automattic\WooCommerce\StoreApi\Utilities\CartController;
-use WC_Tax;

 /**
  * CartSchema class.
@@ -416,14 +416,15 @@ class CartSchema extends AbstractSchema {
 			return $tax_lines;
 		}

-		$cart_tax_totals = $cart->get_tax_totals();
-		$decimals        = wc_get_price_decimals();
+		$cart_tax_totals  = $cart->get_tax_totals();
+		$tax_rate_objects = wc_get_container()->get( TaxRateDataStore::class )->get_rate_objects_for_ids( array_column( $cart_tax_totals, 'tax_rate_id' ) );
+		$decimals         = wc_get_price_decimals();

 		foreach ( $cart_tax_totals as $cart_tax_total ) {
 			$tax_lines[] = array(
 				'name'  => $cart_tax_total->label,
 				'price' => $this->prepare_money_response( $cart_tax_total->amount, $decimals ),
-				'rate'  => WC_Tax::get_rate_percent( $cart_tax_total->tax_rate_id ),
+				'rate'  => \WC_Tax::get_rate_percent( $tax_rate_objects[ $cart_tax_total->tax_rate_id ] ?? $cart_tax_total->tax_rate_id ),
 			);
 		}

diff --git a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
index b1ff8083453..9b38797249d 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
@@ -1998,6 +1998,51 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
 		$this->assertEquals( array(), $tax_totals );
 	}

+	/**
+	 * Test that get_taxes_total excludes compound rates when $compound is false.
+	 */
+	public function test_get_taxes_total_excludes_compound_rates(): void {
+		update_option( 'woocommerce_calc_taxes', 'yes' );
+
+		// Standard (non-compound) 10% rate.
+		$standard_rate = array(
+			'tax_rate_country'  => '',
+			'tax_rate_state'    => '',
+			'tax_rate'          => '10.0000',
+			'tax_rate_name'     => 'TAX',
+			'tax_rate_priority' => '1',
+			'tax_rate_compound' => '0',
+			'tax_rate_shipping' => '1',
+			'tax_rate_order'    => '1',
+			'tax_rate_class'    => '',
+		);
+		WC_Tax::_insert_tax_rate( $standard_rate );
+
+		// Compound 5% rate applied on top.
+		$compound_rate = array(
+			'tax_rate_country'  => '',
+			'tax_rate_state'    => '',
+			'tax_rate'          => '5.0000',
+			'tax_rate_name'     => 'COMPOUND',
+			'tax_rate_priority' => '2',
+			'tax_rate_compound' => '1',
+			'tax_rate_shipping' => '1',
+			'tax_rate_order'    => '2',
+			'tax_rate_class'    => '',
+		);
+		WC_Tax::_insert_tax_rate( $compound_rate );
+
+		$product = WC_Helper_Product::create_simple_product();
+		WC()->customer->set_is_vat_exempt( false );
+		WC()->cart->add_to_cart( $product->get_id(), 1 );
+
+		// $compound = false: only the standard 10% rate included → $10 × 10% = $1.00.
+		$this->assertSame( 1.0, WC()->cart->get_taxes_total( false, false ) );
+
+		// $compound = true: both rates included → $1.00 + ($10 + $1.00) × 5% = $1.55.
+		$this->assertSame( 1.55, WC()->cart->get_taxes_total( true, false ) );
+	}
+
 	/**
 	 * Check subtotals align when using filters. Ref: 23340
 	 */
@@ -2006,7 +2051,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
 		update_option( 'woocommerce_prices_include_tax', 'yes' );
 		update_option( 'woocommerce_calc_taxes', 'yes' );

-		// 5% tax.
+		// 5% non-compound rate for the standard (default) tax class.
 		$tax_rate = array(
 			'tax_rate_country'  => '',
 			'tax_rate_state'    => '',
@@ -2020,7 +2065,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
 		);
 		WC_Tax::_insert_tax_rate( $tax_rate );

-		// 20% tax.
+		// 20% non-compound rate for the reduced-rate class (switched to via filter below).
 		$tax_rate = array(
 			'tax_rate_country'  => '',
 			'tax_rate_state'    => '',
@@ -2042,6 +2087,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
 		WC()->cart->add_to_cart( $product1->get_id(), 1 );
 		WC()->cart->calculate_totals();

+		// Standard class (5%): price is tax-inclusive, subtotal is the ex-tax base, total matches the entered price.
 		$this->assertEquals( '5.71', WC()->cart->get_subtotal() );
 		$this->assertEquals( '6.00', WC()->cart->get_total( 'edit' ) );

@@ -2049,6 +2095,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
 		add_filter( 'woocommerce_product_variation_get_tax_class', array( $this, 'change_tax_class_filter' ) );

 		WC()->cart->calculate_totals();
+		// Reduced-rate class (20%) applied via filter: ex-tax subtotal is unchanged, total rises to reflect higher rate.
 		$this->assertEquals( '5.71', WC()->cart->get_subtotal() );
 		$this->assertEquals( '6.85', WC()->cart->get_total( 'edit' ) );

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 70b7b366721..ef6dfb13ce5 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
@@ -621,6 +621,86 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
 		$this->assertEquals( $expected, $actual );
 	}

+	/**
+	 * @testdox update_taxes persists cart and shipping tax totals as order tax items, and updates existing items in-place on a second call.
+	 */
+	public function test_update_taxes_persists_cart_and_shipping_tax_totals(): void {
+		update_option( 'woocommerce_calc_taxes', 'yes' );
+
+		// German standard 19% non-compound VAT rate.
+		$tax_rate    = array(
+			'tax_rate_country'  => 'DE',
+			'tax_rate_state'    => '',
+			'tax_rate'          => '19.0000',
+			'tax_rate_name'     => 'VAT',
+			'tax_rate_priority' => '1',
+			'tax_rate_compound' => '0',
+			'tax_rate_shipping' => '1',
+			'tax_rate_order'    => '1',
+			'tax_rate_class'    => '',
+		);
+		$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
+
+		$product = WC_Helper_Product::create_simple_product();
+		$order   = new WC_Order();
+
+		// Line item carrying $1.00 cart tax for the rate.
+		// WC_Order_Item_Product::set_taxes() requires both 'total' and 'subtotal' to be non-empty.
+		$line_item = new WC_Order_Item_Product();
+		$line_item->set_product( $product );
+		$line_item->set_taxes(
+			array(
+				'total'    => array( $tax_rate_id => '1.00' ),
+				'subtotal' => array( $tax_rate_id => '1.00' ),
+			)
+		);
+		$order->add_item( $line_item );
+
+		// Shipping item carrying $0.50 shipping tax for the rate.
+		$shipping_item = new WC_Order_Item_Shipping();
+		$shipping_item->set_taxes( array( 'total' => array( $tax_rate_id => '0.50' ) ) );
+		$order->add_item( $shipping_item );
+
+		$order->save();
+		$order->update_taxes();
+
+		$tax_items = $order->get_taxes();
+		$this->assertCount( 1, $tax_items );
+
+		/** @var WC_Order_Item_Tax $tax_item */
+		$tax_item = reset( $tax_items );
+		// Confirm the German 19% VAT rate is correctly associated with the tax item.
+		$this->assertSame( 19.0, (float) $tax_item->get_rate_percent() );
+		// Cart and shipping taxes are accumulated from line items and persisted on the tax item.
+		$this->assertSame( 1.00, (float) $tax_item->get_tax_total() );
+		$this->assertSame( 0.50, (float) $tax_item->get_shipping_tax_total() );
+		// Order-level totals are rolled up from all tax items.
+		$this->assertSame( 1.00, (float) $order->get_cart_tax() );
+		$this->assertSame( 0.50, (float) $order->get_shipping_tax() );
+
+		// Second call: update line item taxes and verify existing tax item is updated in-place, not duplicated.
+		foreach ( $order->get_items() as $item ) {
+			if ( $item instanceof WC_Order_Item_Product ) {
+				$item->set_taxes(
+					array(
+						'total'    => array( $tax_rate_id => '2.00' ),
+						'subtotal' => array( $tax_rate_id => '2.00' ),
+					)
+				);
+				$item->save();
+			}
+		}
+
+		$order->update_taxes();
+
+		$tax_items_after = $order->get_taxes();
+		$this->assertCount( 1, $tax_items_after, 'update_taxes() must update the existing tax item, not create a duplicate.' );
+
+		$tax_item_after = reset( $tax_items_after );
+		$this->assertSame( 2.00, (float) $tax_item_after->get_tax_total() );
+		$this->assertSame( 0.50, (float) $tax_item_after->get_shipping_tax_total() );
+	}
+
 	/**
 	 * Get an order object with a fixed total COGS value.
 	 *
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php b/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
index e12689bc026..cff3fc7ae0b 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-checkout-test.php
@@ -43,6 +43,7 @@ class WC_Checkout_Test extends \WC_Unit_Test_Case {
 	 */
 	public function tearDown(): void {
 		remove_filter( 'woocommerce_checkout_registration_enabled', '__return_true' );
+		delete_option( 'woocommerce_calc_taxes' );
 	}

 	/**
@@ -249,6 +250,46 @@ class WC_Checkout_Test extends \WC_Unit_Test_Case {
 		WC()->customer = $orig_customer;
 	}

+	/**
+	 * @testdox create_order_tax_lines sets rate_code, label, compound and rate_percent on order tax items.
+	 */
+	public function test_create_order_tax_lines_sets_correct_tax_item_props(): void {
+		update_option( 'woocommerce_calc_taxes', 'yes' );
+
+		// German standard 19% non-compound VAT rate.
+		$tax_rate = array(
+			'tax_rate_country'  => 'DE',
+			'tax_rate_state'    => '',
+			'tax_rate'          => '19.0000',
+			'tax_rate_name'     => 'VAT',
+			'tax_rate_priority' => '1',
+			'tax_rate_compound' => '0',
+			'tax_rate_shipping' => '1',
+			'tax_rate_order'    => '1',
+			'tax_rate_class'    => '',
+		);
+		WC_Tax::_insert_tax_rate( $tax_rate );
+
+		$product = WC_Helper_Product::create_simple_product();
+		WC()->customer->set_billing_country( 'DE' );
+		WC()->customer->set_shipping_country( 'DE' );
+		WC()->customer->set_is_vat_exempt( false );
+		WC()->cart->add_to_cart( $product->get_id(), 1 );
+		WC()->cart->calculate_totals();
+
+		$order     = wc_get_order( $this->sut->create_order( array( 'payment_method' => WC_Gateway_BACS::ID ) ) );
+		$tax_items = $order->get_taxes();
+
+		$this->assertCount( 1, $tax_items );
+
+		/** @var WC_Order_Item_Tax $tax_item */
+		$tax_item = array_values( $tax_items )[0];
+		$this->assertSame( 'DE-VAT-1', $tax_item->get_rate_code() );
+		$this->assertSame( 'VAT', $tax_item->get_label() );
+		$this->assertFalse( $tax_item->get_compound() );
+		$this->assertSame( 19.0, $tax_item->get_rate_percent() );
+	}
+
 	/**
 	 * @testdox Checkout page contains login form for guests.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/Tax/TaxRateDataStoreTest.php b/plugins/woocommerce/tests/php/src/Internal/Tax/TaxRateDataStoreTest.php
new file mode 100644
index 00000000000..14c176169f7
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Tax/TaxRateDataStoreTest.php
@@ -0,0 +1,53 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Tax;
+
+use Automattic\WooCommerce\Internal\Tax\TaxRateDataStore;
+
+/**
+ * Tests for TaxRateDataStore.
+ */
+class TaxRateDataStoreTest extends \WC_Unit_Test_Case {
+
+	/**
+	 * @var TaxRateDataStore
+	 */
+	private $sut;
+
+	/**
+	 * Set up subject under test.
+	 */
+	public function set_up() {
+		$this->sut = wc_get_container()->get( TaxRateDataStore::class );
+		parent::set_up();
+	}
+
+	/**
+	 * @testdox get_rate_objects_for_ids() deduplicates mixed int/string IDs and returns a map keyed by int tax_rate_id.
+	 */
+	public function test_get_rate_objects_for_ids(): void {
+		// Arrange.
+		$tax_rate    = array(
+			'tax_rate_country'  => 'DE',
+			'tax_rate_state'    => '',
+			'tax_rate'          => '19.0000',
+			'tax_rate_name'     => 'VAT',
+			'tax_rate_priority' => '1',
+			'tax_rate_compound' => '1',
+			'tax_rate_shipping' => '1',
+			'tax_rate_order'    => '1',
+			'tax_rate_class'    => '',
+		);
+		$tax_rate_id = \WC_Tax::_insert_tax_rate( $tax_rate );
+
+		// Act.
+		$result = $this->sut->get_rate_objects_for_ids( array( $tax_rate_id, (string) $tax_rate_id, PHP_INT_MAX ) );
+
+		// Assert.
+		$this->assertCount( 1, $result );
+		$this->assertSame( array( $tax_rate_id ), array_keys( $result ) );
+		$this->assertSame( 'VAT', $result[ $tax_rate_id ]->tax_rate_name );
+	}
+}