Commit 203c546b78 for woocommerce

commit 203c546b7841e54b1298e21361ef192308f3546a
Author: Mayisha <33387139+Mayisha@users.noreply.github.com>
Date:   Sat Dec 27 00:45:36 2025 +0600

    PayPal Standard Refactor 2: Move helper file under src folder (#62596)

    * create paypal standard  helper class under includes

    * deprecate old helper class

    * use the new helper class in gateway

    * move test file

    * add tests for update_addresses_in_order

    * Add changefile(s) from automation for the following project(s): woocommerce

    * fix lint

    * fix phpstan

    * update baseline

    * address backward compatibility suggestion

    * address bc comment about return type

    * remove duplicate test

diff --git a/plugins/woocommerce/changelog/62596-refactor-paypal-standard-2-helper b/plugins/woocommerce/changelog/62596-refactor-paypal-standard-2-helper
new file mode 100644
index 0000000000..9d7583b405
--- /dev/null
+++ b/plugins/woocommerce/changelog/62596-refactor-paypal-standard-2-helper
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Deprecate WC_Gateway_Paypal_Helper class in favor of Automattic\WooCommerce\Gateways\PayPal\Helper.
\ 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 ca222b51d5..456b5dbc47 100644
--- a/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php
+++ b/plugins/woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php
@@ -14,15 +14,12 @@ use Automattic\Jetpack\Constants;
 use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
 use Automattic\Jetpack\Connection\Manager as Jetpack_Connection_Manager;
 use Automattic\WooCommerce\Gateways\PayPal\Constants as PayPalConstants;
+use Automattic\WooCommerce\Gateways\PayPal\Helper as PayPalHelper;

 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
 }

-if ( ! class_exists( 'WC_Gateway_Paypal_Helper' ) ) {
-	require_once __DIR__ . '/includes/class-wc-gateway-paypal-helper.php';
-}
-
 if ( ! class_exists( 'WC_Gateway_Paypal_Buttons' ) ) {
 	require_once __DIR__ . '/class-wc-gateway-paypal-buttons.php';
 }
@@ -278,7 +275,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
 			$paypal_order_details = $paypal_request->get_paypal_order_details( $paypal_order_id );

 			// Update the addresses in the order with the addresses from the PayPal order details.
