mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +08:00
Merge pull request #2826 from woocommerce/PCP-3890-implement-sandbox-login-via-onboarding-wizard
Sandbox login via onboarding wizard & refactoring (3897)
This commit is contained in:
commit
54f3812f7e
54 changed files with 2134 additions and 937 deletions
117
.psalm/stubs.php
117
.psalm/stubs.php
|
@ -1,71 +1,116 @@
|
|||
<?php
|
||||
if (!defined('PAYPAL_INTEGRATION_DATE')) {
|
||||
define('PAYPAL_INTEGRATION_DATE', '2023-06-02');
|
||||
/**
|
||||
* Stubs to help psalm correctly annotate problems in the plugin.
|
||||
*
|
||||
* @package WooCommerce
|
||||
*/
|
||||
|
||||
if ( ! defined( 'PAYPAL_INTEGRATION_DATE' ) ) {
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2023-06-02' );
|
||||
}
|
||||
if (!defined('PAYPAL_URL')) {
|
||||
if ( ! defined( 'PAYPAL_URL' ) ) {
|
||||
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
||||
}
|
||||
if (!defined('PAYPAL_SANDBOX_URL')) {
|
||||
if ( ! defined( 'PAYPAL_SANDBOX_URL' ) ) {
|
||||
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
||||
}
|
||||
if (!defined('EP_PAGES')) {
|
||||
define('EP_PAGES', 4096);
|
||||
if ( ! defined( 'EP_PAGES' ) ) {
|
||||
define( 'EP_PAGES', 4096 );
|
||||
}
|
||||
if (!defined('MONTH_IN_SECONDS')) {
|
||||
define('MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS);
|
||||
if ( ! defined( 'MONTH_IN_SECONDS' ) ) {
|
||||
define( 'MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS );
|
||||
}
|
||||
if (!defined('HOUR_IN_SECONDS')) {
|
||||
define('HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS);
|
||||
if ( ! defined( 'HOUR_IN_SECONDS' ) ) {
|
||||
define( 'HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS );
|
||||
}
|
||||
if (!defined('MINUTE_IN_SECONDS')) {
|
||||
if ( ! defined( 'MINUTE_IN_SECONDS' ) ) {
|
||||
define( 'MINUTE_IN_SECONDS', 60 );
|
||||
}
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
define('ABSPATH', '');
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
define( 'ABSPATH', '' );
|
||||
}
|
||||
|
||||
if (!defined('PPCP_PAYPAL_BN_CODE')) {
|
||||
define('PPCP_PAYPAL_BN_CODE', 'Woo_PPCP');
|
||||
if ( ! defined( 'PAYPAL_API_URL' ) ) {
|
||||
define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
|
||||
}
|
||||
if ( ! defined( 'PAYPAL_SANDBOX_API_URL' ) ) {
|
||||
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
||||
}
|
||||
if ( ! defined( 'PPCP_PAYPAL_BN_CODE' ) ) {
|
||||
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_CLIENT_ID' ) ) {
|
||||
define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) ) {
|
||||
define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_MERCHANT_ID' ) ) {
|
||||
define( 'CONNECT_WOO_MERCHANT_ID', 'K8SKZ36LQBWXJ' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_SANDBOX_MERCHANT_ID' ) ) {
|
||||
define( 'CONNECT_WOO_SANDBOX_MERCHANT_ID', 'MPMFHQTVMBZ6G' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_URL' ) ) {
|
||||
define( 'CONNECT_WOO_URL', 'https://api.woocommerce.com/integrations/ppc' );
|
||||
}
|
||||
if ( ! defined( 'CONNECT_WOO_SANDBOX_URL' ) ) {
|
||||
define( 'CONNECT_WOO_SANDBOX_URL', 'https://api.woocommerce.com/integrations/ppcsandbox' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the next occurrence of a scheduled action.
|
||||
*
|
||||
* While only the next instance of a recurring or cron action is unscheduled by this method, that will also prevent
|
||||
* all future instances of that recurring or cron action from being run. Recurring and cron actions are scheduled in
|
||||
* a sequence instead of all being scheduled at once. Each successive occurrence of a recurring action is scheduled
|
||||
* only after the former action is run. If the next instance is never run, because it's unscheduled by this function,
|
||||
* then the following instance will never be scheduled (or exist), which is effectively the same as being unscheduled
|
||||
* by this method also.
|
||||
* While only the next instance of a recurring or cron action is unscheduled by this method, that
|
||||
* will also prevent all future instances of that recurring or cron action from being run.
|
||||
* Recurring and cron actions are scheduled in a sequence instead of all being scheduled at once.
|
||||
* Each successive occurrence of a recurring action is scheduled only after the former action is
|
||||
* run. If the next instance is never run, because it's unscheduled by this function, then the
|
||||
* following instance will never be scheduled (or exist), which is effectively the same as being
|
||||
* unscheduled by this method also.
|
||||
*
|
||||
* @param string $hook The hook that the job will trigger.
|
||||
* @param array $args Args that would have been passed to the job.
|
||||
* @param string $hook The hook that the job will trigger.
|
||||
* @param array $args Args that would have been passed to the job.
|
||||
* @param string $group The group the job is assigned to.
|
||||
*
|
||||
* @return string|null The scheduled action ID if a scheduled action was found, or null if no matching action found.
|
||||
* @return string|null The scheduled action ID if a scheduled action was found, or null if no
|
||||
* matching action found.
|
||||
*/
|
||||
function as_unschedule_action($hook, $args = array(), $group = '') {}
|
||||
function as_unschedule_action( $hook, $args = array(), $group = '' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an action to run one time
|
||||
*
|
||||
* @param int $timestamp When the job will run.
|
||||
* @param string $hook The hook to trigger.
|
||||
* @param array $args Arguments to pass when the hook triggers.
|
||||
* @param string $group The group to assign this job to.
|
||||
* @param bool $unique Whether the action should be unique.
|
||||
* @param string $hook The hook to trigger.
|
||||
* @param array $args Arguments to pass when the hook triggers.
|
||||
* @param string $group The group to assign this job to.
|
||||
* @param bool $unique Whether the action should be unique.
|
||||
*
|
||||
* @return int The action ID.
|
||||
*/
|
||||
function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false ) {}
|
||||
function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML API: WP_HTML_Tag_Processor class
|
||||
*/
|
||||
// phpcs:disable
|
||||
class WP_HTML_Tag_Processor {
|
||||
public function __construct( $html ) {}
|
||||
public function next_tag( $query = null ) {}
|
||||
public function set_attribute( $name, $value ) {}
|
||||
public function get_updated_html() {}
|
||||
public function __construct( $html ) {
|
||||
}
|
||||
|
||||
public function next_tag( $query = null ) : bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set_attribute( $name, $value ) : bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get_updated_html() : string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
|
||||
|
@ -79,6 +78,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
|
||||
return array(
|
||||
'api.host' => function( ContainerInterface $container ) : string {
|
||||
|
@ -179,6 +179,22 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.partner-referrals-sandbox' => static function ( ContainerInterface $container ) : PartnerReferrals {
|
||||
|
||||
return new PartnerReferrals(
|
||||
CONNECT_WOO_SANDBOX_URL,
|
||||
new ConnectBearer(),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.partner-referrals-production' => static function ( ContainerInterface $container ) : PartnerReferrals {
|
||||
|
||||
return new PartnerReferrals(
|
||||
CONNECT_WOO_URL,
|
||||
new ConnectBearer(),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.identity-token' => static function ( ContainerInterface $container ) : IdentityToken {
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
|
@ -845,4 +861,22 @@ return array(
|
|||
$container->get( 'api.client-credentials-cache' )
|
||||
);
|
||||
},
|
||||
'api.paypal-host-production' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_API_URL;
|
||||
},
|
||||
'api.paypal-host-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_SANDBOX_API_URL;
|
||||
},
|
||||
'api.paypal-website-url-production' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_URL;
|
||||
},
|
||||
'api.paypal-website-url-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_SANDBOX_URL;
|
||||
},
|
||||
'api.partner_merchant_id-production' => static function( ContainerInterface $container ) : string {
|
||||
return CONNECT_WOO_MERCHANT_ID;
|
||||
},
|
||||
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* @package WooCommerce\PayPalCommerce\ApiClient\Authentication
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Authentication;
|
||||
|
||||
|
@ -28,7 +28,7 @@ class PayPalBearer implements Bearer {
|
|||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var ContainerInterface
|
||||
* @var ?ContainerInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
|
@ -70,12 +70,12 @@ class PayPalBearer implements Bearer {
|
|||
/**
|
||||
* PayPalBearer constructor.
|
||||
*
|
||||
* @param Cache $cache The cache.
|
||||
* @param string $host The host.
|
||||
* @param string $key The key.
|
||||
* @param string $secret The secret.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param ContainerInterface $settings The settings.
|
||||
* @param Cache $cache The cache.
|
||||
* @param string $host The host.
|
||||
* @param string $key The key.
|
||||
* @param string $secret The secret.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param ?ContainerInterface $settings The settings.
|
||||
*/
|
||||
public function __construct(
|
||||
Cache $cache,
|
||||
|
@ -83,7 +83,7 @@ class PayPalBearer implements Bearer {
|
|||
string $key,
|
||||
string $secret,
|
||||
LoggerInterface $logger,
|
||||
ContainerInterface $settings
|
||||
?ContainerInterface $settings
|
||||
) {
|
||||
|
||||
$this->cache = $cache;
|
||||
|
@ -97,27 +97,62 @@ class PayPalBearer implements Bearer {
|
|||
/**
|
||||
* Returns a bearer token.
|
||||
*
|
||||
* @return Token
|
||||
* @throws RuntimeException When request fails.
|
||||
* @return Token
|
||||
*/
|
||||
public function bearer(): Token {
|
||||
public function bearer() : Token {
|
||||
try {
|
||||
$bearer = Token::from_json( (string) $this->cache->get( self::CACHE_KEY ) );
|
||||
|
||||
return ( $bearer->is_valid() ) ? $bearer : $this->newBearer();
|
||||
} catch ( RuntimeException $error ) {
|
||||
return $this->newBearer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client key for authentication.
|
||||
*
|
||||
* @return string The client ID from settings, or the key defined via constructor.
|
||||
*/
|
||||
private function get_key() : string {
|
||||
if (
|
||||
$this->settings
|
||||
&& $this->settings->has( 'client_id' )
|
||||
&& $this->settings->get( 'client_id' )
|
||||
) {
|
||||
return $this->settings->get( 'client_id' );
|
||||
}
|
||||
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the client secret for authentication.
|
||||
*
|
||||
* @return string The client secret from settings, or the value defined via constructor.
|
||||
*/
|
||||
private function get_secret() : string {
|
||||
if (
|
||||
$this->settings
|
||||
&& $this->settings->has( 'client_secret' )
|
||||
&& $this->settings->get( 'client_secret' )
|
||||
) {
|
||||
return $this->settings->get( 'client_secret' );
|
||||
}
|
||||
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bearer token.
|
||||
*
|
||||
* @return Token
|
||||
* @throws RuntimeException When request fails.
|
||||
* @return Token
|
||||
*/
|
||||
private function newBearer(): Token {
|
||||
$key = $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) ? $this->settings->get( 'client_id' ) : $this->key;
|
||||
$secret = $this->settings->has( 'client_secret' ) && $this->settings->get( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : $this->secret;
|
||||
private function newBearer() : Token {
|
||||
$key = $this->get_key();
|
||||
$secret = $this->get_secret();
|
||||
$url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials';
|
||||
|
||||
$args = array(
|
||||
|
@ -127,10 +162,7 @@ class PayPalBearer implements Bearer {
|
|||
'Authorization' => 'Basic ' . base64_encode( $key . ':' . $secret ),
|
||||
),
|
||||
);
|
||||
$response = $this->request(
|
||||
$url,
|
||||
$args
|
||||
);
|
||||
$response = $this->request( $url, $args );
|
||||
|
||||
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
|
||||
$error = new RuntimeException(
|
||||
|
@ -148,6 +180,7 @@ class PayPalBearer implements Bearer {
|
|||
|
||||
$token = Token::from_json( $response['body'] );
|
||||
$this->cache->set( self::CACHE_KEY, $token->as_json() );
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,8 +85,7 @@ class PartnerReferrals {
|
|||
$error = new RuntimeException(
|
||||
__( 'Could not create referral.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
@ -95,6 +94,7 @@ class PartnerReferrals {
|
|||
);
|
||||
throw $error;
|
||||
}
|
||||
|
||||
$json = json_decode( $response['body'] );
|
||||
$status_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( 201 !== $status_code ) {
|
||||
|
@ -102,8 +102,7 @@ class PartnerReferrals {
|
|||
$json,
|
||||
$status_code
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
@ -122,8 +121,7 @@ class PartnerReferrals {
|
|||
$error = new RuntimeException(
|
||||
__( 'Action URL not found.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
|
|
@ -15,7 +15,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
|
||||
|
@ -25,7 +24,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
|
||||
|
||||
return array(
|
||||
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
|
||||
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
|
||||
|
@ -39,7 +38,7 @@ return array(
|
|||
}
|
||||
return CONNECT_WOO_SANDBOX_URL;
|
||||
},
|
||||
'api.production-host' => static function ( ContainerInterface $container ): string {
|
||||
'api.production-host' => static function ( ContainerInterface $container ): string {
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
|
||||
|
@ -54,7 +53,7 @@ return array(
|
|||
}
|
||||
return CONNECT_WOO_URL;
|
||||
},
|
||||
'api.host' => static function ( ContainerInterface $container ): string {
|
||||
'api.host' => static function ( ContainerInterface $container ): string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
|
||||
/**
|
||||
|
@ -66,25 +65,7 @@ return array(
|
|||
? (string) $container->get( 'api.sandbox-host' ) : (string) $container->get( 'api.production-host' );
|
||||
|
||||
},
|
||||
'api.paypal-host-production' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_API_URL;
|
||||
},
|
||||
'api.paypal-host-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_SANDBOX_API_URL;
|
||||
},
|
||||
'api.paypal-website-url-production' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_URL;
|
||||
},
|
||||
'api.paypal-website-url-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return PAYPAL_SANDBOX_URL;
|
||||
},
|
||||
'api.partner_merchant_id-production' => static function( ContainerInterface $container ) : string {
|
||||
return CONNECT_WOO_MERCHANT_ID;
|
||||
},
|
||||
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
|
||||
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
|
||||
},
|
||||
'api.paypal-host' => function( ContainerInterface $container ) : string {
|
||||
'api.paypal-host' => function( ContainerInterface $container ) : string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
/**
|
||||
* The current environment.
|
||||
|
@ -97,7 +78,7 @@ return array(
|
|||
return $container->get( 'api.paypal-host-production' );
|
||||
|
||||
},
|
||||
'api.paypal-website-url' => function( ContainerInterface $container ) : string {
|
||||
'api.paypal-website-url' => function( ContainerInterface $container ) : string {
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
if ( $environment->current_environment_is( Environment::SANDBOX ) ) {
|
||||
|
@ -107,7 +88,7 @@ return array(
|
|||
|
||||
},
|
||||
|
||||
'api.bearer' => static function ( ContainerInterface $container ): Bearer {
|
||||
'api.bearer' => static function ( ContainerInterface $container ): Bearer {
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
|
||||
|
@ -134,16 +115,16 @@ return array(
|
|||
$settings
|
||||
);
|
||||
},
|
||||
'onboarding.state' => function( ContainerInterface $container ) : State {
|
||||
'onboarding.state' => function( ContainerInterface $container ) : State {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return new State( $settings );
|
||||
},
|
||||
'onboarding.environment' => function( ContainerInterface $container ) : Environment {
|
||||
'onboarding.environment' => function( ContainerInterface $container ) : Environment {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return new Environment( $settings );
|
||||
},
|
||||
|
||||
'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets {
|
||||
'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
$login_seller_endpoint = $container->get( 'onboarding.endpoint.login-seller' );
|
||||
return new OnboardingAssets(
|
||||
|
@ -156,14 +137,14 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'onboarding.url' => static function ( ContainerInterface $container ): string {
|
||||
'onboarding.url' => static function ( ContainerInterface $container ): string {
|
||||
return plugins_url(
|
||||
'/modules/ppcp-onboarding/',
|
||||
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
|
||||
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new LoginSeller(
|
||||
|
@ -173,7 +154,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new LoginSeller(
|
||||
|
@ -183,7 +164,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint {
|
||||
'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint {
|
||||
|
||||
$request_data = $container->get( 'button.request-data' );
|
||||
$login_seller_production = $container->get( 'api.endpoint.login-seller-production' );
|
||||
|
@ -203,7 +184,7 @@ return array(
|
|||
new Cache( 'ppcp-client-credentials-cache' )
|
||||
);
|
||||
},
|
||||
'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint {
|
||||
'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint {
|
||||
return new UpdateSignupLinksEndpoint(
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'button.request-data' ),
|
||||
|
@ -213,26 +194,10 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.partner-referrals-sandbox' => static function ( ContainerInterface $container ) : PartnerReferrals {
|
||||
|
||||
return new PartnerReferrals(
|
||||
CONNECT_WOO_SANDBOX_URL,
|
||||
new ConnectBearer(),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.partner-referrals-production' => static function ( ContainerInterface $container ) : PartnerReferrals {
|
||||
|
||||
return new PartnerReferrals(
|
||||
CONNECT_WOO_URL,
|
||||
new ConnectBearer(),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'onboarding.signup-link-cache' => static function( ContainerInterface $container ): Cache {
|
||||
'onboarding.signup-link-cache' => static function( ContainerInterface $container ): Cache {
|
||||
return new Cache( 'ppcp-paypal-signup-link' );
|
||||
},
|
||||
'onboarding.signup-link-ids' => static function ( ContainerInterface $container ): array {
|
||||
'onboarding.signup-link-ids' => static function ( ContainerInterface $container ): array {
|
||||
return array(
|
||||
'production-ppcp',
|
||||
'production-express_checkout',
|
||||
|
@ -240,12 +205,12 @@ return array(
|
|||
'sandbox-express_checkout',
|
||||
);
|
||||
},
|
||||
'onboarding.render-send-only-notice' => static function( ContainerInterface $container ) {
|
||||
'onboarding.render-send-only-notice' => static function( ContainerInterface $container ) {
|
||||
return new OnboardingSendOnlyNoticeRenderer(
|
||||
$container->get( 'wcgateway.send-only-message' )
|
||||
);
|
||||
},
|
||||
'onboarding.render' => static function ( ContainerInterface $container ) : OnboardingRenderer {
|
||||
'onboarding.render' => static function ( ContainerInterface $container ) : OnboardingRenderer {
|
||||
$partner_referrals = $container->get( 'api.endpoint.partner-referrals-production' );
|
||||
$partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' );
|
||||
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
|
||||
|
@ -261,14 +226,14 @@ return array(
|
|||
$logger
|
||||
);
|
||||
},
|
||||
'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer {
|
||||
'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer {
|
||||
return new OnboardingOptionsRenderer(
|
||||
$container->get( 'onboarding.url' ),
|
||||
$container->get( 'api.shop.country' ),
|
||||
$container->get( 'wcgateway.settings' )
|
||||
);
|
||||
},
|
||||
'onboarding.rest' => static function( $container ) : OnboardingRESTController {
|
||||
'onboarding.rest' => static function( $container ) : OnboardingRESTController {
|
||||
return new OnboardingRESTController( $container );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -2,13 +2,22 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__space,
|
||||
&__line {
|
||||
height: 1px;
|
||||
background-color: $color-gray-600;
|
||||
margin: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__line {
|
||||
background-color: $color-gray-600;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&__space {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: $color-gray;
|
||||
@include font(12, 24, 500, 0.8px);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
body:has(.ppcp-r-container--onboarding) {
|
||||
background-color: #fff !important;
|
||||
|
||||
.notice, .nav-tab-wrapper.woo-nav-tab-wrapper, .woocommerce-layout, .wrap.woocommerce form > h2, #screen-meta-links {
|
||||
.notice, .nav-tab-wrapper.woo-nav-tab-wrapper, .woocommerce-layout__header, .wrap.woocommerce form > h2, #screen-meta-links {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,6 @@
|
|||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.ppcp-r-page-welcome-mode-separator {
|
||||
margin: 0 0 48px 0;
|
||||
|
||||
.ppcp-r-separator__line {
|
||||
background-color: $color-gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.components-base-control__field {
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useEffect } from '@wordpress/element';
|
||||
import { Icon } from '@wordpress/components';
|
||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
|
||||
|
@ -5,11 +6,33 @@ import { useState } from 'react';
|
|||
|
||||
const Accordion = ( {
|
||||
title,
|
||||
initiallyOpen = false,
|
||||
initiallyOpen = null,
|
||||
className = '',
|
||||
id = '',
|
||||
children,
|
||||
} ) => {
|
||||
const [ isOpen, setIsOpen ] = useState( initiallyOpen );
|
||||
const determineInitialState = () => {
|
||||
if ( id && initiallyOpen === null ) {
|
||||
return window.location.hash === `#${ id }`;
|
||||
}
|
||||
return !! initiallyOpen;
|
||||
};
|
||||
|
||||
const [ isOpen, setIsOpen ] = useState( determineInitialState );
|
||||
|
||||
useEffect( () => {
|
||||
const handleHashChange = () => {
|
||||
if ( id && window.location.hash === `#${ id }` ) {
|
||||
setIsOpen( true );
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener( 'hashchange', handleHashChange );
|
||||
|
||||
return () => {
|
||||
window.removeEventListener( 'hashchange', handleHashChange );
|
||||
};
|
||||
}, [ id ] );
|
||||
|
||||
const toggleOpen = ( ev ) => {
|
||||
setIsOpen( ! isOpen );
|
||||
|
@ -26,7 +49,7 @@ const Accordion = ( {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={ wrapperClasses.join( ' ' ) }>
|
||||
<div className={ wrapperClasses.join( ' ' ) } id={ id }>
|
||||
<button
|
||||
onClick={ toggleOpen }
|
||||
className="ppcp-r-accordion--title"
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
const Separator = ( props ) => {
|
||||
let separatorClass = 'ppcp-r-separator';
|
||||
const Separator = ( { className = '', text = '', withLine = true } ) => {
|
||||
const separatorClass = [ 'ppcp-r-separator' ];
|
||||
const innerClass = withLine
|
||||
? 'ppcp-r-separator__line'
|
||||
: 'ppcp-r-separator__space';
|
||||
|
||||
if ( props?.className ) {
|
||||
separatorClass += ' ' + props.className;
|
||||
if ( className ) {
|
||||
separatorClass.push( className );
|
||||
}
|
||||
|
||||
if ( props.text ) {
|
||||
return (
|
||||
<div className={ separatorClass }>
|
||||
<span className="ppcp-r-separator__line ppcp-r-separator__line--before"></span>
|
||||
const getClass = ( type ) => `${ innerClass } ${ innerClass }--${ type }`;
|
||||
|
||||
<span className="ppcp-r-separator__text">{ props.text }</span>
|
||||
<span className="ppcp-r-separator__line ppcp-r-separator__line--after"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const renderSeparator = () => {
|
||||
if ( text ) {
|
||||
return (
|
||||
<>
|
||||
<span className={ getClass( 'before' ) }></span>
|
||||
<span className="ppcp-r-separator__text">{ text }</span>
|
||||
<span className={ getClass( 'after' ) }></span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <span className={ getClass( 'full' ) }></span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={ separatorClass }>
|
||||
<span className="ppcp-r-separator__line ppcp-r-separator__line--before"></span>
|
||||
<div className={ separatorClass.join( ' ' ) }>
|
||||
{ renderSeparator() }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,24 +7,25 @@ import { store as noticesStore } from '@wordpress/notices';
|
|||
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
|
||||
import Separator from '../../../ReusableComponents/Separator';
|
||||
import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
|
||||
import { useManualConnect, useOnboardingStepWelcome } from '../../../../data';
|
||||
import { CommonHooks } from '../../../../data';
|
||||
import { openPopup } from '../../../../utils/window';
|
||||
|
||||
const AdvancedOptionsForm = ( { setCompleted } ) => {
|
||||
const { isBusy } = CommonHooks.useBusyState();
|
||||
const { isSandboxMode, setSandboxMode, connectViaSandbox } =
|
||||
CommonHooks.useSandbox();
|
||||
const {
|
||||
isManualConnectionBusy,
|
||||
isSandboxMode,
|
||||
setSandboxMode,
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode,
|
||||
clientId,
|
||||
setClientId,
|
||||
clientSecret,
|
||||
setClientSecret,
|
||||
} = useOnboardingStepWelcome();
|
||||
connectViaIdAndSecret,
|
||||
} = CommonHooks.useManualConnection();
|
||||
|
||||
const { createSuccessNotice, createErrorNotice } =
|
||||
useDispatch( noticesStore );
|
||||
const { connectManual } = useManualConnect();
|
||||
const refClientId = useRef( null );
|
||||
const refClientSecret = useRef( null );
|
||||
|
||||
|
@ -61,17 +62,9 @@ const AdvancedOptionsForm = ( { setCompleted } ) => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const handleServerError = ( res ) => {
|
||||
if ( res.message ) {
|
||||
createErrorNotice( res.message );
|
||||
} else {
|
||||
createErrorNotice(
|
||||
__(
|
||||
'Could not connect to PayPal. Please make sure your Client ID and Secret Key are correct.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
const handleServerError = ( res, genericMessage ) => {
|
||||
console.error( 'Connection error', res );
|
||||
createErrorNotice( res?.message ?? genericMessage );
|
||||
};
|
||||
|
||||
const handleServerSuccess = () => {
|
||||
|
@ -82,17 +75,50 @@ const AdvancedOptionsForm = ( { setCompleted } ) => {
|
|||
setCompleted( true );
|
||||
};
|
||||
|
||||
const handleConnect = async () => {
|
||||
const handleSandboxConnect = async () => {
|
||||
const res = await connectViaSandbox();
|
||||
|
||||
if ( ! res.success || ! res.data ) {
|
||||
handleServerError(
|
||||
res,
|
||||
__(
|
||||
'Could not generate a Sandbox login link.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionUrl = res.data;
|
||||
const popup = openPopup( connectionUrl );
|
||||
|
||||
if ( ! popup ) {
|
||||
createErrorNotice(
|
||||
__(
|
||||
'Popup blocked. Please allow popups for this site to connect to PayPal.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleManualConnect = async () => {
|
||||
if ( ! handleFormValidation() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await connectManual();
|
||||
const res = await connectViaIdAndSecret();
|
||||
|
||||
if ( res.success ) {
|
||||
handleServerSuccess();
|
||||
} else {
|
||||
handleServerError( res );
|
||||
handleServerError(
|
||||
res,
|
||||
__(
|
||||
'Could not connect to PayPal. Please make sure your Client ID and Secret Key are correct.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -118,21 +144,22 @@ const AdvancedOptionsForm = ( { setCompleted } ) => {
|
|||
) }
|
||||
isToggled={ !! isSandboxMode }
|
||||
setToggled={ setSandboxMode }
|
||||
isLoading={ isBusy }
|
||||
>
|
||||
<Button variant="secondary">
|
||||
<Button onClick={ handleSandboxConnect } variant="secondary">
|
||||
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
|
||||
</Button>
|
||||
</SettingsToggleBlock>
|
||||
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
||||
<Separator withLine={ false } />
|
||||
<SettingsToggleBlock
|
||||
label={ __(
|
||||
'Manually Connect',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
label={
|
||||
__( 'Manually Connect', 'woocommerce-paypal-payments' ) +
|
||||
( isBusy ? ' ...' : '' )
|
||||
}
|
||||
description={ advancedUsersDescription }
|
||||
isToggled={ !! isManualConnectionMode }
|
||||
setToggled={ setManualConnectionMode }
|
||||
isLoading={ isManualConnectionBusy }
|
||||
isLoading={ isBusy }
|
||||
>
|
||||
<DataStoreControl
|
||||
control={ TextControl }
|
||||
|
@ -169,7 +196,7 @@ const AdvancedOptionsForm = ( { setCompleted } ) => {
|
|||
onChange={ setClientSecret }
|
||||
type="password"
|
||||
/>
|
||||
<Button variant="secondary" onClick={ handleConnect }>
|
||||
<Button variant="secondary" onClick={ handleManualConnect }>
|
||||
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
|
||||
</Button>
|
||||
</SettingsToggleBlock>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Button } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
useOnboardingStepBusiness,
|
||||
useOnboardingStepProducts,
|
||||
} from '../../data';
|
||||
import data from '../../utils/data';
|
||||
|
||||
import { OnboardingHooks } from '../../../../data';
|
||||
import data from '../../../../utils/data';
|
||||
|
||||
const Navigation = ( { setStep, setCompleted, currentStep, stepperOrder } ) => {
|
||||
const isLastStep = () => currentStep + 1 === stepperOrder.length;
|
||||
|
@ -24,8 +22,8 @@ const Navigation = ( { setStep, setCompleted, currentStep, stepperOrder } ) => {
|
|||
}
|
||||
};
|
||||
|
||||
const { products, toggleProduct } = useOnboardingStepProducts();
|
||||
const { isCasualSeller, setIsCasualSeller } = useOnboardingStepBusiness();
|
||||
const { products } = OnboardingHooks.useProducts();
|
||||
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
||||
|
||||
let navigationTitle = '';
|
||||
let disabled = false;
|
|
@ -1,7 +1,7 @@
|
|||
import Container from '../../ReusableComponents/Container';
|
||||
import { useOnboardingStep } from '../../../data';
|
||||
import { OnboardingHooks } from '../../../data';
|
||||
import { getSteps } from './availableSteps';
|
||||
import Navigation from '../../ReusableComponents/Navigation';
|
||||
import Navigation from './Components/Navigation';
|
||||
|
||||
const getCurrentStep = ( requestedStep, steps ) => {
|
||||
const isValidStep = ( step ) =>
|
||||
|
@ -15,7 +15,7 @@ const getCurrentStep = ( requestedStep, steps ) => {
|
|||
};
|
||||
|
||||
const Onboarding = () => {
|
||||
const { step, setStep, setCompleted, flags } = useOnboardingStep();
|
||||
const { step, setStep, setCompleted, flags } = OnboardingHooks.useSteps();
|
||||
const steps = getSteps( flags );
|
||||
|
||||
const CurrentStepComponent = getCurrentStep( step, steps );
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import SelectBoxWrapper from '../../ReusableComponents/SelectBoxWrapper';
|
||||
import SelectBox from '../../ReusableComponents/SelectBox';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PaymentMethodIcons from '../../ReusableComponents/PaymentMethodIcons';
|
||||
import { useOnboardingStepBusiness } from '../../../data';
|
||||
import { BUSINESS_TYPES } from '../../../data/constants';
|
||||
import { OnboardingHooks, BUSINESS_TYPES } from '../../../data';
|
||||
|
||||
const BUSINESS_RADIO_GROUP_NAME = 'business';
|
||||
|
||||
const StepBusiness = ( {
|
||||
setStep,
|
||||
currentStep,
|
||||
stepperOrder,
|
||||
setCompleted,
|
||||
} ) => {
|
||||
const { isCasualSeller, setIsCasualSeller } = useOnboardingStepBusiness();
|
||||
const StepBusiness = ( {} ) => {
|
||||
const { isCasualSeller, setIsCasualSeller } = OnboardingHooks.useBusiness();
|
||||
|
||||
const handleSellerTypeChange = ( value ) => {
|
||||
setIsCasualSeller( BUSINESS_TYPES.CASUAL_SELLER === value );
|
||||
|
@ -40,23 +34,22 @@ const StepBusiness = ( {
|
|||
/>
|
||||
<div className="ppcp-r-inner-container">
|
||||
<SelectBoxWrapper>
|
||||
<SelectBox
|
||||
title={ __(
|
||||
'Business',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
name={ BUSINESS_RADIO_GROUP_NAME }
|
||||
value={ BUSINESS_TYPES.BUSINESS }
|
||||
changeCallback={ handleSellerTypeChange }
|
||||
currentValue={ getCurrentValue() }
|
||||
checked={ isCasualSeller === false }
|
||||
type="radio"
|
||||
>
|
||||
</SelectBox>
|
||||
<SelectBox
|
||||
title={ __(
|
||||
'Business',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Recommended for individuals and organizations that primarily use PayPal to sell goods or services or receive donations, even if your business is not incorporated.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
name={ BUSINESS_RADIO_GROUP_NAME }
|
||||
value={ BUSINESS_TYPES.BUSINESS }
|
||||
changeCallback={ handleSellerTypeChange }
|
||||
currentValue={ getCurrentValue() }
|
||||
checked={ isCasualSeller === false }
|
||||
type="radio"
|
||||
></SelectBox>
|
||||
<SelectBox
|
||||
title={ __(
|
||||
'Personal Account',
|
||||
|
@ -72,8 +65,7 @@ const StepBusiness = ( {
|
|||
currentValue={ getCurrentValue() }
|
||||
checked={ isCasualSeller === true }
|
||||
type="radio"
|
||||
>
|
||||
</SelectBox>
|
||||
></SelectBox>
|
||||
</SelectBoxWrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
|
||||
const StepCompleteSetup = ( {
|
||||
setStep,
|
||||
currentStep,
|
||||
stepperOrder,
|
||||
setCompleted,
|
||||
} ) => {
|
||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
|
||||
const StepCompleteSetup = ( { setCompleted } ) => {
|
||||
const ButtonIcon = () => (
|
||||
<Icon
|
||||
icon={ () => (
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import SelectBox from '../../ReusableComponents/SelectBox';
|
||||
import SelectBoxWrapper from '../../ReusableComponents/SelectBoxWrapper';
|
||||
import { useOnboardingStepProducts } from '../../../data';
|
||||
import { PRODUCT_TYPES } from '../../../data/constants';
|
||||
import { OnboardingHooks, PRODUCT_TYPES } from '../../../data';
|
||||
|
||||
const PRODUCTS_CHECKBOX_GROUP_NAME = 'products';
|
||||
|
||||
const StepProducts = ( {
|
||||
setStep,
|
||||
currentStep,
|
||||
stepperOrder,
|
||||
setCompleted,
|
||||
} ) => {
|
||||
const { products, toggleProduct } = useOnboardingStepProducts();
|
||||
const StepProducts = () => {
|
||||
const { products, setProducts } = OnboardingHooks.useProducts();
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-page-products">
|
||||
|
@ -33,7 +28,7 @@ const StepProducts = ( {
|
|||
) }
|
||||
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
|
||||
value={ PRODUCT_TYPES.VIRTUAL }
|
||||
changeCallback={ toggleProduct }
|
||||
changeCallback={ setProducts }
|
||||
currentValue={ products }
|
||||
type="checkbox"
|
||||
>
|
||||
|
@ -75,7 +70,7 @@ const StepProducts = ( {
|
|||
) }
|
||||
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
|
||||
value={ PRODUCT_TYPES.PHYSICAL }
|
||||
changeCallback={ toggleProduct }
|
||||
changeCallback={ setProducts }
|
||||
currentValue={ products }
|
||||
type="checkbox"
|
||||
>
|
||||
|
@ -102,7 +97,7 @@ const StepProducts = ( {
|
|||
) }
|
||||
name={ PRODUCTS_CHECKBOX_GROUP_NAME }
|
||||
value={ PRODUCT_TYPES.SUBSCRIPTIONS }
|
||||
changeCallback={ toggleProduct }
|
||||
changeCallback={ setProducts }
|
||||
currentValue={ products }
|
||||
type="checkbox"
|
||||
>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
|
||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import PaymentMethodIcons from '../../ReusableComponents/PaymentMethodIcons';
|
||||
import Separator from '../../ReusableComponents/Separator';
|
||||
import WelcomeDocs from '../../ReusableComponents/WelcomeDocs/WelcomeDocs';
|
||||
import AccordionSection from '../../ReusableComponents/AccordionSection';
|
||||
|
||||
import AdvancedOptionsForm from './Components/AdvancedOptionsForm';
|
||||
import AccordionSection from '../../ReusableComponents/AccordionSection';
|
||||
|
||||
const StepWelcome = ( { setStep, currentStep, setCompleted } ) => {
|
||||
return (
|
||||
|
@ -57,7 +57,7 @@ const StepWelcome = ( { setStep, currentStep, setCompleted } ) => {
|
|||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
className="onboarding-advanced-options"
|
||||
initiallyOpen={ false }
|
||||
id="advanced-options"
|
||||
>
|
||||
<AdvancedOptionsForm setCompleted={ setCompleted } />
|
||||
</AccordionSection>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useOnboardingStep } from '../../data';
|
||||
import { OnboardingHooks } from '../../data';
|
||||
import Onboarding from './Onboarding/Onboarding';
|
||||
import SettingsScreen from './SettingsScreen';
|
||||
|
||||
const Settings = () => {
|
||||
const onboardingProgress = useOnboardingStep();
|
||||
const onboardingProgress = OnboardingHooks.useSteps();
|
||||
|
||||
if ( ! onboardingProgress.isReady ) {
|
||||
// TODO: Use better loading state indicator.
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'COMMON:SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: 'COMMON:SET_PERSISTENT',
|
||||
HYDRATE: 'COMMON:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
|
||||
DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION',
|
||||
DO_SANDBOX_LOGIN: 'COMMON:DO_SANDBOX_LOGIN',
|
||||
};
|
154
modules/ppcp-settings/resources/js/data/common/actions.js
Normal file
154
modules/ppcp-settings/resources/js/data/common/actions.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* Action Creators: Define functions to create action objects.
|
||||
*
|
||||
* These functions update state or trigger side effects (e.g., async operations).
|
||||
* Actions are categorized as Transient, Persistent, or Side effect.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
* @property {string} type - The action type.
|
||||
* @property {Object?} payload - Optional payload for the action.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Persistent. Set the full onboarding details, usually during app initialization.
|
||||
*
|
||||
* @param {{data: {}, flags?: {}}} payload
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const hydrate = ( payload ) => ( {
|
||||
type: ACTION_TYPES.HYDRATE,
|
||||
payload,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Marks the onboarding details as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Changes the "saving" flag.
|
||||
*
|
||||
* @param {boolean} isSaving
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsSaving = ( isSaving ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isSaving },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Changes the "manual connection is busy" flag.
|
||||
*
|
||||
* @param {boolean} isBusy
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsBusy = ( isBusy ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isBusy },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the sandbox mode on or off.
|
||||
*
|
||||
* @param {boolean} useSandbox
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSandboxMode = ( useSandbox ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { useSandbox },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Toggles the "Manual Connection" mode on or off.
|
||||
*
|
||||
* @param {boolean} useManualConnection
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualConnectionMode = ( useManualConnection ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { useManualConnection },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "client ID" value.
|
||||
*
|
||||
* @param {string} clientId
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setClientId = ( clientId ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { clientId },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "client secret" value.
|
||||
*
|
||||
* @param {string} clientSecret
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setClientSecret = ( clientSecret ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { clientSecret },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Side effect. Saves the persistent details to the WP database.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Initiates the sandbox login ISU.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const connectViaSandbox = function* () {
|
||||
yield setIsBusy( true );
|
||||
|
||||
const result = yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN };
|
||||
yield setIsBusy( false );
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Initiates a manual connection attempt using the provided client ID and secret.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const connectViaIdAndSecret = function* () {
|
||||
const { clientId, clientSecret, useSandbox } =
|
||||
yield select( STORE_NAME ).persistentData();
|
||||
|
||||
yield setIsBusy( true );
|
||||
|
||||
const result = yield {
|
||||
type: ACTION_TYPES.DO_MANUAL_CONNECTION,
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
};
|
||||
yield setIsBusy( false );
|
||||
|
||||
return result;
|
||||
};
|
46
modules/ppcp-settings/resources/js/data/common/constants.js
Normal file
46
modules/ppcp-settings/resources/js/data/common/constants.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Name of the module-store in the main Redux store.
|
||||
*
|
||||
* Helps to isolate data, used by reducer and selectors.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const STORE_NAME = 'wc/paypal/common';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB..
|
||||
*
|
||||
* Used by resolvers.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/common';
|
||||
|
||||
/**
|
||||
* REST path to persist data of this module to the WP DB.
|
||||
*
|
||||
* Used by controls.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common';
|
||||
|
||||
/**
|
||||
* REST path to perform the manual connection check, using client ID and secret,
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: ConnectManualRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual';
|
||||
|
||||
/**
|
||||
* REST path to generate an ISU URL for the sandbox-login.
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: LoginLinkRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_SANDBOX_CONNECTION_PATH = '/wc/v3/wc_paypal/login_link';
|
80
modules/ppcp-settings/resources/js/data/common/controls.js
vendored
Normal file
80
modules/ppcp-settings/resources/js/data/common/controls.js
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Controls: Implement side effects, typically asynchronous operations.
|
||||
*
|
||||
* Controls use ACTION_TYPES keys as identifiers.
|
||||
* They are triggered by corresponding actions and handle external interactions.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import {
|
||||
REST_PERSIST_PATH,
|
||||
REST_MANUAL_CONNECTION_PATH,
|
||||
REST_SANDBOX_CONNECTION_PATH,
|
||||
} from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
console.error( 'Error saving data.', error );
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_SANDBOX_LOGIN ]() {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( {
|
||||
path: REST_SANDBOX_CONNECTION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
environment: 'sandbox',
|
||||
products: [ 'EXPRESS_CHECKOUT' ],
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_MANUAL_CONNECTION ]( {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
} ) {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( {
|
||||
path: REST_MANUAL_CONNECTION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
111
modules/ppcp-settings/resources/js/data/common/hooks.js
Normal file
111
modules/ppcp-settings/resources/js/data/common/hooks.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Hooks: Provide the main API for components to interact with the store.
|
||||
*
|
||||
* These encapsulate store interactions, offering a consistent interface.
|
||||
* Hooks simplify data access and manipulation for components.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const useHooks = () => {
|
||||
const {
|
||||
persist,
|
||||
setSandboxMode,
|
||||
setManualConnectionMode,
|
||||
setClientId,
|
||||
setClientSecret,
|
||||
connectViaSandbox,
|
||||
connectViaIdAndSecret,
|
||||
} = useDispatch( STORE_NAME );
|
||||
|
||||
// Transient accessors.
|
||||
const isReady = useTransient( 'isReady' );
|
||||
|
||||
// Persistent accessors.
|
||||
const clientId = usePersistent( 'clientId' );
|
||||
const clientSecret = usePersistent( 'clientSecret' );
|
||||
const isSandboxMode = usePersistent( 'useSandbox' );
|
||||
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
||||
|
||||
const savePersistent = async ( setter, value ) => {
|
||||
setter( value );
|
||||
await persist();
|
||||
};
|
||||
|
||||
return {
|
||||
isReady,
|
||||
isSandboxMode,
|
||||
setSandboxMode: ( state ) => {
|
||||
return savePersistent( setSandboxMode, state );
|
||||
},
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode: ( state ) => {
|
||||
return savePersistent( setManualConnectionMode, state );
|
||||
},
|
||||
clientId,
|
||||
setClientId: ( value ) => {
|
||||
return savePersistent( setClientId, value );
|
||||
},
|
||||
clientSecret,
|
||||
setClientSecret: ( value ) => {
|
||||
return savePersistent( setClientSecret, value );
|
||||
},
|
||||
connectViaSandbox,
|
||||
connectViaIdAndSecret,
|
||||
};
|
||||
};
|
||||
|
||||
export const useBusyState = () => {
|
||||
const { setIsBusy } = useDispatch( STORE_NAME );
|
||||
const isBusy = useTransient( 'isBusy' );
|
||||
|
||||
return {
|
||||
isBusy,
|
||||
setIsBusy: useCallback( ( busy ) => setIsBusy( busy ), [ setIsBusy ] ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useSandbox = () => {
|
||||
const { isSandboxMode, setSandboxMode, connectViaSandbox } = useHooks();
|
||||
|
||||
return { isSandboxMode, setSandboxMode, connectViaSandbox };
|
||||
};
|
||||
|
||||
export const useManualConnection = () => {
|
||||
const {
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode,
|
||||
clientId,
|
||||
setClientId,
|
||||
clientSecret,
|
||||
setClientSecret,
|
||||
connectViaIdAndSecret,
|
||||
} = useHooks();
|
||||
|
||||
return {
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode,
|
||||
clientId,
|
||||
setClientId,
|
||||
clientSecret,
|
||||
setClientSecret,
|
||||
connectViaIdAndSecret,
|
||||
};
|
||||
};
|
24
modules/ppcp-settings/resources/js/data/common/index.js
Normal file
24
modules/ppcp-settings/resources/js/data/common/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { createReduxStore, register } from '@wordpress/data';
|
||||
import { controls as wpControls } from '@wordpress/data-controls';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
45
modules/ppcp-settings/resources/js/data/common/reducer.js
Normal file
45
modules/ppcp-settings/resources/js/data/common/reducer.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Reducer: Defines store structure and state updates for this module.
|
||||
*
|
||||
* Manages both transient (temporary) and persistent (saved) state.
|
||||
* The initial state must define all properties, as dynamic additions are not supported.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { createReducer, createSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
// Store structure.
|
||||
|
||||
const defaultTransient = {
|
||||
isReady: false,
|
||||
isBusy: false,
|
||||
};
|
||||
|
||||
const defaultPersistent = {
|
||||
useSandbox: false,
|
||||
useManualConnection: false,
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
};
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ setTransient, setPersistent ] = createSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
const commonReducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, action ) =>
|
||||
setTransient( state, action ),
|
||||
|
||||
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, action ) =>
|
||||
setPersistent( state, action ),
|
||||
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||
setPersistent( state, payload.data ),
|
||||
} );
|
||||
|
||||
export default commonReducer;
|
36
modules/ppcp-settings/resources/js/data/common/resolvers.js
Normal file
36
modules/ppcp-settings/resources/js/data/common/resolvers.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Resolvers: Handle asynchronous data fetching for the store.
|
||||
*
|
||||
* These functions update store state with data from external sources.
|
||||
* Each resolver corresponds to a specific selector (selector with same name must exist).
|
||||
* Resolvers are called automatically when selectors request unavailable data.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving plugin details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
21
modules/ppcp-settings/resources/js/data/common/selectors.js
Normal file
21
modules/ppcp-settings/resources/js/data/common/selectors.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Selectors: Extract specific pieces of state from the store.
|
||||
*
|
||||
* These functions provide a consistent interface for accessing store data.
|
||||
* They allow components to retrieve data without knowing the store structure.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
const EMPTY_OBJ = Object.freeze( {} );
|
||||
|
||||
const getState = ( state ) => state || EMPTY_OBJ;
|
||||
|
||||
export const persistentData = ( state ) => {
|
||||
return getState( state ).data || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const transientData = ( state ) => {
|
||||
const { data, ...transientState } = getState( state );
|
||||
return transientState || EMPTY_OBJ;
|
||||
};
|
|
@ -1,6 +1,3 @@
|
|||
export const NAMESPACE = '/wc/v3/wc_paypal';
|
||||
export const STORE_NAME = 'wc/paypal';
|
||||
|
||||
export const BUSINESS_TYPES = {
|
||||
CASUAL_SELLER: 'casual_seller',
|
||||
BUSINESS: 'business',
|
||||
|
|
47
modules/ppcp-settings/resources/js/data/debug.js
Normal file
47
modules/ppcp-settings/resources/js/data/debug.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { OnboardingStoreName } from './index';
|
||||
|
||||
export const addDebugTools = ( context, modules ) => {
|
||||
if ( ! context || ! context?.debug ) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.dumpStore = async () => {
|
||||
/* eslint-disable no-console */
|
||||
if ( ! console?.groupCollapsed ) {
|
||||
console.error( 'console.groupCollapsed is not supported.' );
|
||||
return;
|
||||
}
|
||||
|
||||
modules.forEach( ( module ) => {
|
||||
const storeName = module.STORE_NAME;
|
||||
const storeSelector = `wp.data.select( '${ storeName }' )`;
|
||||
console.group( `[STORE] ${ storeSelector }` );
|
||||
|
||||
const dumpStore = ( selector ) => {
|
||||
const contents = wp.data.select( storeName )[ selector ]();
|
||||
|
||||
console.groupCollapsed( `.${ selector }()` );
|
||||
console.table( contents );
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
Object.keys( module.selectors ).forEach( dumpStore );
|
||||
|
||||
console.groupEnd();
|
||||
} );
|
||||
/* eslint-enable no-console */
|
||||
};
|
||||
|
||||
context.resetStore = () => {
|
||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||
onboarding.reset();
|
||||
onboarding.persist();
|
||||
};
|
||||
|
||||
context.startOnboarding = () => {
|
||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||
onboarding.setCompleted( false );
|
||||
onboarding.setStep( 0 );
|
||||
onboarding.persist();
|
||||
};
|
||||
};
|
|
@ -1,7 +1,16 @@
|
|||
import { STORE_NAME } from './constants';
|
||||
import { initStore } from './store';
|
||||
import { addDebugTools } from './debug';
|
||||
import * as Onboarding from './onboarding';
|
||||
import * as Common from './common';
|
||||
|
||||
initStore();
|
||||
Onboarding.initStore();
|
||||
Common.initStore();
|
||||
|
||||
export const WC_PAYPAL_STORE_NAME = STORE_NAME;
|
||||
export * from './onboarding/hooks';
|
||||
export const OnboardingHooks = Onboarding.hooks;
|
||||
export const CommonHooks = Common.hooks;
|
||||
|
||||
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
||||
export const CommonStoreName = Common.STORE_NAME;
|
||||
|
||||
export * from './constants';
|
||||
|
||||
addDebugTools( window.ppcpSettings, [ Onboarding, Common ] );
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
export default {
|
||||
RESET_ONBOARDING: 'RESET_ONBOARDING',
|
||||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_ONBOARDING_IS_READY: 'SET_ONBOARDING_IS_READY',
|
||||
SET_IS_SAVING_ONBOARDING: 'SET_IS_SAVING_ONBOARDING',
|
||||
SET_MANUAL_CONNECTION_BUSY: 'SET_MANUAL_CONNECTION_BUSY',
|
||||
SET_TRANSIENT: 'ONBOARDING:SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_ONBOARDING_COMPLETED: 'SET_ONBOARDING_COMPLETED',
|
||||
SET_ONBOARDING_DETAILS: 'SET_ONBOARDING_DETAILS',
|
||||
SET_ONBOARDING_STEP: 'SET_ONBOARDING_STEP',
|
||||
SET_SANDBOX_MODE: 'SET_SANDBOX_MODE',
|
||||
SET_MANUAL_CONNECTION_MODE: 'SET_MANUAL_CONNECTION_MODE',
|
||||
SET_CLIENT_ID: 'SET_CLIENT_ID',
|
||||
SET_CLIENT_SECRET: 'SET_CLIENT_SECRET',
|
||||
SET_IS_CASUAL_SELLER: 'SET_IS_CASUAL_SELLER',
|
||||
SET_PRODUCTS: 'SET_PRODUCTS',
|
||||
SET_PERSISTENT: 'ONBOARDING:SET_PERSISTENT',
|
||||
RESET: 'ONBOARDING:RESET',
|
||||
HYDRATE: 'ONBOARDING:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'ONBOARDING:DO_PERSIST_DATA',
|
||||
};
|
||||
|
|
|
@ -1,235 +1,103 @@
|
|||
/**
|
||||
* Action Creators: Define functions to create action objects.
|
||||
*
|
||||
* These functions update state or trigger side effects (e.g., async operations).
|
||||
* Actions are categorized as Transient, Persistent, or Side effect.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { NAMESPACE, STORE_NAME } from '../constants';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
* @property {string} type - The action type.
|
||||
* @property {Object?} payload - Optional payload for the action.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special. Resets all values in the onboarding store to initial defaults.
|
||||
*
|
||||
* @return {{type: string}} The action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const resetOnboarding = () => {
|
||||
return { type: ACTION_TYPES.RESET_ONBOARDING };
|
||||
};
|
||||
|
||||
/**
|
||||
* Non-persistent. Marks the onboarding details as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {{type: string, isReady}} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_ONBOARDING_IS_READY,
|
||||
isReady,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Non-persistent. Changes the "saving" flag.
|
||||
*
|
||||
* @param {boolean} isSaving
|
||||
* @return {{type: string, isSaving}} The action.
|
||||
*/
|
||||
export const setIsSaving = ( isSaving ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_IS_SAVING_ONBOARDING,
|
||||
isSaving,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Non-persistent. Changes the "manual connection is busy" flag.
|
||||
*
|
||||
* @param {boolean} isBusy
|
||||
* @return {{type: string, isBusy}} The action.
|
||||
*/
|
||||
export const setManualConnectionIsBusy = ( isBusy ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_MANUAL_CONNECTION_BUSY,
|
||||
isBusy,
|
||||
};
|
||||
};
|
||||
export const reset = () => ( { type: ACTION_TYPES.RESET } );
|
||||
|
||||
/**
|
||||
* Persistent. Set the full onboarding details, usually during app initialization.
|
||||
*
|
||||
* @param {{data: {}, flags?: {}}} payload
|
||||
* @return {{type: string, payload}} The action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setOnboardingDetails = ( payload ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_ONBOARDING_DETAILS,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
export const hydrate = ( payload ) => ( {
|
||||
type: ACTION_TYPES.HYDRATE,
|
||||
payload,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Marks the onboarding details as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
|
||||
*
|
||||
* @param {boolean} completed
|
||||
* @return {{type: string, payload}} The action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setCompleted = ( completed ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_ONBOARDING_COMPLETED,
|
||||
completed,
|
||||
};
|
||||
};
|
||||
export const setCompleted = ( completed ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { completed },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the onboarding wizard to a new step.
|
||||
*
|
||||
* @param {number} step
|
||||
* @return {{type: string, step}} An action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setOnboardingStep = ( step ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_ONBOARDING_STEP,
|
||||
step,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent. Sets the sandbox mode on or off.
|
||||
*
|
||||
* @param {boolean} sandboxMode
|
||||
* @return {{type: string, useSandbox}} An action.
|
||||
*/
|
||||
export const setSandboxMode = ( sandboxMode ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_SANDBOX_MODE,
|
||||
useSandbox: sandboxMode,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent. Toggles the "Manual Connection" mode on or off.
|
||||
*
|
||||
* @param {boolean} manualConnectionMode
|
||||
* @return {{type: string, useManualConnection}} An action.
|
||||
*/
|
||||
export const setManualConnectionMode = ( manualConnectionMode ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_MANUAL_CONNECTION_MODE,
|
||||
useManualConnection: manualConnectionMode,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "client ID" value.
|
||||
*
|
||||
* @param {string} clientId
|
||||
* @return {{type: string, clientId}} The action.
|
||||
*/
|
||||
export const setClientId = ( clientId ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_CLIENT_ID,
|
||||
clientId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "client secret" value.
|
||||
*
|
||||
* @param {string} clientSecret
|
||||
* @return {{type: string, clientSecret}} The action.
|
||||
*/
|
||||
export const setClientSecret = ( clientSecret ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_CLIENT_SECRET,
|
||||
clientSecret,
|
||||
};
|
||||
};
|
||||
export const setStep = ( step ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { step },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the "isCasualSeller" value.
|
||||
*
|
||||
* @param {boolean} isCasualSeller
|
||||
* @return {{type: string, isCasualSeller}} The action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsCasualSeller = ( isCasualSeller ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_IS_CASUAL_SELLER,
|
||||
isCasualSeller,
|
||||
};
|
||||
};
|
||||
export const setIsCasualSeller = ( isCasualSeller ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { isCasualSeller },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the "products" array.
|
||||
*
|
||||
* @param {string[]} products
|
||||
* @return {{type: string, products}} The action.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setProducts = ( products ) => {
|
||||
return {
|
||||
type: ACTION_TYPES.SET_PRODUCTS,
|
||||
products,
|
||||
};
|
||||
export const setProducts = ( products ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { products },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of onboarding data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to establish a connection using client ID and secret via the server-side
|
||||
* connection endpoint.
|
||||
*
|
||||
* @return {Object} The server response object
|
||||
*/
|
||||
export function* connectViaIdAndSecret() {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
const path = `${ NAMESPACE }/connect_manual`;
|
||||
const { clientId, clientSecret, useSandbox } =
|
||||
yield select( STORE_NAME ).getPersistentData();
|
||||
|
||||
yield setManualConnectionIsBusy( true );
|
||||
|
||||
result = yield apiFetch( {
|
||||
path,
|
||||
method: 'POST',
|
||||
data: {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
} finally {
|
||||
yield setManualConnectionIsBusy( false );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the persistent details to the WP database.
|
||||
*
|
||||
* @return {boolean} True, if the values were successfully saved.
|
||||
*/
|
||||
export function* persist() {
|
||||
let error = null;
|
||||
|
||||
try {
|
||||
const path = `${ NAMESPACE }/onboarding`;
|
||||
const data = select( STORE_NAME ).getPersistentData();
|
||||
|
||||
yield setIsSaving( true );
|
||||
|
||||
yield apiFetch( {
|
||||
path,
|
||||
method: 'post',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
error = e;
|
||||
console.error( 'Error saving progress.', e );
|
||||
} finally {
|
||||
yield setIsSaving( false );
|
||||
}
|
||||
|
||||
return error === null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Name of the Redux store module.
|
||||
*
|
||||
* Used by: Reducer, Selector, Index
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const STORE_NAME = 'wc/paypal/onboarding';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB..
|
||||
*
|
||||
* Used by: Resolvers
|
||||
* See: OnboardingRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/onboarding';
|
||||
|
||||
/**
|
||||
* REST path to persist data of this module to the WP DB.
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: OnboardingRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/onboarding';
|
27
modules/ppcp-settings/resources/js/data/onboarding/controls.js
vendored
Normal file
27
modules/ppcp-settings/resources/js/data/onboarding/controls.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Controls: Implement side effects, typically asynchronous operations.
|
||||
*
|
||||
* Controls use ACTION_TYPES keys as identifiers.
|
||||
* They are triggered by corresponding actions and handle external interactions.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,163 +1,90 @@
|
|||
/**
|
||||
* Hooks: Provide the main API for components to interact with the store.
|
||||
*
|
||||
* These encapsulate store interactions, offering a consistent interface.
|
||||
* Hooks simplify data access and manipulation for components.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { NAMESPACE, PRODUCT_TYPES, STORE_NAME } from '../constants';
|
||||
import { getFlags } from './selectors';
|
||||
|
||||
const useOnboardingDetails = () => {
|
||||
const {
|
||||
persist,
|
||||
setOnboardingStep,
|
||||
setCompleted,
|
||||
setSandboxMode,
|
||||
setManualConnectionMode,
|
||||
setClientId,
|
||||
setClientSecret,
|
||||
setIsCasualSeller,
|
||||
setProducts,
|
||||
} = useDispatch( STORE_NAME );
|
||||
import { PRODUCT_TYPES } from '../constants';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
// Transient accessors.
|
||||
const isSaving = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isSaving;
|
||||
}, [] );
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const isReady = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isReady;
|
||||
} );
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const isManualConnectionBusy = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isManualConnectionBusy;
|
||||
}, [] );
|
||||
const useHooks = () => {
|
||||
const { persist, setStep, setCompleted, setIsCasualSeller, setProducts } =
|
||||
useDispatch( STORE_NAME );
|
||||
|
||||
// Read-only flags.
|
||||
const flags = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getFlags();
|
||||
} );
|
||||
const flags = useSelect( ( select ) => select( STORE_NAME ).flags(), [] );
|
||||
|
||||
// Transient accessors.
|
||||
const isReady = useTransient( 'isReady' );
|
||||
|
||||
// Persistent accessors.
|
||||
const step = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().step || 0;
|
||||
} );
|
||||
const step = usePersistent( 'step' );
|
||||
const completed = usePersistent( 'completed' );
|
||||
const isCasualSeller = usePersistent( 'isCasualSeller' );
|
||||
const products = usePersistent( 'products' );
|
||||
|
||||
const completed = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().completed;
|
||||
} );
|
||||
|
||||
const clientId = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().clientId;
|
||||
}, [] );
|
||||
|
||||
const clientSecret = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().clientSecret;
|
||||
}, [] );
|
||||
|
||||
const isSandboxMode = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().useSandbox;
|
||||
}, [] );
|
||||
|
||||
const isManualConnectionMode = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().useManualConnection;
|
||||
}, [] );
|
||||
|
||||
const isCasualSeller = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().isCasualSeller;
|
||||
}, [] );
|
||||
|
||||
const products = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getPersistentData().products || [];
|
||||
}, [] );
|
||||
|
||||
const toggleProduct = ( list ) => {
|
||||
const validProducts = list.filter( ( item ) =>
|
||||
Object.values( PRODUCT_TYPES ).includes( item )
|
||||
);
|
||||
return setDetailAndPersist( setProducts, validProducts );
|
||||
};
|
||||
|
||||
const setDetailAndPersist = async ( setter, value ) => {
|
||||
const savePersistent = async ( setter, value ) => {
|
||||
setter( value );
|
||||
await persist();
|
||||
};
|
||||
|
||||
return {
|
||||
isSaving,
|
||||
isReady,
|
||||
isManualConnectionBusy,
|
||||
step,
|
||||
setStep: ( value ) => setDetailAndPersist( setOnboardingStep, value ),
|
||||
completed,
|
||||
setCompleted: ( state ) => setDetailAndPersist( setCompleted, state ),
|
||||
isSandboxMode,
|
||||
setSandboxMode: ( state ) =>
|
||||
setDetailAndPersist( setSandboxMode, state ),
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode: ( state ) =>
|
||||
setDetailAndPersist( setManualConnectionMode, state ),
|
||||
clientId,
|
||||
setClientId: ( value ) => setDetailAndPersist( setClientId, value ),
|
||||
clientSecret,
|
||||
setClientSecret: ( value ) =>
|
||||
setDetailAndPersist( setClientSecret, value ),
|
||||
isCasualSeller,
|
||||
setIsCasualSeller: ( value ) =>
|
||||
setDetailAndPersist( setIsCasualSeller, value ),
|
||||
products,
|
||||
toggleProduct,
|
||||
flags,
|
||||
isReady,
|
||||
step,
|
||||
setStep: ( value ) => {
|
||||
return savePersistent( setStep, value );
|
||||
},
|
||||
completed,
|
||||
setCompleted: ( state ) => {
|
||||
return savePersistent( setCompleted, state );
|
||||
},
|
||||
isCasualSeller,
|
||||
setIsCasualSeller: ( value ) => {
|
||||
return savePersistent( setIsCasualSeller, value );
|
||||
},
|
||||
products,
|
||||
setProducts: ( activeProducts ) => {
|
||||
const validProducts = activeProducts.filter( ( item ) =>
|
||||
Object.values( PRODUCT_TYPES ).includes( item )
|
||||
);
|
||||
return savePersistent( setProducts, validProducts );
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useOnboardingStepWelcome = () => {
|
||||
const {
|
||||
isSaving,
|
||||
isManualConnectionBusy,
|
||||
isSandboxMode,
|
||||
setSandboxMode,
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode,
|
||||
clientId,
|
||||
setClientId,
|
||||
clientSecret,
|
||||
setClientSecret,
|
||||
} = useOnboardingDetails();
|
||||
|
||||
return {
|
||||
isSaving,
|
||||
isManualConnectionBusy,
|
||||
isSandboxMode,
|
||||
setSandboxMode,
|
||||
isManualConnectionMode,
|
||||
setManualConnectionMode,
|
||||
clientId,
|
||||
setClientId,
|
||||
clientSecret,
|
||||
setClientSecret,
|
||||
};
|
||||
};
|
||||
|
||||
export const useOnboardingStepBusiness = () => {
|
||||
const { isCasualSeller, setIsCasualSeller } = useOnboardingDetails();
|
||||
export const useBusiness = () => {
|
||||
const { isCasualSeller, setIsCasualSeller } = useHooks();
|
||||
|
||||
return { isCasualSeller, setIsCasualSeller };
|
||||
};
|
||||
|
||||
export const useOnboardingStepProducts = () => {
|
||||
const { products, toggleProduct } = useOnboardingDetails();
|
||||
export const useProducts = () => {
|
||||
const { products, setProducts } = useHooks();
|
||||
|
||||
return { products, toggleProduct };
|
||||
return { products, setProducts };
|
||||
};
|
||||
|
||||
export const useOnboardingStep = () => {
|
||||
const { isReady, step, setStep, completed, setCompleted, flags } =
|
||||
useOnboardingDetails();
|
||||
export const useSteps = () => {
|
||||
const { flags, isReady, step, setStep, completed, setCompleted } =
|
||||
useHooks();
|
||||
|
||||
return { isReady, step, setStep, completed, setCompleted, flags };
|
||||
};
|
||||
|
||||
export const useManualConnect = () => {
|
||||
const { connectViaIdAndSecret } = useDispatch( STORE_NAME );
|
||||
|
||||
return {
|
||||
connectManual: connectViaIdAndSecret,
|
||||
};
|
||||
return { flags, isReady, step, setStep, completed, setCompleted };
|
||||
};
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
import { createReduxStore, register } from '@wordpress/data';
|
||||
import { controls as wpControls } from '@wordpress/data-controls';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
|
||||
export { reducer, selectors, actions, resolvers };
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
/**
|
||||
* Reducer: Defines store structure and state updates for this module.
|
||||
*
|
||||
* Manages both transient (temporary) and persistent (saved) state.
|
||||
* The initial state must define all properties, as dynamic additions are not supported.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { createReducer, createSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
const defaultState = {
|
||||
isReady: false,
|
||||
isSaving: false,
|
||||
isManualConnectionBusy: false,
|
||||
// Store structure.
|
||||
|
||||
// Data persisted to the server.
|
||||
data: {
|
||||
completed: false,
|
||||
step: 0,
|
||||
useSandbox: false,
|
||||
useManualConnection: false,
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
isCasualSeller: null, // null value will uncheck both options in the UI.
|
||||
products: [],
|
||||
},
|
||||
const defaultTransient = {
|
||||
isReady: false,
|
||||
|
||||
// Read only values, provided by the server.
|
||||
flags: {
|
||||
|
@ -25,83 +23,40 @@ const defaultState = {
|
|||
},
|
||||
};
|
||||
|
||||
export const onboardingReducer = (
|
||||
state = defaultState,
|
||||
{ type, ...action }
|
||||
) => {
|
||||
const setTransient = ( changes ) => {
|
||||
const { data, ...transientChanges } = changes;
|
||||
return { ...state, ...transientChanges };
|
||||
};
|
||||
|
||||
const setPersistent = ( changes ) => {
|
||||
const validChanges = Object.keys( changes ).reduce( ( acc, key ) => {
|
||||
if ( key in defaultState.data ) {
|
||||
acc[ key ] = changes[ key ];
|
||||
}
|
||||
return acc;
|
||||
}, {} );
|
||||
|
||||
return {
|
||||
...state,
|
||||
data: { ...state.data, ...validChanges },
|
||||
};
|
||||
};
|
||||
|
||||
switch ( type ) {
|
||||
// Reset store to initial state.
|
||||
case ACTION_TYPES.RESET_ONBOARDING:
|
||||
return setPersistent( defaultState.data );
|
||||
|
||||
// Transient data.
|
||||
case ACTION_TYPES.SET_ONBOARDING_IS_READY:
|
||||
return setTransient( { isReady: action.isReady } );
|
||||
|
||||
case ACTION_TYPES.SET_IS_SAVING_ONBOARDING:
|
||||
return setTransient( { isSaving: action.isSaving } );
|
||||
|
||||
case ACTION_TYPES.SET_MANUAL_CONNECTION_BUSY:
|
||||
return setTransient( { isManualConnectionBusy: action.isBusy } );
|
||||
|
||||
// Persistent data.
|
||||
case ACTION_TYPES.SET_ONBOARDING_DETAILS:
|
||||
const newState = setPersistent( action.payload.data );
|
||||
|
||||
if ( action.payload.flags ) {
|
||||
newState.flags = { ...newState.flags, ...action.payload.flags };
|
||||
}
|
||||
|
||||
return newState;
|
||||
|
||||
case ACTION_TYPES.SET_ONBOARDING_COMPLETED:
|
||||
return setPersistent( { completed: action.completed } );
|
||||
|
||||
case ACTION_TYPES.SET_CLIENT_ID:
|
||||
return setPersistent( { clientId: action.clientId } );
|
||||
|
||||
case ACTION_TYPES.SET_CLIENT_SECRET:
|
||||
return setPersistent( { clientSecret: action.clientSecret } );
|
||||
|
||||
case ACTION_TYPES.SET_ONBOARDING_STEP:
|
||||
return setPersistent( { step: action.step } );
|
||||
|
||||
case ACTION_TYPES.SET_SANDBOX_MODE:
|
||||
return setPersistent( { useSandbox: action.useSandbox } );
|
||||
|
||||
case ACTION_TYPES.SET_MANUAL_CONNECTION_MODE:
|
||||
return setPersistent( {
|
||||
useManualConnection: action.useManualConnection,
|
||||
} );
|
||||
|
||||
case ACTION_TYPES.SET_IS_CASUAL_SELLER:
|
||||
return setPersistent( { isCasualSeller: action.isCasualSeller } );
|
||||
|
||||
case ACTION_TYPES.SET_PRODUCTS:
|
||||
return setPersistent( { products: action.products } );
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
const defaultPersistent = {
|
||||
completed: false,
|
||||
step: 0,
|
||||
isCasualSeller: null, // null value will uncheck both options in the UI.
|
||||
products: [],
|
||||
};
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ setTransient, setPersistent ] = createSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
const onboardingReducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||
setTransient( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
|
||||
setPersistent( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.RESET ]: ( state ) =>
|
||||
setPersistent( state, defaultPersistent ),
|
||||
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
|
||||
const newState = setPersistent( state, payload.data );
|
||||
|
||||
// Flags are not updated by `setPersistent()`.
|
||||
if ( payload.flags ) {
|
||||
newState.flags = { ...newState.flags, ...payload.flags };
|
||||
}
|
||||
|
||||
return newState;
|
||||
},
|
||||
} );
|
||||
|
||||
export default onboardingReducer;
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
/**
|
||||
* Resolvers: Handle asynchronous data fetching for the store.
|
||||
*
|
||||
* These functions update store state with data from external sources.
|
||||
* Each resolver corresponds to a specific selector (selector with same name must exist).
|
||||
* Resolvers are called automatically when selectors request unavailable data.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import { NAMESPACE } from '../constants';
|
||||
import { setIsReady, setOnboardingDetails } from './actions';
|
||||
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function* getPersistentData() {
|
||||
const path = `${ NAMESPACE }/onboarding`;
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
try {
|
||||
const result = yield apiFetch( { path } );
|
||||
yield setOnboardingDetails( result );
|
||||
yield setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving onboarding details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving onboarding details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
/**
|
||||
* Selectors: Extract specific pieces of state from the store.
|
||||
*
|
||||
* These functions provide a consistent interface for accessing store data.
|
||||
* They allow components to retrieve data without knowing the store structure.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
const EMPTY_OBJ = Object.freeze( {} );
|
||||
|
||||
const getOnboardingState = ( state ) => {
|
||||
if ( ! state ) {
|
||||
return EMPTY_OBJ;
|
||||
}
|
||||
const getState = ( state ) => state || EMPTY_OBJ;
|
||||
|
||||
return state.onboarding || EMPTY_OBJ;
|
||||
export const persistentData = ( state ) => {
|
||||
return getState( state ).data || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const getPersistentData = ( state ) => {
|
||||
return getOnboardingState( state ).data || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const getTransientData = ( state ) => {
|
||||
const { data, flags, ...transientState } = getOnboardingState( state );
|
||||
export const transientData = ( state ) => {
|
||||
const { data, flags, ...transientState } = getState( state );
|
||||
return transientState || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const getFlags = ( state ) => {
|
||||
return getOnboardingState( state ).flags || EMPTY_OBJ;
|
||||
export const flags = ( state ) => {
|
||||
return getState( state ).flags || EMPTY_OBJ;
|
||||
};
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import { createReduxStore, register, combineReducers } from '@wordpress/data';
|
||||
import { controls } from '@wordpress/data-controls';
|
||||
import { STORE_NAME } from './constants';
|
||||
import * as onboarding from './onboarding';
|
||||
|
||||
const actions = {};
|
||||
const selectors = {};
|
||||
const resolvers = {};
|
||||
|
||||
[ onboarding ].forEach( ( item ) => {
|
||||
Object.assign( actions, { ...item.actions } );
|
||||
Object.assign( selectors, { ...item.selectors } );
|
||||
Object.assign( resolvers, { ...item.resolvers } );
|
||||
} );
|
||||
|
||||
const reducer = combineReducers( {
|
||||
onboarding: onboarding.reducer,
|
||||
} );
|
||||
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls,
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
|
||||
/* eslint-disable no-console */
|
||||
// Provide a debug tool to inspect the Redux store via the JS console.
|
||||
if ( window.ppcpSettings?.debug && console?.groupCollapsed ) {
|
||||
window.ppcpSettings.dumpStore = () => {
|
||||
const storeSelector = `wp.data.select('${ STORE_NAME }')`;
|
||||
console.group( `[STORE] ${ storeSelector }` );
|
||||
|
||||
const storeState = wp.data.select( STORE_NAME );
|
||||
Object.keys( selectors ).forEach( ( selector ) => {
|
||||
console.groupCollapsed( `[SELECTOR] .${ selector }()` );
|
||||
console.table( storeState[ selector ]() );
|
||||
console.groupEnd();
|
||||
} );
|
||||
|
||||
console.groupEnd();
|
||||
};
|
||||
window.ppcpSettings.resetStore = () => {
|
||||
wp.data.dispatch( STORE_NAME ).resetOnboarding();
|
||||
wp.data.dispatch( STORE_NAME ).persist();
|
||||
};
|
||||
window.ppcpSettings.startOnboarding = () => {
|
||||
wp.data.dispatch( STORE_NAME ).setCompleted( false );
|
||||
wp.data.dispatch( STORE_NAME ).setOnboardingStep( 0 );
|
||||
wp.data.dispatch( STORE_NAME ).persist();
|
||||
};
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
};
|
75
modules/ppcp-settings/resources/js/data/utils.js
Normal file
75
modules/ppcp-settings/resources/js/data/utils.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Updates an object with new values, filtering based on allowed keys.
|
||||
*
|
||||
* Helper method used by createSetters.
|
||||
*
|
||||
* @param {Object} oldObject The original object to update.
|
||||
* @param {Object} newValues The new values to apply.
|
||||
* @param {Object} allowedKeys An object whose keys define the allowed keys to update.
|
||||
* @return {Object} A new object with the allowed updates applied.
|
||||
*/
|
||||
const updateObject = ( oldObject, newValues, allowedKeys = {} ) => ( {
|
||||
...oldObject,
|
||||
...Object.keys( newValues ).reduce( ( acc, key ) => {
|
||||
if ( key in allowedKeys ) {
|
||||
acc[ key ] = newValues[ key ];
|
||||
}
|
||||
return acc;
|
||||
}, {} ),
|
||||
} );
|
||||
|
||||
/**
|
||||
* Creates setter functions for updating state.
|
||||
*
|
||||
* Only properties that are present in the "defaultTransient" or "defaultPersistent"
|
||||
* arguments can be updated by the setters. Make sure that the default state defines
|
||||
* ALL possible properties.
|
||||
*
|
||||
* @param {Object} defaultTransient Object defining initial transient values.
|
||||
* @param {Object} defaultPersistent Object defining initial persistent values.
|
||||
* @return {[Function, Function]} An array containing setTransient and setPersistent functions.
|
||||
*/
|
||||
export const createSetters = ( defaultTransient, defaultPersistent ) => {
|
||||
const setTransient = ( oldState, newValues = {} ) =>
|
||||
updateObject( oldState, newValues, defaultTransient );
|
||||
|
||||
const setPersistent = ( oldState, newValues = {} ) => ( {
|
||||
...oldState,
|
||||
data: updateObject( oldState.data, newValues, defaultPersistent ),
|
||||
} );
|
||||
|
||||
return [ setTransient, setPersistent ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a reducer function with predefined action handlers.
|
||||
*
|
||||
* @param {Object} defaultTransient Object defining initial transient values.
|
||||
* @param {Object} defaultPersistent Object defining initial persistent values.
|
||||
* @param {Object} handlers An object mapping action types to handler functions.
|
||||
* @return {Function} A reducer function.
|
||||
*/
|
||||
export const createReducer = (
|
||||
defaultTransient,
|
||||
defaultPersistent,
|
||||
handlers
|
||||
) => {
|
||||
if ( Object.hasOwnProperty.call( defaultTransient, 'data' ) ) {
|
||||
throw new Error(
|
||||
'The transient state cannot contain a "data" property.'
|
||||
);
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
...defaultTransient,
|
||||
data: defaultPersistent,
|
||||
};
|
||||
|
||||
return function reducer( state = initialState, action ) {
|
||||
if ( Object.hasOwnProperty.call( handlers, action.type ) ) {
|
||||
return handlers[ action.type ]( state, action.payload ?? {} );
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
};
|
42
modules/ppcp-settings/resources/js/utils/window.js
Normal file
42
modules/ppcp-settings/resources/js/utils/window.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Opens the provided URL, preferably in a popup window.
|
||||
*
|
||||
* Popups are usually only supported on desktop devices, when the browser is not in fullscreen mode.
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {Object} options
|
||||
* @param {string} options.name
|
||||
* @param {number} options.width
|
||||
* @param {number} options.height
|
||||
* @param {boolean} options.resizeable
|
||||
* @return {null|Window} Popup window instance, or null.
|
||||
*/
|
||||
export const openPopup = (
|
||||
url,
|
||||
{ name = '_blank', width = 450, height = 720, resizeable = false } = {}
|
||||
) => {
|
||||
width = Math.max( 100, Math.min( window.screen.width - 40, width ) );
|
||||
height = Math.max( 100, Math.min( window.screen.height - 40, height ) );
|
||||
|
||||
const left = ( window.screen.width - width ) / 2;
|
||||
const top = ( window.screen.height - height ) / 2;
|
||||
|
||||
const features = [
|
||||
`width=${ width }`,
|
||||
`height=${ height }`,
|
||||
`left=${ left }`,
|
||||
`top=${ top }`,
|
||||
`resizable=${ resizeable ? 'yes' : 'no' }`,
|
||||
`scrollbars=yes`,
|
||||
`status=no`,
|
||||
];
|
||||
|
||||
const popup = window.open( url, name, features.join( ',' ) );
|
||||
|
||||
if ( popup && ! popup.closed ) {
|
||||
popup.focus();
|
||||
return popup;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -9,11 +9,16 @@ declare( strict_types = 1 );
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'settings.url' => static function ( ContainerInterface $container ) : string {
|
||||
|
@ -44,9 +49,15 @@ return array(
|
|||
$can_use_card_payments
|
||||
);
|
||||
},
|
||||
'settings.data.common' => static function ( ContainerInterface $container ) : CommonSettings {
|
||||
return new CommonSettings();
|
||||
},
|
||||
'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint {
|
||||
return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) );
|
||||
},
|
||||
'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint {
|
||||
return new CommonRestEndpoint( $container->get( 'settings.data.common' ) );
|
||||
},
|
||||
'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint {
|
||||
return new ConnectManualRestEndpoint(
|
||||
$container->get( 'api.paypal-host-production' ),
|
||||
|
@ -54,6 +65,11 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
|
||||
return new LoginLinkRestEndpoint(
|
||||
$container->get( 'settings.service.connection-url-generators' ),
|
||||
);
|
||||
},
|
||||
'settings.casual-selling.supported-countries' => static function ( ContainerInterface $container ) : array {
|
||||
return array(
|
||||
'AR',
|
||||
|
@ -110,6 +126,35 @@ return array(
|
|||
|
||||
return in_array( $country, $eligible_countries, true );
|
||||
},
|
||||
'settings.service.signup-link-cache' => static function ( ContainerInterface $container ) : Cache {
|
||||
return new Cache( 'ppcp-paypal-signup-link' );
|
||||
},
|
||||
'settings.service.connection-url-generators' => static function ( ContainerInterface $container ) : array {
|
||||
// Define available environments.
|
||||
$environments = array(
|
||||
'production' => array(
|
||||
'partner_referrals' => $container->get( 'api.endpoint.partner-referrals-production' ),
|
||||
),
|
||||
'sandbox' => array(
|
||||
'partner_referrals' => $container->get( 'api.endpoint.partner-referrals-sandbox' ),
|
||||
),
|
||||
);
|
||||
|
||||
$generators = array();
|
||||
|
||||
// Instantiate URL generators for each environment.
|
||||
foreach ( $environments as $environment => $config ) {
|
||||
$generators[ $environment ] = new ConnectionUrlGenerator(
|
||||
$config['partner_referrals'],
|
||||
$container->get( 'api.repository.partner-referrals-data' ),
|
||||
$container->get( 'settings.service.signup-link-cache' ),
|
||||
$environment,
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
return $generators;
|
||||
},
|
||||
'settings.switch-ui.endpoint' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
|
||||
return new SwitchSettingsUiEndpoint(
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
|
|
119
modules/ppcp-settings/src/Data/CommonSettings.php
Normal file
119
modules/ppcp-settings/src/Data/CommonSettings.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
/**
|
||||
* Common settings class
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Data
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Data;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class CommonSettings
|
||||
*
|
||||
* This class serves as a container for managing the common settings that
|
||||
* are used and managed in various areas of the settings UI
|
||||
*
|
||||
* Those settings mainly describe connection details and are initially collected
|
||||
* in the onboarding wizard, and also appear in the settings screen.
|
||||
*/
|
||||
class CommonSettings extends AbstractDataModel {
|
||||
|
||||
/**
|
||||
* Option key where profile details are stored.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const OPTION_KEY = 'woocommerce-ppcp-data-common';
|
||||
|
||||
/**
|
||||
* Get default values for the model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_defaults() : array {
|
||||
return array(
|
||||
'use_sandbox' => false,
|
||||
'use_manual_connection' => false,
|
||||
'client_id' => '',
|
||||
'client_secret' => '',
|
||||
);
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
/**
|
||||
* Gets the 'use sandbox' setting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_sandbox() : bool {
|
||||
return (bool) $this->data['use_sandbox'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'use sandbox' setting.
|
||||
*
|
||||
* @param bool $use_sandbox Whether to use sandbox mode.
|
||||
*/
|
||||
public function set_sandbox( bool $use_sandbox ) : void {
|
||||
$this->data['use_sandbox'] = $use_sandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'use manual connection' setting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_manual_connection() : bool {
|
||||
return (bool) $this->data['use_manual_connection'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'use manual connection' setting.
|
||||
*
|
||||
* @param bool $use_manual_connection Whether to use manual connection.
|
||||
*/
|
||||
public function set_manual_connection( bool $use_manual_connection ) : void {
|
||||
$this->data['use_manual_connection'] = $use_manual_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_id() : string {
|
||||
return $this->data['client_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client ID.
|
||||
*
|
||||
* @param string $client_id The client ID.
|
||||
*/
|
||||
public function set_client_id( string $client_id ) : void {
|
||||
$this->data['client_id'] = sanitize_text_field( $client_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client secret.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_secret() : string {
|
||||
return $this->data['client_secret'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client secret.
|
||||
*
|
||||
* @param string $client_secret The client secret.
|
||||
*/
|
||||
public function set_client_secret( string $client_secret ) : void {
|
||||
$this->data['client_secret'] = sanitize_text_field( $client_secret );
|
||||
}
|
||||
}
|
|
@ -64,14 +64,10 @@ class OnboardingProfile extends AbstractDataModel {
|
|||
*/
|
||||
protected function get_defaults() : array {
|
||||
return array(
|
||||
'completed' => false,
|
||||
'step' => 0,
|
||||
'use_sandbox' => false,
|
||||
'use_manual_connection' => false,
|
||||
'client_id' => '',
|
||||
'client_secret' => '',
|
||||
'is_casual_seller' => null,
|
||||
'products' => array(),
|
||||
'completed' => false,
|
||||
'step' => 0,
|
||||
'is_casual_seller' => null,
|
||||
'products' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -113,78 +109,6 @@ class OnboardingProfile extends AbstractDataModel {
|
|||
$this->data['step'] = $step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'use sandbox' setting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_sandbox() : bool {
|
||||
return (bool) $this->data['use_sandbox'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'use sandbox' setting.
|
||||
*
|
||||
* @param bool $use_sandbox Whether to use sandbox mode.
|
||||
*/
|
||||
public function set_sandbox( bool $use_sandbox ) : void {
|
||||
$this->data['use_sandbox'] = $use_sandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the 'use manual connection' setting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_manual_connection() : bool {
|
||||
return (bool) $this->data['use_manual_connection'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'use manual connection' setting.
|
||||
*
|
||||
* @param bool $use_manual_connection Whether to use manual connection.
|
||||
*/
|
||||
public function set_manual_connection( bool $use_manual_connection ) : void {
|
||||
$this->data['use_manual_connection'] = $use_manual_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_id() : string {
|
||||
return $this->data['client_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client ID.
|
||||
*
|
||||
* @param string $client_id The client ID.
|
||||
*/
|
||||
public function set_client_id( string $client_id ) : void {
|
||||
$this->data['client_id'] = sanitize_text_field( $client_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the client secret.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_client_secret() : string {
|
||||
return $this->data['client_secret'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client secret.
|
||||
*
|
||||
* @param string $client_secret The client secret.
|
||||
*/
|
||||
public function set_client_secret( string $client_secret ) : void {
|
||||
$this->data['client_secret'] = sanitize_text_field( $client_secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the casual seller flag.
|
||||
*
|
||||
|
|
133
modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
Normal file
133
modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
/**
|
||||
* REST endpoint to manage the common module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Request;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
||||
|
||||
/**
|
||||
* REST controller for "common" settings, which are used and modified by
|
||||
* multiple components. Those settings mainly define connection details.
|
||||
*
|
||||
* This API acts as the intermediary between the "external world" and our
|
||||
* internal data model.
|
||||
*/
|
||||
class CommonRestEndpoint extends RestEndpoint {
|
||||
/**
|
||||
* The base path for this REST controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $rest_base = 'common';
|
||||
|
||||
/**
|
||||
* The settings instance.
|
||||
*
|
||||
* @var CommonSettings
|
||||
*/
|
||||
protected CommonSettings $settings;
|
||||
|
||||
/**
|
||||
* Field mapping for request to profile transformation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $field_map = array(
|
||||
'use_sandbox' => array(
|
||||
'js_name' => 'useSandbox',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'use_manual_connection' => array(
|
||||
'js_name' => 'useManualConnection',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'client_id' => array(
|
||||
'js_name' => 'clientId',
|
||||
'sanitize' => 'sanitize_text_field',
|
||||
),
|
||||
'client_secret' => array(
|
||||
'js_name' => 'clientSecret',
|
||||
'sanitize' => 'sanitize_text_field',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param CommonSettings $settings The settings instance.
|
||||
*/
|
||||
public function __construct( CommonSettings $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_details' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_details' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all common details from the DB.
|
||||
*
|
||||
* @return WP_REST_Response The common settings.
|
||||
*/
|
||||
public function get_details() : WP_REST_Response {
|
||||
$js_data = $this->sanitize_for_javascript(
|
||||
$this->settings->to_array(),
|
||||
$this->field_map
|
||||
);
|
||||
|
||||
return $this->return_success( $js_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates common details based on the request.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return WP_REST_Response The new common settings.
|
||||
*/
|
||||
public function update_details( WP_REST_Request $request ) : WP_REST_Response {
|
||||
$wp_data = $this->sanitize_for_wordpress(
|
||||
$request->get_params(),
|
||||
$this->field_map
|
||||
);
|
||||
|
||||
$this->settings->from_array( $wp_data );
|
||||
$this->settings->save();
|
||||
|
||||
return $this->get_details();
|
||||
}
|
||||
}
|
|
@ -78,9 +78,9 @@ class ConnectManualRestEndpoint extends RestEndpoint {
|
|||
/**
|
||||
* ConnectManualRestEndpoint constructor.
|
||||
*
|
||||
* @param string $live_host The API host for the live mode.
|
||||
* @param string $live_host The API host for the live mode.
|
||||
* @param string $sandbox_host The API host for the sandbox mode.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
string $live_host,
|
||||
|
@ -126,47 +126,38 @@ class ConnectManualRestEndpoint extends RestEndpoint {
|
|||
$use_sandbox = (bool) ( $data['use_sandbox'] ?? false );
|
||||
|
||||
if ( empty( $client_id ) || empty( $client_secret ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => false,
|
||||
'message' => 'No client ID or secret provided.',
|
||||
)
|
||||
);
|
||||
return $this->return_error( 'No client ID or secret provided.' );
|
||||
}
|
||||
|
||||
try {
|
||||
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
|
||||
} catch ( Exception $exception ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => false,
|
||||
'message' => $exception->getMessage(),
|
||||
)
|
||||
);
|
||||
|
||||
return $this->return_error( $exception->getMessage() );
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'merchantId' => $payee->merchant_id,
|
||||
'email' => $payee->email_address,
|
||||
'success' => true,
|
||||
return $this->return_success(
|
||||
array(
|
||||
'merchantId' => $payee->merchant_id,
|
||||
'email' => $payee->email_address,
|
||||
'success' => true,
|
||||
)
|
||||
);
|
||||
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the payee object with the merchant data
|
||||
* by creating a minimal PayPal order.
|
||||
*
|
||||
* @param string $client_id The client ID.
|
||||
* @param string $client_secret The client secret.
|
||||
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||
* @return stdClass The payee object.
|
||||
* @throws Exception When failed to retrieve payee.
|
||||
*
|
||||
* phpcs:disable Squiz.Commenting
|
||||
* phpcs:disable Generic.Commenting
|
||||
*
|
||||
* @param string $client_secret The client secret.
|
||||
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||
* @param string $client_id The client ID.
|
||||
*
|
||||
* @return stdClass The payee object.
|
||||
*/
|
||||
private function request_payee(
|
||||
string $client_id,
|
||||
|
@ -176,24 +167,13 @@ class ConnectManualRestEndpoint extends RestEndpoint {
|
|||
|
||||
$host = $use_sandbox ? $this->sandbox_host : $this->live_host;
|
||||
|
||||
$empty_settings = new class() implements ContainerInterface
|
||||
{
|
||||
public function get( string $id ) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
public function has( string $id ) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$bearer = new PayPalBearer(
|
||||
new InMemoryCache(),
|
||||
$host,
|
||||
$client_id,
|
||||
$client_secret,
|
||||
$this->logger,
|
||||
$empty_settings
|
||||
null
|
||||
);
|
||||
|
||||
$orders = new Orders(
|
||||
|
|
105
modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
Normal file
105
modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/**
|
||||
* REST endpoint to manage the onboarding module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Request;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||
|
||||
/**
|
||||
* REST controller that generates merchant login URLs.
|
||||
*/
|
||||
class LoginLinkRestEndpoint extends RestEndpoint {
|
||||
/**
|
||||
* The base path for this REST controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $rest_base = 'login_link';
|
||||
|
||||
/**
|
||||
* Link generator list, with environment name as array key.
|
||||
*
|
||||
* @var ConnectionUrlGenerator[]
|
||||
*/
|
||||
protected array $url_generators;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ConnectionUrlGenerator[] $url_generators Array of environment-specific URL generators.
|
||||
*/
|
||||
public function __construct( array $url_generators ) {
|
||||
$this->url_generators = $url_generators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'get_login_url' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
'args' => array(
|
||||
'environment' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
),
|
||||
'products' => array(
|
||||
'required' => true,
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'sanitize_callback' => function ( $products ) {
|
||||
return array_map( 'sanitize_text_field', $products );
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full login URL for the requested environment and products.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The login URL or an error response.
|
||||
*/
|
||||
public function get_login_url( WP_REST_Request $request ) : WP_REST_Response {
|
||||
$environment = $request->get_param( 'environment' );
|
||||
$products = $request->get_param( 'products' );
|
||||
|
||||
if ( ! isset( $this->url_generators[ $environment ] ) ) {
|
||||
return new WP_REST_Response(
|
||||
array( 'error' => 'Invalid environment specified.' ),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$url_generator = $this->url_generators[ $environment ];
|
||||
|
||||
try {
|
||||
$url = $url_generator->generate( $products );
|
||||
|
||||
return $this->return_success( $url );
|
||||
} catch ( \Exception $e ) {
|
||||
return $this->return_error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,35 +41,19 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
|||
* @var array
|
||||
*/
|
||||
private array $field_map = array(
|
||||
'completed' => array(
|
||||
'completed' => array(
|
||||
'js_name' => 'completed',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'step' => array(
|
||||
'step' => array(
|
||||
'js_name' => 'step',
|
||||
'sanitize' => 'to_number',
|
||||
),
|
||||
'use_sandbox' => array(
|
||||
'js_name' => 'useSandbox',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'use_manual_connection' => array(
|
||||
'js_name' => 'useManualConnection',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'client_id' => array(
|
||||
'js_name' => 'clientId',
|
||||
'sanitize' => 'sanitize_text_field',
|
||||
),
|
||||
'client_secret' => array(
|
||||
'js_name' => 'clientSecret',
|
||||
'sanitize' => 'sanitize_text_field',
|
||||
),
|
||||
'is_casual_seller' => array(
|
||||
'is_casual_seller' => array(
|
||||
'js_name' => 'isCasualSeller',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'products' => array(
|
||||
'products' => array(
|
||||
'js_name' => 'products',
|
||||
),
|
||||
);
|
||||
|
@ -147,9 +131,9 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
|||
$this->flag_map
|
||||
);
|
||||
|
||||
return rest_ensure_response(
|
||||
return $this->return_success(
|
||||
$js_data,
|
||||
array(
|
||||
'data' => $js_data,
|
||||
'flags' => $js_flags,
|
||||
)
|
||||
);
|
||||
|
|
|
@ -10,14 +10,12 @@ declare( strict_types = 1 );
|
|||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use WC_REST_Controller;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Base class for REST controllers in the settings module.
|
||||
*
|
||||
* This is a base class for specific REST endpoints; do not instantiate this
|
||||
* class directly.
|
||||
*/
|
||||
class RestEndpoint extends WC_REST_Controller {
|
||||
abstract class RestEndpoint extends WC_REST_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
|
@ -34,6 +32,54 @@ class RestEndpoint extends WC_REST_Controller {
|
|||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a successful REST API response.
|
||||
*
|
||||
* @param mixed $data The main response data.
|
||||
* @param array $extra Optional, additional response data.
|
||||
*
|
||||
* @return WP_REST_Response The successful response.
|
||||
*/
|
||||
protected function return_success( $data, array $extra = array() ) : WP_REST_Response {
|
||||
$response = array(
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
);
|
||||
|
||||
if ( $extra ) {
|
||||
foreach ( $extra as $key => $value ) {
|
||||
if ( isset( $response[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error REST API response.
|
||||
*
|
||||
* @param string $reason The reason for the error.
|
||||
* @param mixed $details Optional details about the error.
|
||||
*
|
||||
* @return WP_REST_Response The error response.
|
||||
*/
|
||||
protected function return_error( string $reason, $details = null ) : WP_REST_Response {
|
||||
$response = array(
|
||||
'success' => false,
|
||||
'message' => $reason,
|
||||
);
|
||||
|
||||
if ( ! is_null( $details ) ) {
|
||||
$response['details'] = $details;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes parameters based on a field mapping.
|
||||
*
|
||||
|
|
|
@ -15,6 +15,8 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
|||
|
||||
/**
|
||||
* Class SwitchSettingsUiEndpoint
|
||||
*
|
||||
* Note: This is an ajax handler, not a REST endpoint
|
||||
*/
|
||||
class SwitchSettingsUiEndpoint {
|
||||
|
||||
|
|
227
modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
Normal file
227
modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* Generator service to build URLs to sign in to a PayPal account.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
|
||||
/**
|
||||
* Generator that builds the ISU connection URL.
|
||||
*/
|
||||
class ConnectionUrlGenerator {
|
||||
/**
|
||||
* The partner referrals endpoint.
|
||||
*
|
||||
* @var PartnerReferrals
|
||||
*/
|
||||
protected PartnerReferrals $partner_referrals;
|
||||
|
||||
/**
|
||||
* The default partner referrals data.
|
||||
*
|
||||
* @var PartnerReferralsData
|
||||
*/
|
||||
protected PartnerReferralsData $referrals_data;
|
||||
|
||||
/**
|
||||
* The cache
|
||||
*
|
||||
* @var Cache
|
||||
*/
|
||||
protected Cache $cache;
|
||||
|
||||
/**
|
||||
* Which environment is used for the connection URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $environment = '';
|
||||
|
||||
/**
|
||||
* The logger
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Constructor for the ConnectionUrlGenerator class.
|
||||
*
|
||||
* Initializes the cache and logger properties of the class.
|
||||
*
|
||||
* @param PartnerReferrals $partner_referrals PartnerReferrals for URL generation.
|
||||
* @param PartnerReferralsData $referrals_data Default partner referrals data.
|
||||
* @param Cache $cache The cache object used for storing and
|
||||
* retrieving data.
|
||||
* @param string $environment Environment that is used to generate the URL.
|
||||
* ['production'|'sandbox'].
|
||||
* @param ?LoggerInterface $logger The logger object for logging messages.
|
||||
*/
|
||||
public function __construct(
|
||||
PartnerReferrals $partner_referrals,
|
||||
PartnerReferralsData $referrals_data,
|
||||
Cache $cache,
|
||||
string $environment,
|
||||
?LoggerInterface $logger = null
|
||||
) {
|
||||
$this->partner_referrals = $partner_referrals;
|
||||
$this->referrals_data = $referrals_data;
|
||||
$this->cache = $cache;
|
||||
$this->environment = $environment;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the environment for which the URL is being generated.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function environment() : string {
|
||||
return $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PayPal onboarding URL for merchant sign-up.
|
||||
*
|
||||
* This function creates a URL for merchants to sign up for PayPal services.
|
||||
* It handles caching of the URL, generation of new URLs when necessary,
|
||||
* and works for both production and sandbox environments.
|
||||
*
|
||||
* @param array $products An array of product identifiers to include in the sign-up process.
|
||||
* These determine the PayPal onboarding experience.
|
||||
*
|
||||
* @return string The generated PayPal onboarding URL.
|
||||
*/
|
||||
public function generate( array $products = array() ) : string {
|
||||
$cache_key = $this->cache_key( $products );
|
||||
$user_id = get_current_user_id();
|
||||
$onboarding_url = new OnboardingUrl( $this->cache, $cache_key, $user_id );
|
||||
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
|
||||
|
||||
if ( $cached_url ) {
|
||||
$this->logger->info( 'Using cached onboarding URL for: ' . $cache_key );
|
||||
|
||||
return $cached_url;
|
||||
}
|
||||
|
||||
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
||||
|
||||
$url = $this->generate_new_url( $products, $onboarding_url, $cache_key );
|
||||
|
||||
if ( $url ) {
|
||||
$this->persist_url( $onboarding_url, $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cache key from the environment and sorted product array.
|
||||
*
|
||||
* @param array $products Product identifiers that are part of the cache key.
|
||||
*
|
||||
* @return string The cache key, defining the product list and environment.
|
||||
*/
|
||||
protected function cache_key( array $products = array() ) : string {
|
||||
// Sort products alphabetically, to improve cache implementation.
|
||||
sort( $products );
|
||||
|
||||
return $this->environment() . '-' . implode( '-', $products );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load the URL from cache.
|
||||
*
|
||||
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
||||
* @param string $cache_key The cache key.
|
||||
*
|
||||
* @return string The cached URL, or an empty string if no URL is found.
|
||||
*/
|
||||
protected function try_get_from_cache( OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
||||
try {
|
||||
if ( $onboarding_url->load() ) {
|
||||
$this->logger->debug( 'Loaded onboarding URL from cache: ' . $cache_key );
|
||||
|
||||
return $onboarding_url->get();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// No problem, return an empty string to generate a new URL.
|
||||
$this->logger->warning( 'Failed to load onboarding URL from cache: ' . $cache_key );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new URL.
|
||||
*
|
||||
* @param array $products The products array.
|
||||
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
||||
* @param string $cache_key The cache key.
|
||||
*
|
||||
* @return string The generated URL or an empty string on failure.
|
||||
*/
|
||||
protected function generate_new_url( array $products, OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
||||
$query_args = array( 'displayMode' => 'minibrowser' );
|
||||
$onboarding_url->init();
|
||||
|
||||
try {
|
||||
$onboarding_token = $onboarding_url->token();
|
||||
} catch ( Exception $e ) {
|
||||
$this->logger->warning( 'Could not generate an onboarding token for: ' . $cache_key );
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$data = $this->prepare_referral_data( $products, $onboarding_token );
|
||||
|
||||
try {
|
||||
$url = $this->partner_referrals->signup_link( $data );
|
||||
} catch ( Exception $e ) {
|
||||
$this->logger->warning( 'Could not generate an onboarding URL for: ' . $cache_key );
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return add_query_arg( $query_args, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the referral data.
|
||||
*
|
||||
* @param array $products The products array.
|
||||
* @param string $onboarding_token The onboarding token.
|
||||
*
|
||||
* @return array The prepared referral data.
|
||||
*/
|
||||
protected function prepare_referral_data( array $products, string $onboarding_token ) : array {
|
||||
$data = $this->referrals_data
|
||||
->with_products( $products )
|
||||
->data();
|
||||
|
||||
return $this->referrals_data->append_onboarding_token( $data, $onboarding_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the generated URL.
|
||||
*
|
||||
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
||||
* @param string $url The URL to persist.
|
||||
*/
|
||||
protected function persist_url( OnboardingUrl $onboarding_url, string $url ) : void {
|
||||
$onboarding_url->set( $url );
|
||||
$onboarding_url->persist();
|
||||
}
|
||||
}
|
|
@ -9,8 +9,7 @@ declare( strict_types = 1 );
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
|
@ -26,7 +25,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
/**
|
||||
* Returns whether the old settings UI should be loaded.
|
||||
*/
|
||||
public static function should_use_the_old_ui(): bool {
|
||||
public static function should_use_the_old_ui() : bool {
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_should_use_the_old_ui',
|
||||
(bool) get_option( SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI ) === true
|
||||
|
@ -89,7 +88,13 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
$endpoint = $container->get( 'settings.switch-ui.endpoint' );
|
||||
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
||||
|
||||
add_action( 'wc_ajax_' . SwitchSettingsUiEndpoint::ENDPOINT, array( $endpoint, 'handle_request' ) );
|
||||
add_action(
|
||||
'wc_ajax_' . SwitchSettingsUiEndpoint::ENDPOINT,
|
||||
array(
|
||||
$endpoint,
|
||||
'handle_request',
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -170,13 +175,17 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
add_action(
|
||||
'rest_api_init',
|
||||
static function () use ( $container ) : void {
|
||||
$onboarding_endpoint = $container->get( 'settings.rest.onboarding' );
|
||||
assert( $onboarding_endpoint instanceof OnboardingRestEndpoint );
|
||||
$onboarding_endpoint->register_routes();
|
||||
$endpoints = array(
|
||||
$container->get( 'settings.rest.onboarding' ),
|
||||
$container->get( 'settings.rest.common' ),
|
||||
$container->get( 'settings.rest.connect_manual' ),
|
||||
$container->get( 'settings.rest.login_link' ),
|
||||
);
|
||||
|
||||
$connect_manual_endpoint = $container->get( 'settings.rest.connect_manual' );
|
||||
assert( $connect_manual_endpoint instanceof ConnectManualRestEndpoint );
|
||||
$connect_manual_endpoint->register_routes();
|
||||
foreach ( $endpoints as $endpoint ) {
|
||||
assert( $endpoint instanceof RestEndpoint );
|
||||
$endpoint->register_routes();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
27
yarn.lock
27
yarn.lock
|
@ -2671,9 +2671,9 @@
|
|||
mime "^3.0.0"
|
||||
web-vitals "^4.2.1"
|
||||
|
||||
"@wordpress/element@^6.1.0":
|
||||
"@wordpress/element@*", "@wordpress/element@^6.1.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.npmjs.org/@wordpress/element/-/element-6.11.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/@wordpress/element/-/element-6.11.0.tgz#7bc3e453a95bb806a707b4dc617373afa108af19"
|
||||
integrity sha512-UvHFYkT+EEaXEyEfw+iqLHRO9OwBjjsUydEMHcqntzkNcsYeAbmaL9V8R9ikXHLe6ftdbkwoXgF85xVPhVsL+Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.25.7"
|
||||
|
@ -2715,6 +2715,15 @@
|
|||
globals "^13.12.0"
|
||||
requireindex "^1.2.0"
|
||||
|
||||
"@wordpress/icons@^10.11.0":
|
||||
version "10.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@wordpress/icons/-/icons-10.11.0.tgz#0beedef8ee49c135412fb81fc59440dd48d652aa"
|
||||
integrity sha512-RMetpFwUIeh3sVj2+p6+QX5AW8pF7DvQzxH9jUr8YjaF2iLE64vy6m0cZz/X8xkSktHrXMuPJIr7YIVF20TEyw==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.25.7"
|
||||
"@wordpress/element" "*"
|
||||
"@wordpress/primitives" "*"
|
||||
|
||||
"@wordpress/jest-console@*":
|
||||
version "8.11.0"
|
||||
resolved "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.11.0.tgz"
|
||||
|
@ -2749,6 +2758,15 @@
|
|||
resolved "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.11.0.tgz"
|
||||
integrity sha512-Aoc8+xWOyiXekodjaEjS44z85XK877LzHZqsQuhC0kNgneDLrKkwI5qNgzwzAMbJ9jI58MPqVISCOX0bDLUPbw==
|
||||
|
||||
"@wordpress/primitives@*":
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@wordpress/primitives/-/primitives-4.11.0.tgz#7bc24c07ed11057340832791c1c21e75a5181194"
|
||||
integrity sha512-CoBXbh0mOSxcZtuzL7gK3RVumFx71DXQBfd3IkbRHuuVxa+2hI4KDuFyomSsbjQDshHsfuVrKUvuT3UGt6pdpQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "7.25.7"
|
||||
"@wordpress/element" "*"
|
||||
clsx "^2.1.1"
|
||||
|
||||
"@wordpress/scripts@~30.0.0":
|
||||
version "30.0.6"
|
||||
resolved "https://registry.npmjs.org/@wordpress/scripts/-/scripts-30.0.6.tgz"
|
||||
|
@ -3727,6 +3745,11 @@ clone-deep@^4.0.1:
|
|||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue