From 5a9ebfe02b50a07007575e753941d45963c2c280 Mon Sep 17 00:00:00 2001 From: Mayisha Date: Fri, 24 Oct 2025 03:50:43 +0600 Subject: [PATCH 1/6] retrieve request --- includes/class-wc-stripe-api.php | 59 ++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/includes/class-wc-stripe-api.php b/includes/class-wc-stripe-api.php index 4aaa84d30e..b7800d85de 100644 --- a/includes/class-wc-stripe-api.php +++ b/includes/class-wc-stripe-api.php @@ -38,6 +38,21 @@ class WC_Stripe_API { */ protected const INVALID_API_KEY_ERROR_COUNT_THRESHOLD = 5; + /** + * The API version for the proxy endpoint. + * + * @var int + */ + private const WPCOM_PROXY_ENDPOINT_API_VERSION = 2; + + /** + * The base for the proxy REST endpoint. + * + * @var string + */ + private const WPCOM_PROXY_REST_BASE = 'transact/stripe/proxy'; + + /** * Secret API Key. * @@ -146,7 +161,7 @@ public static function get_headers() { $app_info = $user_agent['application']; $headers = [ - 'Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ), + 'Stripe-Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ), 'Stripe-Version' => self::STRIPE_API_VERSION, ]; @@ -300,16 +315,12 @@ public static function retrieve( $api ) { return null; } - WC_Stripe_Logger::debug( "Stripe API request: GET {$api}" ); + WC_Stripe_Logger::debug( "Stripe API request proxy: GET {$api}" ); - $response = wp_safe_remote_get( - self::ENDPOINT . $api, - [ - 'method' => 'GET', - 'headers' => self::get_headers(), - 'timeout' => 70, - ] - ); + $request_body = [ + 'test_mode' => WC_Stripe_Mode::is_test(), + ]; + $response = self::send_wpcom_proxy_request( 'GET', 'v1/' . $api, $request_body ); // If we get a 401 error, we know the secret key is not valid. if ( is_array( $response ) && isset( $response['response'] ) && is_array( $response['response'] ) && isset( $response['response']['code'] ) && 401 === $response['response']['code'] ) { @@ -358,11 +369,37 @@ public static function retrieve( $api ) { $response_body = json_decode( $response['body'] ); - WC_Stripe_Logger::debug( "Stripe API response: GET {$api}", [ 'response' => $response_body ] ); + WC_Stripe_Logger::debug( "Stripe API response proxy: GET {$api}", [ 'response' => $response_body ] ); return $response_body; } + public static function send_wpcom_proxy_request( $method, $endpoint, $request_body ) { + $site_id = \Jetpack_Options::get_option( 'id' ); + if ( ! $site_id ) { + WC_Stripe_Logger::error( sprintf( 'Site ID not found. Cannot send request to %s.', $endpoint ) ); + throw new Exception( 'Site ID not found. Cannot send proxy request.' ); + } + + if ( 'GET' === $method ) { + $endpoint .= '?' . http_build_query( $request_body ); + } + + $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog( + sprintf( '/sites/%d/%s/%s', $site_id, self::WPCOM_PROXY_REST_BASE, $endpoint ), + self::WPCOM_PROXY_ENDPOINT_API_VERSION, + [ + 'headers' => self::get_headers(), + 'method' => $method, + 'timeout' => 70, + ], + 'GET' === $method ? null : wp_json_encode( $request_body ), + 'wpcom' + ); + + return $response; + } + /** * Send the request to Stripe's API with level 3 data generated * from the order. If the request fails due to an error related From ccf42ee58d0ebbe8e72e208cf7d60adfb997b1ba Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Sat, 25 Oct 2025 09:14:17 -0500 Subject: [PATCH 2/6] Merge try/extract-transact-proxy-api-logic and refactor to use the new WC_Stripe_Transact_API if onboarded --- includes/class-wc-stripe-api.php | 135 +++++++++--------- ...ass-wc-stripe-transact-account-manager.php | 9 -- includes/class-wc-stripe-transact-api.php | 112 +++++++++++++++ includes/class-wc-stripe.php | 1 + 4 files changed, 180 insertions(+), 77 deletions(-) create mode 100644 includes/class-wc-stripe-transact-api.php diff --git a/includes/class-wc-stripe-api.php b/includes/class-wc-stripe-api.php index b7800d85de..8cbe8a9080 100644 --- a/includes/class-wc-stripe-api.php +++ b/includes/class-wc-stripe-api.php @@ -13,8 +13,12 @@ class WC_Stripe_API { /** * Stripe API Endpoint */ - const ENDPOINT = 'https://api.stripe.com/v1/'; - const STRIPE_API_VERSION = '2024-06-20'; + const ENDPOINT = 'https://api.stripe.com/v1/'; + + /** + * Stripe API Version + */ + public const STRIPE_API_VERSION = '2024-06-20'; /** * The invalid API key error count cache key. @@ -38,21 +42,6 @@ class WC_Stripe_API { */ protected const INVALID_API_KEY_ERROR_COUNT_THRESHOLD = 5; - /** - * The API version for the proxy endpoint. - * - * @var int - */ - private const WPCOM_PROXY_ENDPOINT_API_VERSION = 2; - - /** - * The base for the proxy REST endpoint. - * - * @var string - */ - private const WPCOM_PROXY_REST_BASE = 'transact/stripe/proxy'; - - /** * Secret API Key. * @@ -134,19 +123,17 @@ public static function set_secret_key_for_mode( $mode = null ) { * @version 4.0.0 */ public static function get_user_agent() { - $app_info = [ - 'name' => 'WooCommerce Stripe Gateway', - 'version' => WC_STRIPE_VERSION, - 'url' => 'https://woocommerce.com/products/stripe/', - 'partner_id' => 'pp_partner_EYuSt9peR0WTMg', - ]; - return [ 'lang' => 'php', 'lang_version' => phpversion(), 'publisher' => 'woocommerce', 'uname' => function_exists( 'php_uname' ) ? php_uname() : PHP_OS, - 'application' => $app_info, + 'application' => [ + 'name' => 'WooCommerce Stripe Gateway', + 'version' => WC_STRIPE_VERSION, + 'url' => 'https://woocommerce.com/products/stripe/', + 'partner_id' => 'pp_partner_EYuSt9peR0WTMg', + ], ]; } @@ -161,7 +148,7 @@ public static function get_headers() { $app_info = $user_agent['application']; $headers = [ - 'Stripe-Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ), + 'Authorization' => 'Basic ' . base64_encode( self::get_secret_key() . ':' ), 'Stripe-Version' => self::STRIPE_API_VERSION, ]; @@ -250,19 +237,32 @@ public static function request( $request, $api = 'charges', $method = 'POST', $w */ $request = apply_filters( 'wc_stripe_request_body', $request, $api ); - // Log the request after the filters have been applied. - WC_Stripe_Logger::debug( "Stripe API request: {$method} {$api}", [ 'request' => $request ] ); + $is_transact_api_enabled = WC_Stripe_Transact_API::get_instance()->is_transact_api_enabled(); - $response = wp_safe_remote_post( - self::ENDPOINT . $api, + // Log the request after the filters have been applied. + WC_Stripe_Logger::debug( + "Stripe API request: {$method} {$api}", [ - 'method' => $method, - 'headers' => $headers, - 'body' => $request, - 'timeout' => 70, + 'transact_api_enabled' => $is_transact_api_enabled, + 'request' => $request, ] ); + if ( $is_transact_api_enabled ) { + $response = WC_Stripe_Transact_API::get_instance()->send_wpcom_proxy_request( $method, $api, $headers, $request ); + + } else { + $response = wp_safe_remote_post( + self::ENDPOINT . $api, + [ + 'method' => $method, + 'headers' => $headers, + 'body' => $request, + 'timeout' => 70, + ] + ); + } + $response_headers = wp_remote_retrieve_headers( $response ); if ( is_wp_error( $response ) || empty( $response['body'] ) ) { @@ -281,7 +281,13 @@ public static function request( $request, $api = 'charges', $method = 'POST', $w $response_body = json_decode( $response['body'] ); - WC_Stripe_Logger::debug( "Stripe API response: {$method} {$api}", [ 'response' => $response_body ] ); + WC_Stripe_Logger::debug( + "Stripe API response: {$method} {$api}", + [ + 'transact_api_enabled' => $is_transact_api_enabled, + 'response' => $response_body, + ] + ); if ( $with_headers ) { return [ @@ -315,12 +321,23 @@ public static function retrieve( $api ) { return null; } - WC_Stripe_Logger::debug( "Stripe API request proxy: GET {$api}" ); + $is_transact_api_enabled = WC_Stripe_Transact_API::get_instance()->is_transact_api_enabled(); - $request_body = [ - 'test_mode' => WC_Stripe_Mode::is_test(), - ]; - $response = self::send_wpcom_proxy_request( 'GET', 'v1/' . $api, $request_body ); + WC_Stripe_Logger::debug( "Stripe API request: GET {$api}", [ 'transact_api_enabled' => $is_transact_api_enabled ] ); + + if ( $is_transact_api_enabled ) { + $response = WC_Stripe_Transact_API::get_instance()->send_wpcom_proxy_request( 'GET', $api, self::get_headers() ); + + } else { + $response = wp_safe_remote_get( + self::ENDPOINT . $api, + [ + 'method' => 'GET', + 'headers' => self::get_headers(), + 'timeout' => 70, + ] + ); + } // If we get a 401 error, we know the secret key is not valid. if ( is_array( $response ) && isset( $response['response'] ) && is_array( $response['response'] ) && isset( $response['response']['code'] ) && 401 === $response['response']['code'] ) { @@ -328,7 +345,8 @@ public static function retrieve( $api ) { WC_Stripe_Logger::error( "Stripe API error: GET {$api} returned a 401", [ - 'response' => json_decode( $response['body'] ), + 'transact_api_enabled' => $is_transact_api_enabled, + 'response' => json_decode( $response['body'] ), ] ); @@ -361,7 +379,8 @@ public static function retrieve( $api ) { WC_Stripe_Logger::error( "Stripe API error: GET {$api}", [ - 'response' => $response, + 'transact_api_enabled' => $is_transact_api_enabled, + 'response' => $response, ] ); return new WP_Error( 'stripe_error', __( 'There was a problem connecting to the Stripe API endpoint.', 'woocommerce-gateway-stripe' ) ); @@ -369,35 +388,15 @@ public static function retrieve( $api ) { $response_body = json_decode( $response['body'] ); - WC_Stripe_Logger::debug( "Stripe API response proxy: GET {$api}", [ 'response' => $response_body ] ); - - return $response_body; - } - - public static function send_wpcom_proxy_request( $method, $endpoint, $request_body ) { - $site_id = \Jetpack_Options::get_option( 'id' ); - if ( ! $site_id ) { - WC_Stripe_Logger::error( sprintf( 'Site ID not found. Cannot send request to %s.', $endpoint ) ); - throw new Exception( 'Site ID not found. Cannot send proxy request.' ); - } - - if ( 'GET' === $method ) { - $endpoint .= '?' . http_build_query( $request_body ); - } - - $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog( - sprintf( '/sites/%d/%s/%s', $site_id, self::WPCOM_PROXY_REST_BASE, $endpoint ), - self::WPCOM_PROXY_ENDPOINT_API_VERSION, + WC_Stripe_Logger::debug( + "Stripe API response: GET {$api}", [ - 'headers' => self::get_headers(), - 'method' => $method, - 'timeout' => 70, - ], - 'GET' === $method ? null : wp_json_encode( $request_body ), - 'wpcom' + 'transact_api_enabled' => $is_transact_api_enabled, + 'response' => $response_body, + ] ); - return $response; + return $response_body; } /** diff --git a/includes/class-wc-stripe-transact-account-manager.php b/includes/class-wc-stripe-transact-account-manager.php index 895fe83b07..5c4397ec90 100644 --- a/includes/class-wc-stripe-transact-account-manager.php +++ b/includes/class-wc-stripe-transact-account-manager.php @@ -66,15 +66,6 @@ public function __construct( WC_Stripe_UPE_Payment_Gateway $gateway ) { * @return void */ public function do_onboarding(): void { - $stripe_connect = woocommerce_gateway_stripe()->connect; - $mode = WC_Stripe_Mode::is_test() ? 'test' : 'live'; - $oauth_connected = (bool) $stripe_connect->is_connected_via_oauth( $mode ); - - // Check that the merchant is connected via OAuth. Only begin onboarding if this minimum requirement is met. - if ( ! $oauth_connected ) { - return; - } - // Register with Jetpack if not already connected. $jetpack_connection_manager = $this->gateway->get_jetpack_connection_manager(); if ( ! $jetpack_connection_manager ) { diff --git a/includes/class-wc-stripe-transact-api.php b/includes/class-wc-stripe-transact-api.php new file mode 100644 index 0000000000..37a3a6e918 --- /dev/null +++ b/includes/class-wc-stripe-transact-api.php @@ -0,0 +1,112 @@ +is_connected() ) { + return false; + } + + $stripe_settings = WC_Stripe_Helper::get_stripe_settings(); + if ( ! $stripe_settings || ! isset( $stripe_settings['transact_onboarding_complete'] ) ) { + return false; + } + + return 'yes' === $stripe_settings['transact_onboarding_complete']; + } + + /** + * Send a request to the Transact platform. + * + * @param string $method The HTTP method to use. + * @param string $endpoint The endpoint to request. + * @param array $headers The headers to send. + * @param array $request_body The request body. + * + * @return array|null The API response body, or null if the request fails. + */ + public function send_wpcom_proxy_request( $method, $endpoint, $headers = [], $request_body = [] ) { + $site_id = \Jetpack_Options::get_option( 'id' ); + if ( ! $site_id ) { + WC_Stripe_Logger::error( sprintf( 'Site ID not found. Cannot send request to %s.', $endpoint ) ); + throw new Exception( 'Site ID not found. Cannot send proxy request.' ); + } + + if ( isset( $headers['Authorization'] ) ) { + $headers['Stripe-Authorization'] = $headers['Authorization']; + unset( $headers['Authorization'] ); + } + + $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog( + sprintf( '/sites/%d/%s/%s', $site_id, self::WPCOM_PROXY_REST_BASE, $endpoint ), + self::WPCOM_PROXY_ENDPOINT_API_VERSION, + [ + 'headers' => $headers, + 'method' => $method, + 'timeout' => 70, + ], + 'GET' === $method ? null : wp_json_encode( $request_body ), + 'wpcom' + ); + + return $response; + } +} diff --git a/includes/class-wc-stripe.php b/includes/class-wc-stripe.php index 785bcb22ae..402082131a 100644 --- a/includes/class-wc-stripe.php +++ b/includes/class-wc-stripe.php @@ -130,6 +130,7 @@ public function init() { require_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-payment-method-configurations.php'; require_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-database-cache-prefetch.php'; include_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-api.php'; + include_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-transact-api.php'; include_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-mode.php'; include_once WC_STRIPE_PLUGIN_PATH . '/includes/class-wc-stripe-transact-account-manager.php'; require_once WC_STRIPE_PLUGIN_PATH . '/includes/compat/class-wc-stripe-subscriptions-helper.php'; From 401f965633bb19a7a4ab3949bff6aaf51b564a0d Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Sat, 25 Oct 2025 10:14:19 -0500 Subject: [PATCH 3/6] Improve cache handling --- ...ass-wc-stripe-transact-account-manager.php | 149 +++++++----------- 1 file changed, 53 insertions(+), 96 deletions(-) diff --git a/includes/class-wc-stripe-transact-account-manager.php b/includes/class-wc-stripe-transact-account-manager.php index 5c4397ec90..0b3d60838b 100644 --- a/includes/class-wc-stripe-transact-account-manager.php +++ b/includes/class-wc-stripe-transact-account-manager.php @@ -32,10 +32,8 @@ final class WC_Stripe_Transact_Account_Manager { * * @var string */ - private const TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY_LIVE = 'transact_merchant_account_live'; - private const TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY_TEST = 'transact_merchant_account_test'; - private const TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY_LIVE = 'transact_provider_account_live'; - private const TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY_TEST = 'transact_provider_account_test'; + private const TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY = 'transact_merchant_account'; + private const TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY = 'transact_provider_account'; /** * The expiry time for the Transact account cache. @@ -81,88 +79,82 @@ public function do_onboarding(): void { } } - // Fetch (cached) or create the Transact merchant and provider accounts. - $merchant_account_data = $this->get_transact_account_data( 'merchant' ); + // Fetch (cached) or create the Transact merchant account. + $merchant_account_data = $this->maybe_create_merchant_account(); + wc_get_logger()->info( 'merchant_account_data: ' . wc_print_r( $merchant_account_data, true ) ); if ( empty( $merchant_account_data ) ) { - $merchant_account = $this->create_merchant_account(); - if ( empty( $merchant_account ) ) { - WC_Stripe_Logger::error( 'Transact merchant onboarding failed.' ); - return; - } - - // Cache the merchant account data. - $this->update_transact_account_cache( - $this->get_cache_key( 'merchant' ), - $merchant_account - ); + WC_Stripe_Logger::error( 'Transact merchant onboarding failed.' ); + return; } - wc_get_logger()->info( 'merchant_account_data: ' . wc_print_r( $merchant_account_data, true ) ); - - $provider_account_data = $this->get_transact_account_data( 'provider' ); + // Fetch (cached) or create the Transact provider account. + $provider_account_data = $this->maybe_create_provider_account(); + wc_get_logger()->info( 'provider_account_data: ' . wc_print_r( $provider_account_data, true ) ); if ( empty( $provider_account_data ) ) { - $provider_account = $this->create_provider_account(); - if ( ! $provider_account ) { - WC_Stripe_Logger::error( 'Transact provider onboarding failed.' ); - return; - } - - // Cache the provider account data. - $this->update_transact_account_cache( - $this->get_cache_key( 'provider' ), - $provider_account - ); + WC_Stripe_Logger::error( 'Transact provider onboarding failed.' ); + return; } - wc_get_logger()->info( 'provider_account_data: ' . wc_print_r( $provider_account_data, true ) ); - // Set an extra flag to indicate that we've completed onboarding. $this->gateway->set_transact_onboarding_complete(); } /** - * Get the Transact account (merchant or provider) data. Performs a fetch if the account - * is not in cache or expired. + * Maybe create the merchant account. * - * @param string $account_type The type of account to get (merchant or provider). - * @return array|bool|null Returns null if the transact account cannot be retrieved. + * @return array|null The merchant account data, or null if the merchant account cannot be created. */ - public function get_transact_account_data( $account_type ) { - $cache_key = $this->get_cache_key( $account_type ); - - // Get transact account from cache. If not found, fetch/create it. - $transact_account = $this->get_transact_account_from_cache( $cache_key ); - if ( empty( $transact_account ) ) { - $transact_account = 'merchant' === $account_type ? $this->fetch_merchant_account() : $this->fetch_provider_account(); - - // Fetch failed. - if ( empty( $transact_account ) ) { - return null; - } + private function maybe_create_merchant_account(): ?array { + // Get the merchant account from cache. + $merchant_account = WC_Stripe_Database_Cache::get( self::TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY ); + if ( ! empty( $merchant_account ) ) { + return $merchant_account; + } - // Update cache. - $this->update_transact_account_cache( $cache_key, $transact_account ); + // Fetch the merchant account from the Transact platform. + $merchant_account = $this->fetch_merchant_account(); + if ( empty( $merchant_account ) ) { + // Create the merchant account on the Transact platform. + $merchant_account = $this->create_merchant_account(); } - return $transact_account; + // Cache the merchant account data. + WC_Stripe_Database_Cache::set( + self::TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY, + $merchant_account, + self::TRANSACT_ACCOUNT_CACHE_EXPIRY + ); + + return $merchant_account; } /** - * Get the cache key for the transact account. + * Maybe create the provider account. * - * @param string $account_type The type of account to get (merchant or provider). - * @return string|null The cache key, or null if the account type is invalid. + * @return bool|null Ture if the provider account is valid. Provider account response only returns an empty onboarding link, so we map it to true. */ - private function get_cache_key( $account_type ): ?string { - if ( 'merchant' === $account_type ) { - return WC_Stripe_Mode::is_test() ? self::TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY_TEST : self::TRANSACT_MERCHANT_ACCOUNT_CACHE_KEY_LIVE; + private function maybe_create_provider_account(): ?bool { + // Get the provider account from cache. + $provider_account = WC_Stripe_Database_Cache::get( self::TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY ); + if ( ! empty( $provider_account ) ) { + return $provider_account; } - if ( 'provider' === $account_type ) { - return WC_Stripe_Mode::is_test() ? self::TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY_TEST : self::TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY_LIVE; + // Fetch the provider account from the Transact platform. + $provider_account = $this->fetch_provider_account(); + if ( empty( $provider_account ) ) { + // Create the provider account on the Transact platform. + $provider_account = $this->create_provider_account(); } - return null; + // Cache the merchant account data. + WC_Stripe_Database_Cache::set( + self::TRANSACT_PROVIDER_ACCOUNT_CACHE_KEY, + $provider_account, + self::TRANSACT_ACCOUNT_CACHE_EXPIRY + ); + + return $provider_account; } /** @@ -291,41 +283,6 @@ private function create_provider_account(): bool { return true; } - /** - * Update the transact account (merchant or provider) cache. - * - * @param string $cache_key The cache key to update. - * @param array $account_data The transact account data. - */ - private function update_transact_account_cache( $cache_key, $account_data ): void { - $expires = time() + self::TRANSACT_ACCOUNT_CACHE_EXPIRY; - WC_Stripe_Database_Cache::set( - $cache_key, - [ - 'account' => $account_data, - 'expiry' => $expires, - ], - self::TRANSACT_ACCOUNT_CACHE_EXPIRY - ); - } - - /** - * Get the transact account (merchant or provider) from the database cache. - * - * @param string $cache_key The cache key to get the account. - * @return array|bool|null The transact account data, or null if the cache is - * empty or expired. - */ - private function get_transact_account_from_cache( $cache_key ) { - $transact_account = WC_Stripe_Database_Cache::get( $cache_key ); - - if ( empty( $transact_account ) || ( isset( $transact_account['expiry'] ) && $transact_account['expiry'] < time() ) ) { - return null; - } - - return $transact_account['account'] ?? null; - } - /** * Send a request to the Transact platform. * From bac85b8b3edb8c55a9247086130dfb886a339c05 Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Sat, 25 Oct 2025 10:14:32 -0500 Subject: [PATCH 4/6] Fix tests --- ...C_Stripe_Transact_Account_Manager_Test.php | 311 +++++++++--------- 1 file changed, 148 insertions(+), 163 deletions(-) diff --git a/tests/phpunit/WC_Stripe_Transact_Account_Manager_Test.php b/tests/phpunit/WC_Stripe_Transact_Account_Manager_Test.php index fe13289d07..3448321e99 100644 --- a/tests/phpunit/WC_Stripe_Transact_Account_Manager_Test.php +++ b/tests/phpunit/WC_Stripe_Transact_Account_Manager_Test.php @@ -29,7 +29,7 @@ class WC_Stripe_Transact_Account_Manager_Test extends WP_UnitTestCase { /** * Mock Stripe gateway. * - * @var WC_Stripe_UPE_Payment_Gateway + * @var \PHPUnit\Framework\MockObject\MockObject|WC_Stripe_UPE_Payment_Gateway */ private $gateway; @@ -40,13 +40,6 @@ class WC_Stripe_Transact_Account_Manager_Test extends WP_UnitTestCase { */ private $account_manager; - /** - * The original `WC_Stripe_Connect` instance, to be restored after tests. - * - * @var WC_Stripe_Connect - */ - private WC_Stripe_Connect $stripe_connect_original; - /** * Set up test fixtures. */ @@ -56,6 +49,7 @@ public function setUp(): void { // Create mock Stripe gateway. $this->gateway = $this->getMockBuilder( WC_Stripe_UPE_Payment_Gateway::class ) ->disableOriginalConstructor() + ->onlyMethods( [ 'get_jetpack_connection_manager', 'set_transact_onboarding_complete' ] ) ->getMock(); // Set default properties. @@ -63,21 +57,11 @@ public function setUp(): void { $stripe_settings['testmode'] = 'yes'; WC_Stripe_Helper::update_main_stripe_settings( $stripe_settings ); - // overriding the `WC_Stripe_Connect` in woocommerce_gateway_stripe(), - $stripe_connect_mock = $this->createPartialMock( - WC_Stripe_Connect::class, - [ 'is_connected_via_oauth' ] - ); - $stripe_connect_mock - ->expects( $this->any() ) - ->method( 'is_connected_via_oauth' ) - ->willReturn( true ); - - $this->stripe_connect_original = woocommerce_gateway_stripe()->connect; - woocommerce_gateway_stripe()->connect = $stripe_connect_mock; - // Create account manager instance. - $this->account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); + // phpcs:ignore Squiz.Commenting.InlineComment.InvalidEndChar + /** @var WC_Stripe_UPE_Payment_Gateway $gateway */ + $gateway = $this->gateway; + $this->account_manager = new WC_Stripe_Transact_Account_Manager( $gateway ); } /** @@ -87,9 +71,6 @@ public function setUp(): void { */ public function tear_down() { parent::tear_down(); - - // Restoring the original `WC_Stripe_Connect` instance. - woocommerce_gateway_stripe()->connect = $this->stripe_connect_original; } /** @@ -106,29 +87,6 @@ public function test_constructor_sets_gateway() { $this->assertSame( $this->gateway, $gateway_property->getValue( $account_manager ) ); } - /** - * Test do_onboarding when not connected via OAuth. - */ - public function test_do_onboarding_when_not_connected_via_oauth() { - // overriding the `WC_Stripe_Connect` in woocommerce_gateway_stripe(), - $stripe_connect_mock = $this->createPartialMock( - WC_Stripe_Connect::class, - [ 'is_connected_via_oauth' ] - ); - $stripe_connect_mock - ->expects( $this->any() ) - ->method( 'is_connected_via_oauth' ) - ->willReturn( false ); - - $this->stripe_connect_original = woocommerce_gateway_stripe()->connect; - woocommerce_gateway_stripe()->connect = $stripe_connect_mock; - - // Should not throw any errors and should return early. - $this->account_manager->do_onboarding(); - - $this->assertTrue( true ); - } - /** * Test do_onboarding when Jetpack registration fails. */ @@ -226,42 +184,64 @@ public function test_do_onboarding_when_provider_account_creation_fails() { } /** - * Test get_merchant_account_data returns cached data when available. + * Test maybe_create_merchant_account returns cached data when available. */ - public function test_get_merchant_account_data_returns_cached_data() { - // Return valid cache data. - WC_Stripe_Database_Cache::set( 'transact_merchant_account_test', $this->return_valid_merchant_account_cache() ); + public function test_maybe_create_merchant_account_returns_cached_data() { + // Set valid cache data. + WC_Stripe_Database_Cache::set( 'transact_merchant_account', [ 'public_id' => 'test_public_id' ] ); - $result = $this->account_manager->get_transact_account_data( 'merchant' ); + $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_merchant_account' ); + $method->setAccessible( true ); - // Clean up the filter. - WC_Stripe_Database_Cache::delete( 'transact_merchant_account_test' ); + $result = $method->invoke( $account_manager ); - $expected_merchant_account = $this->return_valid_merchant_account_cache(); - $this->assertEquals( $expected_merchant_account['account'], $result ); + // Clean up the cache. + WC_Stripe_Database_Cache::delete( 'transact_merchant_account' ); + + $expected_merchant_account = [ 'public_id' => 'test_public_id' ]; + $this->assertEquals( $expected_merchant_account, $result ); } /** - * Test get_merchant_account_data returns null when cache is expired. + * Test maybe_create_merchant_account fetches and caches when cache is empty. */ - public function test_get_merchant_account_data_returns_null_when_cache_expired() { - // Mock cache to return expired data. - WC_Stripe_Database_Cache::set( 'transact_merchant_account_test', $this->return_expired_merchant_account_cache() ); + public function test_maybe_create_merchant_account_fetches_when_cache_empty() { + // Don't set any cache, so it will fetch from API. - $result = $this->account_manager->get_transact_account_data( 'merchant' ); + // Return a valid site ID. + add_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); - // Clean up the filter. - WC_Stripe_Database_Cache::delete( 'transact_merchant_account_test' ); + // Return a Jetpack blog token. + add_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - $this->assertNull( $result ); + // Return a successful response, with the merchant account data. + add_filter( 'pre_http_request', [ $this, 'return_merchant_account_api_success' ] ); + + $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_merchant_account' ); + $method->setAccessible( true ); + + $result = $method->invoke( $account_manager ); + + // Clean up the filters and cache. + remove_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); + remove_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); + remove_filter( 'pre_http_request', [ $this, 'return_merchant_account_api_success' ] ); + WC_Stripe_Database_Cache::delete( 'transact_merchant_account' ); + + // Check that it returns the data and caches it. + $expected_merchant_account = [ 'public_id' => 'test_public_id' ]; + $this->assertEquals( $expected_merchant_account, $result ); } /** - * Test get_merchant_account_data fetches when cache is empty and caches fetched data. + * Test maybe_create_merchant_account creates account when fetch fails. */ - public function test_get_merchant_account_data_fetches_and_caches_data() { - // Return empty cache. - WC_Stripe_Database_Cache::set( 'transact_merchant_account_test', $this->return_empty_merchant_account_cache() ); + public function test_maybe_create_merchant_account_creates_when_fetch_fails() { + // Don't set any cache, so it will try to fetch from API. // Return a valid site ID. add_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); @@ -269,67 +249,86 @@ public function test_get_merchant_account_data_fetches_and_caches_data() { // Return a Jetpack blog token. add_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - // Return a successful response, with the merchant account data. - add_filter( 'pre_http_request', [ $this, 'return_merchant_account_api_success' ] ); + // Mock the HTTP request to first return 404 (not found), then return success on create. + $this->http_request_count = 0; + add_filter( 'pre_http_request', [ $this, 'return_fetch_fail_then_create_success_merchant' ] ); $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); - $result = $account_manager->get_transact_account_data( 'merchant' ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_merchant_account' ); + $method->setAccessible( true ); - // Clean up the filters and cache. - WC_Stripe_Database_Cache::delete( 'transact_merchant_account_test' ); + $result = $method->invoke( $account_manager ); + // Clean up the filters and cache. remove_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); remove_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - remove_filter( 'pre_http_request', [ $this, 'return_merchant_account_api_success' ] ); + remove_filter( 'pre_http_request', [ $this, 'return_fetch_fail_then_create_success_merchant' ] ); + WC_Stripe_Database_Cache::delete( 'transact_merchant_account' ); - // Check that it returns the data. - $response_data = json_decode( $this->return_merchant_account_api_success()['body'], true ); - $expected_merchant_account = [ 'public_id' => $response_data['public_id'] ]; + // Check that it returns the created account data. + $expected_merchant_account = [ 'public_id' => 'test_public_id' ]; $this->assertEquals( $expected_merchant_account, $result ); - - // Check that the cache was updated. - $cached_data = WC_Stripe_Database_Cache::get( 'transact_merchant_account_test' ); - $this->assertNull( $cached_data ); } /** - * Test get_provider_account_data returns cached data when available. + * Test maybe_create_provider_account returns cached data when available. */ - public function test_get_provider_account_data_returns_cached_data() { - // Return valid cache data. - WC_Stripe_Database_Cache::set( 'transact_provider_account_test', $this->return_valid_provider_account_cache() ); + public function test_maybe_create_provider_account_returns_cached_data() { + // Set valid cache data. + WC_Stripe_Database_Cache::set( 'transact_provider_account', true ); - $result = $this->account_manager->get_transact_account_data( 'provider' ); + $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_provider_account' ); + $method->setAccessible( true ); + + $result = $method->invoke( $account_manager ); // Clean up the cache. - WC_Stripe_Database_Cache::delete( 'transact_provider_account_test' ); + WC_Stripe_Database_Cache::delete( 'transact_provider_account' ); - $expected_provider_account = $this->return_valid_provider_account_cache(); - $this->assertEquals( $expected_provider_account['account'], $result ); + $this->assertTrue( $result ); } /** - * Test get_provider_account_data returns null when cache is expired. + * Test maybe_create_provider_account fetches and caches when cache is empty. */ - public function test_get_provider_account_data_returns_null_when_cache_expired() { - // Mock cache to return expired data. - WC_Stripe_Database_Cache::set( 'transact_provider_account_test', $this->return_expired_provider_account_cache() ); + public function test_maybe_create_provider_account_fetches_when_cache_empty() { + // Don't set any cache, so it will fetch from API. - $result = $this->account_manager->get_transact_account_data( 'provider' ); + // Return a valid site ID. + add_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); - // Clean up the cache. - WC_Stripe_Database_Cache::delete( 'transact_provider_account_test' ); + // Return a Jetpack blog token. + add_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - $this->assertNull( $result ); + // Return a successful response for the provider account. + add_filter( 'pre_http_request', [ $this, 'return_provider_account_api_success' ] ); + + $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_provider_account' ); + $method->setAccessible( true ); + + $result = $method->invoke( $account_manager ); + + // Clean up the filters and cache. + remove_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); + remove_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); + remove_filter( 'pre_http_request', [ $this, 'return_provider_account_api_success' ] ); + WC_Stripe_Database_Cache::delete( 'transact_provider_account' ); + + // Check that it returns true. + $this->assertTrue( $result ); } /** - * Test get_provider_account_data fetches when cache is empty and caches fetched data. + * Test maybe_create_provider_account creates account when fetch fails. */ - public function test_get_provider_account_data_fetches_and_caches_data() { - // Return empty cache. - WC_Stripe_Database_Cache::set( 'transact_provider_account_test', $this->return_empty_provider_account_cache() ); + public function test_maybe_create_provider_account_creates_when_fetch_fails() { + // Don't set any cache, so it will try to fetch from API. // Return a valid site ID. add_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); @@ -337,26 +336,25 @@ public function test_get_provider_account_data_fetches_and_caches_data() { // Return a Jetpack blog token. add_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - // Return a successful response, with the provider account data. - add_filter( 'pre_http_request', [ $this, 'return_provider_account_api_success' ] ); + // Mock the HTTP request to first return 404 (not found), then return success on create. + $this->http_request_count = 0; + add_filter( 'pre_http_request', [ $this, 'return_fetch_fail_then_create_success_provider' ] ); $account_manager = new WC_Stripe_Transact_Account_Manager( $this->gateway ); - $result = $account_manager->get_transact_account_data( 'provider' ); + $reflection = new \ReflectionClass( $account_manager ); + $method = $reflection->getMethod( 'maybe_create_provider_account' ); + $method->setAccessible( true ); - // Clean up the filters and cache. - WC_Stripe_Database_Cache::delete( 'transact_provider_account_test' ); + $result = $method->invoke( $account_manager ); + // Clean up the filters and cache. remove_filter( 'pre_option_jetpack_options', [ $this, 'return_valid_site_id' ] ); remove_filter( 'pre_option_jetpack_private_options', [ $this, 'return_blog_token' ] ); - remove_filter( 'pre_http_request', [ $this, 'return_provider_account_api_success' ] ); + remove_filter( 'pre_http_request', [ $this, 'return_fetch_fail_then_create_success_provider' ] ); + WC_Stripe_Database_Cache::delete( 'transact_provider_account' ); - // Check that it returns the data. + // Check that it returns true. $this->assertTrue( $result ); - - // Check that the cache was updated. - WC_Stripe_Database_Cache::delete( 'transact_provider_account_test' ); - $cached_data = WC_Stripe_Database_Cache::get( 'transact_provider_account_test' ); - $this->assertNull( $cached_data ); } /** @@ -650,68 +648,57 @@ public function return_valid_site_id( $value ) { } /** - * Helper method to return empty merchant account cache. + * Helper property to track HTTP request count for testing. * - * @return false + * @var int */ - public function return_empty_merchant_account_cache() { - return false; - } + private $http_request_count = 0; /** - * Helper method to return expired merchant account cache. + * Helper method to simulate fetch failure then create success for merchant account. + * First request (GET - fetch) returns 404, second request (POST - create) returns success. * - * @return array + * @return array|\WP_Error */ - public function return_expired_merchant_account_cache() { - return [ - 'account' => [ 'public_id' => 'test_public_id' ], - 'expiry' => time() - 3600, // Expired 1 hour ago. - ]; - } + public function return_fetch_fail_then_create_success_merchant() { + $this->http_request_count++; - /** - * Helper method to return valid merchant account cache. - * - * @return array - */ - public function return_valid_merchant_account_cache() { + // First call is the fetch (GET), return 404. + if ( 1 === $this->http_request_count ) { + return [ + 'response' => [ 'code' => 404 ], + 'body' => wp_json_encode( [ 'error' => 'not_found' ] ), + ]; + } + + // Second call is the create (POST), return success. return [ - 'account' => [ 'public_id' => 'test_public_id' ], - 'expiry' => time() + 3600, // Expires in 1 hour. + 'response' => [ 'code' => 200 ], + 'body' => wp_json_encode( [ 'public_id' => 'test_public_id' ] ), ]; } /** - * Helper method to return empty provider account cache. + * Helper method to simulate fetch failure then create success for provider account. + * First request (GET - fetch) returns 404, second request (POST - create) returns success. * - * @return false + * @return array|\WP_Error */ - public function return_empty_provider_account_cache() { - return false; - } + public function return_fetch_fail_then_create_success_provider() { + $this->http_request_count++; - /** - * Helper method to return expired provider account cache. - * - * @return array - */ - public function return_expired_provider_account_cache() { - return [ - 'account' => true, - 'expiry' => time() - 3600, // Expired 1 hour ago. - ]; - } + // First call is the fetch (GET), return 404. + if ( 1 === $this->http_request_count ) { + return [ + 'response' => [ 'code' => 404 ], + 'body' => wp_json_encode( [ 'error' => 'not_found' ] ), + ]; + } - /** - * Helper method to return valid provider account cache. - * - * @return array - */ - public function return_valid_provider_account_cache() { + // Second call is the create (POST), return success. return [ - 'account' => true, - 'expiry' => time() + 3600, // Expires in 1 hour. + 'response' => [ 'code' => 200 ], + 'body' => '', ]; } @@ -731,10 +718,8 @@ public function return_blog_token( $value ) { */ public function tearDown(): void { // Clean up any options we created. - WC_Stripe_Database_Cache::delete( 'transact_merchant_account_live' ); - WC_Stripe_Database_Cache::delete( 'transact_merchant_account_test' ); - WC_Stripe_Database_Cache::delete( 'transact_provider_account_live' ); - WC_Stripe_Database_Cache::delete( 'transact_provider_account_test' ); + WC_Stripe_Database_Cache::delete( 'transact_merchant_account' ); + WC_Stripe_Database_Cache::delete( 'transact_provider_account' ); parent::tearDown(); } From af8d88330166d662b0b1abbd98c624661757bf85 Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Sat, 25 Oct 2025 11:49:35 -0500 Subject: [PATCH 5/6] Consolidate WPCOM constants in WC_Stripe_Transact_API --- ...class-wc-stripe-transact-account-manager.php | 17 ++--------------- includes/class-wc-stripe-transact-api.php | 6 +++--- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/includes/class-wc-stripe-transact-account-manager.php b/includes/class-wc-stripe-transact-account-manager.php index 0b3d60838b..93769e4c9e 100644 --- a/includes/class-wc-stripe-transact-account-manager.php +++ b/includes/class-wc-stripe-transact-account-manager.php @@ -6,19 +6,6 @@ * Handles transact account management for WooCommerce Stripe integration. */ final class WC_Stripe_Transact_Account_Manager { - /** - * The API version for the proxy endpoint. - * - * @var int - */ - private const WPCOM_PROXY_ENDPOINT_API_VERSION = 2; - - /** - * The timeout for requests to the WPCOM proxy endpoint. - * - * @var int - */ - private const WPCOM_PROXY_REQUEST_TIMEOUT = 60; /** * Transact provider type, for provider onboarding. @@ -299,11 +286,11 @@ private function send_transact_api_request( $method, $endpoint, $request_body ) $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_blog( $endpoint, - self::WPCOM_PROXY_ENDPOINT_API_VERSION, + WC_Stripe_Transact_API::WPCOM_PROXY_ENDPOINT_API_VERSION, [ 'headers' => [ 'Content-Type' => 'application/json' ], 'method' => $method, - 'timeout' => self::WPCOM_PROXY_REQUEST_TIMEOUT, + 'timeout' => WC_Stripe_Transact_API::WPCOM_PROXY_REQUEST_TIMEOUT, ], 'GET' === $method ? null : wp_json_encode( $request_body ), 'wpcom' diff --git a/includes/class-wc-stripe-transact-api.php b/includes/class-wc-stripe-transact-api.php index 37a3a6e918..d8d398d9f9 100644 --- a/includes/class-wc-stripe-transact-api.php +++ b/includes/class-wc-stripe-transact-api.php @@ -15,14 +15,14 @@ class WC_Stripe_Transact_API { * * @var int */ - private const WPCOM_PROXY_ENDPOINT_API_VERSION = 2; + public const WPCOM_PROXY_ENDPOINT_API_VERSION = 2; /** * The timeout for requests to the WPCOM proxy endpoint. * * @var int */ - private const WPCOM_PROXY_REQUEST_TIMEOUT = 60; + public const WPCOM_PROXY_REQUEST_TIMEOUT = 70; /** * The base for the proxy REST endpoint. @@ -101,7 +101,7 @@ public function send_wpcom_proxy_request( $method, $endpoint, $headers = [], $re [ 'headers' => $headers, 'method' => $method, - 'timeout' => 70, + 'timeout' => self::WPCOM_PROXY_REQUEST_TIMEOUT, ], 'GET' === $method ? null : wp_json_encode( $request_body ), 'wpcom' From 9a5b84753067cb64fa80d9e81b89b757c1a27c69 Mon Sep 17 00:00:00 2001 From: Diego Curbelo Date: Sat, 25 Oct 2025 12:21:13 -0500 Subject: [PATCH 6/6] Remove body json_enconde when making a transact_api call --- includes/class-wc-stripe-transact-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-stripe-transact-api.php b/includes/class-wc-stripe-transact-api.php index d8d398d9f9..f1c192d23a 100644 --- a/includes/class-wc-stripe-transact-api.php +++ b/includes/class-wc-stripe-transact-api.php @@ -103,7 +103,7 @@ public function send_wpcom_proxy_request( $method, $endpoint, $headers = [], $re 'method' => $method, 'timeout' => self::WPCOM_PROXY_REQUEST_TIMEOUT, ], - 'GET' === $method ? null : wp_json_encode( $request_body ), + 'GET' === $method ? null : $request_body, 'wpcom' );