-			WC_Gateway_Paypal_Helper::update_addresses_in_order( $order, $paypal_order_details );
+			PayPalHelper::update_addresses_in_order( $order, $paypal_order_details );
 		} catch ( Exception $e ) {
 			self::log( 'Error updating addresses for order #' . $order_id . ': ' . $e->getMessage(), 'error' );
 			$order->update_meta_data( '_paypal_addresses_updated', 'no' );
@@ -310,7 +307,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
 		 */
 		$use_orders_v2 = apply_filters(
 			'woocommerce_paypal_use_orders_v2',
-			WC_Gateway_Paypal_Helper::is_orders_v2_migration_eligible()
+			PayPalHelper::is_orders_v2_migration_eligible()
 		);

 		// If the conditions are met, but there is an override to not use Orders v2,
@@ -933,7 +930,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
 		 */
 		$use_orders_v2 = apply_filters(
 			'woocommerce_paypal_use_orders_v2',
-			WC_Gateway_Paypal_Helper::is_orders_v2_migration_eligible()
+			PayPalHelper::is_orders_v2_migration_eligible()
 		);

 		// If the conditions are met, but there is an override to not use Orders v2,
diff --git a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php
index 58212c0fc3..4ca777f86b 100644
--- a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php
+++ b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php
@@ -2,6 +2,7 @@
 /**
  * PayPal Helper Class
  *
+ * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper instead. This class will be removed in 11.0.0.
  * @package WooCommerce\Gateways
  */

@@ -11,47 +12,42 @@ if ( ! defined( 'ABSPATH' ) ) {
 	exit;
 }

+use Automattic\WooCommerce\Gateways\PayPal\Helper as PayPalHelper;
+
 if ( ! class_exists( 'WC_Gateway_Paypal_Constants' ) ) {
 	require_once __DIR__ . '/class-wc-gateway-paypal-constants.php';
 }

 /**
  * Helper for PayPal gateway.
+ *
+ * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper instead. This class will be removed in 11.0.0.
  */
 class WC_Gateway_Paypal_Helper {
 	/**
 	 * Check if the PayPal gateway is enabled.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::is_paypal_gateway_available() instead.
 	 * @return bool
 	 */
 	public static function is_paypal_gateway_available() {
-		$settings    = get_option( 'woocommerce_paypal_settings', array() );
-		$enabled     = isset( $settings['enabled'] ) && 'yes' === $settings['enabled'];
-		$should_load = isset( $settings['_should_load'] ) && 'yes' === $settings['_should_load'];
-		return $enabled && $should_load;
+		return PayPalHelper::is_paypal_gateway_available();
 	}

 	/**
 	 * Check if the merchant is eligible for migration from WPS to PPCP.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::is_orders_v2_migration_eligible() instead.
 	 * @return bool
 	 */
 	public static function is_orders_v2_migration_eligible() {
-		$settings = get_option( 'woocommerce_paypal_settings', array() );
-
-		// If API keys are set, the merchant is not eligible for migration
-		// as they may be using features that cannot be seamlessly migrated.
-		$is_test_mode  = isset( $settings['testmode'] ) && 'yes' === $settings['testmode'];
-		$api_username  = $is_test_mode ? ( $settings['sandbox_api_username'] ?? null ) : ( $settings['api_username'] ?? null );
-		$api_password  = $is_test_mode ? ( $settings['sandbox_api_password'] ?? null ) : ( $settings['api_password'] ?? null );
-		$api_signature = $is_test_mode ? ( $settings['sandbox_api_signature'] ?? null ) : ( $settings['api_signature'] ?? null );
-
-		return empty( $api_username ) && empty( $api_password ) && empty( $api_signature );
+		return PayPalHelper::is_orders_v2_migration_eligible();
 	}

 	/**
 	 * Get the WC order from the PayPal custom ID.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::get_wc_order_from_paypal_custom_id() instead.
 	 * @param string $custom_id The custom ID string from the PayPal order.
 	 * @return WC_Order|null
 	 */
@@ -60,28 +56,7 @@ class WC_Gateway_Paypal_Helper {
 			return null;
 		}

-		$data = json_decode( $custom_id, true );
-		if ( ! is_array( $data ) ) {
-			return null;
-		}
-
-		$order_id = $data['order_id'] ?? null;
-		if ( ! $order_id ) {
-			return null;
-		}
-
-		$order = wc_get_order( $order_id );
-		if ( ! $order ) {
-			return null;
-		}
-
-		// Validate the order key.
-		$order_key = $data['order_key'] ?? null;
-		if ( $order_key !== $order->get_order_key() ) {
-			return null;
-		}
-
-		return $order;
+		return PayPalHelper::get_wc_order_from_paypal_custom_id( (string) $custom_id );
 	}

 	/**
@@ -90,126 +65,34 @@ class WC_Gateway_Paypal_Helper {
 	 * This function recursively traverses the data array and redacts sensitive information
 	 * while preserving the structure for debugging purposes.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::redact_data() instead.
 	 * @param mixed $data The data to remove PII from (array, string, or other types).
 	 * @return mixed The data with PII redacted.
 	 */
 	public static function redact_data( $data ) {
-		if ( ! is_array( $data ) ) {
-			return $data;
-		}
-
-		$redacted_data = array();
-
-		foreach ( $data as $key => $value ) {
-			// Skip redacting the payee information as it belongs to the store merchant.
-			if ( 'payee' === $key ) {
-				$redacted_data[ $key ] = $value;
-				continue;
-			}
-			// Mask the email address.
-			if ( 'email_address' === $key || 'email' === $key ) {
-				$redacted_data[ $key ] = self::mask_email( $value );
-				continue;
-			}
-
-			if ( is_array( $value ) ) {
-				$redacted_data[ $key ] = self::redact_data( $value );
-			} elseif ( in_array( $key, WC_Gateway_Paypal_Constants::FIELDS_TO_REDACT, true ) ) {
-				$redacted_data[ $key ] = '[redacted]';
-			} else {
-				// Keep non-PII data as is.
-				$redacted_data[ $key ] = $value;
-			}
-		}
-
-		return $redacted_data;
+		return PayPalHelper::redact_data( $data );
 	}

 	/**
 	 * Mask email address before @ keeping the full domain.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::mask_email() instead.
 	 * @param string $email The email address to mask.
 	 * @return string The masked email address or original input if invalid.
 	 */
 	public static function mask_email( $email ) {
-		if ( ! is_string( $email ) || empty( $email ) ) {
-			return $email;
-		}
-
-		$parts = explode( '@', $email, 2 );
-		if ( count( $parts ) !== 2 || empty( $parts[0] ) || empty( $parts[1] ) ) {
-			return $email;
-		}
-		list( $local, $domain ) = $parts;
-
-		if ( strlen( $local ) <= 3 ) {
-			$masked_local = str_repeat( '*', strlen( $local ) );
-		} else {
-			$masked_local = substr( $local, 0, 2 )
-						. str_repeat( '*', max( 1, strlen( $local ) - 3 ) )
-						. substr( $local, -1 );
-		}
-
-		return $masked_local . '@' . $domain;
+		return PayPalHelper::mask_email( (string) $email );
 	}

 	/**
 	 * Update the addresses in the order.
 	 *
+	 * @deprecated 10.5.0 Use Automattic\WooCommerce\Gateways\PayPal\Helper::update_addresses_in_order() instead.
 	 * @param WC_Order|null $order The order object.
 	 * @param array         $paypal_order_details The PayPal order details.
 	 * @return void
 	 */
 	public static function update_addresses_in_order( ?WC_Order $order, array $paypal_order_details ): void {
-		if ( empty( $order ) || empty( $paypal_order_details ) ) {
-			return;
-		}
-
-		// Bail early if '_paypal_addresses_updated' is 'yes', meaning the addresses update already have been successful.
-		if ( 'yes' === $order->get_meta( '_paypal_addresses_updated', true ) ) {
-			return;
-		}
-
-		// Update the shipping information.
-		$full_name = $paypal_order_details['purchase_units'][0]['shipping']['name']['full_name'] ?? '';
-		if ( ! empty( $full_name ) ) {
-			$name_parts             = explode( ' ', $full_name, 2 );
-			$approximate_first_name = $name_parts[0] ?? '';
-			$approximate_last_name  = isset( $name_parts[1] ) ? $name_parts[1] : '';
-			$order->set_shipping_first_name( $approximate_first_name );
-			$order->set_shipping_last_name( $approximate_last_name );
-		}
-
-		$shipping_address = $paypal_order_details['purchase_units'][0]['shipping']['address'] ?? array();
-		if ( ! empty( $shipping_address ) ) {
-			$order->set_shipping_country( $shipping_address['country_code'] ?? '' );
-			$order->set_shipping_postcode( $shipping_address['postal_code'] ?? '' );
-			$order->set_shipping_state( $shipping_address['admin_area_1'] ?? '' );
-			$order->set_shipping_city( $shipping_address['admin_area_2'] ?? '' );
-			$order->set_shipping_address_1( $shipping_address['address_line_1'] ?? '' );
-			$order->set_shipping_address_2( $shipping_address['address_line_2'] ?? '' );
-		}
-
-		// Update the billing information.
-		$full_name = $paypal_order_details['payer']['name'] ?? array();
-		$email     = $paypal_order_details['payer']['email_address'] ?? '';
-		if ( ! empty( $full_name ) ) {
-			$order->set_billing_first_name( $full_name['given_name'] ?? '' );
-			$order->set_billing_last_name( $full_name['surname'] ?? '' );
-			$order->set_billing_email( $email );
-		}
-
-		$billing_address = $paypal_order_details['payer']['address'] ?? array();
-		if ( ! empty( $billing_address ) ) {
-			$order->set_billing_country( $billing_address['country_code'] ?? '' );
-			$order->set_billing_postcode( $billing_address['postal_code'] ?? '' );
-			$order->set_billing_state( $billing_address['admin_area_1'] ?? '' );
-			$order->set_billing_city( $billing_address['admin_area_2'] ?? '' );
-			$order->set_billing_address_1( $billing_address['address_line_1'] ?? '' );
-			$order->set_billing_address_2( $billing_address['address_line_2'] ?? '' );
-		}
-
-		$order->update_meta_data( '_paypal_addresses_updated', 'yes' );
-		$order->save();
+		PayPalHelper::update_addresses_in_order( $order, $paypal_order_details );
 	}
 }
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index bd66679524..7fe8aaa897 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -24900,18 +24900,6 @@ parameters:
 			count: 1
 			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-api-handler.php

