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.
*