Commit 7f485054a9 for woocommerce
commit 7f485054a9a62aa92fcd185052cf1b1f44cc74b0
Author: Leonardo Lopes de Albuquerque <leonardo.albuquerque@automattic.com>
Date: Sun Jan 11 12:04:14 2026 -0300
Fraud Protection: Display blocked notice on add payment method page (#62757)
* Fraud Protection: Display blocked notice on add payment method page
Add user-friendly messaging to the My Account add payment method page
for blocked sessions. The page was already protected (no gateways available)
by https://github.com/woocommerce/woocommerce/pull/62691 but lacked an
explanation for the user.
* Add generic session blocked message to display on payment methods page
---------
Co-authored-by: Luiz Reis <luiz.reis@automattic.com>
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/BlockedSessionNotice.php b/plugins/woocommerce/src/Internal/FraudProtection/BlockedSessionNotice.php
index 317ea1c980..02f9e50545 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/BlockedSessionNotice.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/BlockedSessionNotice.php
@@ -52,20 +52,39 @@ class BlockedSessionNotice implements RegisterHooksInterface {
* @return void
*/
public function register(): void {
- add_action( 'woocommerce_before_checkout_form', array( $this, 'display_blocked_notice' ), 1, 0 );
+ add_action( 'woocommerce_before_checkout_form', array( $this, 'display_checkout_blocked_notice' ), 1, 0 );
+ add_action( 'before_woocommerce_add_payment_method', array( $this, 'display_generic_blocked_notice' ), 1, 0 );
}
/**
* Display blocked notice on shortcode checkout page.
*
- * Shows a user-friendly message explaining that the request cannot be
+ * Shows a checkout-specific message explaining that the purchase cannot be
+ * completed online and provides contact information for support.
+ *
+ * @internal
+ *
+ * @return void
+ */
+ public function display_checkout_blocked_notice(): void {
+ if ( ! $this->session_manager->is_session_blocked() ) {
+ return;
+ }
+
+ wc_print_notice( $this->get_message_html( 'checkout' ), 'error' );
+ }
+
+ /**
+ * Display blocked notice for non-checkout pages.
+ *
+ * Shows a generic message explaining that the request cannot be
* processed online and provides contact information for support.
*
* @internal
*
* @return void
*/
- public function display_blocked_notice(): void {
+ public function display_generic_blocked_notice(): void {
if ( ! $this->session_manager->is_session_blocked() ) {
return;
}
@@ -76,16 +95,26 @@ class BlockedSessionNotice implements RegisterHooksInterface {
/**
* Get the blocked session message as HTML.
*
- * Includes a mailto link for the support email. Used by shortcode checkout.
+ * Includes a mailto link for the support email.
*
+ * @param string $context Message context: 'checkout' for purchase-specific message, 'generic' for general use.
* @return string HTML message with mailto link.
*/
- public function get_message_html(): string {
+ public function get_message_html( string $context = 'generic' ): string {
$email = WC()->mailer()->get_from_address();
+ if ( 'checkout' === $context ) {
+ return sprintf(
+ /* translators: %1$s: mailto link, %2$s: email address */
+ __( 'We are unable to process this request online. Please <a href="%1$s">contact support (%2$s)</a> to complete your purchase.', 'woocommerce' ),
+ esc_url( 'mailto:' . $email ),
+ esc_html( $email )
+ );
+ }
+
return sprintf(
/* translators: %1$s: mailto link, %2$s: email address */
- __( 'We are unable to process this request online. Please <a href="%1$s">contact support (%2$s)</a> to complete your purchase.', 'woocommerce' ),
+ __( 'We are unable to process this request online. Please <a href="%1$s">contact support (%2$s)</a> for assistance.', 'woocommerce' ),
esc_url( 'mailto:' . $email ),
esc_html( $email )
);
@@ -96,14 +125,23 @@ class BlockedSessionNotice implements RegisterHooksInterface {
*
* Used by Store API responses where HTML is not supported.
*
+ * @param string $context Message context: 'checkout' for purchase-specific message, 'generic' for general use.
* @return string Plaintext message with email address.
*/
- public function get_message_plaintext(): string {
+ public function get_message_plaintext( string $context = 'generic' ): string {
$email = WC()->mailer()->get_from_address();
+ if ( 'checkout' === $context ) {
+ return sprintf(
+ /* translators: %s: support email address */
+ __( 'We are unable to process this request online. Please contact support (%s) to complete your purchase.', 'woocommerce' ),
+ $email
+ );
+ }
+
return sprintf(
/* translators: %s: support email address */
- __( 'We are unable to process this request online. Please contact support (%s) to complete your purchase.', 'woocommerce' ),
+ __( 'We are unable to process this request online. Please contact support (%s) for assistance.', 'woocommerce' ),
$email
);
}
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
index d488769982..cc82c98bd7 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
@@ -161,7 +161,7 @@ class Checkout extends AbstractCartRoute {
&& wc_get_container()->get( SessionClearanceManager::class )->is_session_blocked() ) {
$response = $this->get_route_error_response(
'woocommerce_rest_checkout_error',
- wc_get_container()->get( BlockedSessionNotice::class )->get_message_plaintext(),
+ wc_get_container()->get( BlockedSessionNotice::class )->get_message_plaintext( 'checkout' ),
403
);
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
index 10e5628bf6..9aa32670d8 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
@@ -1839,5 +1839,6 @@ class Checkout extends MockeryTestCase {
$this->assertEquals( 403, $response->get_status(), 'Should return 403 when session is blocked' );
$this->assertEquals( 'woocommerce_rest_checkout_error', $response->get_data()['code'] );
$this->assertStringContainsString( 'unable to process this request online', $response->get_data()['message'] );
+ $this->assertStringContainsString( 'to complete your purchase', $response->get_data()['message'], 'Should use checkout-specific message' );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
index 33452e182b..2cc710b7b4 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
@@ -53,11 +53,12 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
public function tearDown(): void {
parent::tearDown();
remove_all_actions( 'woocommerce_before_checkout_form' );
+ remove_all_actions( 'before_woocommerce_add_payment_method' );
delete_option( 'woocommerce_email_from_address' );
}
/**
- * @testdox Should display error notice when woocommerce_before_checkout_form action fires for blocked sessions.
+ * @testdox Should display checkout-specific error notice when woocommerce_before_checkout_form action fires for blocked sessions.
*/
public function test_checkout_action_displays_blocked_message(): void {
$this->mock_session_manager->method( 'is_session_blocked' )->willReturn( true );
@@ -67,6 +68,7 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
$output = ob_get_clean();
$this->assertStringContainsString( 'unable to process this request online', $output, 'Should display blocked message on checkout' );
+ $this->assertStringContainsString( 'to complete your purchase', $output, 'Should display checkout-specific message' );
$this->assertStringContainsString( 'support@example.com', $output, 'Should include support email in message' );
$this->assertStringContainsString( 'mailto:support@example.com', $output, 'Should include mailto link' );
}
@@ -85,10 +87,39 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
}
/**
- * @testdox get_message_html should return the expected HTML message with mailto link.
+ * @testdox Should display generic error notice when before_woocommerce_add_payment_method action fires for blocked sessions.
*/
- public function test_get_message_html(): void {
- $message = $this->sut->get_message_html();
+ public function test_add_payment_method_action_displays_blocked_message(): void {
+ $this->mock_session_manager->method( 'is_session_blocked' )->willReturn( true );
+
+ ob_start();
+ do_action( 'before_woocommerce_add_payment_method' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString( 'unable to process this request online', $output, 'Should display blocked message on add payment method page' );
+ $this->assertStringContainsString( 'for assistance', $output, 'Should display generic message' );
+ $this->assertStringContainsString( 'support@example.com', $output, 'Should include support email in message' );
+ $this->assertStringContainsString( 'mailto:support@example.com', $output, 'Should include mailto link' );
+ }
+
+ /**
+ * @testdox Should not display message when add payment method action fires for non-blocked sessions.
+ */
+ public function test_add_payment_method_action_no_message_for_non_blocked_session(): void {
+ $this->mock_session_manager->method( 'is_session_blocked' )->willReturn( false );
+
+ ob_start();
+ do_action( 'before_woocommerce_add_payment_method' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+ $output = ob_get_clean();
+
+ $this->assertEmpty( $output, 'Non-blocked sessions should not display any message' );
+ }
+
+ /**
+ * @testdox get_message_html should return checkout-specific message when context is 'checkout'.
+ */
+ public function test_get_message_html_checkout_context(): void {
+ $message = $this->sut->get_message_html( 'checkout' );
$this->assertEquals(
'We are unable to process this request online. Please <a href="mailto:support@example.com">contact support (support@example.com)</a> to complete your purchase.',
@@ -97,14 +128,40 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
}
/**
- * @testdox get_message_plaintext should return the expected plaintext message.
+ * @testdox get_message_html should return generic message when context is 'generic' or not specified.
+ */
+ public function test_get_message_html_generic_context(): void {
+ $message_default = $this->sut->get_message_html();
+ $message_explicit = $this->sut->get_message_html( 'generic' );
+
+ $expected = 'We are unable to process this request online. Please <a href="mailto:support@example.com">contact support (support@example.com)</a> for assistance.';
+
+ $this->assertEquals( $expected, $message_default, 'Default context should return generic message' );
+ $this->assertEquals( $expected, $message_explicit, 'Explicit generic context should return generic message' );
+ }
+
+ /**
+ * @testdox get_message_plaintext should return checkout-specific message when context is 'checkout'.
*/
- public function test_get_message_plaintext(): void {
- $message = $this->sut->get_message_plaintext();
+ public function test_get_message_plaintext_checkout_context(): void {
+ $message = $this->sut->get_message_plaintext( 'checkout' );
$this->assertEquals(
'We are unable to process this request online. Please contact support (support@example.com) to complete your purchase.',
$message
);
}
+
+ /**
+ * @testdox get_message_plaintext should return generic message when context is 'generic' or not specified.
+ */
+ public function test_get_message_plaintext_generic_context(): void {
+ $message_default = $this->sut->get_message_plaintext();
+ $message_explicit = $this->sut->get_message_plaintext( 'generic' );
+
+ $expected = 'We are unable to process this request online. Please contact support (support@example.com) for assistance.';
+
+ $this->assertEquals( $expected, $message_default, 'Default context should return generic message' );
+ $this->assertEquals( $expected, $message_explicit, 'Explicit generic context should return generic message' );
+ }
}