-		-
-			message: '#^Cannot call method get_order_key\(\) on WC_Order\|WC_Order_Refund\|true\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php
-
-		-
-			message: '#^Method WC_Gateway_Paypal_Helper\:\:get_wc_order_from_paypal_custom_id\(\) should return WC_Order\|null but returns WC_Order\|WC_Order_Refund\|true\.$#'
-			identifier: return.type
-			count: 1
-			path: includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php
-
 		-
 			message: '#^Cannot call method get_id\(\) on WC_Order\|true\.$#'
 			identifier: method.nonObject
diff --git a/plugins/woocommerce/src/Gateways/PayPal/Helper.php b/plugins/woocommerce/src/Gateways/PayPal/Helper.php
new file mode 100644
index 0000000000..ab55429203
--- /dev/null
+++ b/plugins/woocommerce/src/Gateways/PayPal/Helper.php
@@ -0,0 +1,213 @@
+<?php
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Gateways\PayPal;
+
+use WC_Order;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * PayPal Helper Class
+ *
+ * Helper methods for PayPal gateway operations including order validation,
+ * data redaction, and address updates.
+ *
+ * @since 10.5.0
+ */
+class Helper {
+	/**
+	 * Check if the PayPal gateway is enabled.
+	 *
+	 * @return bool
+	 */
+	public static function is_paypal_gateway_available(): bool {
+		$settings    = get_option( 'woocommerce_paypal_settings', array() );
+		$enabled     = isset( $settings['enabled'] ) && 'yes' === $settings['enabled'];
+		$should_load = isset( $settings['_should_load'] ) && 'yes' === $settings['_should_load'];
+		return $enabled && $should_load;
+	}
+
+	/**
+	 * Check if the merchant is eligible for migration from WPS to PPCP.
+	 *
+	 * @return bool
+	 */
+	public static function is_orders_v2_migration_eligible(): bool {
+		$settings = get_option( 'woocommerce_paypal_settings', array() );
+
+		// If API keys are set, the merchant is not eligible for migration
+		// as they may be using features that cannot be seamlessly migrated.
+		$is_test_mode  = isset( $settings['testmode'] ) && 'yes' === $settings['testmode'];
+		$api_username  = $is_test_mode ? ( $settings['sandbox_api_username'] ?? null ) : ( $settings['api_username'] ?? null );
+		$api_password  = $is_test_mode ? ( $settings['sandbox_api_password'] ?? null ) : ( $settings['api_password'] ?? null );
+		$api_signature = $is_test_mode ? ( $settings['sandbox_api_signature'] ?? null ) : ( $settings['api_signature'] ?? null );
+
+		return empty( $api_username ) && empty( $api_password ) && empty( $api_signature );
+	}
+
+	/**
+	 * Get the WC order from the PayPal custom ID.
+	 *
+	 * @param string $custom_id The custom ID string from the PayPal order.
+	 * @return WC_Order|null
+	 */
+	public static function get_wc_order_from_paypal_custom_id( string $custom_id ): ?WC_Order {
+		if ( '' === $custom_id ) {
+			return null;
+		}
+
+		$data = json_decode( $custom_id, true );
+		if ( ! is_array( $data ) ) {
+			return null;
+		}
+
+		$order_id = $data['order_id'] ?? null;
+		if ( ! $order_id ) {
+			return null;
+		}
+
+		$order = wc_get_order( $order_id );
+		if ( ! $order instanceof \WC_Order ) {
+			return null;
+		}
+
+		// Validate the order key.
+		$order_key = $data['order_key'] ?? null;
+		if ( $order_key !== $order->get_order_key() ) {
+			return null;
+		}
+
+		return $order;
+	}
+
+	/**
+	 * Remove PII (Personally Identifiable Information) from data for logging.
+	 *
+	 * This function recursively traverses the data array and redacts sensitive information
+	 * while preserving the structure for debugging purposes.
+	 *
+	 * @param mixed $data The data to remove PII from (array, string, or other types).
+	 * @return mixed The data with PII redacted.
+	 */
+	public static function redact_data( $data ) {
+		if ( ! is_array( $data ) ) {
+			return $data;
+		}
+
+		$redacted_data = array();
+
+		foreach ( $data as $key => $value ) {
+			// Skip redacting the payee information as it belongs to the store merchant.
+			if ( 'payee' === $key ) {
+				$redacted_data[ $key ] = $value;
+				continue;
+			}
+			// Mask the email address.
+			if ( 'email_address' === $key || 'email' === $key ) {
+				$redacted_data[ $key ] = self::mask_email( (string) $value );
+				continue;
+			}
+
+			if ( is_array( $value ) ) {
+				$redacted_data[ $key ] = self::redact_data( $value );
+			} elseif ( in_array( $key, Constants::FIELDS_TO_REDACT, true ) ) {
+				$redacted_data[ $key ] = '[redacted]';
+			} else {
+				// Keep non-PII data as is.
+				$redacted_data[ $key ] = $value;
+			}
+		}
+
+		return $redacted_data;
+	}
+
+	/**
+	 * Mask email address before @ keeping the full domain.
+	 *
+	 * @param string $email The email address to mask.
+	 * @return string The masked email address or original input if invalid.
+	 */
+	public static function mask_email( string $email ): string {
+		if ( empty( $email ) ) {
+			return $email;
+		}
+
+		$parts = explode( '@', $email, 2 );
+		if ( count( $parts ) !== 2 || empty( $parts[0] ) || empty( $parts[1] ) ) {
+			return $email;
+		}
+		list( $local, $domain ) = $parts;
+
+		if ( strlen( $local ) <= 3 ) {
+			$masked_local = str_repeat( '*', strlen( $local ) );
+		} else {
+			$masked_local = substr( $local, 0, 2 )
+						. str_repeat( '*', max( 1, strlen( $local ) - 3 ) )
+						. substr( $local, -1 );
+		}
+
+		return $masked_local . '@' . $domain;
+	}
+
+	/**
+	 * Update the addresses in the order.
+	 *
+	 * @param WC_Order|null $order The order object.
+	 * @param array         $paypal_order_details The PayPal order details.
+	 * @return void
+	 */
+	public static function update_addresses_in_order( ?WC_Order $order, array $paypal_order_details ): void {
+		if ( empty( $order ) || empty( $paypal_order_details ) ) {
+			return;
+		}
+
+		// Bail early if '_paypal_addresses_updated' is 'yes', meaning the addresses update already have been successful.
+		if ( 'yes' === $order->get_meta( '_paypal_addresses_updated', true ) ) {
+			return;
+		}
+
+		// Update the shipping information.
+		$full_name = $paypal_order_details['purchase_units'][0]['shipping']['name']['full_name'] ?? '';
+		if ( ! empty( $full_name ) ) {
+			$name_parts             = explode( ' ', $full_name, 2 );
+			$approximate_first_name = $name_parts[0] ?? '';
+			$approximate_last_name  = isset( $name_parts[1] ) ? $name_parts[1] : '';
+			$order->set_shipping_first_name( $approximate_first_name );
+			$order->set_shipping_last_name( $approximate_last_name );
+		}
+
+		$shipping_address = $paypal_order_details['purchase_units'][0]['shipping']['address'] ?? array();
+		if ( ! empty( $shipping_address ) ) {
+			$order->set_shipping_country( $shipping_address['country_code'] ?? '' );
+			$order->set_shipping_postcode( $shipping_address['postal_code'] ?? '' );
+			$order->set_shipping_state( $shipping_address['admin_area_1'] ?? '' );
+			$order->set_shipping_city( $shipping_address['admin_area_2'] ?? '' );
+			$order->set_shipping_address_1( $shipping_address['address_line_1'] ?? '' );
+			$order->set_shipping_address_2( $shipping_address['address_line_2'] ?? '' );
+		}
+
+		// Update the billing information.
+		$full_name = $paypal_order_details['payer']['name'] ?? array();
+		$email     = $paypal_order_details['payer']['email_address'] ?? '';
+		if ( ! empty( $full_name ) ) {
+			$order->set_billing_first_name( $full_name['given_name'] ?? '' );
+			$order->set_billing_last_name( $full_name['surname'] ?? '' );
+			$order->set_billing_email( $email );
+		}
+
+		$billing_address = $paypal_order_details['payer']['address'] ?? array();
+		if ( ! empty( $billing_address ) ) {
+			$order->set_billing_country( $billing_address['country_code'] ?? '' );
+			$order->set_billing_postcode( $billing_address['postal_code'] ?? '' );
+			$order->set_billing_state( $billing_address['admin_area_1'] ?? '' );
+			$order->set_billing_city( $billing_address['admin_area_2'] ?? '' );
+			$order->set_billing_address_1( $billing_address['address_line_1'] ?? '' );
+			$order->set_billing_address_2( $billing_address['address_line_2'] ?? '' );
+		}
+
+		$order->update_meta_data( '_paypal_addresses_updated', 'yes' );
+		$order->save();
+	}
+}
diff --git a/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-helper-test.php b/plugins/woocommerce/tests/php/src/Gateways/PayPal/HelperTest.php
similarity index 58%
rename from plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-helper-test.php
rename to plugins/woocommerce/tests/php/src/Gateways/PayPal/HelperTest.php
index b124b2bb56..00e2575c31 100644
--- a/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-helper-test.php
+++ b/plugins/woocommerce/tests/php/src/Gateways/PayPal/HelperTest.php
@@ -1,18 +1,20 @@
 <?php
 /**
- * Unit tests for WC_Gateway_Paypal_Helper class.
+ * Unit tests for Automattic\WooCommerce\Gateways\PayPal\Helper class.
  *
- * @package WooCommerce\Tests\Paypal.
+ * @package WooCommerce\Tests\Gateways\PayPal
  */

 declare(strict_types=1);

