diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index ac87010b4c..3c95775101 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; +use WooCommerce\PayPalCommerce\Onboarding\Onboarding_REST_Controller; return array( 'api.sandbox-host' => static function ( $container ): string { @@ -207,4 +208,7 @@ $partner_referrals_sandbox ); }, + 'onboarding.rest' => static function( $container ) : Onboarding_REST_Controller { + return new Onboarding_REST_Controller( $container ); + }, ); diff --git a/modules/ppcp-onboarding/src/Assets/class-onboardingassets.php b/modules/ppcp-onboarding/src/Assets/class-onboardingassets.php index 8cc1d87b41..33ff787305 100644 --- a/modules/ppcp-onboarding/src/Assets/class-onboardingassets.php +++ b/modules/ppcp-onboarding/src/Assets/class-onboardingassets.php @@ -90,16 +90,25 @@ public function register(): bool { wp_localize_script( 'ppcp-onboarding', 'PayPalCommerceGatewayOnboarding', - array( - 'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ), - 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ), - 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', - ) + $this->get_script_data() ); return true; } + /** + * Returns the data associated to the onboarding script. + * + * @return array + */ + public function get_script_data() { + return array( + 'endpoint' => home_url( \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ) ), + 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ), + 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', + ); + } + /** * Enqueues the necessary scripts. * diff --git a/modules/ppcp-onboarding/src/class-onboarding-rest-controller.php b/modules/ppcp-onboarding/src/class-onboarding-rest-controller.php new file mode 100644 index 0000000000..5bfa06eac4 --- /dev/null +++ b/modules/ppcp-onboarding/src/class-onboarding-rest-controller.php @@ -0,0 +1,311 @@ +container = $container; + } + + /** + * Registers REST routes under 'wc-paypal/v1/onboarding'. + * Specifically: + * - `/onboarding/get-params`, which returns information useful to display an onboarding button. + * - `/onboarding/get-status`, which returns information about the current environment and its onboarding state. + * - `/onboarding/set-credentials`, which allows setting merchant/API credentials. + */ + public function register_routes() { + register_rest_route( + $this->rest_namespace, + '/' . $this->rest_base . '/get-params', + array( + 'methods' => 'POST', + 'callback' => array( $this, 'get_params' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + + register_rest_route( + $this->rest_namespace, + '/' . $this->rest_base . '/get-status', + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_status' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + + register_rest_route( + $this->rest_namespace, + '/' . $this->rest_base . '/set-credentials', + array( + 'methods' => 'POST', + 'callback' => array( $this, 'set_credentials' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + } + + /** + * Validate the requester's permissions. + * + * @param WP_REST_Request $request The request. + * @return bool + */ + public function check_permission( $request ) { + return current_user_can( 'install_plugins' ); + } + + /** + * Callback for the `/onboarding/get-params` endpoint. + * + * @param WP_REST_Request $request The request. + * @return array + */ + public function get_params( $request ) { + $params = $request->get_json_params(); + + $environment = ( isset( $params['environment'] ) && in_array( $params['environment'], array( 'production', 'sandbox' ), true ) ) ? $params['environment'] : 'sandbox'; + + return array( + 'scriptURL' => trailingslashit( $this->container->get( 'onboarding.url' ) ) . 'assets/js/onboarding.js', + 'scriptData' => $this->container->get( 'onboarding.assets' )->get_script_data(), + 'environment' => $environment, + 'onboardCompleteCallback' => 'ppcp_onboarding_' . $environment . 'Callback', + 'signupLink' => $this->generate_signup_link( $environment, ( ! empty( $params['returnUrlArgs'] ) ? $params['returnUrlArgs'] : array() ) ), + ); + } + + /** + * Callback for the `/onboarding/get-status` endpoint. + * + * @param WP_REST_Request $request The request. + * @return array + */ + public function get_status( $request ) { + $environment = $this->container->get( 'onboarding.environment' ); + $state = $this->container->get( 'onboarding.state' ); + + return array( + 'environment' => $environment->current_environment(), + 'onboarded' => ( $state->current_state() >= State::STATE_ONBOARDED ), + 'state' => $this->get_onboarding_state_name( $state->current_state() ), + 'sandbox' => array( + 'state' => $this->get_onboarding_state_name( $state->sandbox_state() ), + 'onboarded' => ( $state->sandbox_state() >= State::STATE_ONBOARDED ), + ), + 'production' => array( + 'state' => $this->get_onboarding_state_name( $state->production_state() ), + 'onboarded' => ( $state->production_state() >= State::STATE_ONBOARDED ), + ), + ); + } + + /** + * Callback for the `/onboarding/set-credentials` endpoint. + * + * @param WP_REST_Request $request The request. + * @return WP_Error|array + */ + public function set_credentials( $request ) { + static $credential_keys = array( + 'merchant_id', + 'merchant_email', + 'client_id', + 'client_secret', + ); + + // Sanitize params. + $params = array_filter( array_map( 'trim', $request->get_json_params() ) ); + + // Validate 'environment'. + if ( empty( $params['environment'] ) || ! in_array( $params['environment'], array( 'sandbox', 'production' ), true ) ) { + return new \WP_Error( + 'woocommerce_paypal_payments_invalid_environment', + sprintf( + /* translators: placeholder is an arbitrary string. */ + __( 'Environment "%s" is invalid. Use "sandbox" or "production".', 'woocommerce-paypal-payments' ), + isset( $params['environment'] ) ? $params['environment'] : '' + ), + array( 'status' => 400 ) + ); + } + + // Validate the other fields. + $missing_keys = array_values( array_diff( $credential_keys, array_keys( $params ) ) ); + if ( $missing_keys ) { + return new \WP_Error( + 'woocommerce_paypal_payments_credentials_incomplete', + sprintf( + /* translators: placeholder is a comma-separated list of fields. */ + __( 'Credentials are incomplete. Missing fields: %s.', 'woocommerce-paypal-payments' ), + implode( ', ', $missing_keys ) + ), + array( + 'missing_fields' => $missing_keys, + 'status' => 400, + ) + ); + } + + $settings = $this->container->get( 'wcgateway.settings' ); + $skip_persist = true; + + $sandbox_on = ( 'sandbox' === $params['environment'] ); + if ( ! $settings->has( 'sandbox_on' ) || ( (bool) $settings->get( 'sandbox_on' ) !== $sandbox_on ) ) { + $settings->set( 'sandbox_on', $sandbox_on ); + $skip_persist = false; + } + + // Enable gateway. + if ( ! $settings->has( 'enabled' ) || ! $settings->get( 'enabled' ) ) { + $settings->set( 'enabled', true ); + $skip_persist = false; + } + + foreach ( WC()->payment_gateways->payment_gateways() as $gateway ) { + if ( PayPalGateway::ID === $gateway->id ) { + $gateway->update_option( 'enabled', 'yes' ); + break; + } + } + + // Update settings. + foreach ( $credential_keys as $key ) { + $value = $params[ $key ]; + $env_key = $key . '_' . $params['environment']; + + if ( ! $settings->has( $key ) || ! $settings->has( $env_key ) || $settings->get( $key ) !== $value || $settings->get( $env_key ) !== $value ) { + $settings->set( $key, $value ); + $settings->set( $env_key, $value ); + $skip_persist = false; + } + } + + if ( ! $skip_persist && ! $settings->persist() ) { + return new \WP_Error( + 'woocommerce_paypal_payments_credentials_not_saved', + __( 'An error occurred while saving the credentials.', 'woocommerce-paypal-payments' ), + array( + 'status' => 500, + ) + ); + } + + return array(); + } + + /** + * Appends URL parameters stored in this class to a given URL. + * + * @hooked woocommerce_paypal_payments_partner_config_override_return_url - 10 + * @param string $url URL. + * @return string The URL with the stored URL parameters added to it. + */ + public function add_args_to_return_url( $url ) { + return add_query_arg( $this->return_url_args, $url ); + } + + /** + * Translates an onboarding state to a string. + * + * @param int $state An onboarding state to translate as returned by {@link State} methods. + * @return string A string representing the state: "start", "progressive" or "onboarded". + * @see State::current_state(), State::sandbox_state(), State::production_state(). + */ + public function get_onboarding_state_name( $state ) { + $name = 'unknown'; + + switch ( absint( $state ) ) { + case State::STATE_START: + $name = 'start'; + break; + case State::STATE_PROGRESSIVE: + $name = 'progressive'; + break; + case State::STATE_ONBOARDED: + $name = 'onboarded'; + break; + default: + break; + + } + + return $name; + } + + /** + * Generates a signup link for onboarding for a given environment and optionally adding certain URL arguments + * to the URL users are redirected after completing the onboarding flow. + * + * @param string $environment The environment to use. Either 'sandbox' or 'production'. Defaults to 'sandbox'. + * @param array $url_args An array of URL arguments to add to the return URL via {@link add_query_arg()}. + * @return string + */ + private function generate_signup_link( $environment = 'sandbox', $url_args = array() ) { + $this->return_url_args = ( ! empty( $url_args ) && is_array( $url_args ) ) ? $url_args : array(); + + if ( $this->return_url_args ) { + add_filter( 'woocommerce_paypal_payments_partner_config_override_return_url', array( $this, 'add_args_to_return_url' ) ); + } + + $link = $this->container->get( 'onboarding.render' )->get_signup_link( 'production' === $environment ); + + if ( $this->return_url_args ) { + remove_filter( 'woocommerce_paypal_payments_partner_config_override_return_url', array( $this, 'add_args_to_return_url' ) ); + $this->return_url_args = array(); + } + + return $link; + } + +} diff --git a/modules/ppcp-onboarding/src/class-onboardingmodule.php b/modules/ppcp-onboarding/src/class-onboardingmodule.php index f5dbf0ec51..3526f7c330 100644 --- a/modules/ppcp-onboarding/src/class-onboardingmodule.php +++ b/modules/ppcp-onboarding/src/class-onboardingmodule.php @@ -41,7 +41,6 @@ public function setup(): ServiceProviderInterface { * @param ContainerInterface|null $container The container. */ public function run( ContainerInterface $container = null ) { - $asset_loader = $container->get( 'onboarding.assets' ); /** * The OnboardingAssets. @@ -100,6 +99,10 @@ static function () use ( $container ) { $endpoint->handle_request(); } ); + + // Initialize REST routes at the appropriate time. + $rest_controller = $container->get( 'onboarding.rest' ); + add_action( 'rest_api_init', array( $rest_controller, 'register_routes' ) ); } /**