Commit f7615572e33 for woocommerce
commit f7615572e331d50ea8d658bcb2f46552b59eb330
Author: Tom Cafferkey <tjcafferkey@gmail.com>
Date: Tue Aug 4 14:48:58 2026 +0100
Add merchant inbox notification for order withdrawal requests (#67155)
* Add note for withdraw order requests
* Changelog
* Update order withdrawal inbox note privacy
* Update test
* Support HPOS and legacy order for cleanup
* Remove PII from order note
* Add order notes after marking the request
* Add order number to inbox note title
* OrderID guards
* Add order ID into error log
* Fix test case for failing emails
* Use appropriate note method for test helpers
* Final review fixes
diff --git a/plugins/woocommerce/changelog/add-withdraw-order-note b/plugins/woocommerce/changelog/add-withdraw-order-note
new file mode 100644
index 00000000000..47901e470d1
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-withdraw-order-note
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Adds an inbox note when a customer submits a withdraw order request
diff --git a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalController.php b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalController.php
index 1de5b630cab..0ffa4568780 100644
--- a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalController.php
+++ b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalController.php
@@ -58,6 +58,9 @@ final class OrderWithdrawalController implements RegisterHooksInterface {
add_filter( 'woocommerce_endpoint_' . self::ENDPOINT_KEY . '_title', array( $this, 'get_endpoint_title' ), 10, 1 );
add_filter( 'woocommerce_settings_pages', array( $this, 'add_endpoint_setting' ), 10, 1 );
add_action( 'woocommerce_account_' . self::ENDPOINT_KEY . '_endpoint', array( $this, 'render_view' ) );
+ add_action( 'woocommerce_before_delete_order', array( $this->form_processor, 'delete_order_withdrawal_inbox_note_for_order' ), 10, 1 );
+ add_action( 'before_delete_post', array( $this->form_processor, 'delete_order_withdrawal_inbox_note_for_order' ), 10, 1 );
+ add_action( 'woocommerce_privacy_remove_order_personal_data', array( $this->form_processor, 'delete_order_withdrawal_inbox_note_for_order' ), 10, 1 );
}
/**
diff --git a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
index da0d2064a6e..67956193205 100644
--- a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
+++ b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
@@ -3,7 +3,10 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\OrderWithdrawal;
+use Automattic\WooCommerce\Admin\Notes\Note;
+use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Internal\Orders\OrderNoteGroup;
+use Automattic\WooCommerce\Utilities\OrderUtil;
use Throwable;
use WC_Geolocation;
use WC_Order;
@@ -37,6 +40,7 @@ final class OrderWithdrawalFormProcessor {
private const LOGGER_SOURCE = 'order-withdrawal';
private const ORDER_WITHDRAWAL_REQUESTED_META_KEY = '_order_withdrawal_requested';
private const ORDER_WITHDRAWAL_REQUESTED_VALUE = 'yes';
+ private const INBOX_NOTE_NAME_PREFIX = 'wc-order-withdrawal-requested-order-';
private const RATE_LIMIT_IP_PREFIX = 'order_withdrawal_ip_';
private const RATE_LIMIT_EMAIL_PREFIX = 'order_withdrawal_email_';
private const RATE_LIMIT_DELAY = MINUTE_IN_SECONDS / 2;
@@ -266,19 +270,15 @@ final class OrderWithdrawalFormProcessor {
$matched_order = $this->get_matching_order( $data );
- if ( $matched_order ) {
- if ( $this->has_order_withdrawal_request( $matched_order ) ) {
- wc_add_notice(
- __( 'A withdrawal request has already been submitted for this order. Please contact us if you need help or want to make changes.', 'woocommerce' ),
- 'error'
- );
-
- $this->apply_rate_limits( $rate_limit_ids, -1 );
+ if ( $matched_order && $this->has_order_withdrawal_request( $matched_order ) ) {
+ wc_add_notice(
+ __( 'A withdrawal request has already been submitted for this order. Please contact us if you need help or want to make changes.', 'woocommerce' ),
+ 'error'
+ );
- return false;
- }
+ $this->apply_rate_limits( $rate_limit_ids, -1 );
- $this->add_order_withdrawal_note( $matched_order, $data );
+ return false;
}
if ( ! $this->send_order_withdrawal_emails( $data, $matched_order ) ) {
@@ -290,6 +290,8 @@ final class OrderWithdrawalFormProcessor {
if ( $matched_order ) {
$this->mark_order_withdrawal_requested( $matched_order );
+ $this->add_order_withdrawal_note( $matched_order, $data );
+ $this->add_order_withdrawal_inbox_note( $matched_order );
}
return true;
@@ -458,20 +460,11 @@ final class OrderWithdrawalFormProcessor {
*/
private function add_order_withdrawal_note( WC_Order $order, array $data ): void {
$note = sprintf(
- /* translators: 1: customer name, 2: customer email address. */
- __( 'Order withdrawal requested by %1$s (%2$s).', 'woocommerce' ),
- $this->get_customer_name( $data ),
- $data[ self::FIELD_EMAIL ]
+ /* translators: %s: withdrawal type label. */
+ __( 'Order withdrawal requested. Withdrawal type: %s.', 'woocommerce' ),
+ $this->get_withdrawal_type_label( $data[ self::FIELD_WITHDRAWAL_TYPE ] )
);
- if ( self::WITHDRAWAL_TYPE_SPECIFIC === $data[ self::FIELD_WITHDRAWAL_TYPE ] ) {
- $note .= "\n\n" . sprintf(
- /* translators: %s: items the customer listed for partial withdrawal. */
- __( 'Items requested for withdrawal: %s', 'woocommerce' ),
- $data[ self::FIELD_ADDITIONAL_DETAILS ]
- );
- }
-
try {
if ( ! $order->add_order_note( $note, 0, false, array( 'note_group' => OrderNoteGroup::ORDER_UPDATE ) ) ) {
$this->log_order_note_error( $order );
@@ -481,6 +474,73 @@ final class OrderWithdrawalFormProcessor {
}
}
+ /**
+ * Add a withdrawal request notification to the merchant's WooCommerce inbox.
+ *
+ * @param WC_Order $matched_order Matched order.
+ */
+ private function add_order_withdrawal_inbox_note( WC_Order $matched_order ): void {
+ try {
+ $note = new Note();
+ $note->set_title(
+ sprintf(
+ /* translators: %s: order number. */
+ __( 'Order withdrawal request for #%s', 'woocommerce' ),
+ $matched_order->get_order_number()
+ )
+ );
+ $note->set_content(
+ sprintf(
+ /* translators: %s: order number. */
+ __( 'A customer submitted an order withdrawal request for order #%s. Review the matched order to confirm the request details.', 'woocommerce' ),
+ $matched_order->get_order_number()
+ )
+ );
+ $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
+ $note->set_name( self::INBOX_NOTE_NAME_PREFIX . $matched_order->get_id() );
+ $note->set_source( 'woocommerce-admin' );
+
+ $order_url = $matched_order->get_edit_order_url();
+
+ if ( '' !== $order_url ) {
+ $note->add_action( 'view-order', __( 'View order', 'woocommerce' ), $order_url );
+ }
+
+ $note->save();
+ } catch ( Throwable $e ) {
+ $this->log_inbox_note_error( $e, $matched_order->get_id() );
+ }
+ }
+
+ /**
+ * Delete the withdrawal request inbox notification associated with an order.
+ *
+ * @param int|WC_Order $order Order ID or order object.
+ */
+ public function delete_order_withdrawal_inbox_note_for_order( $order ): void {
+ if ( $order instanceof WC_Order ) {
+ $order_id = $order->get_id();
+ } elseif ( is_int( $order ) ) {
+ if ( ! OrderUtil::is_order( $order ) ) {
+ return;
+ }
+
+ $order_id = $order;
+ } else {
+ return;
+ }
+
+ if ( 0 >= $order_id ) {
+ return;
+ }
+
+ try {
+ Notes::delete_notes_with_name( self::INBOX_NOTE_NAME_PREFIX . $order_id );
+ } catch ( Throwable $e ) {
+ $this->log_inbox_note_error( $e, $order_id );
+ }
+ }
+
/**
* Whether the matched order already has a submitted withdrawal request.
*
@@ -720,6 +780,19 @@ final class OrderWithdrawalFormProcessor {
);
}
+ /**
+ * Log an inbox note failure without failing the submission.
+ *
+ * @param Throwable $e Inbox note error.
+ * @param int $order_id Order ID.
+ */
+ private function log_inbox_note_error( Throwable $e, int $order_id ): void {
+ wc_get_logger()->warning(
+ sprintf( 'Order withdrawal inbox note could not be processed for order %1$d. Error: %2$s', $order_id, $e->getMessage() ),
+ array( 'source' => self::LOGGER_SOURCE )
+ );
+ }
+
/**
* Log an order note failure without failing the submission.
*
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
index fd1560da7dc..9d997df6389 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
@@ -3,6 +3,10 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Internal\OrderWithdrawal;
+use Automattic\WooCommerce\Admin\Notes\Note;
+use Automattic\WooCommerce\Admin\Notes\Notes;
+use Automattic\WooCommerce\Internal\Features\FeaturesController;
+use Automattic\WooCommerce\Internal\OrderWithdrawal\OrderWithdrawalController;
use Automattic\WooCommerce\Internal\OrderWithdrawal\OrderWithdrawalFormProcessor;
use Automattic\WooCommerce\Internal\OrderWithdrawal\OrderWithdrawalFormState;
use Automattic\WooCommerce\Internal\OrderWithdrawal\OrderWithdrawalFormView;
@@ -20,7 +24,9 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
private const FLUSH_QUEUE_OPTION = 'woocommerce_queue_flush_rewrite_rules';
private const MISSING_OPTION_MARK = '__woocommerce_order_withdrawal_missing_option__';
private const ORDER_WITHDRAWAL_REQUESTED_META_KEY = '_order_withdrawal_requested';
+ private const INBOX_NOTE_NAME_PREFIX = 'wc-order-withdrawal-requested-order-';
private const RATE_LIMIT_PREFIX = 'order_withdrawal_';
+ private const ORDER_NOTE_WITHDRAWAL_REQUESTED = 'Order withdrawal requested. Withdrawal type: Specific items only.';
/**
* The System Under Test.
@@ -153,6 +159,7 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->restore_option( self::FLUSH_QUEUE_OPTION, $this->original_flush_queue_option );
wc_clear_notices();
$this->clear_order_withdrawal_rate_limits();
+ $this->delete_created_inbox_notes();
$this->delete_created_orders();
WC()->session = $this->original_session;
@@ -222,8 +229,10 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$merchant_email = $this->get_captured_mail_to( (string) get_option( 'admin_email' ), $capture['captures'] );
$this->assertStringContainsString( str_replace( '&', '&', $order->get_edit_order_url() ), (string) $merchant_email['message'], 'The merchant email should link to the matched order.' );
$this->assertStringContainsString( 'View matched order', (string) $merchant_email['message'], 'The merchant email should include clear link text for the matched order.' );
- $this->assertTrue( $this->order_has_note_containing( $order, 'Order withdrawal requested by Jane Doe (jane@example.test).' ), 'The matched order should receive a withdrawal note.' );
- $this->assertTrue( $this->order_has_note_containing( $order, 'Items requested for withdrawal: Line item 1' ), 'Specific-item details should be included in the order note.' );
+ $this->assertTrue( $this->order_has_note_containing( $order, self::ORDER_NOTE_WITHDRAWAL_REQUESTED ), 'The matched order should receive a withdrawal note.' );
+ $this->assertFalse( $this->order_has_note_containing( $order, 'Jane Doe' ), 'The order note should not include the customer name.' );
+ $this->assertFalse( $this->order_has_note_containing( $order, 'jane@example.test' ), 'The order note should not include the customer email address.' );
+ $this->assertFalse( $this->order_has_note_containing( $order, 'Line item 1' ), 'The order note should not include free-form withdrawal details.' );
$this->assert_order_withdrawal_requested( $order );
} finally {
$capture['remove']();
@@ -257,7 +266,7 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertSame( 'confirmation', $state->screen, 'Custom order number submissions should reach the confirmation screen.' );
$this->assertCount( 2, $capture['captures'], 'The customer and merchant emails should both be sent.' );
- $this->assertTrue( $this->order_has_note_containing( $order, 'Order withdrawal requested by Jane Doe (jane@example.test).' ), 'The custom-number matched order should receive a withdrawal note.' );
+ $this->assertTrue( $this->order_has_note_containing( $order, self::ORDER_NOTE_WITHDRAWAL_REQUESTED ), 'The custom-number matched order should receive a withdrawal note.' );
$this->assert_order_withdrawal_requested( $order );
} finally {
remove_filter( 'woocommerce_order_number', $filter, 10 );
@@ -286,7 +295,7 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertCount( 2, $capture['captures'], 'The customer and merchant emails should both be sent.' );
$this->assertStringContainsString( str_replace( '&', '&', $target_order->get_edit_order_url() ), (string) $merchant_email['message'], 'The merchant email should link to the intended order.' );
$this->assertStringNotContainsString( str_replace( '&', '&', $wrong_order->get_edit_order_url() ), (string) $merchant_email['message'], 'The merchant email should not link to the wrong order.' );
- $this->assertTrue( $this->order_has_note_containing( $target_order, 'Order withdrawal requested by Jane Doe (jane@example.test).' ), 'The intended order should receive a withdrawal note.' );
+ $this->assertTrue( $this->order_has_note_containing( $target_order, self::ORDER_NOTE_WITHDRAWAL_REQUESTED ), 'The intended order should receive a withdrawal note.' );
$this->assertFalse( $this->order_has_note_containing( $wrong_order, 'Order withdrawal requested' ), 'The wrong order should not receive a withdrawal note.' );
$this->assert_order_withdrawal_requested( $target_order );
} finally {
@@ -333,7 +342,7 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertCount( 2, $capture['captures'], 'The customer and merchant emails should both be sent.' );
$this->assertStringContainsString( str_replace( '&', '&', $target_order->get_edit_order_url() ), (string) $merchant_email['message'], 'The merchant email should link to the intended order.' );
$this->assertStringNotContainsString( str_replace( '&', '&', $different_name_order->get_edit_order_url() ), (string) $merchant_email['message'], 'The merchant email should not link to the order with a different billing name.' );
- $this->assertTrue( $this->order_has_note_containing( $target_order, 'Order withdrawal requested by Jane Doe (jane@example.test).' ), 'The intended order should receive a withdrawal note.' );
+ $this->assertTrue( $this->order_has_note_containing( $target_order, self::ORDER_NOTE_WITHDRAWAL_REQUESTED ), 'The intended order should receive a withdrawal note.' );
$this->assertFalse( $this->order_has_note_containing( $different_name_order, 'Order withdrawal requested' ), 'The order with a different billing name should not receive a withdrawal note.' );
$this->assert_order_withdrawal_requested( $target_order );
} finally {
@@ -365,6 +374,7 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertStringContainsString( 'already been submitted for this order', $error_notices[0]['notice'], 'The notice should explain that the order already has a withdrawal request.' );
$this->assertCount( 0, $capture['captures'], 'Duplicate matched submissions should not send notification emails.' );
$this->assertFalse( $this->order_has_note_containing( $order, 'Order withdrawal requested' ), 'Duplicate matched submissions should not add another order note.' );
+ $this->assertCount( 0, $this->get_created_inbox_note_ids(), 'Duplicate matched submissions should not create merchant inbox notifications.' );
wc_clear_notices();
$this->prepare_post_request(
@@ -448,16 +458,171 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
}
}
+ /**
+ * @testdox Should add a merchant inbox notification with a view order action when a confirmed submission matches an order.
+ */
+ public function test_process_current_request_adds_inbox_note_with_order_action_for_exact_order_match(): void {
+ $order = $this->create_order_for_form_data();
+ $capture = $this->capture_wp_mail();
+
+ try {
+ $this->prepare_post_request(
+ OrderWithdrawalFormProcessor::ACTION_CONFIRM,
+ array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => (string) $order->get_id() )
+ );
+
+ $state = $this->sut->process_current_request();
+ $note_ids = $this->get_created_inbox_note_ids();
+
+ $this->assertSame( 'confirmation', $state->screen, 'Matched confirm submissions should reach the confirmation screen.' );
+ $this->assertCount( 1, $note_ids, 'A matched submission should create one merchant inbox notification.' );
+
+ $note = Notes::get_note( $note_ids[0] );
+
+ $this->assertInstanceOf( Note::class, $note, 'The merchant inbox notification should be readable.' );
+ $this->assertSame( Note::E_WC_ADMIN_NOTE_INFORMATIONAL, $note->get_type(), 'The inbox notification should be informational.' );
+ $this->assertSame( Note::E_WC_ADMIN_NOTE_UNACTIONED, $note->get_status(), 'The inbox notification should start unactioned.' );
+ $this->assertSame( sprintf( 'Order withdrawal request for #%s', $order->get_order_number() ), $note->get_title(), 'The inbox notification should have the expected title.' );
+ $this->assertStringContainsString( sprintf( 'order #%s', $order->get_order_number() ), $note->get_content(), 'The inbox notification should consistently prefix the order number.' );
+ $this->assertStringContainsString( (string) $order->get_order_number(), $note->get_content(), 'The inbox notification should include the order number.' );
+ $this->assertStringContainsString( 'Review the matched order to confirm the request details.', $note->get_content(), 'The inbox notification should direct merchants to the matched order.' );
+ $this->assertStringNotContainsString( 'Jane Doe', $note->get_content(), 'The inbox notification should not include the customer name.' );
+ $this->assertStringNotContainsString( 'jane@example.test', $note->get_content(), 'The inbox notification should not include the customer email address.' );
+ $this->assertStringNotContainsString( 'Line item 1', $note->get_content(), 'The inbox notification should not include free-form withdrawal details.' );
+
+ $actions = $note->get_actions();
+
+ $this->assertCount( 1, $actions, 'The inbox notification should have one action.' );
+ $this->assertSame( 'view-order', $actions[0]->name, 'The inbox notification action should be the view order action.' );
+ $this->assertSame( $order->get_edit_order_url(), $actions[0]->query, 'The inbox notification action should link to the matched order.' );
+ } finally {
+ $capture['remove']();
+ }
+ }
+
+ /**
+ * @testdox Should skip the merchant inbox notification when no order matches.
+ */
+ public function test_process_current_request_skips_inbox_note_when_order_does_not_match(): void {
+ $order = $this->create_order_for_form_data(
+ array(
+ OrderWithdrawalFormProcessor::FIELD_EMAIL => 'different@example.test',
+ OrderWithdrawalFormProcessor::FIELD_EMAIL_CONFIRMATION => 'different@example.test',
+ )
+ );
+ $capture = $this->capture_wp_mail();
+
+ try {
+ $this->prepare_post_request(
+ OrderWithdrawalFormProcessor::ACTION_CONFIRM,
+ array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => (string) $order->get_id() )
+ );
+
+ $state = $this->sut->process_current_request();
+ $note_ids = $this->get_created_inbox_note_ids();
+
+ $this->assertSame( 'confirmation', $state->screen, 'Unmatched confirm submissions should still reach the confirmation screen.' );
+ $this->assertCount( 0, $note_ids, 'An unmatched submission should not create a merchant inbox notification.' );
+ } finally {
+ $capture['remove']();
+ }
+ }
+
+ /**
+ * @testdox Should delete a matched merchant inbox notification when its order is deleted.
+ */
+ public function test_delete_order_withdrawal_inbox_note_for_order_deletes_matched_inbox_note(): void {
+ $order = $this->create_order_for_form_data();
+ $capture = $this->capture_wp_mail();
+
+ try {
+ $this->prepare_post_request(
+ OrderWithdrawalFormProcessor::ACTION_CONFIRM,
+ array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => (string) $order->get_id() )
+ );
+
+ $this->sut->process_current_request();
+
+ $note_ids = $this->get_created_inbox_note_ids();
+
+ $this->assertCount( 1, $note_ids, 'A matched submission should create one merchant inbox notification.' );
+
+ $this->sut->delete_order_withdrawal_inbox_note_for_order( $order );
+
+ $this->assertCount( 0, $this->get_created_inbox_note_ids(), 'Cleaning up the order should remove the associated merchant inbox notification.' );
+ } finally {
+ $capture['remove']();
+ }
+ }
+
+ /**
+ * @testdox Should not delete a merchant inbox notification for a non-order post ID.
+ */
+ public function test_delete_order_withdrawal_inbox_note_for_order_ignores_non_order_post_ids(): void {
+ $post_id = wp_insert_post(
+ array(
+ 'post_title' => 'Not an order',
+ 'post_status' => 'publish',
+ 'post_type' => 'post',
+ )
+ );
+
+ $this->assertIsInt( $post_id, 'The test post should be created.' );
+
+ $note_name = self::INBOX_NOTE_NAME_PREFIX . $post_id;
+ $note = new Note();
+ $note->set_title( 'Order withdrawal request for non-order post' );
+ $note->set_content( 'This note should not be deleted.' );
+ $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
+ $note->set_name( $note_name );
+ $note->set_source( 'woocommerce-admin' );
+ $note->save();
+
+ try {
+ $this->sut->delete_order_withdrawal_inbox_note_for_order( $post_id );
+
+ $this->assertInstanceOf( Note::class, Notes::get_note_by_name( $note_name ), 'Non-order post deletion should not delete matching inbox notes.' );
+ } finally {
+ Notes::delete_notes_with_name( $note_name );
+ wp_delete_post( $post_id, true );
+ }
+ }
+
+ /**
+ * @testdox Should register cleanup hooks for HPOS and legacy order deletion.
+ */
+ public function test_controller_registers_order_deletion_cleanup_hooks(): void {
+ $controller = new OrderWithdrawalController();
+ $controller->init( $this->sut, new OrderWithdrawalFormView() );
+
+ try {
+ $controller->register();
+
+ $this->assertNotFalse( has_action( 'woocommerce_before_delete_order', array( $this->sut, 'delete_order_withdrawal_inbox_note_for_order' ) ) );
+ $this->assertNotFalse( has_action( 'before_delete_post', array( $this->sut, 'delete_order_withdrawal_inbox_note_for_order' ) ) );
+ } finally {
+ remove_action( FeaturesController::FEATURE_ENABLED_CHANGED_ACTION, array( $controller, 'maybe_flush_rewrite_rules' ), 10 );
+ remove_filter( 'woocommerce_get_query_vars', array( $controller, 'add_query_var' ), 10 );
+ remove_filter( 'woocommerce_endpoint_order-withdrawal_title', array( $controller, 'get_endpoint_title' ), 10 );
+ remove_filter( 'woocommerce_settings_pages', array( $controller, 'add_endpoint_setting' ), 10 );
+ remove_action( 'woocommerce_account_order-withdrawal_endpoint', array( $controller, 'render_view' ), 10 );
+ remove_action( 'woocommerce_before_delete_order', array( $this->sut, 'delete_order_withdrawal_inbox_note_for_order' ), 10 );
+ remove_action( 'before_delete_post', array( $this->sut, 'delete_order_withdrawal_inbox_note_for_order' ), 10 );
+ remove_action( 'woocommerce_privacy_remove_order_personal_data', array( $this->sut, 'delete_order_withdrawal_inbox_note_for_order' ), 10 );
+ }
+ }
+
/**
* @testdox Should keep the user on review with an error notice when notification emails fail.
*/
public function test_process_current_request_surfaces_error_when_emails_fail(): void {
+ $order = $this->create_order_for_form_data();
$capture = $this->capture_wp_mail( false );
try {
$this->prepare_post_request(
OrderWithdrawalFormProcessor::ACTION_CONFIRM,
- array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => '999999999' )
+ array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => (string) $order->get_id() )
);
$state = $this->sut->process_current_request();
@@ -467,11 +632,22 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertCount( 2, $capture['captures'], 'The processor should attempt both notification emails before surfacing the failure.' );
$this->assertNotEmpty( $error_notices, 'Email failures should add an error notice.' );
$this->assertStringContainsString( 'We could not submit your withdrawal request.', $error_notices[0]['notice'], 'The error notice should tell the user the submission did not complete.' );
+ $this->assertFalse( $this->order_has_note_containing( $order, 'Order withdrawal requested' ), 'Email failures should not add a retryable request to the order notes.' );
+
+ $updated_order = wc_get_order( $order->get_id() );
+
+ $this->assertInstanceOf( WC_Order::class, $updated_order, 'The matched order should still exist.' );
+ $this->assertNotSame(
+ 'yes',
+ $updated_order->get_meta( self::ORDER_WITHDRAWAL_REQUESTED_META_KEY, true, 'edit' ),
+ 'Email failures should not mark the matched order as having a withdrawal request.'
+ );
+ $this->assertCount( 0, $this->get_created_inbox_note_ids(), 'Email failures should not create merchant inbox notifications.' );
wc_clear_notices();
$this->prepare_post_request(
OrderWithdrawalFormProcessor::ACTION_CONFIRM,
- array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => '999999999' )
+ array( OrderWithdrawalFormProcessor::FIELD_ORDER_NUMBER => (string) $order->get_id() )
);
$second_state = $this->sut->process_current_request();
@@ -797,6 +973,40 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
$this->assertSame( 'yes', $updated_order->get_meta( self::ORDER_WITHDRAWAL_REQUESTED_META_KEY, true, 'edit' ), 'The matched order should be flagged as having a withdrawal request.' );
}
+ /**
+ * Get the IDs of order withdrawal inbox notes created during a test.
+ *
+ * @return int[]
+ */
+ private function get_created_inbox_note_ids(): array {
+ $note_ids = array();
+
+ foreach ( $this->created_order_ids as $order_id ) {
+ $note = Notes::get_note_by_name( self::INBOX_NOTE_NAME_PREFIX . $order_id );
+
+ if ( $note instanceof Note ) {
+ $note_ids[] = $note->get_id();
+ }
+ }
+
+ return array_map( 'intval', $note_ids );
+ }
+
+ /**
+ * Delete inbox notes created during a test.
+ */
+ private function delete_created_inbox_notes(): void {
+ $note_names = array();
+
+ foreach ( $this->created_order_ids as $order_id ) {
+ $note_names[] = self::INBOX_NOTE_NAME_PREFIX . $order_id;
+ }
+
+ if ( ! empty( $note_names ) ) {
+ Notes::delete_notes_with_name( $note_names );
+ }
+ }
+
/**
* Delete orders created during a test.
*/