Commit 1f396922632 for woocommerce
commit 1f3969226324ac97ab413405af499e9a7bc029ff
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Tue Aug 25 14:21:39 2026 +0100
Fix orphaned order notes when HPOS orders are deleted (#67935)
* Fix: Delete orphaned order notes when order is deleted with HPOS (sync disabled) (#62620)
- Use wp_delete_comment() API for proper cache invalidation and meta cleanup
- Add regression tests for both deletion paths:
1. Placeholder post: verifies notes and commentmeta are deleted
2. Non-placeholder post: verifies notes are preserved + deletion marker created
Addresses review feedback from @jorgeatorres and follow-up from @isaeedam-ir
* Delete order notes immediately regardless of sync state or post type
* Simplify order notes tests, dropping a redundant one
---------
Co-authored-by: shsajalchowdhury <sajal.marketer@gmail.com>
diff --git a/plugins/woocommerce/changelog/fix-62620-hpos-order-notes-orphaned b/plugins/woocommerce/changelog/fix-62620-hpos-order-notes-orphaned
new file mode 100644
index 00000000000..c210a410bdb
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-62620-hpos-order-notes-orphaned
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Delete orphaned order notes (comments) when an order is permanently deleted with HPOS enabled and sync disabled.
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
index 4b8c7c3778a..c9b072378e6 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php
@@ -2710,6 +2710,18 @@ FROM $order_meta_table
protected function handle_order_deletion_with_sync_disabled( $order_id ): void {
global $wpdb;
+ // Notes are the order's own data, not the backup post's, so they shouldn't wait on a deferred post record.
+ $comments = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_post_ID = %d",
+ $order_id
+ )
+ );
+
+ foreach ( $comments as $comment_id ) {
+ wp_delete_comment( $comment_id, true );
+ }
+
$post_type = $wpdb->get_var(
$wpdb->prepare( "SELECT post_type FROM {$wpdb->posts} WHERE ID=%d", $order_id )
);
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index a5db22b42eb..f36e0a78a12 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -4264,4 +4264,38 @@ class OrdersTableDataStoreTests extends \HposTestCase {
$order->delete();
$product->delete();
}
+
+ /**
+ * @testDox Order notes (and their commentmeta) are deleted immediately when an order is deleted, regardless of whether sync was enabled when the order was created or when it's deleted.
+ *
+ * @testWith [false, false]
+ * [true, false]
+ * [false, true]
+ * [true, true]
+ *
+ * @param bool $sync_enabled_at_creation Whether sync was enabled when the order (and its backup post) was created.
+ * @param bool $sync_enabled_at_deletion Whether sync is enabled when the order is deleted.
+ */
+ public function test_order_notes_deleted_regardless_of_sync_state( bool $sync_enabled_at_creation, bool $sync_enabled_at_deletion ) {
+ $this->allow_current_user_to_delete_posts();
+ $this->toggle_cot_feature_and_usage( true );
+ $this->toggle_cot_authoritative( true );
+ $sync_enabled_at_creation ? $this->enable_cot_sync() : $this->disable_cot_sync();
+
+ $order = OrderHelper::create_order();
+ $order_id = $order->get_id();
+ $note_id = $order->add_order_note( 'Test note' );
+ add_comment_meta( $note_id, 'test_key', 'test_value' );
+
+ $this->assertSame( 'test_value', get_comment_meta( $note_id, 'test_key', true ), 'Commentmeta should exist before the order is deleted' );
+
+ $expected_post_type = $sync_enabled_at_creation ? 'shop_order' : DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE;
+ $this->assertEquals( $expected_post_type, get_post_type( $order_id ) );
+
+ $sync_enabled_at_deletion ? $this->enable_cot_sync() : $this->disable_cot_sync();
+ $order->delete( true );
+
+ $this->assertNull( get_comment( $note_id ), 'Order note should be deleted' );
+ $this->assertEmpty( get_comment_meta( $note_id ), 'Commentmeta should be deleted along with the note' );
+ }
}