Commit d8d86c5efd7 for woocommerce

commit d8d86c5efd7a881b56f41be7cc70b651d3312bb3
Author: Cvetan Cvetanov <cvetan.cvetanov@automattic.com>
Date:   Wed Jul 15 16:52:47 2026 +0300

    Fix shipping tax for manual orders without products (#66639)

    * Fix shipping tax for orders without products

    * Add changelog entry for shipping-only order tax fix

    * Clarify inherited shipping tax fallback scope

diff --git a/plugins/woocommerce/changelog/fix-shipping-only-order-tax b/plugins/woocommerce/changelog/fix-shipping-only-order-tax
new file mode 100644
index 00000000000..bd92a4811db
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-shipping-only-order-tax
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Apply shipping tax to manual orders that contain shipping but no products.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index e9116bdd302..02717210d99 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -1998,6 +1998,11 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 		if ( 'inherit' === $shipping_tax_class ) {
 			$found_classes      = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
 			$shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
+
+			// Orders without product line items have no tax class to inherit, so use the standard class.
+			if ( false === $shipping_tax_class && 0 === count( $this->get_items() ) ) {
+				$shipping_tax_class = '';
+			}
 		}

 		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
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 b9370e0cfa0..06357426cad 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
@@ -8,6 +8,7 @@
 use Automattic\WooCommerce\Internal\CostOfGoodsSold\CogsAwareUnitTestSuiteTrait;
 use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
 use Automattic\WooCommerce\Enums\OrderStatus;
+use Automattic\WooCommerce\Enums\ProductTaxStatus;

 // phpcs:disable Squiz.Classes.ClassFileName.NoMatch, Squiz.Classes.ValidClassName.NotCamelCaps -- Backward compatibility.
 /**
@@ -714,6 +715,86 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
 		$this->assertSame( 0.50, (float) $tax_item_after->get_shipping_tax_total() );
 	}

+	/**
+	 * @testdox calculate_taxes handles inherited shipping tax when no taxable product class is available.
+	 * @dataProvider inherited_shipping_tax_without_taxable_products_provider
+	 *
+	 * @param bool  $add_non_taxable_product Whether to add a non-taxable product to the order.
+	 * @param bool  $add_non_taxable_fee     Whether to add a non-taxable fee to the order.
+	 * @param float $expected_shipping_tax   Expected shipping tax total.
+	 */
+	public function test_calculate_taxes_handles_inherited_shipping_tax_without_taxable_products( bool $add_non_taxable_product, bool $add_non_taxable_fee, float $expected_shipping_tax ): void {
+		$original_calc_taxes         = get_option( 'woocommerce_calc_taxes', 'no' );
+		$original_shipping_tax_class = get_option( 'woocommerce_shipping_tax_class', 'inherit' );
+		$order                       = new WC_Order();
+		$product                     = null;
+
+		update_option( 'woocommerce_calc_taxes', 'yes' );
+		update_option( 'woocommerce_shipping_tax_class', 'inherit' );
+
+		$tax_rate_id = WC_Tax::_insert_tax_rate(
+			array(
+				'tax_rate_country'  => '',
+				'tax_rate_state'    => '',
+				'tax_rate'          => '10.0000',
+				'tax_rate_name'     => 'Standard tax',
+				'tax_rate_priority' => '1',
+				'tax_rate_compound' => '0',
+				'tax_rate_shipping' => '1',
+				'tax_rate_order'    => '1',
+				'tax_rate_class'    => '',
+			)
+		);
+
+		try {
+			if ( $add_non_taxable_product ) {
+				$product = WC_Helper_Product::create_simple_product();
+				$product->set_tax_status( ProductTaxStatus::NONE );
+				$product->save();
+				$order->add_product( $product );
+			}
+
+			if ( $add_non_taxable_fee ) {
+				$fee_item = new WC_Order_Item_Fee();
+				$fee_item->set_name( 'Manual fee' );
+				$fee_item->set_amount( '5' );
+				$fee_item->set_total( '5' );
+				$fee_item->set_tax_status( ProductTaxStatus::NONE );
+				$order->add_item( $fee_item );
+			}
+
+			$shipping_item = new WC_Order_Item_Shipping();
+			$shipping_item->set_method_title( 'Manual shipping' );
+			$shipping_item->set_total( '10' );
+			$order->add_item( $shipping_item );
+
+			$order->calculate_totals();
+
+			$this->assertSame( $expected_shipping_tax, (float) $order->get_shipping_tax() );
+		} finally {
+			WC_Tax::_delete_tax_rate( $tax_rate_id );
+			update_option( 'woocommerce_calc_taxes', $original_calc_taxes );
+			update_option( 'woocommerce_shipping_tax_class', $original_shipping_tax_class );
+			$order->delete( true );
+			if ( $product ) {
+				$product->delete( true );
+			}
+		}
+	}
+
+	/**
+	 * Data provider for inherited shipping tax calculations without taxable products.
+	 *
+	 * @return array<string, array{bool, bool, float}>
+	 */
+	public function inherited_shipping_tax_without_taxable_products_provider(): array {
+		return array(
+			'shipping-only order'           => array( false, false, 1.0 ),
+			'non-taxable product'           => array( true, false, 0.0 ),
+			'non-taxable fee with shipping' => array( false, true, 1.0 ),
+		);
+	}
+
 	/**
 	 * Get an order object with a fixed total COGS value.
 	 *