Commit 1740387ec5 for woocommerce

commit 1740387ec557dbe462159157d65d5ef7b4d732ce
Author: Mayisha <33387139+Mayisha@users.noreply.github.com>
Date:   Thu Dec 4 15:23:39 2025 +0600

    PayPal Standard: Show account restricted notice (#62058)

    * set flag to show/hide paypal account restricted notice

    * render notice html

    * match response data

    * extract duplicate code to common method

    * rename option

    * use constant and more common methods

    * refactor maybe_add_or_remove_notice function

    * check before changing the flag

    * use explicit type

    * add screen check

    * add type safety

    * cast type

    * use hook to show/hide notice

    * fix lint

    * rename method

    * fix lint

    * ensure response_data is array

    * add log

    * add aria label

    * singleton instance of notice

    * update action comment

    * remove singleton as unnecessary

    * re-add public methods and deprecate properly

    * fix deprecated method

    * use separate option

    * simplify logic

    * move add_action to the gateway class

    * additional check to fix phpstan

    * address feedback

    * update baseline

diff --git a/plugins/woocommerce/changelog/62058-task-paypal-standard-account-restricted-notice b/plugins/woocommerce/changelog/62058-task-paypal-standard-account-restricted-notice
new file mode 100644
index 0000000000..c43ed9d00c
--- /dev/null
+++ b/plugins/woocommerce/changelog/62058-task-paypal-standard-account-restricted-notice
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Display a notice when merchant's PayPal Standard account is locked or restricted by PayPal.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php b/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php
index f46f6e907c..662571cf85 100644
--- a/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php
+++ b/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php
@@ -30,6 +30,10 @@ if ( ! class_exists( 'WC_Gateway_Paypal_Buttons' ) ) {
 	require_once __DIR__ . '/class-wc-gateway-paypal-buttons.php';
 }

+if ( ! class_exists( 'WC_Gateway_Paypal_Notices' ) ) {
+	require_once __DIR__ . '/includes/class-wc-gateway-paypal-notices.php';
+}
+
 /**
  * WC_Gateway_Paypal Class.
  */
@@ -211,6 +215,9 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
 				// Hook for updating the shipping information on order approval (Orders v2).
 				add_action( 'woocommerce_before_thankyou', array( $this, 'update_addresses_in_order' ), 10 );

+				// Hook for PayPal order responses to manage account restriction notices.
+				add_action( 'woocommerce_paypal_standard_order_created_response', array( $this, 'manage_account_restriction_status' ), 10, 3 );
+
 				$buttons = new WC_Gateway_Paypal_Buttons( $this );
 				if ( $buttons->is_enabled() && ! $this->needs_setup() ) {
 					add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
@@ -1019,6 +1026,37 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
 		$this->update_option( 'transact_onboarding_complete', 'yes' );
 		$this->transact_onboarding_complete = true;
 	}
+
+	/**
+	 * Handle PayPal order response to manage account restriction notices.
+	 *
+	 * This method is called via the 'woocommerce_paypal_standard_order_created_response' hook
+	 * and manages the account restriction flag based on PayPal API responses.
+	 *
+	 * Extensions can disable this feature using the filter:
+	 * add_filter( 'woocommerce_paypal_account_restriction_notices_enabled', '__return_false' );
+	 *
+	 * @param int|string $http_code     The HTTP status code from the PayPal API response.
+	 * @param array      $response_data The decoded response data from the PayPal API.
+	 * @param WC_Order   $order         The WooCommerce order object.
+	 * @return void
+	 */
+	public function manage_account_restriction_status( $http_code, $response_data, $order ): void {
+		/**
+		 * Filters whether account restriction notices should be enabled.
+		 *
+		 * This filter allows extensions to opt out of the account restriction notice functionality.
+		 *
+		 * @since 10.4.0
+		 *
+		 * @param bool $enabled Whether account restriction notices are enabled. Default true.
+		 */
+		if ( ! apply_filters( 'woocommerce_paypal_account_restriction_notices_enabled', true ) ) {
+			return;
+		}
+
+		WC_Gateway_Paypal_Notices::manage_account_restriction_flag_for_notice( $http_code, $response_data, $order );
+	}
 }

 // Initialize PayPal admin notices handler on 'init' hook to ensure the class loads before admin_init and admin_notices hooks fire.
