mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +08:00
Merge remote-tracking branch 'origin/trunk' into PCP-3896-dynamic-content-for-prices-in-onboarding-wizard
# Conflicts: # modules/ppcp-settings/resources/js/Components/ReusableComponents/WelcomeDocs/AcdcFlow.js # modules/ppcp-settings/resources/js/Components/ReusableComponents/WelcomeDocs/BcdcFlow.js # modules/ppcp-settings/resources/js/Components/ReusableComponents/WelcomeDocs/WelcomeDocs.js
This commit is contained in:
commit
d0ba54e1c9
64 changed files with 2716 additions and 972 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 );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-image-badge {
|
||||
.ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge {
|
||||
margin-left: 7px;
|
||||
|
||||
img {
|
||||
|
|
|
@ -8,16 +8,16 @@ button.components-button, a.components-button {
|
|||
color: $color-white;
|
||||
}
|
||||
|
||||
border-radius: 2px;
|
||||
padding: 14px 17px;
|
||||
border-radius: 50px;
|
||||
padding: 15px 32px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.is-primary {
|
||||
@include font(13, 20, 400);
|
||||
@include font(14, 18, 900);
|
||||
|
||||
&:not(:disabled) {
|
||||
background-color: $color-blueberry;
|
||||
background-color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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-400;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&__space {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: $color-gray;
|
||||
@include font(12, 24, 500, 0.8px);
|
||||
|
|
|
@ -7,16 +7,6 @@
|
|||
margin: 0 0 32px 0;
|
||||
}
|
||||
|
||||
&__description {
|
||||
text-align: center;
|
||||
@include font(14, 22, 400);
|
||||
font-style: italic;
|
||||
|
||||
a {
|
||||
color: $color-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
padding: 8px;
|
||||
margin: 0 0 48px 0;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@import './onboarding/step-welcome';
|
||||
@import './onboarding/step-business';
|
||||
@import './onboarding/step-products';
|
||||
@import './onboarding/step-payment-methods';
|
||||
|
||||
.ppcp-r-tabs.onboarding,
|
||||
.ppcp-r-container--onboarding {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
.ppcp-r-page-optional-payment-methods {
|
||||
.ppcp-r-select-box:first-child {
|
||||
.ppcp-r-select-box__title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-optional-payment-methods {
|
||||
&__wrapper {
|
||||
.ppcp-r-badge-box {
|
||||
margin: 0 0 24px 0;
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-r-badge-box__description {
|
||||
margin: 12px 0 0 0;
|
||||
@include font(14, 20, 400);
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin: 32px 0 0 0;
|
||||
text-align: center;
|
||||
@include font(14, 22, 400);
|
||||
font-style: italic;
|
||||
|
||||
a {
|
||||
color: $color-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
}
|
|
@ -16,21 +16,10 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.ppcp-r-page-welcome-or-separator {
|
||||
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;
|
||||
}
|
||||
|
||||
.ppcp-r-toggle-block__toggled-content > button{
|
||||
@include small-button;
|
||||
color: $color-white;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
import BadgeBox, { BADGE_BOX_TITLE_BIG } from '../BadgeBox';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import Separator from '../Separator';
|
||||
|
||||
const AcdcOptionalPaymentMethods = ( {
|
||||
isFastlane,
|
||||
isPayLater,
|
||||
storeCountry,
|
||||
} ) => {
|
||||
if ( isFastlane && isPayLater && storeCountry === 'us' ) {
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods__wrapper">
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Custom Card Fields',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-visa.svg',
|
||||
'icon-button-mastercard.svg',
|
||||
'icon-button-amex.svg',
|
||||
'icon-button-discover.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 2.59% + $0.49 USD<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Digital Wallets',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-apple-pay.svg',
|
||||
'icon-button-google-pay.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 2.59% + $0.49 USD<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Alternative Payment Methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-sepa.svg',
|
||||
'icon-button-ideal.svg',
|
||||
'icon-button-blik.svg',
|
||||
'icon-button-bancontact.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 3.49% + $0.49 USD<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __( '', 'woocommerce-paypal-payments' ) }
|
||||
imageBadge={ [ 'icon-payment-method-fastlane-small.svg' ] }
|
||||
textBadge={ __(
|
||||
'from 2.59% + $0.49 USD<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Speed up guest checkout with Fatslane. Link a customer\'s email address to their payment details. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ( isPayLater && storeCountry === 'uk' ) {
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods__wrapper">
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Custom Card Fields',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-visa.svg',
|
||||
'icon-button-mastercard.svg',
|
||||
'icon-button-amex.svg',
|
||||
'icon-button-discover.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 1.20% + £0.30 GBP<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Digital Wallets',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-apple-pay.svg',
|
||||
'icon-button-google-pay.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 1.20% + £0.30 GBP<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Alternative Payment Methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-sepa.svg',
|
||||
'icon-button-ideal.svg',
|
||||
'icon-button-blik.svg',
|
||||
'icon-button-bancontact.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 1.20% + £0.30 GBP<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods__wrapper">
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Optional payment methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
titleType={ BADGE_BOX_TITLE_BIG }
|
||||
description={ __(
|
||||
'with additional application',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
/>
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Custom Card Fields',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-visa.svg',
|
||||
'icon-button-mastercard.svg',
|
||||
'icon-button-amex.svg',
|
||||
'icon-button-discover.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 3.40% + €0.35 EUR<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Style the credit card fields to match your own style. Includes advanced processing with risk management, 3D Secure, fraud protection options, and chargeback protection. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __( 'Digital Wallets', 'woocommerce-paypal-payments' ) }
|
||||
imageBadge={ [
|
||||
'icon-button-apple-pay.svg',
|
||||
'icon-button-google-pay.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 3.40% + €0.35 EUR<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Accept Apple Pay on eligible devices and Google Pay through mobile and web. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
<Separator className="ppcp-r-optional-payment-methods__separator" />
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Alternative Payment Methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-sepa.svg',
|
||||
'icon-button-ideal.svg',
|
||||
'icon-button-blik.svg',
|
||||
'icon-button-bancontact.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 3.40% + €0.35 EUR<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Seamless payments for customers across the globe using their preferred payment methods. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AcdcOptionalPaymentMethods;
|
|
@ -0,0 +1,66 @@
|
|||
import BadgeBox from '../BadgeBox';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
const BcdcOptionalPaymentMethods = ( { isPayLater, storeCountry } ) => {
|
||||
if ( isPayLater && storeCountry === 'us' ) {
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods__wrapper">
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Credit and Debit Cards',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-visa.svg',
|
||||
'icon-button-mastercard.svg',
|
||||
'icon-button-amex.svg',
|
||||
'icon-button-discover.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 2.59% + $0.49 USD<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Process major credit and debit cards through PayPal’s card fields. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods__wrapper">
|
||||
<BadgeBox
|
||||
title={ __(
|
||||
'Credit and Debit Cards',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-visa.svg',
|
||||
'icon-button-mastercard.svg',
|
||||
'icon-button-amex.svg',
|
||||
'icon-button-discover.svg',
|
||||
] }
|
||||
textBadge={ __(
|
||||
'from 3.40% + €0.35 EUR<sup>1</sup>',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'Process major credit and debit cards through PayPal’s card fields. <a target="_blank" href="%s">Learn more</a>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BcdcOptionalPaymentMethods;
|
|
@ -0,0 +1,28 @@
|
|||
import AcdcOptionalPaymentMethods from './AcdcOptionalPaymentMethods';
|
||||
import BcdcOptionalPaymentMethods from './BcdcOptionalPaymentMethods';
|
||||
|
||||
const OptionalPaymentMethods = ( {
|
||||
useAcdc,
|
||||
isFastlane,
|
||||
isPayLater,
|
||||
storeCountry,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="ppcp-r-optional-payment-methods">
|
||||
{ useAcdc ? (
|
||||
<AcdcOptionalPaymentMethods
|
||||
isFastlane={ isFastlane }
|
||||
isPayLater={ isPayLater }
|
||||
storeCountry={ storeCountry }
|
||||
/>
|
||||
) : (
|
||||
<BcdcOptionalPaymentMethods
|
||||
isPayLater={ isPayLater }
|
||||
storeCountry={ storeCountry }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OptionalPaymentMethods;
|
|
@ -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,31 +1,23 @@
|
|||
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={ () => (
|
||||
<svg
|
||||
width="24"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.5001 4.5H12.5001V6H16.9394L10.9697 11.9697L12.0304 13.0303L18.0001 7.06066V11.5H19.5001V4.5Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M6.5 5.5C5.39543 5.5 4.5 6.39543 4.5 7.5V17.5C4.5 18.6046 5.39543 19.5 6.5 19.5H16.5C17.6046 19.5 18.5 18.6046 18.5 17.5V14.5H17V17.5C17 17.7761 16.7761 18 16.5 18H6.5C6.22386 18 6 17.7761 6 17.5V7.5C6 7.22386 6.22386 7 6.5 7H9.5V5.5H6.5Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M12.4999 12.75V18.75C12.4999 18.9489 12.4209 19.1397 12.2803 19.2803C12.1396 19.421 11.9488 19.5 11.7499 19.5C11.551 19.5 11.3603 19.421 11.2196 19.2803C11.0789 19.1397 10.9999 18.9489 10.9999 18.75V14.5613L4.78055 20.7806C4.71087 20.8503 4.62815 20.9056 4.5371 20.9433C4.44606 20.981 4.34847 21.0004 4.24993 21.0004C4.15138 21.0004 4.0538 20.981 3.96276 20.9433C3.87171 20.9056 3.78899 20.8503 3.7193 20.7806C3.64962 20.7109 3.59435 20.6282 3.55663 20.5372C3.51892 20.4461 3.49951 20.3485 3.49951 20.25C3.49951 20.1515 3.51892 20.0539 3.55663 19.9628C3.59435 19.8718 3.64962 19.7891 3.7193 19.7194L9.93868 13.5H5.74993C5.55102 13.5 5.36025 13.421 5.2196 13.2803C5.07895 13.1397 4.99993 12.9489 4.99993 12.75C4.99993 12.5511 5.07895 12.3603 5.2196 12.2197C5.36025 12.079 5.55102 12 5.74993 12H11.7499C11.9488 12 12.1396 12.079 12.2803 12.2197C12.4209 12.3603 12.4999 12.5511 12.4999 12.75ZM19.9999 3H7.99993C7.6021 3 7.22057 3.15804 6.93927 3.43934C6.65796 3.72064 6.49993 4.10218 6.49993 4.5V9C6.49993 9.19891 6.57895 9.38968 6.7196 9.53033C6.86025 9.67098 7.05102 9.75 7.24993 9.75C7.44884 9.75 7.63961 9.67098 7.78026 9.53033C7.92091 9.38968 7.99993 9.19891 7.99993 9V4.5H19.9999V16.5H15.4999C15.301 16.5 15.1103 16.579 14.9696 16.7197C14.8289 16.8603 14.7499 17.0511 14.7499 17.25C14.7499 17.4489 14.8289 17.6397 14.9696 17.7803C15.1103 17.921 15.301 18 15.4999 18H19.9999C20.3978 18 20.7793 17.842 21.0606 17.5607C21.3419 17.2794 21.4999 16.8978 21.4999 16.5V4.5C21.4999 4.10218 21.3419 3.72064 21.0606 3.43934C20.7793 3.15804 20.3978 3 19.9999 3Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||
import SelectBoxWrapper from '../../ReusableComponents/SelectBoxWrapper';
|
||||
import SelectBox from '../../ReusableComponents/SelectBox';
|
||||
import { OnboardingHooks } from '../../../data';
|
||||
import OptionalPaymentMethods from '../../ReusableComponents/OptionalPaymentMethods/OptionalPaymentMethods';
|
||||
|
||||
const OPM_RADIO_GROUP_NAME = 'optional-payment-methods';
|
||||
|
||||
const StepPaymentMethods = ( {} ) => {
|
||||
const {
|
||||
areOptionalPaymentMethodsEnabled,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
} = OnboardingHooks.useOptionalPaymentMethods();
|
||||
const pricesBasedDescription = sprintf(
|
||||
// translators: %s: Link to PayPal REST application guide
|
||||
__(
|
||||
'<sup>1</sup>Prices based on domestic transactions as of October 25th, 2024. <a target="_blank" href="%s">Click here</a> for full pricing details.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-page-optional-payment-methods">
|
||||
<OnboardingHeader
|
||||
title={ __(
|
||||
'Add optional payment methods to your Checkout',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
/>
|
||||
<div className="ppcp-r-inner-container">
|
||||
<SelectBoxWrapper>
|
||||
<SelectBox
|
||||
title={ __(
|
||||
'Available with additional application',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={
|
||||
<OptionalPaymentMethods
|
||||
useAcdc={ true }
|
||||
isFastlane={ true }
|
||||
isPayLater={ true }
|
||||
storeCountry={ 'us' }
|
||||
storeCurrency={ 'usd' }
|
||||
/>
|
||||
}
|
||||
name={ OPM_RADIO_GROUP_NAME }
|
||||
value={ true }
|
||||
changeCallback={ setAreOptionalPaymentMethodsEnabled }
|
||||
currentValue={ areOptionalPaymentMethodsEnabled }
|
||||
type="radio"
|
||||
></SelectBox>
|
||||
<SelectBox
|
||||
title={ __(
|
||||
'No thanks, I prefer to use a different provider for processing credit cards, digital wallets, and local payment methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
name={ OPM_RADIO_GROUP_NAME }
|
||||
value={ false }
|
||||
changeCallback={ setAreOptionalPaymentMethodsEnabled }
|
||||
currentValue={ areOptionalPaymentMethodsEnabled }
|
||||
type="radio"
|
||||
></SelectBox>
|
||||
</SelectBoxWrapper>
|
||||
<p
|
||||
className="ppcp-r-optional-payment-methods__description"
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: pricesBasedDescription,
|
||||
} }
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepPaymentMethods;
|
|
@ -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,6 +1,7 @@
|
|||
import StepWelcome from './StepWelcome';
|
||||
import StepBusiness from './StepBusiness';
|
||||
import StepProducts from './StepProducts';
|
||||
import StepPaymentMethods from './StepPaymentMethods';
|
||||
import StepCompleteSetup from './StepCompleteSetup';
|
||||
|
||||
export const getSteps = ( flags ) => {
|
||||
|
@ -8,6 +9,7 @@ export const getSteps = ( flags ) => {
|
|||
StepWelcome,
|
||||
StepBusiness,
|
||||
StepProducts,
|
||||
StepPaymentMethods,
|
||||
StepCompleteSetup,
|
||||
];
|
||||
|
||||
|
|
|
@ -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,116 @@
|
|||
/**
|
||||
* 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 "areOptionalPaymentMethodsEnabled" value.
|
||||
*
|
||||
* @param {boolean} areOptionalPaymentMethodsEnabled
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setAreOptionalPaymentMethodsEnabled = (
|
||||
areOptionalPaymentMethodsEnabled
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { areOptionalPaymentMethodsEnabled },
|
||||
} );
|
||||
|
||||
/**
|
||||
* 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,115 @@
|
|||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { NAMESPACE, PRODUCT_TYPES, STORE_NAME } from '../constants';
|
||||
import { getFlags } from './selectors';
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const useOnboardingDetails = () => {
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
import { PRODUCT_TYPES } from '../constants';
|
||||
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,
|
||||
setOnboardingStep,
|
||||
setStep,
|
||||
setCompleted,
|
||||
setSandboxMode,
|
||||
setManualConnectionMode,
|
||||
setClientId,
|
||||
setClientSecret,
|
||||
setIsCasualSeller,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
setProducts,
|
||||
} = useDispatch( STORE_NAME );
|
||||
|
||||
// Transient accessors.
|
||||
const isSaving = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isSaving;
|
||||
}, [] );
|
||||
|
||||
const isReady = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isReady;
|
||||
} );
|
||||
|
||||
const isManualConnectionBusy = useSelect( ( select ) => {
|
||||
return select( STORE_NAME ).getTransientData().isManualConnectionBusy;
|
||||
}, [] );
|
||||
|
||||
// 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 areOptionalPaymentMethodsEnabled = usePersistent(
|
||||
'areOptionalPaymentMethodsEnabled'
|
||||
);
|
||||
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 );
|
||||
},
|
||||
areOptionalPaymentMethodsEnabled,
|
||||
setAreOptionalPaymentMethodsEnabled: ( value ) => {
|
||||
return savePersistent( setAreOptionalPaymentMethodsEnabled, 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();
|
||||
|
||||
return { isReady, step, setStep, completed, setCompleted, flags };
|
||||
};
|
||||
|
||||
export const useManualConnect = () => {
|
||||
const { connectViaIdAndSecret } = useDispatch( STORE_NAME );
|
||||
export const useOptionalPaymentMethods = () => {
|
||||
const {
|
||||
areOptionalPaymentMethodsEnabled,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
} = useHooks();
|
||||
|
||||
return {
|
||||
connectManual: connectViaIdAndSecret,
|
||||
areOptionalPaymentMethodsEnabled,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSteps = () => {
|
||||
const { flags, isReady, step, setStep, completed, setCompleted } =
|
||||
useHooks();
|
||||
|
||||
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,41 @@ 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.
|
||||
areOptionalPaymentMethodsEnabled: true,
|
||||
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,17 @@ 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\GeneralSettings;
|
||||
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,14 +50,29 @@ return array(
|
|||
$can_use_card_payments
|
||||
);
|
||||
},
|
||||
'settings.data.general' => static function ( ContainerInterface $container ) : GeneralSettings {
|
||||
return new GeneralSettings();
|
||||
},
|
||||
'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' ),
|
||||
$container->get( 'api.paypal-host-sandbox' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
$container->get( 'settings.data.general' )
|
||||
);
|
||||
},
|
||||
'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 {
|
||||
|
@ -110,6 +131,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,11 @@ 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,
|
||||
'are_optional_payment_methods_enabled' => true,
|
||||
'products' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -113,78 +110,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.
|
||||
*
|
||||
|
@ -203,6 +128,15 @@ class OnboardingProfile extends AbstractDataModel {
|
|||
$this->data['is_casual_seller'] = $casual_seller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional payment methods flag.
|
||||
*
|
||||
* @param bool|null $are_optional_payment_methods_enabled Whether the PayPal optional payment methods are enabled.
|
||||
*/
|
||||
public function set_are_optional_payment_methods_enabled( ?bool $are_optional_payment_methods_enabled ) : void {
|
||||
$this->data['are_optional_payment_methods_enabled'] = $are_optional_payment_methods_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active product types for this store.
|
||||
*
|
||||
|
|
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 $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();
|
||||
}
|
||||
}
|
|
@ -10,17 +10,16 @@ declare( strict_types = 1 );
|
|||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Request;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||
|
||||
/**
|
||||
* REST controller for connection via manual credentials input.
|
||||
|
@ -55,6 +54,13 @@ class ConnectManualRestEndpoint extends RestEndpoint {
|
|||
*/
|
||||
protected $rest_base = 'connect_manual';
|
||||
|
||||
/**
|
||||
* Settings instance.
|
||||
*
|
||||
* @var GeneralSettings
|
||||
*/
|
||||
private $settings = null;
|
||||
|
||||
/**
|
||||
* Field mapping for request.
|
||||
*
|
||||
|
@ -78,19 +84,21 @@ 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.
|
||||
* @param GeneralSettings $settings Settings instance.
|
||||
*/
|
||||
public function __construct(
|
||||
string $live_host,
|
||||
string $sandbox_host,
|
||||
LoggerInterface $logger
|
||||
LoggerInterface $logger,
|
||||
GeneralSettings $settings
|
||||
) {
|
||||
|
||||
$this->live_host = $live_host;
|
||||
$this->sandbox_host = $sandbox_host;
|
||||
$this->logger = $logger;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,47 +134,52 @@ 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,
|
||||
);
|
||||
if ( $use_sandbox ) {
|
||||
$this->settings->set_is_sandbox( true );
|
||||
$this->settings->set_sandbox_client_id( $client_id );
|
||||
$this->settings->set_sandbox_client_secret( $client_secret );
|
||||
$this->settings->set_sandbox_merchant_id( $payee->merchant_id );
|
||||
$this->settings->set_sandbox_merchant_email( $payee->email_address );
|
||||
} else {
|
||||
$this->settings->set_is_sandbox( false );
|
||||
$this->settings->set_live_client_id( $client_id );
|
||||
$this->settings->set_live_client_secret( $client_secret );
|
||||
$this->settings->set_live_merchant_id( $payee->merchant_id );
|
||||
$this->settings->set_live_merchant_email( $payee->email_address );
|
||||
}
|
||||
$this->settings->save();
|
||||
|
||||
return rest_ensure_response( $result );
|
||||
return $this->return_success(
|
||||
array(
|
||||
'merchantId' => $payee->merchant_id,
|
||||
'email' => $payee->email_address,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +189,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 $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,23 @@ 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(
|
||||
'are_optional_payment_methods_enabled' => array(
|
||||
'js_name' => 'areOptionalPaymentMethodsEnabled',
|
||||
'sanitize' => 'to_boolean',
|
||||
),
|
||||
'products' => array(
|
||||
'js_name' => 'products',
|
||||
),
|
||||
);
|
||||
|
@ -147,9 +135,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