tracks_event( $name, $properties ); } /** * Check if an onboarding action should be allowed to be processed. * * @return void * @throws ApiException If the extension is not active or onboarding is locked. */ private function check_if_onboarding_action_is_acceptable() { // If the WooPayments plugin is not active, we can't do anything. if ( ! $this->is_extension_active() ) { throw new ApiException( 'woocommerce_woopayments_onboarding_extension_not_active', /* translators: %s: WooPayments. */ sprintf( esc_html__( 'The %s extension is not active.', 'woocommerce' ), 'WooPayments' ), (int) WP_Http::FORBIDDEN ); } // If the WooPayments installed version is less than the minimum required version, we can't do anything. if ( Constants::is_defined( 'WCPAY_VERSION_NUMBER' ) && version_compare( Constants::get_constant( 'WCPAY_VERSION_NUMBER' ), self::EXTENSION_MINIMUM_VERSION, '<' ) ) { throw new ApiException( 'woocommerce_woopayments_onboarding_extension_version', /* translators: %s: WooPayments. */ sprintf( esc_html__( 'The %s extension is not up-to-date. Please update to the latest version and try again.', 'woocommerce' ), 'WooPayments' ), (int) WP_Http::FORBIDDEN ); } // If the onboarding is locked, we shouldn't do anything. if ( $this->is_onboarding_locked() ) { throw new ApiException( 'woocommerce_woopayments_onboarding_locked', esc_html__( 'Another onboarding action is already in progress. Please wait for it to finish.', 'woocommerce' ), (int) WP_Http::CONFLICT ); } } /** * Check if an onboarding step action should be allowed to be processed. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * * @return void * @throws ApiArgumentException If the onboarding step ID is invalid. * @throws ApiException If the extension is not active or step requirements are not met. */ private function check_if_onboarding_step_action_is_acceptable( string $step_id, string $location ): void { // First, check general onboarding actions. $this->check_if_onboarding_action_is_acceptable(); // Second, do onboarding step specific checks. if ( ! $this->is_valid_onboarding_step_id( $step_id ) ) { throw new ApiArgumentException( 'woocommerce_woopayments_onboarding_invalid_step_id', esc_html__( 'Invalid onboarding step ID.', 'woocommerce' ), (int) WP_Http::BAD_REQUEST ); } if ( ! $this->check_onboarding_step_requirements( $step_id, $location ) ) { throw new ApiException( 'woocommerce_woopayments_onboarding_step_requirements_not_met', esc_html__( 'Onboarding step requirements are not met.', 'woocommerce' ), (int) WP_Http::FORBIDDEN ); } if ( $this->is_onboarding_step_blocked( $step_id, $location ) ) { throw new ApiException( 'woocommerce_woopayments_onboarding_step_blocked', esc_html__( 'There are environment or store setup issues which are blocking progress. Please resolve them to proceed.', 'woocommerce' ), (int) WP_Http::FORBIDDEN, array( 'error' => map_deep( $this->get_onboarding_step_error( $step_id, $location ), 'esc_html' ), ), ); } } /** * Check if the onboarding is locked. * * @return bool Whether the onboarding is locked. */ private function is_onboarding_locked(): bool { return 'yes' === $this->proxy->call_function( 'get_option', self::NOX_ONBOARDING_LOCKED_KEY, 'no' ); } /** * Lock the onboarding. * * This will save a flag in the database to indicate that onboarding is locked. * This is used to prevent certain onboarding actions to happen while others have not finished. * This is especially important for actions that modify the account (initializing it, deleting it, etc.) * These actions tend to be longer-running and we want to have backstops in place to prevent race conditions. * * @return void */ private function set_onboarding_lock(): void { $this->proxy->call_function( 'update_option', self::NOX_ONBOARDING_LOCKED_KEY, 'yes' ); } /** * Unlock the onboarding. * * @return void */ private function clear_onboarding_lock(): void { // We update rather than delete the option for performance reasons. $this->proxy->call_function( 'update_option', self::NOX_ONBOARDING_LOCKED_KEY, 'no' ); } /** * Get the onboarding details for each step. * * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $rest_path The REST API path to use for constructing REST API URLs. * @param string|null $source Optional. The source for the onboarding flow. * * @return array[] The list of onboarding steps details. * @throws Exception If there was an error generating the onboarding steps details. */ private function get_onboarding_steps( string $location, string $rest_path, ?string $source = self::SESSION_ENTRY_DEFAULT ): array { $steps = array(); // Add the payment methods onboarding step details, but only if we have recommended payment methods. $recommended_pms = $this->get_onboarding_recommended_payment_methods( $location ); if ( ! empty( $recommended_pms ) ) { $steps[] = $this->standardize_onboarding_step_details( array( 'id' => self::ONBOARDING_STEP_PAYMENT_METHODS, 'context' => array( 'recommended_pms' => $recommended_pms, 'pms_state' => $this->get_onboarding_payment_methods_state( $location, $recommended_pms ), ), 'actions' => array( 'start' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_PAYMENT_METHODS . '/start' ), ), 'save' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_PAYMENT_METHODS . '/save' ), ), 'finish' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_PAYMENT_METHODS . '/finish' ), ), ), ), $location, $rest_path ); } // Add the WPCOM connection onboarding step details. $wpcom_step = $this->standardize_onboarding_step_details( array( 'id' => self::ONBOARDING_STEP_WPCOM_CONNECTION, 'context' => array( 'connection_state' => $this->get_wpcom_connection_state(), ), ), $location, $rest_path ); // If the WPCOM connection is already set up, we don't need to add anything more. if ( self::ONBOARDING_STEP_STATUS_COMPLETED !== $wpcom_step['status'] ) { // Craft the return URL. switch ( $source ) { case self::SESSION_ENTRY_LYS: // If the source is LYS, we return the user to the Launch Your Store flow. $return_url = $this->proxy->call_function( 'admin_url', 'admin.php?page=wc-admin&path=/launch-your-store' . self::ONBOARDING_PATH_BASE . '&sidebar=hub&content=payments' ); break; default: // By default, we return the user to the onboarding modal in the Settings > Payments page. $return_url = $this->proxy->call_static( Utils::class, 'wc_payments_settings_url', self::ONBOARDING_PATH_BASE ); break; } // Add standardized query arguments to the return URL. $return_url = add_query_arg( array( // URL query flag so we can properly identify when the user returns // either by accepting or rejecting the WPCOM connection. self::WPCOM_CONNECTION_RETURN_PARAM => '1', // Keep the source. 'source' => $source, // Attach the `from` parameter to more easily identify where the return request is coming from. 'from' => self::FROM_WPCOM, ), $return_url ); // Try to generate the authorization URL. $wpcom_connection = $this->get_wpcom_connection_authorization( $return_url ); if ( ! $wpcom_connection['success'] ) { // In case of errors, make sure we work with a list of error messages. $wpcom_step['errors'] = array_values( (array) ( $wpcom_connection['errors'] ?? array() ) ); } $wpcom_step['actions'] = array( 'start' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_WPCOM_CONNECTION . '/start' ), ), 'auth' => array( 'type' => self::ACTION_TYPE_REDIRECT, 'href' => $wpcom_connection['url'], ), ); } $steps[] = $wpcom_step; // Test account onboarding step is unavailable in UAE and Singapore. if ( ! in_array( $location, array( 'AE', 'SG' ), true ) ) { $test_account_step = $this->standardize_onboarding_step_details( array( 'id' => self::ONBOARDING_STEP_TEST_ACCOUNT, ), $location, $rest_path ); // If the step is not completed, we need to add the actions. if ( self::ONBOARDING_STEP_STATUS_COMPLETED !== $test_account_step['status'] ) { $test_account_step['actions'] = array( 'start' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_TEST_ACCOUNT . '/start' ), ), 'init' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_TEST_ACCOUNT . '/init' ), ), 'finish' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_TEST_ACCOUNT . '/finish' ), ), ); } $steps[] = $test_account_step; } // Add the live account business verification onboarding step details. $business_verification_step = $this->standardize_onboarding_step_details( array( 'id' => self::ONBOARDING_STEP_BUSINESS_VERIFICATION, 'context' => array( 'fields' => array(), 'sub_steps' => $this->get_nox_profile_onboarding_step_data_entry( self::ONBOARDING_STEP_BUSINESS_VERIFICATION, $location, 'sub_steps', array() ), 'self_assessment' => $this->get_nox_profile_onboarding_step_data_entry( self::ONBOARDING_STEP_BUSINESS_VERIFICATION, $location, 'self_assessment', array() ), 'has_test_account' => $this->has_test_account(), ), ), $location, $rest_path ); // Try to get the pre-KYC fields, but only if the required step is completed. // This is because WooPayments needs a working WPCOM connection to be able to fetch the fields. if ( $this->check_onboarding_step_requirements( self::ONBOARDING_STEP_BUSINESS_VERIFICATION, $location ) ) { try { $business_verification_step['context']['fields'] = $this->get_onboarding_kyc_fields( $location ); } catch ( Exception $e ) { $business_verification_step['errors'][] = array( 'code' => 'fields_error', 'message' => $e->getMessage(), ); } } // If the step is not completed, we need to add the actions. if ( self::ONBOARDING_STEP_STATUS_COMPLETED !== $business_verification_step['status'] ) { $business_verification_step['actions'] = array( 'start' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_BUSINESS_VERIFICATION . '/start' ), ), 'save' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_BUSINESS_VERIFICATION . '/save' ), ), 'kyc_session' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_BUSINESS_VERIFICATION . '/kyc_session' ), ), 'kyc_session_finish' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_BUSINESS_VERIFICATION . '/kyc_session/finish' ), ), 'kyc_fallback' => array( 'type' => self::ACTION_TYPE_REDIRECT, 'href' => $this->get_onboarding_kyc_fallback_url(), ), 'finish' => array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . self::ONBOARDING_STEP_BUSINESS_VERIFICATION . '/finish' ), ), ); } $steps[] = $business_verification_step; // Do a complete list standardization, for safety. return $this->standardize_onboarding_steps_details( $steps, $location, $rest_path ); } /** * Standardize (and sanity check) the onboarding step details. * * @param array $step_details The onboarding step details to standardize. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $rest_path The REST API path to use for constructing REST API URLs. * * @return array The standardized onboarding step details. * @throws Exception If the onboarding step details are missing required entries or if the step ID is invalid. */ private function standardize_onboarding_step_details( array $step_details, string $location, string $rest_path ): array { // If the required keys are not present, throw. if ( ! isset( $step_details['id'] ) ) { /* translators: %s: The required key that is missing. */ throw new Exception( sprintf( esc_html__( 'The onboarding step is missing required entries: %s', 'woocommerce' ), 'id' ) ); } // Validate the step ID. if ( ! $this->is_valid_onboarding_step_id( $step_details['id'] ) ) { /* translators: %s: The invalid step ID. */ throw new Exception( sprintf( esc_html__( 'The onboarding step ID is invalid: %s', 'woocommerce' ), esc_attr( $step_details['id'] ) ) ); } if ( empty( $step_details['status'] ) ) { $step_details['status'] = $this->get_onboarding_step_status( $step_details['id'], $location ); } if ( empty( $step_details['errors'] ) ) { $step_details['errors'] = array(); // For blocked or failed steps, we include any stored error. if ( in_array( $step_details['status'], array( self::ONBOARDING_STEP_STATUS_BLOCKED, self::ONBOARDING_STEP_STATUS_FAILED ), true ) ) { $stored_error = $this->get_onboarding_step_error( $step_details['id'], $location ); if ( ! empty( $stored_error ) ) { $step_details['errors'] = array( $stored_error ); } } } // Ensure that any step has the general actions. if ( empty( $step_details['actions'] ) ) { $step_details['actions'] = array(); } // Any step can be checked for its status. if ( empty( $step_details['actions']['check'] ) ) { $step_details['actions']['check'] = array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . $step_details['id'] . '/check' ), ); } // Any step can be cleaned of its progress. if ( empty( $step_details['actions']['clean'] ) ) { $step_details['actions']['clean'] = array( 'type' => self::ACTION_TYPE_REST, 'href' => rest_url( trailingslashit( $rest_path ) . $step_details['id'] . '/clean' ), ); } return array( 'id' => $step_details['id'], 'path' => $step_details['path'] ?? trailingslashit( self::ONBOARDING_PATH_BASE ) . $step_details['id'], 'required_steps' => $step_details['required_steps'] ?? $this->get_onboarding_step_required_steps( $step_details['id'] ), 'status' => $step_details['status'], 'errors' => $step_details['errors'], 'actions' => $step_details['actions'], 'context' => $step_details['context'] ?? array(), ); } /** * Standardize (and sanity check) the onboarding steps list. * * @param array $steps The onboarding steps list to standardize. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $rest_path The REST API path to use for constructing REST API URLs. * * @return array The standardized onboarding steps list. * @throws Exception If some onboarding steps are missing required entries or if invalid step IDs are present. */ private function standardize_onboarding_steps_details( array $steps, string $location, string $rest_path ): array { $standardized_steps = array(); foreach ( $steps as $step ) { $standardized_steps[] = $this->standardize_onboarding_step_details( $step, $location, $rest_path ); } return $standardized_steps; } /** * Get the entire stored NOX profile data * * @return array The stored NOX profile. */ private function get_nox_profile(): array { $nox_profile = $this->proxy->call_function( 'get_option', self::NOX_PROFILE_OPTION_KEY, array() ); if ( empty( $nox_profile ) ) { $nox_profile = array(); } else { $nox_profile = maybe_unserialize( $nox_profile ); } return $nox_profile; } /** * Get the onboarding step data from the NOX profile. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * * @return array The onboarding step stored data from the NOX profile. * If the step data is not found, an empty array is returned. */ private function get_nox_profile_onboarding_step( string $step_id, string $location ): array { $nox_profile = $this->get_nox_profile(); if ( empty( $nox_profile['onboarding'] ) ) { $nox_profile['onboarding'] = array(); } if ( empty( $nox_profile['onboarding'][ $location ] ) ) { $nox_profile['onboarding'][ $location ] = array(); } if ( empty( $nox_profile['onboarding'][ $location ]['steps'] ) ) { $nox_profile['onboarding'][ $location ]['steps'] = array(); } if ( empty( $nox_profile['onboarding'][ $location ]['steps'][ $step_id ] ) ) { $nox_profile['onboarding'][ $location ]['steps'][ $step_id ] = array(); } return $nox_profile['onboarding'][ $location ]['steps'][ $step_id ]; } /** * Save the onboarding step data in the NOX profile. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param array $data The onboarding step data to save in the profile. * * @return bool Whether the onboarding step data was saved. */ private function save_nox_profile_onboarding_step( string $step_id, string $location, array $data ): bool { $nox_profile = $this->get_nox_profile(); if ( empty( $nox_profile['onboarding'] ) ) { $nox_profile['onboarding'] = array(); } if ( empty( $nox_profile['onboarding'][ $location ] ) ) { $nox_profile['onboarding'][ $location ] = array(); } if ( empty( $nox_profile['onboarding'][ $location ]['steps'] ) ) { $nox_profile['onboarding'][ $location ]['steps'] = array(); } // Update the stored step data. $nox_profile['onboarding'][ $location ]['steps'][ $step_id ] = $data; return $this->proxy->call_function( 'update_option', self::NOX_PROFILE_OPTION_KEY, $nox_profile, false ); } /** * Get an entry from the NOX profile onboarding step details. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $entry The entry to get from the step data. * @param mixed $default_value The default value to return if the entry is not found. * * @return mixed The entry from the NOX profile step details. If the entry is not found, the default value is returned. */ private function get_nox_profile_onboarding_step_entry( string $step_id, string $location, string $entry, $default_value = array() ): array { $step_details = $this->get_nox_profile_onboarding_step( $step_id, $location ); if ( ! isset( $step_details[ $entry ] ) ) { return $default_value; } return $step_details[ $entry ]; } /** * Save an entry in the NOX profile onboarding step details. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $entry The entry key under which to save in the step data. * @param array $data The data to save in the step data. * * @return bool Whether the onboarding step data was saved. */ private function save_nox_profile_onboarding_step_entry( string $step_id, string $location, string $entry, array $data ): bool { $step_details = $this->get_nox_profile_onboarding_step( $step_id, $location ); // Update the stored step data. $step_details[ $entry ] = $data; return $this->save_nox_profile_onboarding_step( $step_id, $location, $step_details ); } /** * Get a data entry from the NOX profile onboarding step details. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $entry The entry to get from the step `data`. * @param mixed $default_value The default value to return if the entry is not found. * * @return mixed The entry value from the NOX profile stored step data. * If the entry is not found, the default value is returned. */ private function get_nox_profile_onboarding_step_data_entry( string $step_id, string $location, string $entry, $default_value = false ) { $step_details_data = $this->get_nox_profile_onboarding_step_entry( $step_id, $location, 'data' ); if ( ! isset( $step_details_data[ $entry ] ) ) { return $default_value; } return $step_details_data[ $entry ]; } /** * Save a data entry in the NOX profile onboarding step details. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param string $entry The entry key under which to save in the step `data`. * @param mixed $data The value to save. * * @return bool Whether the onboarding step data was saved. */ private function save_nox_profile_onboarding_step_data_entry( string $step_id, string $location, string $entry, $data ): bool { $step_details_data = $this->get_nox_profile_onboarding_step_entry( $step_id, $location, 'data' ); // Update the stored step data. $step_details_data[ $entry ] = $data; return $this->save_nox_profile_onboarding_step_entry( $step_id, $location, 'data', $step_details_data ); } /** * Get the IDs of the onboarding steps that are required for the given step. * * @param string $step_id The ID of the onboarding step. * * @return array|string[] The IDs of the onboarding steps that are required for the given step. */ private function get_onboarding_step_required_steps( string $step_id ): array { switch ( $step_id ) { // Both the test account and business verification (live account) steps require a working WPCOM connection. case self::ONBOARDING_STEP_TEST_ACCOUNT: case self::ONBOARDING_STEP_BUSINESS_VERIFICATION: return array( self::ONBOARDING_STEP_WPCOM_CONNECTION, ); default: return array(); } } /** * Check if the requirements for an onboarding step are met. * * @param string $step_id The ID of the onboarding step. * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * * @return bool Whether the onboarding step requirements are met. * @throws ApiArgumentException If the given onboarding step ID is invalid. */ private function check_onboarding_step_requirements( string $step_id, string $location ): bool { $requirements = $this->get_onboarding_step_required_steps( $step_id ); foreach ( $requirements as $required_step_id ) { if ( $this->get_onboarding_step_status( $required_step_id, $location ) !== self::ONBOARDING_STEP_STATUS_COMPLETED ) { return false; } } return true; } /** * Get the payment methods state for onboarding. * * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * @param array|null $recommended_pms Optional. The recommended payment methods to use. * * @return array The onboarding payment methods state. */ private function get_onboarding_payment_methods_state( string $location, ?array $recommended_pms ): array { // First, get the recommended payment methods details from the provider. // We will use their enablement state as the default. // Note: The list is validated and standardized by the provider, so we don't need to do it here. if ( null === $recommended_pms ) { $recommended_pms = $this->get_onboarding_recommended_payment_methods( $location ); } if ( empty( $recommended_pms ) ) { // If there are no recommended payment methods, return an empty array. return array(); } // Grab the stored payment methods state // (a key-value array of payment method IDs and if they should be automatically enabled or not). $step_pms_data = (array) $this->get_nox_profile_onboarding_step_data_entry( self::ONBOARDING_STEP_PAYMENT_METHODS, $location, 'payment_methods' ); $payment_methods_state = array(); $apple_pay_enabled = false; $google_pay_enabled = false; foreach ( $recommended_pms as $recommended_pm ) { $pm_id = $recommended_pm['id']; /** * We need to handle Apple Pay and Google Pay separately. * They are not stored in the same way as the other payment methods. */ if ( 'apple_pay' === $pm_id ) { $apple_pay_enabled = $recommended_pm['enabled']; continue; } if ( 'google_pay' === $pm_id ) { $google_pay_enabled = $recommended_pm['enabled']; continue; } // Start with the recommended enabled state. $payment_methods_state[ $pm_id ] = $recommended_pm['enabled']; // Force enable if required. if ( $recommended_pm['required'] ) { $payment_methods_state[ $pm_id ] = true; continue; } // Check the stored state, if any. if ( isset( $step_pms_data[ $pm_id ] ) ) { $payment_methods_state[ $pm_id ] = wc_string_to_bool( $step_pms_data[ $pm_id ] ); } } // Combine Apple Pay and Google Pay into a single `apple_google` entry. $apple_google_enabled = $apple_pay_enabled || $google_pay_enabled; // Optionally also respect stored state or forced requirements if needed here. $payment_methods_state['apple_google'] = $apple_google_enabled; return $payment_methods_state; } /** * Get the WPCOM (Jetpack) connection authorization details. * * @param string $return_url The URL to redirect to after the connection is set up. * * @return array The WPCOM connection authorization details. */ private function get_wpcom_connection_authorization( string $return_url ): array { return $this->proxy->call_static( Utils::class, 'get_wpcom_connection_authorization', $return_url ); } /** * Get the store's WPCOM (Jetpack) connection state. * * @return array The WPCOM connection state. */ private function get_wpcom_connection_state(): array { $is_connected = $this->wpcom_connection_manager->is_connected(); $has_connected_owner = $this->wpcom_connection_manager->has_connected_owner(); return array( 'has_working_connection' => $this->has_working_wpcom_connection(), 'is_store_connected' => $is_connected, 'has_connected_owner' => $has_connected_owner, 'is_connection_owner' => $has_connected_owner && $this->wpcom_connection_manager->is_connection_owner(), ); } /** * Check if the store has a working WPCOM connection. * * The store is considered to have a working WPCOM connection if: * - The store is connected to WPCOM (blog ID and tokens are set). * - The store connection has a connected owner (connection owner is set). * * @return bool Whether the store has a working WPCOM connection. */ private function has_working_wpcom_connection(): bool { return $this->wpcom_connection_manager->is_connected() && $this->wpcom_connection_manager->has_connected_owner(); } /** * Check if the WooPayments plugin is active. * * @return boolean */ private function is_extension_active(): bool { return $this->proxy->call_function( 'class_exists', '\WC_Payments' ); } /** * Get the main payment gateway instance. * * @return \WC_Payment_Gateway The main payment gateway instance. */ private function get_payment_gateway(): \WC_Payment_Gateway { return $this->proxy->call_static( '\WC_Payments', 'get_gateway' ); } /** * Determine if WooPayments has an account set up. * * @return bool Whether WooPayments has an account set up. */ private function has_account(): bool { return $this->provider->is_account_connected( $this->get_payment_gateway() ); } /** * Determine if WooPayments has a valid, fully onboarded account set up. * * @return bool Whether WooPayments has a valid, fully onboarded account set up. */ private function has_valid_account(): bool { if ( ! $this->has_account() ) { return false; } $account_service = $this->proxy->call_static( '\WC_Payments', 'get_account_service' ); return $account_service->is_stripe_account_valid(); } /** * Determine if WooPayments has a working account set up. * * This is a more specific check than has_valid_account() and checks if payments are enabled for the account. * * @return bool Whether WooPayments has a working account set up. */ private function has_working_account(): bool { if ( ! $this->has_account() ) { return false; } $account_service = $this->proxy->call_static( '\WC_Payments', 'get_account_service' ); $account_status = $account_service->get_account_status_data(); return ! empty( $account_status['paymentsEnabled'] ); } /** * Determine if WooPayments has a test account set up. * * @return bool Whether WooPayments has a test account set up. */ private function has_test_account(): bool { if ( ! $this->has_account() ) { return false; } $account_service = $this->proxy->call_static( '\WC_Payments', 'get_account_service' ); $account_status = $account_service->get_account_status_data(); return ! empty( $account_status['testDrive'] ); } /** * Determine if WooPayments has a live account set up. * * @return bool Whether WooPayments has a test account set up. */ private function has_live_account(): bool { if ( ! $this->has_account() ) { return false; } $account_service = $this->proxy->call_static( '\WC_Payments', 'get_account_service' ); $account_status = $account_service->get_account_status_data(); return ! empty( $account_status['isLive'] ); } /** * Get the onboarding fields data for the KYC business verification. * * @param string $location The location for which we are onboarding. * This is an ISO 3166-1 alpha-2 country code. * * @return array The onboarding fields data. * @throws Exception If the onboarding fields data could not be retrieved or there was an error. */ private function get_onboarding_kyc_fields( string $location ): array { // Call the WooPayments API to get the onboarding fields. $response = $this->proxy->call_static( Utils::class, 'rest_endpoint_get_request', '/wc/v3/payments/onboarding/fields' ); if ( is_wp_error( $response ) ) { throw new Exception( esc_html( $response->get_error_message() ) ); } if ( ! is_array( $response ) || ! isset( $response['data'] ) ) { throw new Exception( esc_html__( 'Failed to get onboarding fields data.', 'woocommerce' ) ); } $fields = $response['data']; // If there is no available_countries entry, add it. if ( ! isset( $fields['available_countries'] ) && $this->proxy->call_function( 'is_callable', '\WC_Payments_Utils::supported_countries' ) ) { $fields['available_countries'] = $this->proxy->call_static( '\WC_Payments_Utils', 'supported_countries' ); } $fields['location'] = $location; return $fields; } /** * Get the fallback URL for the embedded KYC flow. * * @return string The fallback URL for the embedded KYC flow. */ private function get_onboarding_kyc_fallback_url(): string { if ( $this->proxy->call_function( 'is_callable', '\WC_Payments_Account::get_connect_url' ) ) { return $this->proxy->call_static( '\WC_Payments_Account', 'get_connect_url', self::FROM_NOX_IN_CONTEXT ); } // Fall back to the provider onboarding URL. return $this->provider->get_onboarding_url( $this->get_payment_gateway(), Utils::wc_payments_settings_url( self::ONBOARDING_PATH_BASE, array( 'from' => self::FROM_KYC ) ) ); } /** * Get the WooPayments Overview page URL. * * @return string The WooPayments Overview page URL. */ private function get_overview_page_url(): string { if ( $this->proxy->call_function( 'is_callable', '\WC_Payments_Account::get_overview_page_url' ) ) { return add_query_arg( array( 'from' => self::FROM_NOX_IN_CONTEXT, ), $this->proxy->call_static( '\WC_Payments_Account', 'get_overview_page_url' ) ); } // Fall back to the known WooPayments Overview page URL. return add_query_arg( array( 'page' => 'wc-admin', 'path' => '/payments/overview', 'from' => self::FROM_NOX_IN_CONTEXT, ), admin_url( 'admin.php' ) ); } /** * Check the onboarding source and ensure it is a valid value. * * @param string|null $source The source of the onboarding request. * * @return string The validated onboarding source. */ private function validate_onboarding_source( ?string $source ): string { if ( empty( $source ) ) { return self::SESSION_ENTRY_DEFAULT; } $valid_sources = array( self::SESSION_ENTRY_DEFAULT, self::SESSION_ENTRY_LYS, ); return in_array( $source, $valid_sources, true ) ? $source : self::SESSION_ENTRY_DEFAULT; } }