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