Commit 91c1afe3cce for woocommerce
commit 91c1afe3cce56e045bb9707189d1b666720d0e5b
Author: Mike Jolley <mike.jolley@me.com>
Date: Thu Oct 22 13:52:29 2026 +0100
Don't record a status change when an auto-draft order stays auto-draft (#70126)
* Fix status change note when an auto-draft order is saved as auto-draft
Only treat an auto-draft order as pending when its status is actually
changing. Re-saving it as auto-draft, as REST clients like the mobile
app do, no longer records a pending to auto-draft transition, so no
order note is added and the status change hooks don't fire.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add changelog entry for auto-draft status note fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note b/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note
new file mode 100644
index 00000000000..098d5676599
--- /dev/null
+++ b/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Stop adding a status change note each time an auto-draft order is saved again through the REST API.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index 40b5dcd1428..aa99e70e986 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -929,9 +929,10 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$new_status = OrderStatus::PENDING;
}
- // If the old status is set but unknown (e.g. draft) assume its pending for action usage.
+ // If the status is changing from auto-draft or an unknown status (e.g. draft), assume it was pending for action usage.
if (
$old_status
+ && $old_status !== $new_status
&& (
OrderStatus::AUTO_DRAFT === $old_status
|| (
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
index 004e1d37182..7be45c27e4c 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
@@ -391,6 +391,88 @@ class WC_REST_Orders_Controller_Tests extends WC_REST_Unit_Test_Case {
$this->assertSame( $requested_status, $persisted_order->get_status(), 'The updated order should persist the requested status.' );
}
+ /**
+ * Create an auto-draft order the same way the admin "Add order" screen does.
+ *
+ * @return int Order ID.
+ */
+ private function create_admin_auto_draft_order(): int {
+ $order = new WC_Order();
+ $order->set_object_read( false );
+ $order->set_status( OrderStatus::AUTO_DRAFT );
+ $order->set_created_via( 'admin' );
+
+ return $order->save();
+ }
+
+ /**
+ * Update an order's status through the V3 route and record the status changes it fires.
+ *
+ * @param int $order_id Order ID.
+ * @param string $requested_status Status supplied in the request.
+ * @return array<int, array{string, string}> The from and to statuses of each `woocommerce_order_status_changed` call.
+ */
+ private function update_order_status_and_record_changes( int $order_id, string $requested_status ): array {
+ $status_changes = array();
+ add_action(
+ 'woocommerce_order_status_changed',
+ function ( $changed_order_id, $from, $to ) use ( &$status_changes ) {
+ $status_changes[] = array( $from, $to );
+ },
+ 10,
+ 3
+ );
+
+ $request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order_id );
+ $request->set_body_params( array( 'status' => $requested_status ) );
+ $response = $this->server->dispatch( $request );
+
+ $this->assertSame( 200, $response->get_status(), 'Updating the order should return HTTP 200.' );
+ $this->assertSame( $requested_status, $response->get_data()['status'], 'The update response should contain the requested order status.' );
+
+ return $status_changes;
+ }
+
+ /**
+ * @testdox Saving an auto-draft order as auto-draft again through the V3 route adds no order note and fires no status change.
+ *
+ * @testWith [true]
+ * [false]
+ *
+ * @param bool $hpos_enabled True to store orders in the HPOS tables, false to store them as posts.
+ */
+ public function test_orders_update_auto_draft_order_as_auto_draft_records_no_status_change( bool $hpos_enabled ): void {
+ $this->toggle_cot_feature_and_usage( $hpos_enabled );
+ $order_id = $this->create_admin_auto_draft_order();
+
+ $status_changes = $this->update_order_status_and_record_changes( $order_id, OrderStatus::AUTO_DRAFT );
+
+ $this->assertSame( array(), $status_changes, 'An auto-draft order saved as auto-draft should not fire woocommerce_order_status_changed.' );
+ $this->assertSame( array(), wc_get_order_notes( array( 'order_id' => $order_id ) ), 'An auto-draft order saved as auto-draft should not get a status change note.' );
+ }
+
+ /**
+ * @testdox Moving an auto-draft order to processing through the V3 route records the change as coming from pending.
+ *
+ * @testWith [true]
+ * [false]
+ *
+ * @param bool $hpos_enabled True to store orders in the HPOS tables, false to store them as posts.
+ */
+ public function test_orders_update_auto_draft_order_to_processing_records_change_from_pending( bool $hpos_enabled ): void {
+ $this->toggle_cot_feature_and_usage( $hpos_enabled );
+ $order_id = $this->create_admin_auto_draft_order();
+
+ $status_changes = $this->update_order_status_and_record_changes( $order_id, OrderStatus::PROCESSING );
+
+ $this->assertSame( array( array( OrderStatus::PENDING, OrderStatus::PROCESSING ) ), $status_changes, 'An auto-draft order should move to processing as if it were pending.' );
+ $this->assertContains(
+ 'Order status changed from Pending payment to Processing.',
+ wp_list_pluck( wc_get_order_notes( array( 'order_id' => $order_id ) ), 'content' ),
+ 'The status change note should name pending as the old status.'
+ );
+ }
+
/**
* Search terms accepted by the V3 orders collection route.
*