Commit 5fd32378df6 for woocommerce

commit 5fd32378df6723b734bfedfec42b6f442c5b2533
Author: Abdalsalaam Halawa <abdalsalaamnafez@gmail.com>
Date:   Wed Jun 3 17:49:49 2026 +0300

    Store API: ensure create_order_from_cart removes its default_order_status filter on exception (#65417)

    * Store API: clean up default_order_status filter on exception in create_order_from_cart

    OrderController::create_order_from_cart() registered the woocommerce_default_order_status filter and removed it only after update_order_from_cart() returned. If that call threw, the filter was left registered for the rest of the request. Wrap the body in try/finally so the filter is always removed, including on exceptions, matching the cleanup pattern used for other temporary filters in this class.

    * Add regression tests for create_order_from_cart default_order_status filter cleanup

    Cover that create_order_from_cart() always removes its temporary woocommerce_default_order_status filter: on the success path (no filter-chain growth across repeated calls) and when totals calculation throws (cleanup via the finally block).

    * Add changefile(s) from automation for the following project(s): woocommerce

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/65417-fix-storeapi-create-order-default-order-status-filter-leak b/plugins/woocommerce/changelog/65417-fix-storeapi-create-order-default-order-status-filter-leak
new file mode 100644
index 00000000000..4f20c3d93b1
--- /dev/null
+++ b/plugins/woocommerce/changelog/65417-fix-storeapi-create-order-default-order-status-filter-leak
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Store API: ensure the woocommerce_default_order_status filter is always removed after OrderController::create_order_from_cart(), even if order creation throws.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
index 4aeae5755c6..7d30f515219 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
@@ -50,12 +50,14 @@ class OrderController {

 		add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );

-		$order = new \WC_Order();
-		$order->set_status( 'checkout-draft' );
-		$order->set_created_via( 'store-api' );
-		$this->update_order_from_cart( $order );
-
-		remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
+		try {
+			$order = new \WC_Order();
+			$order->set_status( 'checkout-draft' );
+			$order->set_created_via( 'store-api' );
+			$this->update_order_from_cart( $order );
+		} finally {
+			remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
+		}

 		return $order;
 	}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
index f9b7ab7ab03..504fb84163e 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
@@ -4,6 +4,7 @@ declare( strict_types = 1 );
 namespace Automattic\WooCommerce\Tests\Blocks\StoreApi\Utilities;

 use WC_Helper_Order;
+use WC_Helper_Product;
 use Automattic\WooCommerce\Enums\OrderStatus;
 use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
 use Automattic\WooCommerce\StoreApi\Utilities\OrderController;
@@ -325,6 +326,67 @@ class OrderControllerTests extends TestCase {
 		remove_filter( 'woocommerce_get_country_locale', $hide_postcode );
 	}

+	/**
+	 * @testdox create_order_from_cart() removes its woocommerce_default_order_status filter even when the order update throws.
+	 */
+	public function test_create_order_from_cart_removes_default_order_status_filter_on_exception(): void {
+		$hook           = 'woocommerce_default_order_status';
+		$filters_before = has_filter( $hook );
+
+		$product = WC_Helper_Product::create_simple_product();
+		WC()->cart->empty_cart();
+		WC()->cart->add_to_cart( $product->get_id() );
+		$this->assertFalse( WC()->cart->is_empty(), 'The cart must be non-empty so create_order_from_cart() reaches the filter logic instead of throwing for an empty cart.' );
+
+		$thrower = static function () {
+			throw new \RuntimeException( 'Forced failure during totals calculation.' );
+		};
+		add_action( 'woocommerce_before_calculate_totals', $thrower );
+
+		$threw = false;
+		try {
+			$this->sut->create_order_from_cart();
+		} catch ( \Throwable $e ) {
+			$threw = true;
+		} finally {
+			remove_action( 'woocommerce_before_calculate_totals', $thrower );
+			WC()->cart->empty_cart();
+		}
+
+		$this->assertTrue( $threw, 'The injected exception should propagate out of create_order_from_cart().' );
+		$this->assertSame(
+			$filters_before,
+			has_filter( $hook ),
+			'create_order_from_cart() must remove the woocommerce_default_order_status filter even when the order update throws.'
+		);
+	}
+
+	/**
+	 * @testdox create_order_from_cart() leaves no woocommerce_default_order_status callbacks registered, so the filter chain does not grow across calls.
+	 */
+	public function test_create_order_from_cart_removes_default_order_status_filter(): void {
+		$hook           = 'woocommerce_default_order_status';
+		$filters_before = has_filter( $hook );
+
+		$product = WC_Helper_Product::create_simple_product();
+		WC()->cart->empty_cart();
+		WC()->cart->add_to_cart( $product->get_id() );
+		$this->assertFalse( WC()->cart->is_empty(), 'The cart must be non-empty so create_order_from_cart() runs to completion.' );
+
+		try {
+			$this->sut->create_order_from_cart();
+			$this->sut->create_order_from_cart();
+		} finally {
+			WC()->cart->empty_cart();
+		}
+
+		$this->assertSame(
+			$filters_before,
+			has_filter( $hook ),
+			'create_order_from_cart() must remove its woocommerce_default_order_status filter; the chain must not grow across repeated calls.'
+		);
+	}
+
 	/**
 	 * Helper method to set shipping address on an order.
 	 *