-require_once WC_ABSPATH . 'includes/gateways/paypal/includes/class-wc-gateway-paypal-helper.php';
+namespace Automattic\WooCommerce\Tests\Gateways\PayPal;
+
+use Automattic\WooCommerce\Gateways\PayPal\Helper;

 /**
- * Class WC_Gateway_Paypal_Helper_Test.
+ * Class HelperTest.
  */
-class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
+class HelperTest extends \WC_Unit_Test_Case {

 	/**
 	 * Tear down the test environment.
@@ -84,7 +86,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	public function test_is_paypal_gateway_available_scenarios( $settings, $expected ) {
 		update_option( 'woocommerce_paypal_settings', $settings );

-		$result = WC_Gateway_Paypal_Helper::is_paypal_gateway_available();
+		$result = Helper::is_paypal_gateway_available();

 		$this->assertEquals( $expected, $result );
 	}
@@ -168,7 +170,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	public function test_is_orders_v2_migration_eligible_scenarios( $settings, $expected ) {
 		update_option( 'woocommerce_paypal_settings', $settings );

-		$result = WC_Gateway_Paypal_Helper::is_orders_v2_migration_eligible();
+		$result = Helper::is_orders_v2_migration_eligible();

 		$this->assertEquals( $expected, $result );
 	}
@@ -178,7 +180,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 */
 	public function test_get_wc_order_from_paypal_custom_id_returns_order_when_valid() {
 		// Create a test order.
-		$order = WC_Helper_Order::create_order();
+		$order = \WC_Helper_Order::create_order();
 		$order->save();

 		$custom_id = wp_json_encode(
@@ -190,9 +192,9 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 			)
 		);

-		$result = WC_Gateway_Paypal_Helper::get_wc_order_from_paypal_custom_id( $custom_id );
+		$result = Helper::get_wc_order_from_paypal_custom_id( $custom_id );

-		$this->assertInstanceOf( WC_Order::class, $result );
+		$this->assertInstanceOf( \WC_Order::class, $result );
 		$this->assertEquals( $order->get_id(), $result->get_id() );

 		// Clean up.
@@ -210,7 +212,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 			)
 		);

