From c17029a4bec068c13374d978d2e5ad107202ffb7 Mon Sep 17 00:00:00 2001 From: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com> Date: Thu, 27 Jun 2024 12:00:29 +0300 Subject: [PATCH] Refactor redirects logic in payments (#8590) Co-authored-by: oaratovskyi --- ...-7654-refactor-redirects-logic-in-payments | 4 + includes/admin/class-wc-payments-admin.php | 55 +-- includes/class-wc-payments-account.php | 442 +++++------------- .../class-wc-payments-redirect-service.php | 189 ++++++++ includes/class-wc-payments.php | 11 +- .../admin/test-class-wc-payments-admin.php | 113 +---- ...test-class-wc-payments-account-capital.php | 84 +--- .../test-class-wc-payments-account-link.php | 65 +-- tests/unit/test-class-wc-payments-account.php | 206 ++++---- ...est-class-wc-payments-redirect-service.php | 174 +++++++ 10 files changed, 633 insertions(+), 710 deletions(-) create mode 100644 changelog/dev-7654-refactor-redirects-logic-in-payments create mode 100644 includes/class-wc-payments-redirect-service.php create mode 100644 tests/unit/test-class-wc-payments-redirect-service.php diff --git a/changelog/dev-7654-refactor-redirects-logic-in-payments b/changelog/dev-7654-refactor-redirects-logic-in-payments new file mode 100644 index 00000000000..988c5ba661e --- /dev/null +++ b/changelog/dev-7654-refactor-redirects-logic-in-payments @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Refactor redirects logic in payments diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 27604515859..38dbe0d0db5 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -6,7 +6,6 @@ */ use Automattic\Jetpack\Identity_Crisis as Jetpack_Identity_Crisis; -use Automattic\WooCommerce\Admin\PageController; use WCPay\Core\Server\Request; use WCPay\Database_Cache; use WCPay\Logger; @@ -203,8 +202,7 @@ public function init_hooks() { // Add menu items. add_action( 'admin_menu', [ $this, 'add_payments_menu' ], 0 ); - add_action( 'admin_init', [ $this, 'maybe_redirect_to_onboarding' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic. - add_action( 'admin_enqueue_scripts', [ $this, 'maybe_redirect_overview_to_connect' ], 1 ); // Run this late (after `admin_init`) but before any scripts are actually enqueued. + add_action( 'admin_init', [ $this, 'maybe_redirect_from_payments_admin_child_pages' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic. add_action( 'admin_enqueue_scripts', [ $this, 'register_payments_scripts' ], 9 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_payments_scripts' ], 9 ); add_action( 'woocommerce_admin_field_payment_gateways', [ $this, 'payment_gateways_container' ] ); @@ -1086,10 +1084,10 @@ private function get_settings_menu_item_name() { } /** - * Checks if Stripe account is connected and redirects to the onboarding page - * if it is not and the user is attempting to view a WCPay admin page. + * If the user is attempting to view a WCPay admin page without a connected Stripe account, + * redirect them to the connect account page. */ - public function maybe_redirect_to_onboarding() { + public function maybe_redirect_from_payments_admin_child_pages() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } @@ -1126,51 +1124,6 @@ public function maybe_redirect_to_onboarding() { $this->account->redirect_to_onboarding_welcome_page(); } - /** - * Avoid WC Admin /payments/overview error page when the current account associated Stripe account is not valid. - * - * The errored page happens because we don't register a /payments/overview WC admin page when the Stripe account - * is not valid and register only a /payments/connect top level menu page. - * - * Places around our plugin redirect merchants to the overview page by default (or using it for the Stripe KYC - * return URL) leading to poor UX. - * This is a safety net to prevent that from happening. - * - * @see self::add_payments_menu() - */ - public function maybe_redirect_overview_to_connect() { - if ( ! current_user_can( 'manage_woocommerce' ) ) { - return; - } - if ( wp_doing_ajax() ) { - return; - } - - // If the current page is registered, let it pass. - if ( wc_admin_is_registered_page() ) { - return; - } - - $url_params = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification - if ( empty( $url_params['page'] ) || 'wc-admin' !== $url_params['page'] - || empty( $url_params['path'] ) || '/payments/overview' !== $url_params['path'] ) { - return; - } - - /** - * Determine the path of the top level menu page since that can change between payments/connect and payments/overview. - * - * @see self::add_payments_menu() - */ - $top_level_page_path = PageController::get_instance()->get_path_from_id( 'wc-payments' ); - // If the top level page path is not the payments/connect one, bail. - if ( 'wc-admin&path=/payments/connect' !== $top_level_page_path ) { - return; - } - - $this->account->redirect_to_onboarding_welcome_page(); - } - /** * Add woopay as a payment method to the edit order on admin. * diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 807f64750e8..e8369a00d15 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -14,8 +14,6 @@ use WCPay\Constants\Country_Code; use WCPay\Constants\Currency_Code; use WCPay\Core\Server\Request\Get_Account; -use WCPay\Core\Server\Request\Get_Account_Capital_Link; -use WCPay\Core\Server\Request\Get_Account_Login_Data; use WCPay\Core\Server\Request; use WCPay\Core\Server\Request\Update_Account; use WCPay\Exceptions\API_Exception; @@ -68,6 +66,13 @@ class WC_Payments_Account { */ private $session_service; + /** + * WC_Payments_Redirect_Service instance for handling redirects business logic + * + * @var WC_Payments_Redirect_Service + */ + private $redirect_service; + /** * Class constructor * @@ -75,17 +80,20 @@ class WC_Payments_Account { * @param Database_Cache $database_cache Database cache util. * @param WC_Payments_Action_Scheduler_Service $action_scheduler_service Action scheduler service. * @param WC_Payments_Session_Service $session_service Session service. + * @param WC_Payments_Redirect_Service $redirect_service Redirect service. */ public function __construct( WC_Payments_API_Client $payments_api_client, Database_Cache $database_cache, WC_Payments_Action_Scheduler_Service $action_scheduler_service, - WC_Payments_Session_Service $session_service + WC_Payments_Session_Service $session_service, + WC_Payments_Redirect_Service $redirect_service ) { $this->payments_api_client = $payments_api_client; $this->database_cache = $database_cache; $this->action_scheduler_service = $action_scheduler_service; $this->session_service = $session_service; + $this->redirect_service = $redirect_service; } /** @@ -96,13 +104,12 @@ public function __construct( public function init_hooks() { // Add admin init hooks. add_action( 'admin_init', [ $this, 'maybe_handle_onboarding' ] ); - add_action( 'admin_init', [ $this, 'maybe_redirect_to_onboarding' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic. - add_action( 'admin_init', [ $this, 'maybe_redirect_to_wcpay_connect' ], 12 ); // Run this after the redirect to onboarding logic. - add_action( 'admin_init', [ $this, 'maybe_redirect_to_capital_offer' ] ); - add_action( 'admin_init', [ $this, 'maybe_redirect_to_server_link' ] ); - add_action( 'admin_init', [ $this, 'maybe_redirect_settings_to_connect_or_overview' ] ); - add_action( 'admin_init', [ $this, 'maybe_redirect_onboarding_flow_to_overview' ] ); - add_action( 'admin_init', [ $this, 'maybe_redirect_onboarding_flow_to_connect' ] ); + + add_action( 'admin_init', [ $this, 'maybe_redirect_after_plugin_activation' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic. + add_action( 'admin_init', [ $this, 'maybe_redirect_by_get_param' ], 12 ); // Run this after the redirect to onboarding logic. + add_action( 'admin_init', [ $this, 'maybe_redirect_from_settings_page' ] ); + add_action( 'admin_init', [ $this, 'maybe_redirect_from_onboarding_page' ] ); + add_action( 'admin_init', [ $this, 'maybe_activate_woopay' ] ); // Add handlers for inbox notes and reminders. @@ -600,126 +607,83 @@ public function get_is_live() { } /** - * Checks if the request is for the Capital view offer redirection page, and redirects to the offer if so. + * Checks if the request contains specific get param to redirect further, and redirects to the relevant link if so. * * Only admins are be able to perform this action. The redirect doesn't happen if the request is an AJAX request. - * This method will end execution after the redirect if the user requests and is allowed to view the loan offer. */ - public function maybe_redirect_to_capital_offer() { - if ( wp_doing_ajax() ) { - return; - } - + public function maybe_redirect_by_get_param() { // Safety check to prevent non-admin users to be redirected to the view offer page. - if ( ! current_user_can( 'manage_woocommerce' ) ) { + if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { return; } - // This is an automatic redirection page, used to authenticate users that come from the offer email. For this reason + // This is an automatic redirection page, used to authenticate users that come from the KYC reminder email. For this reason // we're not using a nonce. The GET parameter accessed here is just to indicate that we should process the redirection. // phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( ! isset( $_GET['wcpay-loan-offer'] ) ) { - return; - } + if ( isset( $_GET['wcpay-connect-redirect'] ) ) { + $params = [ + 'page' => 'wc-admin', + 'path' => '/payments/connect', + ]; - $return_url = static::get_overview_page_url(); - $refresh_url = add_query_arg( [ 'wcpay-loan-offer' => '' ], admin_url( 'admin.php' ) ); + // We're not in the connect page, don't redirect. + if ( count( $params ) !== count( array_intersect_assoc( $_GET, $params ) ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended + return; + } - try { - $request = Get_Account_Capital_Link::create(); - $type = 'capital_financing_offer'; - $request->set_type( $type ); - $request->set_return_url( $return_url ); - $request->set_refresh_url( $refresh_url ); - - $capital_link = $request->send(); - $this->redirect_to( $capital_link['url'] ); - } catch ( Exception $e ) { - $error_url = add_query_arg( - [ 'wcpay-loan-offer-error' => '1' ], - self::get_overview_page_url() - ); + $redirect_param = sanitize_text_field( wp_unslash( $_GET['wcpay-connect-redirect'] ) ); + + // Let's record in Tracks merchants returning via the KYC reminder email. + if ( 'initial' === $redirect_param ) { + $offset = 1; + $description = 'initial'; + } elseif ( 'second' === $redirect_param ) { + $offset = 3; + $description = 'second'; + } else { + $follow_number = in_array( $redirect_param, [ '1', '2', '3', '4' ], true ) ? $redirect_param : '0'; + // offset is recorded in days, $follow_number maps to the week number. + $offset = (int) $follow_number * 7; + $description = 'weekly-' . $follow_number; + } - $this->redirect_to( $error_url ); - } - } + $track_props = [ + 'offset' => $offset, + 'description' => $description, + ]; + $this->tracks_event( self::TRACKS_EVENT_KYC_REMINDER_MERCHANT_RETURNED, $track_props ); - /** - * Checks if the request is for the server links handler, and redirects to the link if it's valid. - * - * Only admins are be able to perform this action. The redirect doesn't happen if the request is an AJAX request. - * This method will end execution after the redirect if the user is allowed to view the link and the link is valid. - */ - public function maybe_redirect_to_server_link() { - if ( wp_doing_ajax() ) { - return; + $this->redirect_service->redirect_to_wcpay_connect( 'WCPAY_KYC_REMINDER' ); } - // Safety check to prevent non-admin users to be redirected to the view offer page. - if ( ! current_user_can( 'manage_woocommerce' ) ) { - return; + // This is an automatic redirection page, used to authenticate users that come from the capitcal offer email. For this reason + // we're not using a nonce. The GET parameter accessed here is just to indicate that we should process the redirection. + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['wcpay-loan-offer'] ) ) { + $this->redirect_service->redirect_to_capital_view_offer_page(); } // This is an automatic redirection page, used to authenticate users that come from an email link. For this reason // we're not using a nonce. The GET parameter accessed here is just to indicate that we should process the redirection. // phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( ! isset( $_GET['wcpay-link-handler'] ) ) { - return; - } - - // Get all request arguments to be forwarded and remove the link handler identifier. - $args = $_GET; - unset( $args['wcpay-link-handler'] ); - - $this->redirect_to_account_link( $args ); - } - - /** - * Function to immediately redirect to the account link. - * - * @param array $args The arguments to be sent with the link request. - */ - private function redirect_to_account_link( array $args ) { - try { - $link = $this->payments_api_client->get_link( $args ); - - if ( isset( $args['type'] ) && 'complete_kyc_link' === $args['type'] && isset( $link['state'] ) ) { - set_transient( 'wcpay_stripe_onboarding_state', $link['state'], DAY_IN_SECONDS ); - } + if ( isset( $_GET['wcpay-link-handler'] ) ) { + // Get all request arguments to be forwarded and remove the link handler identifier. + $args = $_GET; + unset( $args['wcpay-link-handler'] ); - $this->redirect_to( $link['url'] ); - } catch ( API_Exception $e ) { - $error_url = add_query_arg( - [ 'wcpay-server-link-error' => '1' ], - self::get_overview_page_url() - ); - - $this->redirect_to( $error_url ); + $this->redirect_service->redirect_to_account_link( $args ); } } /** - * Utility function to immediately redirect to the main "Welcome to WooPayments" onboarding page. + * Proxy method that's called in other classes that have access to account (not redirect_service) + * to immediately redirect to the main "Welcome to WooPayments" onboarding page. * Note that this function immediately ends the execution. * - * @param string $error_message Optional error message to show in a notice. + * @param string|null $error_message Optional error message to show in a notice. */ public function redirect_to_onboarding_welcome_page( $error_message = null ) { - if ( isset( $error_message ) ) { - set_transient( self::ERROR_MESSAGE_TRANSIENT, $error_message, 30 ); - } - - $params = [ - 'page' => 'wc-admin', - 'path' => '/payments/connect', - ]; - if ( count( $params ) === count( array_intersect_assoc( $_GET, $params ) ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended - // We are already in the onboarding page, do nothing. - return; - } - - wp_safe_redirect( admin_url( add_query_arg( $params, 'admin.php' ) ) ); - exit(); + $this->redirect_service->redirect_to_connect_page( $error_message ); } /** @@ -727,7 +691,7 @@ public function redirect_to_onboarding_welcome_page( $error_message = null ) { * * @return bool True if the redirection happened. */ - public function maybe_redirect_to_onboarding() { + public function maybe_redirect_after_plugin_activation() { if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { return false; } @@ -765,77 +729,11 @@ public function maybe_redirect_to_onboarding() { // Redirect directly to onboarding page if come from WC Admin task. $http_referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) ); if ( 0 < strpos( $http_referer, 'task=payments' ) ) { - $this->redirect_to_onboarding_flow_page( WC_Payments_Onboarding_Service::SOURCE_WCADMIN_PAYMENT_TASK ); + $this->redirect_to_onboarding_page_or_start_server_connection( WC_Payments_Onboarding_Service::SOURCE_WCADMIN_PAYMENT_TASK ); } // Redirect if not connected. - $this->redirect_to_onboarding_welcome_page(); - return true; - } - - /** - * Redirects to the wcpay-connect URL, which then redirects to the KYC flow. - * - * This URL is used by the KYC reminder email. We can't take the merchant - * directly to the wcpay-connect URL because it's nonced, and the - * nonce will likely be expired by the time the user follows the link. - * That's why we need this middleman instead. - * - * @return bool True if the redirection happened, false otherwise. - */ - public function maybe_redirect_to_wcpay_connect(): bool { - if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { - return false; - } - - $params = [ - 'page' => 'wc-admin', - 'path' => '/payments/connect', - ]; - - // We're not in the onboarding page, don't redirect. - if ( count( $params ) !== count( array_intersect_assoc( $_GET, $params ) ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended - return false; - } - - if ( ! isset( $_GET['wcpay-connect-redirect'] ) ) { - return false; - } - - $redirect_param = sanitize_text_field( wp_unslash( $_GET['wcpay-connect-redirect'] ) ); - - // Let's record in Tracks merchants returning via the KYC reminder email. - if ( 'initial' === $redirect_param ) { - $offset = 1; - $description = 'initial'; - } elseif ( 'second' === $redirect_param ) { - $offset = 3; - $description = 'second'; - } else { - $follow_number = in_array( $redirect_param, [ '1', '2', '3', '4' ], true ) ? $redirect_param : '0'; - // offset is recorded in days, $follow_number maps to the week number. - $offset = (int) $follow_number * 7; - $description = 'weekly-' . $follow_number; - } - - $track_props = [ - 'offset' => $offset, - 'description' => $description, - ]; - $this->tracks_event( self::TRACKS_EVENT_KYC_REMINDER_MERCHANT_RETURNED, $track_props ); - - // Take the user to the 'wcpay-connect' URL. - // We handle creating and redirecting to the account link there. - $connect_url = add_query_arg( - [ - 'wcpay-connect' => '1', - '_wpnonce' => wp_create_nonce( 'wcpay-connect' ), - 'from' => 'WCPAY_KYC_REMINDER', - ], - admin_url( 'admin.php' ) - ); - - $this->redirect_to( $connect_url ); + $this->redirect_service->redirect_to_connect_page(); return true; } @@ -848,7 +746,7 @@ public function maybe_redirect_to_wcpay_connect(): bool { * * @return bool True if a redirection happened, false otherwise. */ - public function maybe_redirect_settings_to_connect_or_overview(): bool { + public function maybe_redirect_from_settings_page(): bool { if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { return false; } @@ -866,18 +764,7 @@ public function maybe_redirect_settings_to_connect_or_overview(): bool { // Not able to establish Stripe connection, redirect to the Connect page. if ( ! $this->is_stripe_connected() ) { - $this->redirect_to( - admin_url( - add_query_arg( - [ - 'page' => 'wc-admin', - 'path' => '/payments/connect', - 'from' => 'WCADMIN_PAYMENT_SETTINGS', - ], - 'admin.php' - ) - ) - ); + $this->redirect_service->redirect_to_connect_page( null, 'WCADMIN_PAYMENT_SETTINGS' ); return true; } @@ -886,21 +773,9 @@ public function maybe_redirect_settings_to_connect_or_overview(): bool { return false; } else { // Account not yet fully onboarded so redirect to overview page. - $this->redirect_to( - admin_url( - add_query_arg( - [ - 'page' => 'wc-admin', - 'path' => '/payments/overview', - 'from' => 'WCADMIN_PAYMENT_SETTINGS', - ], - 'admin.php' - ) - ) - ); + $this->redirect_service->redirect_to_overview_page( 'WCADMIN_PAYMENT_SETTINGS' ); + return true; } - - return true; } /** @@ -911,7 +786,7 @@ public function maybe_redirect_settings_to_connect_or_overview(): bool { * * @return bool True if the redirection happened, false otherwise. */ - public function maybe_redirect_onboarding_flow_to_overview(): bool { + public function maybe_redirect_from_onboarding_page(): bool { if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { return false; } @@ -926,6 +801,28 @@ public function maybe_redirect_onboarding_flow_to_overview(): bool { return false; } + // Prevent access to onboarding flow if the server is not connected. Redirect back to the connect page with an error message. + if ( ! $this->payments_api_client->is_server_connected() ) { + $referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) ); + + // Track unsuccessful Jetpack connection. + if ( strpos( $referer, 'wordpress.com' ) ) { + $this->tracks_event( + self::TRACKS_EVENT_ACCOUNT_CONNECT_WPCOM_CONNECTION_FAILURE, + [ 'mode' => WC_Payments::mode()->is_test() ? 'test' : 'live' ] + ); + } + + $this->redirect_service->redirect_to_connect_page( + sprintf( + /* translators: %s: WooPayments */ + __( 'Please connect to WordPress.com to start using %s.', 'woocommerce-payments' ), + 'WooPayments' + ) + ); + return true; + } + // We check it here after refreshing the cache, because merchant might have clicked back in browser (after Stripe KYC). // That will mean that no redirect from Stripe happened and user might be able to go through onboarding again if no webhook processed yet. // That might cause issues if user selects sandbox onboarding after live one. @@ -939,67 +836,11 @@ public function maybe_redirect_onboarding_flow_to_overview(): bool { return false; } - $this->redirect_to( - admin_url( - add_query_arg( - [ - 'page' => 'wc-admin', - 'path' => '/payments/overview', - 'from' => 'WCPAY_ONBOARDING_FLOW', - ], - 'admin.php' - ) - ) - ); + $this->redirect_service->redirect_to_overview_page( 'WCPAY_ONBOARDING_FLOW' ); return true; } - /** - * Prevent access to onboarding flow if the server is not connected. - * Redirect back to the connect page with an error message. - * - * @return void - */ - public function maybe_redirect_onboarding_flow_to_connect(): void { - if ( wp_doing_ajax() || ! current_user_can( 'manage_woocommerce' ) ) { - return; - } - - $params = [ - 'page' => 'wc-admin', - 'path' => '/payments/onboarding', - ]; - - // We're not in the onboarding flow page, don't redirect. - if ( count( $params ) !== count( array_intersect_assoc( $_GET, $params ) ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended - return; - } - - // Server is connected, don't redirect. - if ( $this->payments_api_client->is_server_connected() ) { - return; - } - - $referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) ); - - // Track unsuccessful Jetpack connection. - if ( strpos( $referer, 'wordpress.com' ) ) { - $this->tracks_event( - self::TRACKS_EVENT_ACCOUNT_CONNECT_WPCOM_CONNECTION_FAILURE, - [ 'mode' => WC_Payments::mode()->is_test() ? 'test' : 'live' ] - ); - } - - $this->redirect_to_onboarding_welcome_page( - sprintf( - /* translators: %s: WooPayments */ - __( 'Please connect to WordPress.com to start using %s.', 'woocommerce-payments' ), - 'WooPayments' - ) - ); - } - /** * Filter function to add Stripe to the list of allowed redirect hosts * @@ -1031,20 +872,16 @@ public function maybe_handle_onboarding() { $args['is_progressive_onboarding'] = $this->is_progressive_onboarding_in_progress() ?? false; } - $this->redirect_to_account_link( $args ); + $this->redirect_service->redirect_to_account_link( $args ); } - $this->redirect_to_login(); + // Clear account transient when generating Stripe dashboard's login link. + $this->clear_cache(); + $this->redirect_service->redirect_to_login(); } catch ( Exception $e ) { Logger::error( 'Failed redirect_to_login: ' . $e ); - wp_safe_redirect( - add_query_arg( - [ 'wcpay-login-error' => '1' ], - self::get_overview_page_url() - ) - ); - exit; + $this->redirect_service->redirect_to_overview_page_with_error( [ 'wcpay-login-error' => '1' ] ); } return; } @@ -1101,9 +938,9 @@ public function maybe_handle_onboarding() { } if ( WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE === $connect_page_source ) { - $this->redirect_to_onboarding_welcome_page(); + $this->redirect_service->redirect_to_connect_page(); } else { - $this->redirect_to_onboarding_flow_page( $connect_page_source ); + $this->redirect_to_onboarding_page_or_start_server_connection( $connect_page_source ); } } elseif ( WC_Payments_Onboarding_Service::SOURCE_WCADMIN_SETTINGS_PAGE === $connect_page_source && ! $this->is_details_submitted() ) { try { @@ -1116,14 +953,14 @@ public function maybe_handle_onboarding() { ); } catch ( Exception $e ) { Logger::error( 'Init Stripe onboarding flow failed. ' . $e ); - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( __( 'There was a problem redirecting you to the account connection page. Please try again.', 'woocommerce-payments' ) ); } return; } else { // Accounts with Stripe account connected will be redirected to the overview page. - $this->redirect_to( static::get_overview_page_url() ); + $this->redirect_service->redirect_to_overview_page(); } } @@ -1138,7 +975,7 @@ public function maybe_handle_onboarding() { // Set the test mode to false now that we are handling a real onboarding. WC_Payments_Onboarding_Service::set_test_mode( false ); - $this->redirect_to_onboarding_flow_page( $connect_page_source ); + $this->redirect_to_onboarding_page_or_start_server_connection( $connect_page_source ); return; } @@ -1147,7 +984,7 @@ public function maybe_handle_onboarding() { // Delete the account. $this->payments_api_client->delete_account( $test_mode ); - $this->redirect_to_onboarding_flow_page( $connect_page_source ); + $this->redirect_to_onboarding_page_or_start_server_connection( $connect_page_source ); return; } @@ -1162,7 +999,7 @@ public function maybe_handle_onboarding() { $event_properties ); - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( sprintf( /* translators: %s: WooPayments */ __( 'Connection to WordPress.com failed. Please connect to WordPress.com to start using %s.', 'woocommerce-payments' ), @@ -1194,7 +1031,7 @@ public function maybe_handle_onboarding() { ] ); } catch ( Exception $e ) { - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( /* translators: error message. */ sprintf( __( 'There was a problem connecting this site to WordPress.com: "%s"', 'woocommerce-payments' ), $e->getMessage() ) ); @@ -1211,7 +1048,7 @@ public function maybe_handle_onboarding() { ); } catch ( Exception $e ) { Logger::error( 'Init Stripe onboarding flow failed. ' . $e ); - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( __( 'There was a problem redirecting you to the account connection page. Please try again.', 'woocommerce-payments' ) ); } @@ -1343,18 +1180,6 @@ public static function is_on_boarding_disabled() { return get_transient( self::ON_BOARDING_DISABLED_TRANSIENT ); } - /** - * Calls wp_safe_redirect and exit. - * - * This method will end the execution immediately after the redirection. - * - * @param string $location The URL to redirect to. - */ - protected function redirect_to( $location ) { - wp_safe_redirect( $location ); - exit; - } - /** * Starts the Jetpack connection flow if it's not already fully connected. * @@ -1386,23 +1211,6 @@ private function maybe_init_jetpack_connection( $wcpay_connect_from, $additional $this->payments_api_client->start_server_connection( $redirect ); } - /** - * For the connected account, fetches the login url from the API and redirects to it - */ - private function redirect_to_login() { - // Clear account transient when generating Stripe dashboard's login link. - $this->clear_cache(); - $redirect_url = static::get_overview_page_url(); - - $request = Get_Account_Login_Data::create(); - $request->set_redirect_url( $redirect_url ); - - $response = $request->send(); - $login_data = $response->to_array(); - wp_safe_redirect( $login_data['url'] ); - exit; - } - /** * Builds the URL to return the user to after the Jetpack/Onboarding flow. * @@ -1441,7 +1249,7 @@ private function get_onboarding_return_url( $wcpay_connect_from ) { */ private function init_stripe_onboarding( $wcpay_connect_from, $additional_args = [] ) { if ( get_transient( self::ON_BOARDING_STARTED_TRANSIENT ) ) { - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( __( 'There was a duplicate attempt to initiate account setup. Please wait a few seconds and try again.', 'woocommerce-payments' ) ); return; @@ -1546,20 +1354,17 @@ private function init_stripe_onboarding( $wcpay_connect_from, $additional_args = if ( false === $onboarding_data['url'] ) { WC_Payments::get_gateway()->update_option( 'enabled', 'yes' ); update_option( '_wcpay_onboarding_stripe_connected', [ 'is_existing_stripe_account' => true ] ); - wp_safe_redirect( - add_query_arg( - [ 'wcpay-connection-success' => '1' ], - $return_url - ) + $redirect_url = add_query_arg( + [ 'wcpay-connection-success' => '1' ], + $return_url ); - exit; + $this->redirect_service->redirect_to( $redirect_url ); } set_transient( 'woopay_enabled_by_default', $onboarding_data['woopay_enabled_by_default'], DAY_IN_SECONDS ); set_transient( 'wcpay_stripe_onboarding_state', $onboarding_data['state'], DAY_IN_SECONDS ); - wp_safe_redirect( $onboarding_data['url'] ); - exit; + $this->redirect_service->redirect_to( $onboarding_data['url'] ); } /** @@ -1584,7 +1389,7 @@ public function maybe_activate_woopay() { */ private function finalize_connection( $state, $mode ) { if ( get_transient( 'wcpay_stripe_onboarding_state' ) !== $state ) { - $this->redirect_to_onboarding_welcome_page( + $this->redirect_service->redirect_to_connect_page( __( 'There was a problem processing your account data. Please try again.', 'woocommerce-payments' ) ); return; @@ -1623,8 +1428,7 @@ private function finalize_connection( $state, $mode ) { $params['wcpay-connection-success'] = '1'; } - wp_safe_redirect( add_query_arg( $params ) ); - exit; + $this->redirect_service->redirect_to( add_query_arg( $params ) ); } /** @@ -2053,20 +1857,20 @@ function (): array { * * @return void */ - private function redirect_to_onboarding_flow_page( string $source ) { + private function redirect_to_onboarding_page_or_start_server_connection( string $source ) { if ( ! WC_Payments_Utils::should_use_new_onboarding_flow() ) { return; } - // Track the Jetpack connection start. - $this->tracks_event( self::TRACKS_EVENT_ACCOUNT_CONNECT_WPCOM_CONNECTION_START ); - $onboarding_url = add_query_arg( [ 'source' => $source ], admin_url( 'admin.php?page=wc-admin&path=/payments/onboarding' ) ); if ( ! $this->payments_api_client->is_server_connected() ) { + // TODO extract it to redirect service when we have a chance to refactor tracks events. + // Track the Jetpack connection start. + $this->tracks_event( self::TRACKS_EVENT_ACCOUNT_CONNECT_WPCOM_CONNECTION_START ); try { $this->payments_api_client->start_server_connection( $onboarding_url ); } catch ( API_Exception $e ) { @@ -2074,7 +1878,7 @@ private function redirect_to_onboarding_flow_page( string $source ) { return; } } else { - $this->redirect_to( $onboarding_url ); + $this->redirect_service->redirect_to( $onboarding_url ); } } diff --git a/includes/class-wc-payments-redirect-service.php b/includes/class-wc-payments-redirect-service.php new file mode 100644 index 00000000000..a07d135e454 --- /dev/null +++ b/includes/class-wc-payments-redirect-service.php @@ -0,0 +1,189 @@ +payments_api_client = $payments_api_client; + } + + /** + * Calls wp_safe_redirect and exit. + * + * This method will end the execution immediately after the redirection. + * + * @param string $location The URL to redirect to. + */ + public function redirect_to( string $location ): void { + wp_safe_redirect( $location ); + exit; + } + + /** + * Redirects to the wcpay-connect URL, which then redirects to the KYC flow. + * + * This URL is used by the KYC reminder email. We can't take the merchant + * directly to the wcpay-connect URL because it's nonced, and the + * nonce will likely be expired by the time the user follows the link. + * That's why we need this middleman instead. + * + * @param string $from Source of the redirect. + */ + public function redirect_to_wcpay_connect( string $from = '' ): void { + // Take the user to the 'wcpay-connect' URL. + // We handle creating and redirecting to the account link there. + $params = [ + 'wcpay-connect' => '1', + '_wpnonce' => wp_create_nonce( 'wcpay-connect' ), + ]; + if ( '' !== $from ) { + $params['from'] = $from; + } + $connect_url = add_query_arg( + $params, + admin_url( 'admin.php' ) + ); + + $this->redirect_to( $connect_url ); + } + + /** + * Redirects to the capital view offer page or overview page with error message. + */ + public function redirect_to_capital_view_offer_page(): void { + $return_url = WC_Payments_Account::get_overview_page_url(); + $refresh_url = add_query_arg( [ 'wcpay-loan-offer' => '' ], admin_url( 'admin.php' ) ); + + try { + $request = Get_Account_Capital_Link::create(); + $type = 'capital_financing_offer'; + $request->set_type( $type ); + $request->set_return_url( $return_url ); + $request->set_refresh_url( $refresh_url ); + + $capital_link = $request->send(); + $this->redirect_to( $capital_link['url'] ); + } catch ( Exception $e ) { + + $this->redirect_to_overview_page_with_error( [ 'wcpay-loan-offer-error' => '1' ] ); + } + } + + /** + * Function to immediately redirect to the account link. + * + * @param array $args The arguments to be sent with the link request. + */ + public function redirect_to_account_link( array $args ): void { + try { + $link = $this->payments_api_client->get_link( $args ); + + if ( isset( $args['type'] ) && 'complete_kyc_link' === $args['type'] && isset( $link['state'] ) ) { + set_transient( 'wcpay_stripe_onboarding_state', $link['state'], DAY_IN_SECONDS ); + } + + $this->redirect_to( $link['url'] ); + } catch ( API_Exception $e ) { + $this->redirect_to_overview_page_with_error( [ 'wcpay-server-link-error' => '1' ] ); + } + } + + /** + * Function to immediately redirect to the main "Welcome to WooPayments" connect page. + * Note that this function immediately ends the execution. + * + * @param string|null $error_message Optional error message to show in a notice. + * @param string $from Optional source of the redirect. + */ + public function redirect_to_connect_page( ?string $error_message = null, string $from = '' ): void { + if ( isset( $error_message ) ) { + set_transient( WC_Payments_Account::ERROR_MESSAGE_TRANSIENT, $error_message, 30 ); + } + + $params = [ + 'page' => 'wc-admin', + 'path' => '/payments/connect', + ]; + + if ( count( $params ) === count( array_intersect_assoc( $_GET, $params ) ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended + // We are already in the onboarding page, do nothing. + return; + } + + if ( '' !== $from ) { + $params['from'] = $from; + } + + $this->redirect_to( admin_url( add_query_arg( $params, 'admin.php' ) ) ); + } + + /** + * Redirect to the overview page. + * + * @param string $from Source of the redirect. + */ + public function redirect_to_overview_page( string $from = '' ): void { + $overview_page_url = WC_Payments_Account::get_overview_page_url(); + if ( '' !== $from ) { + $overview_page_url = add_query_arg( 'from', $from, $overview_page_url ); + } + $this->redirect_to( $overview_page_url ); + } + + /** + * Redirect to the overview page with an error message. + * + * @param array $error The error data to show. + */ + public function redirect_to_overview_page_with_error( array $error ): void { + $overview_url_with_error = add_query_arg( + $error, + WC_Payments_Account::get_overview_page_url() + ); + $this->redirect_to( $overview_url_with_error ); + } + + /** + * For the connected account, fetches the login url from the API and redirects to it. + */ + public function redirect_to_login(): void { + + $redirect_url = WC_Payments_Account::get_overview_page_url(); + + $request = Get_Account_Login_Data::create(); + $request->set_redirect_url( $redirect_url ); + + $response = $request->send(); + $login_data = $response->to_array(); + $this->redirect_to( $login_data['url'] ); + } +} diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index afa27c4f4aa..862135efbad 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -82,6 +82,13 @@ class WC_Payments { */ private static $session_service; + /** + * Instance of WC_Payments_Redirect_Service, created in init function. + * + * @var WC_Payments_Redirect_Service + */ + private static $redirect_service; + /** * Instance of WC_Payments_Customer_Service, created in init function. * @@ -385,6 +392,7 @@ public static function init() { include_once __DIR__ . '/compat/subscriptions/trait-wc-payments-subscriptions-utilities.php'; include_once __DIR__ . '/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php'; include_once __DIR__ . '/class-wc-payments-session-service.php'; + include_once __DIR__ . '/class-wc-payments-redirect-service.php'; include_once __DIR__ . '/class-wc-payments-account.php'; include_once __DIR__ . '/class-wc-payments-customer-service.php'; include_once __DIR__ . '/class-logger.php'; @@ -495,7 +503,8 @@ public static function init() { self::$order_service = new WC_Payments_Order_Service( self::$api_client ); self::$action_scheduler_service = new WC_Payments_Action_Scheduler_Service( self::$api_client, self::$order_service ); self::$session_service = new WC_Payments_Session_Service( self::$api_client ); - self::$account = new WC_Payments_Account( self::$api_client, self::$database_cache, self::$action_scheduler_service, self::$session_service ); + self::$redirect_service = new WC_Payments_Redirect_Service( self::$api_client ); + self::$account = new WC_Payments_Account( self::$api_client, self::$database_cache, self::$action_scheduler_service, self::$session_service, self::$redirect_service ); self::$customer_service = new WC_Payments_Customer_Service( self::$api_client, self::$account, self::$database_cache, self::$session_service, self::$order_service ); self::$token_service = new WC_Payments_Token_Service( self::$api_client, self::$customer_service ); self::$remote_note_service = new WC_Payments_Remote_Note_Service( WC_Data_Store::load( 'admin-note' ) ); diff --git a/tests/unit/admin/test-class-wc-payments-admin.php b/tests/unit/admin/test-class-wc-payments-admin.php index ca7917968cb..e4a9ca98bd2 100644 --- a/tests/unit/admin/test-class-wc-payments-admin.php +++ b/tests/unit/admin/test-class-wc-payments-admin.php @@ -221,9 +221,9 @@ private function mock_current_user_is_admin() { } /** - * @dataProvider data_maybe_redirect_to_onboarding + * @dataProvider data_maybe_redirect_from_payments_admin_child_pages */ - public function test_maybe_redirect_to_onboarding( $expected_times_redirect_called, $is_stripe_connected, $get_params ) { + public function test_maybe_redirect_from_payments_admin_child_pages( $expected_times_redirect_called, $is_stripe_connected, $get_params ) { $this->mock_current_user_is_admin(); $_GET = $get_params; @@ -235,13 +235,13 @@ public function test_maybe_redirect_to_onboarding( $expected_times_redirect_call ->expects( $this->exactly( $expected_times_redirect_called ) ) ->method( 'redirect_to_onboarding_welcome_page' ); - $this->payments_admin->maybe_redirect_to_onboarding(); + $this->payments_admin->maybe_redirect_from_payments_admin_child_pages(); } /** - * Data provider for test_maybe_redirect_to_onboarding + * Data provider for test_maybe_redirect_from_payments_admin_child_pages */ - public function data_maybe_redirect_to_onboarding() { + public function data_maybe_redirect_from_payments_admin_child_pages() { return [ 'no_get_params' => [ 0, @@ -297,109 +297,6 @@ public function data_maybe_redirect_to_onboarding() { ]; } - /** - * @dataProvider data_maybe_redirect_overview_to_connect - */ - public function test_maybe_redirect_overview_to_connect( $expected_times_redirect_called, $is_wc_registered_page, $get_params ) { - global $wp_actions; - $this->mock_current_user_is_admin(); - // Avoid WP doing_it_wrong warnings. - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $wp_actions['current_screen'] = true; - - $_GET = $get_params; - - // Register the Payments > Connect page as the top level menu item. - wc_admin_register_page( - [ - 'id' => 'wc-payments', - 'title' => __( 'Payments', 'woocommerce-payments' ), - 'capability' => 'manage_woocommerce', - 'path' => '/payments/connect', - 'position' => '55.7', // After WooCommerce & Product menu items. - 'icon' => '', - 'nav_args' => [ - 'title' => 'WooPayments', - 'is_category' => false, - 'menuId' => 'plugins', - 'is_top_level' => true, - ], - ] - ); - - // Whether the current page should be treated as a registered WC admin page or not. - if ( $is_wc_registered_page ) { - add_filter( 'woocommerce_navigation_is_registered_page', '__return_true', 999 ); - } - - $this->mock_account - ->expects( $this->exactly( $expected_times_redirect_called ) ) - ->method( 'redirect_to_onboarding_welcome_page' ); - - $this->payments_admin->maybe_redirect_overview_to_connect(); - - remove_filter( 'woocommerce_navigation_is_registered_page', '__return_true', 999 ); - } - - /** - * Data provider for test_maybe_redirect_overview_to_connect - */ - public function data_maybe_redirect_overview_to_connect() { - return [ - 'no_get_params' => [ - 0, - false, - [], - ], - 'empty_page_param' => [ - 0, - false, - [ - 'path' => '/payments/overview', - ], - ], - 'incorrect_page_param' => [ - 0, - false, - [ - 'page' => 'wc-settings', - 'path' => '/payments/overview', - ], - ], - 'empty_path_param' => [ - 0, - false, - [ - 'page' => 'wc-admin', - ], - ], - 'incorrect_path_param' => [ - 0, - false, - [ - 'page' => 'wc-admin', - 'path' => '/payments/does-not-exist', - ], - ], - 'wc registered page' => [ - 0, - true, - [ - 'page' => 'wc-admin', - 'path' => '/payments/overview', - ], - ], - 'happy_path' => [ - 1, - false, - [ - 'page' => 'wc-admin', - 'path' => '/payments/overview', - ], - ], - ]; - } - /** * Tests WC_Payments_Admin::add_disputes_notification_badge() */ diff --git a/tests/unit/test-class-wc-payments-account-capital.php b/tests/unit/test-class-wc-payments-account-capital.php index a32610f1625..240d2bc2e51 100644 --- a/tests/unit/test-class-wc-payments-account-capital.php +++ b/tests/unit/test-class-wc-payments-account-capital.php @@ -56,6 +56,13 @@ class WC_Payments_Account_Capital_Test extends WCPAY_UnitTestCase { */ private $mock_session_service; + /** + * Mock WC_Payments_Redirect_Service. + * + * @var WC_Payments_Redirect_Service|MockObject + */ + private $mock_redirect_service; + /** * Pre-test setup */ @@ -74,11 +81,12 @@ public function set_up() { $this->mock_database_cache = $this->createMock( Database_Cache::class ); $this->mock_action_scheduler_service = $this->createMock( WC_Payments_Action_Scheduler_Service::class ); $this->mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); + $this->mock_redirect_service = $this->createMock( WC_Payments_Redirect_Service::class ); // Mock WC_Payments_Account without redirect_to to prevent headers already sent error. $this->wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethods( [ 'redirect_to', 'init_hooks' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) + ->setMethods( [ 'init_hooks' ] ) + ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service, $this->mock_redirect_service ] ) ->getMock(); $this->wcpay_account->init_hooks(); } @@ -94,93 +102,45 @@ public function tear_down() { parent::tear_down(); } - public function test_maybe_redirect_to_capital_offer_will_run() { + public function test_maybe_redirect_by_get_param_will_run() { $wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethodsExcept( [ 'maybe_redirect_to_capital_offer', 'init_hooks' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) + ->setMethodsExcept( [ 'init_hooks' ] ) + ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service, $this->mock_redirect_service ] ) ->getMock(); $wcpay_account->init_hooks(); $this->assertNotFalse( - has_action( 'admin_init', [ $wcpay_account, 'maybe_redirect_to_capital_offer' ] ) + has_action( 'admin_init', [ $wcpay_account, 'maybe_redirect_by_get_param' ] ) ); } public function test_maybe_redirect_to_capital_offer_skips_ajax_requests() { add_filter( 'wp_doing_ajax', '__return_true' ); - $this->mock_wcpay_request( Get_Account_Capital_Link::class, 0 ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_capital_view_offer_page' ); - $this->wcpay_account->maybe_redirect_to_capital_offer(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_capital_offer_skips_non_admin_users() { wp_set_current_user( 0 ); - $this->mock_wcpay_request( Get_Account_Capital_Link::class, 0 ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_capital_view_offer_page' ); - $this->wcpay_account->maybe_redirect_to_capital_offer(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_capital_offer_skips_regular_requests() { unset( $_GET['wcpay-loan-offer'] ); - $this->mock_wcpay_request( Get_Account_Capital_Link::class, 0 ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_capital_view_offer_page' ); - $this->wcpay_account->maybe_redirect_to_capital_offer(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_capital_offer_redirects_to_capital_offer() { - $request = $this->mock_wcpay_request( Get_Account_Capital_Link::class ); - $request - ->expects( $this->once() ) - ->method( 'set_type' ) - ->with( 'capital_financing_offer' ); - - $request - ->expects( $this->once() ) - ->method( 'set_return_url' ) - ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/overview' ); - - $request - ->expects( $this->once() ) - ->method( 'set_refresh_url' ) - ->with( 'http://example.org/wp-admin/admin.php?wcpay-loan-offer' ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willReturn( new Response( [ 'url' => 'https://capital.url' ] ) ); - - $this->wcpay_account->expects( $this->once() )->method( 'redirect_to' )->with( 'https://capital.url' ); - - $this->wcpay_account->maybe_redirect_to_capital_offer(); - } - - public function test_maybe_redirect_to_capital_offer_redirects_to_overview_on_error() { - $request = $this->mock_wcpay_request( Get_Account_Capital_Link::class ); - $request - ->expects( $this->once() ) - ->method( 'set_type' ) - ->with( 'capital_financing_offer' ); - - $request - ->expects( $this->once() ) - ->method( 'set_return_url' ) - ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/overview' ); - - $request - ->expects( $this->once() ) - ->method( 'set_refresh_url' ) - ->with( 'http://example.org/wp-admin/admin.php?wcpay-loan-offer' ); - - $request->expects( $this->once() ) - ->method( 'format_response' ) - ->willThrowException( - new API_Exception( 'Error: This account has no offer of financing from Capital.', 'invalid_request_error', 400 ) - ); - - $this->wcpay_account->expects( $this->once() )->method( 'redirect_to' )->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Foverview&wcpay-loan-offer-error=1' ); + $this->mock_redirect_service->expects( $this->once() )->method( 'redirect_to_capital_view_offer_page' ); - $this->wcpay_account->maybe_redirect_to_capital_offer(); + $this->wcpay_account->maybe_redirect_by_get_param(); } } diff --git a/tests/unit/test-class-wc-payments-account-link.php b/tests/unit/test-class-wc-payments-account-link.php index 09ec6e4a039..4ab2c92a8af 100644 --- a/tests/unit/test-class-wc-payments-account-link.php +++ b/tests/unit/test-class-wc-payments-account-link.php @@ -54,6 +54,13 @@ class WC_Payments_Account_Server_Links_Test extends WCPAY_UnitTestCase { */ private $mock_session_service; + /** + * Mock WC_Payments_Redirect_Service. + * + * @var WC_Payments_Redirect_Service|MockObject + */ + private $mock_redirect_service; + /** * Pre-test setup */ @@ -72,11 +79,12 @@ public function set_up() { $this->mock_database_cache = $this->createMock( Database_Cache::class ); $this->mock_action_scheduler_service = $this->createMock( WC_Payments_Action_Scheduler_Service::class ); $this->mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); + $this->mock_redirect_service = $this->createMock( WC_Payments_Redirect_Service::class ); // Mock WC_Payments_Account without redirect_to to prevent headers already sent error. $this->wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethods( [ 'redirect_to' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) + ->setMethods( [ 'init_hooks' ] ) + ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service, $this->mock_redirect_service ] ) ->getMock(); $this->wcpay_account->init_hooks(); @@ -93,47 +101,28 @@ public function tear_down() { parent::tear_down(); } - public function test_maybe_redirect_to_server_link_will_run() { - $this->assertNotFalse( - has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_to_server_link' ] ) - ); - } - public function test_maybe_redirect_to_server_link_skips_ajax_requests() { add_filter( 'wp_doing_ajax', '__return_true' ); - $this->mock_api_client->expects( $this->never() )->method( 'get_link' ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_account_link' ); - $this->wcpay_account->maybe_redirect_to_server_link(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_server_link_skips_non_admin_users() { wp_set_current_user( 0 ); - $this->mock_api_client->expects( $this->never() )->method( 'get_link' ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_account_link' ); - $this->wcpay_account->maybe_redirect_to_server_link(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_server_link_skips_regular_requests() { unset( $_GET['wcpay-link-handler'] ); - $this->mock_api_client->expects( $this->never() )->method( 'get_link' ); - - $this->wcpay_account->maybe_redirect_to_server_link(); - } - - public function test_maybe_redirect_to_server_link_redirects_to_link() { - $this->mock_api_client - ->method( 'get_link' ) - ->willReturn( [ 'url' => 'https://link.url' ] ); - - $this->wcpay_account - ->expects( $this->once() ) - ->method( 'redirect_to' ) - ->with( 'https://link.url' ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to_account_link' ); - $this->wcpay_account->maybe_redirect_to_server_link(); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_server_link_forwards_all_arguments() { @@ -141,31 +130,17 @@ public function test_maybe_redirect_to_server_link_forwards_all_arguments() { $_GET['id'] = 'link_id'; $_GET['random_arg'] = 'random_arg'; - $this->mock_api_client + $this->mock_redirect_service ->expects( $this->once() ) - ->method( 'get_link' ) + ->method( 'redirect_to_account_link' ) ->with( [ 'type' => 'login_link', 'id' => 'link_id', 'random_arg' => 'random_arg', ] - ) - ->willReturn( [ 'url' => 'https://link.url' ] ); - - $this->wcpay_account->maybe_redirect_to_server_link(); - } - - public function test_maybe_redirect_to_server_link_redirects_to_overview_on_error() { - $this->mock_api_client - ->method( 'get_link' ) - ->willThrowException( new API_Exception( 'Error: The requested link is invalid.', 'invalid_request_error', 400 ) ); - - $this->wcpay_account - ->expects( $this->once() ) - ->method( 'redirect_to' ) - ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Foverview&wcpay-server-link-error=1' ); + ); - $this->wcpay_account->maybe_redirect_to_server_link(); + $this->wcpay_account->maybe_redirect_by_get_param(); } } diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index a3725e2c412..e285d8a609a 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -57,6 +57,13 @@ class WC_Payments_Account_Test extends WCPAY_UnitTestCase { */ private $mock_session_service; + /** + * Mock WC_Payments_Redirect_Service. + * + * @var WC_Payments_Redirect_Service|MockObject + */ + private $mock_redirect_service; + /** * Pre-test setup */ @@ -77,8 +84,9 @@ public function set_up() { $this->mock_database_cache = $this->createMock( Database_Cache::class ); $this->mock_action_scheduler_service = $this->createMock( WC_Payments_Action_Scheduler_Service::class ); $this->mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); + $this->mock_redirect_service = $this->createMock( WC_Payments_Redirect_Service::class ); - $this->wcpay_account = new WC_Payments_Account( $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ); + $this->wcpay_account = new WC_Payments_Account( $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service, $this->mock_redirect_service ); $this->wcpay_account->init_hooks(); } @@ -90,13 +98,10 @@ public function tear_down() { public function test_filters_registered_properly() { $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_handle_onboarding' ] ), 'maybe_handle_onboarding action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_to_onboarding' ] ), 'maybe_redirect_to_onboarding action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_to_wcpay_connect' ] ), 'maybe_redirect_to_wcpay_connect action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_to_capital_offer' ] ), 'maybe_redirect_to_capital_offer action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_to_server_link' ] ), 'maybe_redirect_to_server_link action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_settings_to_connect_or_overview' ] ), 'maybe_redirect_settings_to_connect_or_overview action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_onboarding_flow_to_overview' ] ), 'maybe_redirect_onboarding_flow_to_overview action does not exist.' ); - $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_onboarding_flow_to_connect' ] ), 'maybe_redirect_onboarding_flow_to_connect action does not exist.' ); + $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_after_plugin_activation' ] ), 'maybe_redirect_after_plugin_activation action does not exist.' ); + $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_by_get_param' ] ), 'maybe_redirect_by_get_param action does not exist.' ); + $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_from_settings_page' ] ), 'maybe_redirect_from_settings_page action does not exist.' ); + $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_redirect_from_onboarding_page' ] ), 'maybe_redirect_from_onboarding_page action does not exist.' ); $this->assertNotFalse( has_action( 'admin_init', [ $this->wcpay_account, 'maybe_activate_woopay' ] ), 'maybe_activate_woopay action does not exist.' ); $this->assertNotFalse( has_action( 'woocommerce_payments_account_refreshed', [ $this->wcpay_account, 'handle_instant_deposits_inbox_note' ] ), 'handle_instant_deposits_inbox_note action does not exist.' ); $this->assertNotFalse( has_action( 'woocommerce_payments_account_refreshed', [ $this->wcpay_account, 'handle_loan_approved_inbox_note' ] ), 'handle_loan_approved_inbox_note action does not exist.' ); @@ -123,7 +128,7 @@ public function test_maybe_redirect_to_onboarding_stripe_disconnected_redirects( new API_Exception( 'test', 'wcpay_account_not_found', 401 ) ); - $this->assertTrue( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertTrue( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); $this->assertFalse( WC_Payments_Account::is_on_boarding_disabled() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); @@ -149,7 +154,7 @@ public function test_maybe_redirect_to_onboarding_stripe_disconnected_and_on_boa ) ); - $this->assertTrue( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertTrue( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); $this->assertTrue( WC_Payments_Account::is_on_boarding_disabled() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); @@ -173,7 +178,7 @@ public function test_maybe_redirect_to_onboarding_account_error() { $this->expectException( Exception::class ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // Should not update the option. $this->assertTrue( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); } @@ -203,7 +208,7 @@ public function test_maybe_redirect_to_onboarding_account_connected() { ) ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); } @@ -219,7 +224,7 @@ public function test_maybe_redirect_to_onboarding_with_non_admin_user() { $this->mock_wcpay_request( Get_Account::class, 0 ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertTrue( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); } @@ -249,9 +254,9 @@ public function test_maybe_redirect_to_onboarding_checks_the_account_once() { ) ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // call the method twice but use the mock_api_client to make sure the account has been retrieved only once. - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_onboarding() ); + $this->assertFalse( $this->wcpay_account->maybe_redirect_after_plugin_activation() ); // The option should be updated. $this->assertFalse( (bool) get_option( 'wcpay_should_redirect_to_onboarding', false ) ); } @@ -298,14 +303,14 @@ public function test_maybe_redirect_to_onboarding_returns_true_and_on_boarding_r update_option( 'wcpay_should_redirect_to_onboarding', true ); // First call, on-boarding is disabled. - $this->wcpay_account->maybe_redirect_to_onboarding(); + $this->wcpay_account->maybe_redirect_after_plugin_activation(); $this->assertTrue( WC_Payments_Account::is_on_boarding_disabled() ); // Simulate the situation where the redirect has not happened yet. update_option( 'wcpay_should_redirect_to_onboarding', true ); // Second call, on-boarding re-enabled. - $this->wcpay_account->maybe_redirect_to_onboarding(); + $this->wcpay_account->maybe_redirect_after_plugin_activation(); $this->assertFalse( WC_Payments_Account::is_on_boarding_disabled() ); } @@ -316,15 +321,9 @@ public function test_maybe_redirect_to_wcpay_connect_do_redirect() { // Set the redirection parameter. $_GET['wcpay-connect-redirect'] = 1; - // Mock WC_Payments_Account without redirect_to to prevent headers already sent error. - $mock_wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethods( [ 'redirect_to' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) - ->getMock(); - - $mock_wcpay_account->expects( $this->once() )->method( 'redirect_to' ); + $this->mock_redirect_service->expects( $this->once() )->method( 'redirect_to_wcpay_connect' ); - $this->assertTrue( $mock_wcpay_account->maybe_redirect_to_wcpay_connect() ); + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_wcpay_connect_unauthorized_user() { @@ -332,7 +331,9 @@ public function test_maybe_redirect_to_wcpay_connect_unauthorized_user() { $editor_user = $this->factory()->user->create( [ 'role' => 'editor' ] ); wp_set_current_user( $editor_user ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_wcpay_connect() ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to' ); + + $this->wcpay_account->maybe_redirect_by_get_param(); } public function test_maybe_redirect_to_wcpay_connect_doing_ajax() { @@ -345,7 +346,9 @@ public function test_maybe_redirect_to_wcpay_connect_doing_ajax() { // Simulate we're in an AJAX request. add_filter( 'wp_doing_ajax', '__return_true' ); - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_wcpay_connect() ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to' ); + + $this->wcpay_account->maybe_redirect_by_get_param(); // Cleaning up. remove_filter( 'wp_doing_ajax', '__return_true' ); @@ -360,13 +363,15 @@ public function test_maybe_redirect_to_wcpay_connect_wrong_page() { $_GET['path'] = '/payments/overview'; - $this->assertFalse( $this->wcpay_account->maybe_redirect_to_wcpay_connect() ); + $this->mock_redirect_service->expects( $this->never() )->method( 'redirect_to' ); + + $this->wcpay_account->maybe_redirect_by_get_param(); } /** - * @dataProvider data_maybe_redirect_onboarding_flow_to_overview + * @dataProvider data_maybe_redirect_from_onboarding_page */ - public function test_maybe_redirect_onboarding_flow_to_overview( $expected_redirect_to_count, $stripe_account_connected, $get_params ) { + public function test_maybe_redirect_from_onboarding_page( $expected_redirect_to_count, $expected_method, $stripe_account_connected, $is_server_connected, $get_params ) { wp_set_current_user( 1 ); $_GET = $get_params; @@ -379,68 +384,6 @@ public function test_maybe_redirect_onboarding_flow_to_overview( $expected_redir ); } - // Mock WC_Payments_Account without redirect_to to prevent headers already sent error. - $mock_wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethods( [ 'redirect_to' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) - ->getMock(); - - $mock_wcpay_account->expects( $this->exactly( $expected_redirect_to_count ) )->method( 'redirect_to' ); - - $mock_wcpay_account->maybe_redirect_onboarding_flow_to_overview(); - } - - /** - * Data provider for test_maybe_redirect_onboarding_flow_to_overview - */ - public function data_maybe_redirect_onboarding_flow_to_overview() { - return [ - 'no_get_params' => [ - 0, - false, - [], - ], - 'missing_param' => [ - 0, - false, - [ - 'page' => 'wc-admin', - ], - ], - 'incorrect_param' => [ - 0, - false, - [ - 'page' => 'wc-settings', - 'path' => '/payments/onboarding', - ], - ], - 'account_fully_onboarded' => [ - 0, - false, - [ - 'page' => 'wc-admin', - 'path' => '/payments/onboarding', - ], - ], - 'happy_path' => [ - 1, - true, - [ - 'page' => 'wc-admin', - 'path' => '/payments/onboarding', - ], - ], - ]; - } - - /** - * @dataProvider data_maybe_redirect_onboarding_flow_to_connect - */ - public function test_maybe_redirect_onboarding_flow_to_connect( $expected_times_redirect_called, $is_server_connected, $get_params ) { - wp_set_current_user( 1 ); - $_GET = $get_params; - $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) ->disableOriginalConstructor() ->getMock(); @@ -449,39 +392,39 @@ public function test_maybe_redirect_onboarding_flow_to_connect( $expected_times_ ->method( 'is_server_connected' ) ->willReturn( $is_server_connected ); - // Mock WC_Payments_Account without redirect_to_onboarding_welcome_page to prevent headers already sent error. - $this->wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) - ->onlyMethods( [ 'redirect_to_onboarding_welcome_page' ] ) - ->getMock(); + $this->wcpay_account = new WC_Payments_Account( $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service, $this->mock_redirect_service ); - $this->wcpay_account - ->expects( $this->exactly( $expected_times_redirect_called ) ) - ->method( 'redirect_to_onboarding_welcome_page' ); + $this->mock_redirect_service->expects( $this->exactly( $expected_redirect_to_count ) )->method( $expected_method ); - $this->wcpay_account->maybe_redirect_onboarding_flow_to_connect(); + $this->wcpay_account->maybe_redirect_from_onboarding_page(); } /** - * Data provider for test_maybe_redirect_onboarding_flow_to_connect + * Data provider for test_maybe_redirect_from_onboarding_page */ - public function data_maybe_redirect_onboarding_flow_to_connect() { + public function data_maybe_redirect_from_onboarding_page() { return [ 'no_get_params' => [ 0, + 'redirect_to_connect_page', false, + true, [], ], - 'empty_page_param' => [ + 'missing_param' => [ 0, + 'redirect_to_connect_page', false, + true, [ - 'path' => '/payments/onboarding', + 'page' => 'wc-admin', ], ], - 'incorrect_page_param' => [ + 'incorrect_param' => [ 0, + 'redirect_to_connect_page', false, + true, [ 'page' => 'wc-settings', 'path' => '/payments/onboarding', @@ -489,21 +432,37 @@ public function data_maybe_redirect_onboarding_flow_to_connect() { ], 'empty_path_param' => [ 0, + 'redirect_to_connect_page', false, + true, [ 'page' => 'wc-admin', ], ], 'incorrect_path_param' => [ 0, + 'redirect_to_connect_page', false, + true, [ 'page' => 'wc-admin', 'path' => '/payments/does-not-exist', ], ], - 'server_connected' => [ + 'server_not_connected' => [ + 1, + 'redirect_to_connect_page', + false, + false, + [ + 'page' => 'wc-admin', + 'path' => '/payments/onboarding', + ], + ], + 'stripe not connected' => [ 0, + 'redirect_to_connect_page', + false, true, [ 'page' => 'wc-admin', @@ -512,7 +471,9 @@ public function data_maybe_redirect_onboarding_flow_to_connect() { ], 'happy_path' => [ 1, - false, + 'redirect_to_overview_page', + true, + true, [ 'page' => 'wc-admin', 'path' => '/payments/onboarding', @@ -522,9 +483,9 @@ public function data_maybe_redirect_onboarding_flow_to_connect() { } /** - * @dataProvider data_maybe_redirect_settings_to_connect_or_overview + * @dataProvider data_maybe_redirect_from_settings_page */ - public function test_maybe_redirect_settings_to_connect_or_overview( $expected_redirect_to_count, $details_submitted, $get_params, $no_account = false, $path = null ) { + public function test_maybe_redirect_from_settings_page( $expected_redirect_to_count, $expected_method, $details_submitted, $get_params, $no_account = false ) { wp_set_current_user( 1 ); $_GET = $get_params; @@ -537,31 +498,26 @@ public function test_maybe_redirect_settings_to_connect_or_overview( $expected_r ] ); } - // Mock WC_Payments_Account without redirect_to to prevent headers already sent error. - $mock_wcpay_account = $this->getMockBuilder( WC_Payments_Account::class ) - ->setMethods( [ 'redirect_to' ] ) - ->setConstructorArgs( [ $this->mock_api_client, $this->mock_database_cache, $this->mock_action_scheduler_service, $this->mock_session_service ] ) - ->getMock(); - - $mock_wcpay_account->expects( $this->exactly( $expected_redirect_to_count ) ) - ->method( 'redirect_to' ) - ->with( "http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/$path" ); + $this->mock_redirect_service->expects( $this->exactly( $expected_redirect_to_count ) ) + ->method( $expected_method ); - $mock_wcpay_account->maybe_redirect_settings_to_connect_or_overview(); + $this->wcpay_account->maybe_redirect_from_settings_page(); } /** - * Data provider for test_maybe_redirect_settings_to_connect_or_overview + * Data provider for test_maybe_redirect_from_settings_page */ - public function data_maybe_redirect_settings_to_connect_or_overview() { + public function data_maybe_redirect_from_settings_page() { return [ 'no_get_params' => [ 0, + 'redirect_to_connect_page', false, [], ], 'missing_param' => [ 0, + 'redirect_to_connect_page', false, [ 'page' => 'wc-settings', @@ -570,6 +526,7 @@ public function data_maybe_redirect_settings_to_connect_or_overview() { ], 'incorrect_param' => [ 0, + 'redirect_to_connect_page', false, [ 'page' => 'wc-admin', @@ -579,6 +536,7 @@ public function data_maybe_redirect_settings_to_connect_or_overview() { ], 'no_account' => [ 1, + 'redirect_to_connect_page', false, [ 'page' => 'wc-settings', @@ -586,10 +544,10 @@ public function data_maybe_redirect_settings_to_connect_or_overview() { 'section' => 'woocommerce_payments', ], true, - 'connect&from=WCADMIN_PAYMENT_SETTINGS', ], 'account_partially_onboarded' => [ 1, + 'redirect_to_overview_page', false, [ 'page' => 'wc-settings', @@ -597,10 +555,10 @@ public function data_maybe_redirect_settings_to_connect_or_overview() { 'section' => 'woocommerce_payments', ], false, - 'overview&from=WCADMIN_PAYMENT_SETTINGS', ], 'account_fully_onboarded' => [ 0, + 'redirect_to_connect_page', true, [ 'page' => 'wc-settings', diff --git a/tests/unit/test-class-wc-payments-redirect-service.php b/tests/unit/test-class-wc-payments-redirect-service.php new file mode 100644 index 00000000000..f122ba615ed --- /dev/null +++ b/tests/unit/test-class-wc-payments-redirect-service.php @@ -0,0 +1,174 @@ +previous_user_id = get_current_user_id(); + // Set admin as the current user. + wp_set_current_user( 1 ); + + $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class ); + + $this->redirect_service = $this->getMockBuilder( WC_Payments_Redirect_Service::class ) + ->setMethods( [ 'redirect_to' ] ) + ->setConstructorArgs( [ $this->mock_api_client ] ) + ->getMock(); + } + + public function tear_down() { + wp_set_current_user( $this->previous_user_id ); + + parent::tear_down(); + } + + public function test_maybe_redirect_to_capital_offer_redirects_to_capital_offer() { + // Set the request as if the user is requesting to view a capital offer. + $request = $this->mock_wcpay_request( Get_Account_Capital_Link::class ); + $request + ->expects( $this->once() ) + ->method( 'set_type' ) + ->with( 'capital_financing_offer' ); + + $request + ->expects( $this->once() ) + ->method( 'set_return_url' ) + ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/overview' ); + + $request + ->expects( $this->once() ) + ->method( 'set_refresh_url' ) + ->with( 'http://example.org/wp-admin/admin.php?wcpay-loan-offer' ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( new Response( [ 'url' => 'https://capital.url' ] ) ); + + $this->redirect_service->expects( $this->once() )->method( 'redirect_to' )->with( 'https://capital.url' ); + + $this->redirect_service->redirect_to_capital_view_offer_page(); + } + + public function test_maybe_redirect_to_capital_offer_redirects_to_overview_on_error() { + $request = $this->mock_wcpay_request( Get_Account_Capital_Link::class ); + $request + ->expects( $this->once() ) + ->method( 'set_type' ) + ->with( 'capital_financing_offer' ); + + $request + ->expects( $this->once() ) + ->method( 'set_return_url' ) + ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/overview' ); + + $request + ->expects( $this->once() ) + ->method( 'set_refresh_url' ) + ->with( 'http://example.org/wp-admin/admin.php?wcpay-loan-offer' ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willThrowException( + new API_Exception( 'Error: This account has no offer of financing from Capital.', 'invalid_request_error', 400 ) + ); + + $this->redirect_service->expects( $this->once() )->method( 'redirect_to' )->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Foverview&wcpay-loan-offer-error=1' ); + + $this->redirect_service->redirect_to_capital_view_offer_page(); + } + + public function test_redirect_to_account_link_success() { + $this->mock_api_client + ->method( 'get_link' ) + ->willReturn( [ 'url' => 'https://link.url' ] ); + + $this->redirect_service + ->expects( $this->once() ) + ->method( 'redirect_to' ) + ->with( 'https://link.url' ); + + $this->redirect_service->redirect_to_account_link( + [ + 'type' => 'login_link', + 'id' => 'link_id', + 'random_arg' => 'random_arg', + ] + ); + } + + public function test_redirect_to_account_link_to_overview_on_error() { + $this->mock_api_client + ->method( 'get_link' ) + ->willThrowException( new API_Exception( 'Error: The requested link is invalid.', 'invalid_request_error', 400 ) ); + + $this->redirect_service + ->expects( $this->once() ) + ->method( 'redirect_to' ) + ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Foverview&wcpay-server-link-error=1' ); + + $this->redirect_service->redirect_to_account_link( + [ + 'type' => 'login_link', + 'id' => 'link_id', + 'random_arg' => 'random_arg', + ] + ); + } + + public function test_redirect_to_login_success() { + $request = $this->mock_wcpay_request( Get_Account_Login_Data::class ); + + $request->expects( $this->once() ) + ->method( 'set_redirect_url' ) + ->with( 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/payments/overview' ); + + $request->expects( $this->once() ) + ->method( 'format_response' ) + ->willReturn( new Response( [ 'url' => 'https://login.url' ] ) ); + + $this->redirect_service + ->expects( $this->once() ) + ->method( 'redirect_to' ) + ->with( 'https://login.url' ); + + $this->redirect_service->redirect_to_login(); + } +}