diff --git a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php
index 9c46a9a7c8..757a1e34fa 100644
--- a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php
+++ b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php
@@ -17,6 +17,30 @@ require_once __DIR__ . '/class-wc-gateway-paypal-helper.php';
  * Class WC_Gateway_Paypal_Notices.
  */
 class WC_Gateway_Paypal_Notices {
+	/**
+	 * The name of the notice for PayPal migration.
+	 *
+	 * @var string
+	 */
+	private const PAYPAL_MIGRATION_NOTICE = 'paypal_migration_completed';
+
+	/**
+	 * The name of the notice for PayPal account restriction.
+	 *
+	 * @var string
+	 */
+	private const PAYPAL_ACCOUNT_RESTRICTED_NOTICE = 'paypal_account_restricted';
+
+	/**
+	 * PayPal account restriction issue codes from PayPal API.
+	 *
+	 * @var array
+	 */
+	protected const PAYPAL_ACCOUNT_RESTRICTION_ISSUES = array(
+		'PAYEE_ACCOUNT_LOCKED_OR_CLOSED',
+		'PAYEE_ACCOUNT_RESTRICTED',
+	);
+
 	/**
 	 * The PayPal gateway instance.
 	 *
@@ -24,6 +48,7 @@ class WC_Gateway_Paypal_Notices {
 	 */
 	private $gateway;

+
 	/**
 	 * Constructor.
 	 */
@@ -33,20 +58,23 @@ class WC_Gateway_Paypal_Notices {
 			return;
 		}

-		add_action( 'admin_notices', array( $this, 'add_paypal_migration_notice' ) );
+		// Only register admin notice hooks in the admin area.
+		if ( is_admin() ) {
+			add_action( 'admin_notices', array( $this, 'add_paypal_notices' ) );

-		// Use admin_head to inject notice on payments settings page.
-		// This bypasses the suppress_admin_notices() function which removes all admin_notices hooks on the payments page.
-		// This is a workaround to avoid the notice being suppressed by the suppress_admin_notices() function.
-		add_action( 'admin_head', array( $this, 'add_paypal_migration_notice_on_payments_settings_page' ) );
+			// Use admin_head to inject notice on payments settings page.
+			// This bypasses the suppress_admin_notices() function which removes all admin_notices hooks on the payments page.
+			// This is a workaround to avoid the notice being suppressed by the suppress_admin_notices() function.
+			add_action( 'admin_head', array( $this, 'add_paypal_notices_on_payments_settings_page' ) );
+		}
 	}

 	/**
-	 * Add notice warning about the migration to PayPal Payments.
+	 * Add PayPal Standard notices.
 	 *
 	 * @return void
 	 */