-		$result = WC_Gateway_Paypal_Helper::get_wc_order_from_paypal_custom_id( $custom_id );
+		$result = Helper::get_wc_order_from_paypal_custom_id( $custom_id );

 		$this->assertNull( $result );
 	}
@@ -227,7 +229,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 			)
 		);

-		$result = WC_Gateway_Paypal_Helper::get_wc_order_from_paypal_custom_id( $custom_id );
+		$result = Helper::get_wc_order_from_paypal_custom_id( $custom_id );

 		$this->assertNull( $result );
 	}
@@ -237,7 +239,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 */
 	public function test_get_wc_order_from_paypal_custom_id_returns_null_when_order_key_mismatch() {
 		// Create a test order.
-		$order = WC_Helper_Order::create_order();
+		$order = \WC_Helper_Order::create_order();
 		$order->save();

 		$custom_id = wp_json_encode(
@@ -248,7 +250,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 			)
 		);

-		$result = WC_Gateway_Paypal_Helper::get_wc_order_from_paypal_custom_id( $custom_id );
+		$result = Helper::get_wc_order_from_paypal_custom_id( $custom_id );

 		$this->assertNull( $result );

@@ -263,22 +265,10 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 */
 	public function provider_custom_id_scenarios() {
 		return array(
-			'null_input'            => array(
-				'custom_id' => null,
-				'expected'  => null,
-			),
 			'empty_string'          => array(
 				'custom_id' => '',
 				'expected'  => null,
 			),
-			'integer_input'         => array(
-				'custom_id' => 123,
-				'expected'  => null,
-			),
-			'array_input'           => array(
-				'custom_id' => array( 'test' ),
-				'expected'  => null,
-			),
 			'invalid_json'          => array(
 				'custom_id' => 'not-json',
 				'expected'  => null,
@@ -295,11 +285,11 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 *
 	 * @dataProvider provider_custom_id_scenarios
 	 *
-	 * @param mixed $custom_id The custom ID to test.
-	 * @param mixed $expected  The expected result.
+	 * @param string $custom_id The custom ID to test.
+	 * @param mixed  $expected  The expected result.
 	 */
 	public function test_get_wc_order_from_paypal_custom_id_invalid_inputs( $custom_id, $expected ) {
-		$result = WC_Gateway_Paypal_Helper::get_wc_order_from_paypal_custom_id( $custom_id );
+		$result = Helper::get_wc_order_from_paypal_custom_id( $custom_id );

 		$this->assertEquals( $expected, $result );
 	}
@@ -313,7 +303,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 * @param string $expected The expected masked result.
 	 */
 	public function test_mask_email( $email, $expected ) {
-		$result = WC_Gateway_Paypal_Helper::mask_email( $email );
+		$result = Helper::mask_email( $email );

 		$this->assertEquals( $expected, $result );
 	}
@@ -345,10 +335,6 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 				'email'    => '',
 				'expected' => '',
 			),
-			'not_string'                 => array(
-				'email'    => 123,
-				'expected' => 123,
-			),
 			'invalid_email_string'       => array(
 				'email'    => 'notanemail',
 				'expected' => 'notanemail',
@@ -392,7 +378,7 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 			),
 		);

-		$result = WC_Gateway_Paypal_Helper::redact_data( $data );
+		$result = Helper::redact_data( $data );

 		// PII fields should be redacted.
 		$this->assertEquals( '[redacted]', $result['given_name'] );
@@ -420,19 +406,213 @@ class WC_Gateway_Paypal_Helper_Test extends \WC_Unit_Test_Case {
 	 * Test redact_data handles non-array inputs.
 	 */
 	public function test_redact_data_handles_non_array_inputs() {
-		$this->assertEquals( 'string', WC_Gateway_Paypal_Helper::redact_data( 'string' ) );
-		$this->assertEquals( 123, WC_Gateway_Paypal_Helper::redact_data( 123 ) );
-		$this->assertEquals( null, WC_Gateway_Paypal_Helper::redact_data( null ) );
-		$this->assertEquals( true, WC_Gateway_Paypal_Helper::redact_data( true ) );
+		$this->assertEquals( 'string', Helper::redact_data( 'string' ) );
+		$this->assertEquals( 123, Helper::redact_data( 123 ) );
+		$this->assertEquals( null, Helper::redact_data( null ) );
+		$this->assertEquals( true, Helper::redact_data( true ) );
 	}

 	/**
 	 * Test redact_data handles empty arrays.
 	 */
 	public function test_redact_data_handles_empty_array() {
-		$result = WC_Gateway_Paypal_Helper::redact_data( array() );
+		$result = Helper::redact_data( array() );

 		$this->assertIsArray( $result );
 		$this->assertEmpty( $result );
 	}
+
+	/**
+	 * Test update_addresses_in_order updates both shipping and billing addresses.
+	 */
+	public function test_update_addresses_in_order_updates_addresses() {
+		$order = \WC_Helper_Order::create_order();
+		$order->save();
+
+		$paypal_order_details = array(
+			'purchase_units' => array(
+				array(
+					'shipping' => array(
+						'name'    => array(
+							'full_name' => 'John Doe',
+						),
+						'address' => array(
+							'country_code'   => 'US',
+							'postal_code'    => '12345',
+							'admin_area_1'   => 'CA',
+							'admin_area_2'   => 'San Francisco',
+							'address_line_1' => '123 Main St',
+							'address_line_2' => 'Apt 4B',
+						),
+					),
+				),
+			),
+			'payer'          => array(
+				'name'          => array(
+					'given_name' => 'Jane',
+					'surname'    => 'Smith',
+				),
+				'email_address' => 'jane.smith@example.com',
+				'address'       => array(
+					'country_code'   => 'US',
+					'postal_code'    => '54321',
+					'admin_area_1'   => 'NY',
+					'admin_area_2'   => 'New York',
+					'address_line_1' => '456 Broadway',
+					'address_line_2' => 'Suite 100',
+				),
+			),
+		);
+
+		Helper::update_addresses_in_order( $order, $paypal_order_details );
+
+		// Verify shipping address was updated.
+		$this->assertEquals( 'John', $order->get_shipping_first_name() );
+		$this->assertEquals( 'Doe', $order->get_shipping_last_name() );
+		$this->assertEquals( 'US', $order->get_shipping_country() );
+		$this->assertEquals( '12345', $order->get_shipping_postcode() );
+		$this->assertEquals( 'CA', $order->get_shipping_state() );
+		$this->assertEquals( 'San Francisco', $order->get_shipping_city() );
+		$this->assertEquals( '123 Main St', $order->get_shipping_address_1() );
+		$this->assertEquals( 'Apt 4B', $order->get_shipping_address_2() );
+
+		// Verify billing address was updated.
+		$this->assertEquals( 'Jane', $order->get_billing_first_name() );
+		$this->assertEquals( 'Smith', $order->get_billing_last_name() );
+		$this->assertEquals( 'jane.smith@example.com', $order->get_billing_email() );
+		$this->assertEquals( 'US', $order->get_billing_country() );
+		$this->assertEquals( '54321', $order->get_billing_postcode() );
+		$this->assertEquals( 'NY', $order->get_billing_state() );
+		$this->assertEquals( 'New York', $order->get_billing_city() );
+		$this->assertEquals( '456 Broadway', $order->get_billing_address_1() );
+		$this->assertEquals( 'Suite 100', $order->get_billing_address_2() );
+
+		// Verify meta flag was set.
+		$this->assertEquals( 'yes', $order->get_meta( '_paypal_addresses_updated', true ) );
+
+		// Clean up.
+		$order->delete( true );
+	}
+
+	/**
+	 * Test update_addresses_in_order does not update when order is null.
+	 */
+	public function test_update_addresses_in_order_skips_null_order() {
+		$paypal_order_details = array(
+			'purchase_units' => array(
+				array(
+					'shipping' => array(
+						'name' => array( 'full_name' => 'John Doe' ),
+					),
+				),
+			),
+		);
+
+		// Should not throw an error.
+		Helper::update_addresses_in_order( null, $paypal_order_details );
+
+		// No assertions, just ensuring no exception is thrown.
+		$this->assertTrue( true );
+	}
+
+	/**
+	 * Test update_addresses_in_order does not update when paypal_order_details is empty.
+	 */
+	public function test_update_addresses_in_order_skips_empty_details() {
+		$order = \WC_Helper_Order::create_order();
+		$order->save();
+
+		$original_shipping_first_name = $order->get_shipping_first_name();
+		$original_billing_first_name  = $order->get_billing_first_name();
+
+		Helper::update_addresses_in_order( $order, array() );
+
+		// Order should not be modified.
+		$this->assertEquals( $original_shipping_first_name, $order->get_shipping_first_name() );
+		$this->assertEquals( $original_billing_first_name, $order->get_billing_first_name() );
+		$this->assertEmpty( $order->get_meta( '_paypal_addresses_updated', true ) );
+
+		// Clean up.
+		$order->delete( true );
+	}
+
+	/**
+	 * Test update_addresses_in_order does not update when already updated.
+	 */
+	public function test_update_addresses_in_order_skips_already_updated() {
+		$order = \WC_Helper_Order::create_order();
+		$order->update_meta_data( '_paypal_addresses_updated', 'yes' );
+		$order->save();
+
+		$original_shipping_first_name = $order->get_shipping_first_name();
+
+		$paypal_order_details = array(
+			'purchase_units' => array(
+				array(
+					'shipping' => array(
+						'name' => array(
+							'full_name' => 'Different Name',
+						),
+					),
+				),
+			),
+		);
+
+		Helper::update_addresses_in_order( $order, $paypal_order_details );
+
+		// Order should not be modified.
+		$this->assertEquals( $original_shipping_first_name, $order->get_shipping_first_name() );
+
+		// Clean up.
+		$order->delete( true );
+	}
+
+	/**
+	 * Test update_addresses_in_order handles partial address data.
+	 */
+	public function test_update_addresses_in_order_handles_partial_address_data() {
+		$order = \WC_Helper_Order::create_order();
+		$order->save();
+
+		$paypal_order_details = array(
+			'purchase_units' => array(
+				array(
+					'shipping' => array(
+						'name'    => array(
+							'full_name' => 'John Doe',
+						),
+						'address' => array(
+							'country_code' => 'US',
+							// Only country code, missing other fields.
+						),
+					),
+				),
+			),
+			'payer'          => array(
+				'name'    => array(
+					'given_name' => 'Jane',
+					// Missing surname.
+				),
+				'address' => array(
+					'postal_code' => '12345',
+					// Only postal code.
+				),
+			),
+		);
+
+		Helper::update_addresses_in_order( $order, $paypal_order_details );
+
+		// Shipping country should be set, other fields should be empty strings.
+		$this->assertEquals( 'US', $order->get_shipping_country() );
+		$this->assertEquals( '', $order->get_shipping_postcode() );
+		$this->assertEquals( '', $order->get_shipping_state() );
+
+		// Billing should have given name and postal code.
+		$this->assertEquals( 'Jane', $order->get_billing_first_name() );
+		$this->assertEquals( '', $order->get_billing_last_name() );
+		$this->assertEquals( '12345', $order->get_billing_postcode() );
+
+		// Clean up.
+		$order->delete( true );
+	}
 }