Commit c3270b5c736 for woocommerce
commit c3270b5c736d80295d3a06c8b05a33439b0ce104
Author: Tom Cafferkey <tjcafferkey@gmail.com>
Date: Tue Aug 4 14:48:58 2026 +0100
Add withdrawal window warning for merchants (#67160)
* Add withdrawal window warning for merchants
* Add changelog for withdrawal window warning
* Update warning message
* Merge condition statementsfor messaging
* Add regression test
* Translator comments
* Merge tests
* Merge
* Fix linting
diff --git a/plugins/woocommerce/changelog/add-withdraw-order-window-warning b/plugins/woocommerce/changelog/add-withdraw-order-window-warning
new file mode 100644
index 00000000000..84975598080
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-withdraw-order-window-warning
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Add merchant warning for order withdrawal requests outside the 14-day window.
diff --git a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
index 67956193205..173f5bc9301 100644
--- a/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
+++ b/plugins/woocommerce/src/Internal/OrderWithdrawal/OrderWithdrawalFormProcessor.php
@@ -40,6 +40,8 @@ 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 WITHDRAWAL_WINDOW_IN_DAYS = 14;
+ private const WITHDRAWAL_WINDOW_IN_SECONDS = self::WITHDRAWAL_WINDOW_IN_DAYS * DAY_IN_SECONDS;
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_';
@@ -481,6 +483,16 @@ final class OrderWithdrawalFormProcessor {
*/
private function add_order_withdrawal_inbox_note( WC_Order $matched_order ): void {
try {
+ $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()
+ );
+
+ if ( $this->is_order_outside_withdrawal_window( $matched_order ) ) {
+ $content .= ' ' . $this->get_withdrawal_window_warning_message();
+ }
+
$note = new Note();
$note->set_title(
sprintf(
@@ -489,13 +501,7 @@ final class OrderWithdrawalFormProcessor {
$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_content( $content );
$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' );
@@ -541,6 +547,33 @@ final class OrderWithdrawalFormProcessor {
}
}
+ /**
+ * Whether the matched order is outside the valid withdrawal request window.
+ *
+ * @param WC_Order $order Matched order.
+ */
+ private function is_order_outside_withdrawal_window( WC_Order $order ): bool {
+ $date_created = $order->get_date_created( 'edit' );
+
+ if ( ! $date_created ) {
+ return false;
+ }
+
+ return ( time() - self::WITHDRAWAL_WINDOW_IN_SECONDS ) > $date_created->getTimestamp();
+ }
+
+ /**
+ * Get the warning shown to merchants when a request is outside the valid window.
+ */
+ private function get_withdrawal_window_warning_message(): string {
+ return sprintf(
+ /* translators: 1: number of days since the order was placed. 2: length of the withdrawal window in days. */
+ __( 'This order is older than %1$d days. Only orders within %2$d days of delivery are eligible for withdrawal.', 'woocommerce' ),
+ self::WITHDRAWAL_WINDOW_IN_DAYS,
+ self::WITHDRAWAL_WINDOW_IN_DAYS
+ );
+ }
+
/**
* Whether the matched order already has a submitted withdrawal request.
*
@@ -649,6 +682,10 @@ final class OrderWithdrawalFormProcessor {
if ( $matched_order instanceof WC_Order ) {
$order_url = $matched_order->get_edit_order_url();
+ if ( $this->is_order_outside_withdrawal_window( $matched_order ) ) {
+ $body .= '<p>' . esc_html( $this->get_withdrawal_window_warning_message() ) . '</p>';
+ }
+
$body .= sprintf(
'<p>%s</p>',
sprintf(
diff --git a/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php b/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
index 9d997df6389..af70f40538a 100644
--- a/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/OrderWithdrawal/OrderWithdrawalTest.php
@@ -500,6 +500,55 @@ class OrderWithdrawalTest extends WC_Unit_Test_Case {
}
}
+ /**
+ * @testdox Should warn merchants only when the matched order is older than fourteen days.
+ * @testWith [15, true]
+ * [13, false]
+ *
+ * @param int $order_age_days Order age in days.
+ * @param bool $is_warning_expected Whether the warning should be included for merchants.
+ */
+ public function test_process_current_request_warns_merchants_only_for_orders_older_than_fourteen_days( int $order_age_days, bool $is_warning_expected ): void {
+ $order = $this->create_order_for_form_data();
+ $order->set_date_created( time() - ( $order_age_days * DAY_IN_SECONDS ) );
+ $order->save();
+
+ $capture = $this->capture_wp_mail();
+ $warning_message = 'This order is older than 14 days. Only orders within 14 days of delivery are eligible for withdrawal.';
+
+ 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.' );
+
+ $customer_email = $this->get_captured_mail_to( 'jane@example.test', $capture['captures'] );
+ $merchant_email = $this->get_captured_mail_to( (string) get_option( 'admin_email' ), $capture['captures'] );
+
+ if ( $is_warning_expected ) {
+ $this->assertStringContainsString( $warning_message, $note->get_content(), 'The inbox notification should warn when the order is outside the withdrawal window.' );
+ $this->assertStringContainsString( $warning_message, (string) $merchant_email['message'], 'The merchant email should warn when the order is outside the withdrawal window.' );
+ } else {
+ $this->assertStringNotContainsString( $warning_message, $note->get_content(), 'The inbox notification should not warn when the order is inside the withdrawal window.' );
+ $this->assertStringNotContainsString( $warning_message, (string) $merchant_email['message'], 'The merchant email should not warn when the order is inside the withdrawal window.' );
+ }
+
+ $this->assertStringNotContainsString( $warning_message, (string) $customer_email['message'], 'The customer email should not include the merchant withdrawal window warning.' );
+ } finally {
+ $capture['remove']();
+ }
+ }
+
/**
* @testdox Should skip the merchant inbox notification when no order matches.
*/