-	public function add_paypal_migration_notice() {
+	public function add_paypal_notices() {
 		// Show only to users who can manage the site.
 		if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'manage_options' ) ) {
 			return;
@@ -57,17 +85,55 @@ class WC_Gateway_Paypal_Notices {
 			return;
 		}

+		$this->add_paypal_migration_notice();
+		$this->add_paypal_account_restricted_notice();
+	}
+
+	/**
+	 * Add PayPal notices on the payments settings page.
+	 *
+	 * @return void
+	 */
+	public function add_paypal_notices_on_payments_settings_page() {
+		global $current_tab, $current_section;
+
+		$screen    = get_current_screen();
+		$screen_id = $screen ? $screen->id : '';
+
+		$is_payments_settings_page = 'woocommerce_page_wc-settings' === $screen_id && 'checkout' === $current_tab && empty( $current_section );
+
+		// Only add the notice from this callback on the payments settings page.
+		if ( ! $is_payments_settings_page ) {
+			return;
+		}
+
+		$this->add_paypal_notices();
+	}
+
+	/**
+	 * Add notice warning about the migration to PayPal Payments on the Payments settings page.
+	 *
+	 * @deprecated 10.4.0 No longer used. Functionality is now handled by add_paypal_notices_on_payments_settings_page().
+	 * @return void
+	 */
+	public function add_paypal_migration_notice_on_payments_settings_page() {
+		wc_deprecated_function( __METHOD__, '10.4.0', 'WC_Gateway_Paypal_Notices::add_paypal_notices_on_payments_settings_page' );
+		$this->add_paypal_notices_on_payments_settings_page();
+	}
+
+	/**
+	 * Add notice warning about the migration to PayPal Payments.
+	 *
+	 * @return void
+	 */
+	public function add_paypal_migration_notice() {
 		// Skip if the notice has been dismissed.
-		if ( $this->paypal_migration_notice_dismissed() ) {
+		if ( $this->is_notice_dismissed( self::PAYPAL_MIGRATION_NOTICE ) ) {
 			return;
 		}

 		$doc_url     = 'https://woocommerce.com/document/woocommerce-paypal-payments/paypal-payments-upgrade-guide/';
-		$dismiss_url = wp_nonce_url(
-			add_query_arg( 'wc-hide-notice', 'paypal_migration_completed' ),
-			'woocommerce_hide_notices_nonce',
-			'_wc_notice_nonce'
-		);
+		$dismiss_url = $this->get_dismiss_url( self::PAYPAL_MIGRATION_NOTICE );
 		$message     = sprintf(
 			/* translators: 1: opening <a> tag, 2: closing </a> tag */
 			esc_html__( 'WooCommerce has upgraded your PayPal integration from PayPal Standard to PayPal Payments (PPCP), for a more reliable and modern checkout experience. If you do not prefer the upgraded integration in WooCommerce, we recommend switching to %1$sPayPal Payments%2$s extension.', 'woocommerce' ),
@@ -76,7 +142,7 @@ class WC_Gateway_Paypal_Notices {
 		);

 		$notice_html = '<div class="notice notice-warning is-dismissible">'
-			. '<a class="woocommerce-message-close notice-dismiss" style="text-decoration: none;" href="' . esc_url( $dismiss_url ) . '"></a>'
+			. '<a class="woocommerce-message-close notice-dismiss" style="text-decoration: none;" href="' . esc_url( $dismiss_url ) . '" aria-label="' . esc_attr__( 'Dismiss this notice', 'woocommerce' ) . '"></a>'
 			. '<p>' . $message . '</p>'
 			. '</div>';

@@ -84,27 +150,132 @@ class WC_Gateway_Paypal_Notices {
 	}

 	/**
-	 * Add notice warning about the migration to PayPal Payments on the Payments settings page.
+	 * Add notice warning about PayPal account restriction.
 	 *
 	 * @return void
 	 */
-	public function add_paypal_migration_notice_on_payments_settings_page() {
-		global $current_tab, $current_section;
-		$is_payments_settings_page = 'woocommerce_page_wc-settings' === get_current_screen()->id && 'checkout' === $current_tab && empty( $current_section );
+	private function add_paypal_account_restricted_notice() {
+		// Skip if there's no account restriction flag.
+		if ( ! $this->has_account_restriction_flag() ) {
+			return;
+		}

-		// Only add the notice from this callback on the payments settings page.
-		if ( ! $is_payments_settings_page ) {
+		// Skip if the notice has been dismissed.
+		if ( $this->is_notice_dismissed( self::PAYPAL_ACCOUNT_RESTRICTED_NOTICE ) ) {
 			return;
 		}
-		$this->add_paypal_migration_notice();
+
+		$support_url = 'https://www.paypal.com/smarthelp/contact-us';
+		$dismiss_url = $this->get_dismiss_url( self::PAYPAL_ACCOUNT_RESTRICTED_NOTICE );
+		$message     = sprintf(
+			/* translators: 1: opening <a> tag, 2: closing </a> tag */
+			esc_html__( 'Your PayPal account has been restricted by PayPal. This may prevent customers from completing payments. Please %1$scontact PayPal support%2$s to resolve this issue and restore full functionality to your account.', 'woocommerce' ),
+			'<a href="' . esc_url( $support_url ) . '" target="_blank" rel="noopener noreferrer">',
+			'</a>',
+		);
+
+		$notice_html = '<div class="notice notice-error is-dismissible">'
+			. '<a class="woocommerce-message-close notice-dismiss" style="text-decoration: none;" href="' . esc_url( $dismiss_url ) . '" aria-label="' . esc_attr__( 'Dismiss this notice', 'woocommerce' ) . '"></a>'
+			. '<p><strong>' . esc_html__( 'PayPal Account Restricted', 'woocommerce' ) . '</strong></p>'
+			. '<p>' . $message . '</p>'
+			. '</div>';
+
+		echo wp_kses_post( $notice_html );
+	}
+
+	/**
+	 * Get the dismiss URL for a notice.
+	 *
+	 * @param string $notice_name The name of the notice.
+	 * @return string
+	 */
+	private function get_dismiss_url( string $notice_name ): string {
+		return wp_nonce_url(
+			add_query_arg( 'wc-hide-notice', $notice_name ),
+			'woocommerce_hide_notices_nonce',
+			'_wc_notice_nonce'
+		);
+	}
+
+	/**
+	 * Check if the notice has been dismissed.
+	 *
+	 * @param string $notice_name The name of the notice.
+	 * @return bool
+	 */
+	private function is_notice_dismissed( string $notice_name ): bool {
+		return (bool) get_user_meta( get_current_user_id(), 'dismissed_' . $notice_name . '_notice', true );
 	}

 	/**
 	 * Check if the installation notice has been dismissed.
 	 *
+	 * @deprecated 10.4.0 No longer used. Functionality is now handled by is_notice_dismissed().
 	 * @return bool
 	 */
-	protected static function paypal_migration_notice_dismissed() {
-		return get_user_meta( get_current_user_id(), 'dismissed_paypal_migration_completed_notice', true );
+	protected static function paypal_migration_notice_dismissed(): bool {
+		wc_deprecated_function( __METHOD__, '10.4.0', 'WC_Gateway_Paypal_Notices::is_notice_dismissed' );
+		return (bool) get_user_meta( get_current_user_id(), 'dismissed_paypal_migration_completed_notice', true );
+	}
+
+	/**
+	 * Check if there's a flag indicating PayPal account restriction.
+	 *
+	 * @return bool
+	 */
+	private function has_account_restriction_flag(): bool {
+		return 'yes' === get_option( 'woocommerce_paypal_account_restricted_status', 'no' );
+	}
+
+	/**
+	 * Set the flag indicating PayPal account restriction.
+	 *
+	 * @return void
+	 */
+	public static function set_account_restriction_flag(): void {
+		if ( 'no' === get_option( 'woocommerce_paypal_account_restricted_status', 'no' ) ) {
+			update_option( 'woocommerce_paypal_account_restricted_status', 'yes', false );
+		}
+	}
+
+	/**
+	 * Clear the flag indicating PayPal account restriction.
+	 *
+	 * @return void
+	 */
+	public static function clear_account_restriction_flag(): void {
+		if ( 'yes' === get_option( 'woocommerce_paypal_account_restricted_status', 'no' ) ) {
+			update_option( 'woocommerce_paypal_account_restricted_status', 'no' );
+		}
+	}
+
+	/**
+	 * Handle PayPal order response to manage account restriction notices.
+	 *
+	 * @param int|string $http_code     The HTTP status code from the PayPal API response.
+	 * @param array      $response_data The decoded response data from the PayPal API.
+	 * @param WC_Order   $order         The WooCommerce order object.
+	 * @return void
+	 */
+	public static function manage_account_restriction_flag_for_notice( $http_code, array $response_data, WC_Order $order ): void {
+		// Clear the restriction flag on successful responses.
+		if ( in_array( (int) $http_code, array( 200, 201 ), true ) ) {
+			self::clear_account_restriction_flag();
+			return;
+		}
+
+		if ( empty( $response_data ) ) {
+			return;
+		}
+
+		// Set the restriction flag for account-related errors.
+		if ( 422 === (int) $http_code ) {
+			$issue = isset( $response_data['details'][0]['issue'] ) ? $response_data['details'][0]['issue'] : '';
+
+			if ( in_array( $issue, self::PAYPAL_ACCOUNT_RESTRICTION_ISSUES, true ) ) {
+				WC_Gateway_Paypal::log( 'PayPal account restriction flag set due to issues when handling the order: ' . $order->get_id() );
+				self::set_account_restriction_flag();
+			}
+		}
 	}
 }
diff --git a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php
index 12d2be08cc..25c439935c 100644
--- a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php
+++ b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php
@@ -158,6 +158,27 @@ class WC_Gateway_Paypal_Request {
 			$body          = wp_remote_retrieve_body( $response );
 			$response_data = json_decode( $body, true );

+			$response_array = is_array( $response_data ) ? $response_data : array();
+
+			/**
+			 * Fires after receiving a response from PayPal order creation.
+			 *
+			 * This hook allows extensions to react to PayPal API responses, such as
+			 * displaying admin notices or logging response data.
+			 *
+			 * Note: This hook fires on EVERY order creation attempt (success or failure),
+			 * and can be called multiple times for the same order if retried. Extensions
+			 * hooking this should be idempotent and check order state/meta before taking
+			 * action to avoid duplicate processing.
+			 *
+			 * @since 10.4.0
+			 *
+			 * @param int|string $http_code     The HTTP status code from the PayPal API response.
+			 * @param array      $response_data The decoded response data from the PayPal API
+			 * @param WC_Order   $order         The WooCommerce order object.
+			 */
+			do_action( 'woocommerce_paypal_standard_order_created_response', $http_code, $response_array, $order );
+
 			if ( ! in_array( $http_code, array( 200, 201 ), true ) ) {
 				$paypal_debug_id = isset( $response_data['debug_id'] ) ? $response_data['debug_id'] : null;
 				throw new Exception( 'PayPal order creation failed. Response status: ' . $http_code . '. Response body: ' . $body );
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 25386a0754..3acf04d8f4 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -33012,18 +33012,6 @@ parameters:
 			count: 1
 			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php

-		-
-			message: '#^Cannot access property \$id on WP_Screen\|null\.$#'
-			identifier: property.nonObject
-			count: 1
-			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php
-
-		-
-			message: '#^Method WC_Gateway_Paypal_Notices\:\:paypal_migration_notice_dismissed\(\) should return bool but returns mixed\.$#'
-			identifier: return.type
-			count: 1
-			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-notices.php
-
 		-
 			message: '#^Binary operation "\." between ''WooCommerce/'' and mixed results in an error\.$#'
 			identifier: binaryOp.invalid