From a5bbee307d732573eabbf39f906c9f1f5b56765a Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 17 Dec 2024 16:04:36 +0100
Subject: [PATCH 01/57] =?UTF-8?q?=F0=9F=9A=9A=20Move=20ajax=20handler=20to?=
=?UTF-8?q?=20separate=20folder?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 2 +-
.../src/{Endpoint => Ajax}/SwitchSettingsUiEndpoint.php | 4 ++--
modules/ppcp-settings/src/SettingsModule.php | 2 +-
modules/ppcp-uninstall/services.php | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
rename modules/ppcp-settings/src/{Endpoint => Ajax}/SwitchSettingsUiEndpoint.php (94%)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index 349c13350..f1c1730c4 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
-use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
+use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
diff --git a/modules/ppcp-settings/src/Endpoint/SwitchSettingsUiEndpoint.php b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php
similarity index 94%
rename from modules/ppcp-settings/src/Endpoint/SwitchSettingsUiEndpoint.php
rename to modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php
index 244c26dfe..04a6cc86e 100644
--- a/modules/ppcp-settings/src/Endpoint/SwitchSettingsUiEndpoint.php
+++ b/modules/ppcp-settings/src/Ajax/SwitchSettingsUiEndpoint.php
@@ -1,13 +1,13 @@
Date: Tue, 17 Dec 2024 16:08:09 +0100
Subject: [PATCH 02/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Adjust=20service=20n?=
=?UTF-8?q?ame=20for=20consistency?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 2 +-
modules/ppcp-settings/src/SettingsModule.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index f1c1730c4..c785111aa 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -192,7 +192,7 @@ return array(
return $generators;
},
- 'settings.switch-ui.endpoint' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
+ 'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
return new SwitchSettingsUiEndpoint(
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.request-data' ),
diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php
index 3197b1cba..931a9782e 100644
--- a/modules/ppcp-settings/src/SettingsModule.php
+++ b/modules/ppcp-settings/src/SettingsModule.php
@@ -86,7 +86,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
}
);
- $endpoint = $container->get( 'settings.switch-ui.endpoint' ) ? $container->get( 'settings.switch-ui.endpoint' ) : null;
+ $endpoint = $container->get( 'settings.ajax.switch_ui' ) ? $container->get( 'settings.ajax.switch_ui' ) : null;
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
add_action(
From a2221e52337ea7f90ec9d22ecb42f4c0cce3018d Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 17 Dec 2024 19:34:22 +0100
Subject: [PATCH 03/57] =?UTF-8?q?=E2=9C=A8=20Introduce=20new=20connection?=
=?UTF-8?q?=20manager=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 9 +
.../ppcp-settings/src/Data/CommonSettings.php | 14 +
.../src/Service/ConnectionManager.php | 242 ++++++++++++++++++
3 files changed, 265 insertions(+)
create mode 100644 modules/ppcp-settings/src/Service/ConnectionManager.php
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index c785111aa..1397b137b 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
+use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
return array(
'settings.url' => static function ( ContainerInterface $container ) : string {
@@ -192,6 +193,14 @@ return array(
return $generators;
},
+ 'settings.service.connection_manager' => static function ( ContainerInterface $container ) : ConnectionManager {
+ return new ConnectionManager(
+ $container->get( 'settings.data.common' ),
+ $container->get( 'api.paypal-host-production' ),
+ $container->get( 'api.paypal-host-sandbox' ),
+ $container->get( 'woocommerce.logger.woocommerce' ),
+ );
+ },
'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
return new SwitchSettingsUiEndpoint(
$container->get( 'woocommerce.logger.woocommerce' ),
diff --git a/modules/ppcp-settings/src/Data/CommonSettings.php b/modules/ppcp-settings/src/Data/CommonSettings.php
index 1894255ff..0935734b8 100644
--- a/modules/ppcp-settings/src/Data/CommonSettings.php
+++ b/modules/ppcp-settings/src/Data/CommonSettings.php
@@ -169,6 +169,20 @@ class CommonSettings extends AbstractDataModel {
$this->data['merchant_connected'] = true;
}
+ /**
+ * Reset all connection details to the initial, disconnected state.
+ *
+ * @return void
+ */
+ public function reset_merchant_data() : void {
+ $defaults = $this->get_defaults();
+
+ $this->data['sandbox_merchant'] = $defaults['sandbox_merchant'];
+ $this->data['merchant_id'] = $defaults['merchant_id'];
+ $this->data['merchant_email'] = $defaults['merchant_email'];
+ $this->data['merchant_connected'] = $defaults['merchant_connected'];
+ }
+
/**
* Whether the currently connected merchant is a sandbox account.
*
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
new file mode 100644
index 000000000..1cffb155c
--- /dev/null
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -0,0 +1,242 @@
+
+ */
+ private array $connection_hosts;
+
+ /**
+ * Constructor.
+ *
+ * @param CommonSettings $common_settings Data model that stores the connection details.
+ * @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 Logging instance.
+ */
+ public function __construct( CommonSettings $common_settings, string $live_host, string $sandbox_host, LoggerInterface $logger ) {
+ $this->common_settings = $common_settings;
+ $this->logger = $logger;
+ $this->connection_hosts = array(
+ 'live' => $live_host,
+ 'sandbox' => $sandbox_host,
+ );
+ }
+
+ /**
+ * Returns details about the currently connected merchant.
+ *
+ * @return array
+ */
+ public function get_account_details() : array {
+ return array(
+ 'is_sandbox' => $this->common_settings->is_sandbox_merchant(),
+ 'is_connected' => $this->common_settings->is_merchant_connected(),
+ 'merchant_id' => $this->common_settings->get_merchant_id(),
+ 'merchant_email' => $this->common_settings->get_merchant_email(),
+ );
+ }
+
+ /**
+ * Removes any connection details we currently have stored.
+ *
+ * @return void
+ */
+ public function disconnect() : void {
+ $this->logger->info( 'Disconnecting merchant from PayPal...' );
+
+ $this->common_settings->reset_merchant_data();
+ $this->common_settings->save();
+ }
+
+ /**
+ * Checks if the provided ID and secret have a valid format.
+ *
+ * On failure, an Exception is thrown, while a successful check does not
+ * generate any return value.
+ *
+ * @param string $client_id The client ID.
+ * @param string $client_secret The client secret.
+ * @return void
+ * @throws RuntimeException When invalid client ID or secret provided.
+ */
+ public function validate_id_and_secret( string $client_id, string $client_secret ) : void {
+ if ( empty( $client_id ) ) {
+ throw new RuntimeException( 'No client ID provided.' );
+ }
+
+ if ( false === preg_match( '/^A[\w-]{79}$/', $client_secret ) ) {
+ throw new RuntimeException( 'Invalid client ID provided.' );
+ }
+
+ if ( empty( $client_secret ) ) {
+ throw new RuntimeException( 'No client secret provided.' );
+ }
+ }
+
+ /**
+ * Disconnects the current merchant, and then attempts to connect to a
+ * PayPal account using a client ID and secret.
+ *
+ * @param bool $use_sandbox Whether to use the sandbox mode.
+ * @param string $client_id The client ID.
+ * @param string $client_secret The client secret.
+ * @return void
+ * @throws RuntimeException When failed to retrieve payee.
+ */
+ public function connect_via_secret( bool $use_sandbox, string $client_id, string $client_secret ) : void {
+ $this->disconnect();
+
+ $this->logger->info(
+ 'Attempting manual connection to PayPal...',
+ array(
+ 'sandbox' => $use_sandbox,
+ 'client_id' => $client_id,
+ )
+ );
+
+ $payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
+
+ $this->update_connection_details( $use_sandbox, $payee['merchant_id'], $payee['email_address'] );
+ }
+
+
+ // ----------------------------------------------------------------------------
+ // Internal helper methods
+
+
+ /**
+ * Returns the API host for the relevant environment.
+ *
+ * @param bool $for_sandbox Whether to return the sandbox API host.
+ * @return string
+ */
+ private function get_host( bool $for_sandbox = false ) : string {
+ return $for_sandbox ? $this->connection_hosts['sandbox'] : $this->connection_hosts['live'];
+ }
+
+ /**
+ * 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 array Payee details, containing 'merchant_id' and 'merchant_email' keys.
+ * @throws RuntimeException When failed to retrieve payee.
+ */
+ private function request_payee(
+ string $client_id,
+ string $client_secret,
+ bool $use_sandbox
+ ) : array {
+ $host = $this->get_host( $use_sandbox );
+
+ $bearer = new PayPalBearer(
+ new InMemoryCache(),
+ $host,
+ $client_id,
+ $client_secret,
+ $this->logger,
+ null
+ );
+
+ $orders = new Orders(
+ $host,
+ $bearer,
+ $this->logger
+ );
+
+ $request_body = array(
+ 'intent' => 'CAPTURE',
+ 'purchase_units' => array(
+ array(
+ 'amount' => array(
+ 'currency_code' => 'USD',
+ 'value' => 1.0,
+ ),
+ ),
+ ),
+ );
+
+ $response = $orders->create( $request_body );
+ $body = json_decode( $response['body'] );
+
+ $order_id = $body->id;
+
+ $order_response = $orders->order( $order_id );
+ $order_body = json_decode( $order_response['body'] );
+
+ $pu = $order_body->purchase_units[0];
+ $payee = $pu->payee;
+
+ if ( ! is_object( $payee ) ) {
+ throw new RuntimeException( 'Payee not found.' );
+ }
+ if ( ! isset( $payee->merchant_id ) || ! isset( $payee->email_address ) ) {
+ throw new RuntimeException( 'Payee info not found.' );
+ }
+
+ return array(
+ 'merchant_id' => $payee->merchant_id,
+ 'email_address' => $payee->email_address,
+ );
+ }
+
+ /**
+ * Stores the provided details in the data model.
+ *
+ * @param bool $is_sandbox Whether the details are for a sandbox account.
+ * @param string $merchant_id PayPal's internal merchant ID.
+ * @param string $merchant_email Email address associated with the PayPal account.
+ * @return void
+ */
+ private function update_connection_details( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
+ $this->logger->info(
+ 'Updating connection details',
+ array(
+ 'sandbox' => $is_sandbox,
+ 'merchant_id' => $merchant_id,
+ 'merchant_email' => $merchant_email,
+ )
+ );
+
+ $this->common_settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
+ $this->common_settings->save();
+ }
+}
From 45db5abb34422dc25b656bcc975742fb1455bde0 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 17 Dec 2024 19:35:43 +0100
Subject: [PATCH 04/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20new=20connecti?=
=?UTF-8?q?on=20manager=20in=20REST=20endpoint?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 5 +-
.../Endpoint/ConnectManualRestEndpoint.php | 161 +++---------------
2 files changed, 24 insertions(+), 142 deletions(-)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index 1397b137b..6b676850d 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -81,10 +81,7 @@ return array(
},
'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( 'settings.data.general' )
+ $container->get( 'settings.service.connection_manager' ),
);
},
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
diff --git a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php
index 7046342a2..efeda393d 100644
--- a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php
@@ -20,33 +20,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
+use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
/**
* REST controller for connection via manual credentials input.
*/
class ConnectManualRestEndpoint extends RestEndpoint {
-
- /**
- * The API host for the live mode.
- *
- * @var string
- */
- private string $live_host;
-
- /**
- * The API host for the sandbox mode.
- *
- * @var string
- */
- private string $sandbox_host;
-
- /**
- * The logger.
- *
- * @var LoggerInterface
- */
- private $logger;
-
/**
* The base path for this REST controller.
*
@@ -54,13 +33,6 @@ class ConnectManualRestEndpoint extends RestEndpoint {
*/
protected $rest_base = 'connect_manual';
- /**
- * Settings instance.
- *
- * @var GeneralSettings
- */
- private $settings = null;
-
/**
* Field mapping for request.
*
@@ -81,24 +53,27 @@ class ConnectManualRestEndpoint extends RestEndpoint {
),
);
+ /**
+ * Defines the JSON response format (when connection was successful).
+ *
+ * @var array
+ */
+ private array $response_map = array(
+ 'merchant_id' => array(
+ 'js_name' => 'merchantId',
+ ),
+ 'merchant_email' => array(
+ 'js_name' => 'email',
+ ),
+ );
+
/**
* ConnectManualRestEndpoint constructor.
*
- * @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 GeneralSettings $settings Settings instance.
+ * @param ConnectionManager $connection_manager The connection manager.
*/
- public function __construct(
- string $live_host,
- string $sandbox_host,
- LoggerInterface $logger,
- GeneralSettings $settings
- ) {
- $this->live_host = $live_host;
- $this->sandbox_host = $sandbox_host;
- $this->logger = $logger;
- $this->settings = $settings;
+ public function __construct( ConnectionManager $connection_manager ) {
+ $this->connection_manager = $connection_manager;
}
/**
@@ -133,106 +108,16 @@ class ConnectManualRestEndpoint extends RestEndpoint {
$client_secret = $data['client_secret'] ?? '';
$use_sandbox = (bool) ( $data['use_sandbox'] ?? false );
- if ( empty( $client_id ) || empty( $client_secret ) ) {
- return $this->return_error( 'No client ID or secret provided.' );
- }
-
try {
- $payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
+ $this->connection_manager->validate_id_and_secret( $client_id, $client_secret );
+ $this->connection_manager->connect_via_secret( $use_sandbox, $client_id, $client_secret );
} catch ( Exception $exception ) {
return $this->return_error( $exception->getMessage() );
}
- 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();
+ $account = $this->connection_manager->get_account_details();
+ $response = $this->sanitize_for_javascript( $this->response_map, $account );
- 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.
- *
- * @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,
- string $client_secret,
- bool $use_sandbox
- ) : stdClass {
-
- $host = $use_sandbox ? $this->sandbox_host : $this->live_host;
-
- $bearer = new PayPalBearer(
- new InMemoryCache(),
- $host,
- $client_id,
- $client_secret,
- $this->logger,
- null
- );
-
- $orders = new Orders(
- $host,
- $bearer,
- $this->logger
- );
-
- $request_body = array(
- 'intent' => 'CAPTURE',
- 'purchase_units' => array(
- array(
- 'amount' => array(
- 'currency_code' => 'USD',
- 'value' => 1.0,
- ),
- ),
- ),
- );
-
- $response = $orders->create( $request_body );
- $body = json_decode( $response['body'] );
-
- $order_id = $body->id;
-
- $order_response = $orders->order( $order_id );
- $order_body = json_decode( $order_response['body'] );
-
- $pu = $order_body->purchase_units[0];
- $payee = $pu->payee;
- if ( ! is_object( $payee ) ) {
- throw new RuntimeException( 'Payee not found.' );
- }
- if ( ! isset( $payee->merchant_id ) || ! isset( $payee->email_address ) ) {
- throw new RuntimeException( 'Payee info not found.' );
- }
-
- return $payee;
+ return $this->return_success( $response );
}
}
From 607021c9ec2cd76e6e3ca46cacd5e339e9a45754 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 18 Dec 2024 12:51:22 +0100
Subject: [PATCH 05/57] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unnecessary=20"us?=
=?UTF-8?q?e"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php b/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
index 028740cb9..b2483160a 100644
--- a/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
+++ b/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
@@ -12,7 +12,6 @@ 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\WooCommerce\Logging\Logger\NullLogger;
From da96c084abd12dd7a9a1fee02425014ae256a99e Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 19 Dec 2024 13:23:40 +0100
Subject: [PATCH 06/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Consolidate=20the=20?=
=?UTF-8?q?onboarding-url-generation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/action-types.js | 3 +-
.../resources/js/data/common/actions.js | 14 ++++++++--
.../resources/js/data/common/controls.js | 28 ++++---------------
3 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js
index ac08cdcf7..5c9c83fe5 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -20,8 +20,7 @@ export default {
// 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',
- DO_PRODUCTION_LOGIN: 'COMMON:DO_PRODUCTION_LOGIN',
+ DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
};
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index ccbf34ce0..e9d89e5f7 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -7,7 +7,7 @@
* @file
*/
-import { dispatch, select } from '@wordpress/data';
+import { select } from '@wordpress/data';
import ACTION_TYPES from './action-types';
import { STORE_NAME } from './constants';
@@ -151,7 +151,11 @@ export const persist = function* () {
* @return {Action} The action.
*/
export const connectToSandbox = function* () {
- return yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN };
+ return yield {
+ type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
+ environment: 'sandbox',
+ products: [ 'EXPRESS_CHECKOUT' ],
+ };
};
/**
@@ -161,7 +165,11 @@ export const connectToSandbox = function* () {
* @return {Action} The action.
*/
export const connectToProduction = function* ( products = [] ) {
- return yield { type: ACTION_TYPES.DO_PRODUCTION_LOGIN, products };
+ return yield {
+ type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
+ environment: 'production',
+ products,
+ };
};
/**
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index a088660b9..28e93eaf4 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -31,33 +31,15 @@ export const controls = {
}
},
- async [ ACTION_TYPES.DO_SANDBOX_LOGIN ]() {
+ async [ ACTION_TYPES.DO_GENERATE_ONBOARDING_URL ]( {
+ products,
+ environment,
+ } ) {
try {
return apiFetch( {
path: REST_CONNECTION_URL_PATH,
method: 'POST',
- data: {
- environment: 'sandbox',
- products: [ 'EXPRESS_CHECKOUT' ], // Sandbox always uses EXPRESS_CHECKOUT.
- },
- } );
- } catch ( e ) {
- return {
- success: false,
- error: e,
- };
- }
- },
-
- async [ ACTION_TYPES.DO_PRODUCTION_LOGIN ]( { products } ) {
- try {
- return apiFetch( {
- path: REST_CONNECTION_URL_PATH,
- method: 'POST',
- data: {
- environment: 'production',
- products,
- },
+ data: { environment, products },
} );
} catch ( e ) {
return {
From 1e26852aa1fe145bbcfe2f6b8ca47ee4b4067d95 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 19 Dec 2024 13:26:19 +0100
Subject: [PATCH 07/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20actions=20f?=
=?UTF-8?q?or=20better=20semantics?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/actions.js | 4 ++--
.../resources/js/data/common/hooks.js | 16 ++++++++--------
.../resources/js/hooks/useHandleConnections.js | 8 ++++----
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index e9d89e5f7..2b6dda353 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -150,7 +150,7 @@ export const persist = function* () {
*
* @return {Action} The action.
*/
-export const connectToSandbox = function* () {
+export const sandboxOnboardingUrl = function* () {
return yield {
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
environment: 'sandbox',
@@ -164,7 +164,7 @@ export const connectToSandbox = function* () {
* @param {string[]} products Which products/features to display in the ISU popup.
* @return {Action} The action.
*/
-export const connectToProduction = function* ( products = [] ) {
+export const productionOnboardingUrl = function* ( products = [] ) {
return yield {
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
environment: 'production',
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 8eaaa3924..7057bbf2a 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -31,8 +31,8 @@ const useHooks = () => {
setManualConnectionMode,
setClientId,
setClientSecret,
- connectToSandbox,
- connectToProduction,
+ sandboxOnboardingUrl,
+ productionOnboardingUrl,
connectViaIdAndSecret,
} = useDispatch( STORE_NAME );
@@ -77,8 +77,8 @@ const useHooks = () => {
setClientSecret: ( value ) => {
return savePersistent( setClientSecret, value );
},
- connectToSandbox,
- connectToProduction,
+ sandboxOnboardingUrl,
+ productionOnboardingUrl,
connectViaIdAndSecret,
merchant,
wooSettings,
@@ -86,15 +86,15 @@ const useHooks = () => {
};
export const useSandbox = () => {
- const { isSandboxMode, setSandboxMode, connectToSandbox } = useHooks();
+ const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks();
- return { isSandboxMode, setSandboxMode, connectToSandbox };
+ return { isSandboxMode, setSandboxMode, sandboxOnboardingUrl };
};
export const useProduction = () => {
- const { connectToProduction } = useHooks();
+ const { productionOnboardingUrl } = useHooks();
- return { connectToProduction };
+ return { productionOnboardingUrl };
};
export const useManualConnection = () => {
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index d34e74f42..6686ab05f 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -118,11 +118,11 @@ const useConnectionAttempt = ( connectFn, errorMessage ) => {
};
export const useSandboxConnection = () => {
- const { connectToSandbox, isSandboxMode, setSandboxMode } =
+ const { sandboxOnboardingUrl, isSandboxMode, setSandboxMode } =
CommonHooks.useSandbox();
const { withActivity } = CommonHooks.useBusyState();
const connectionAttempt = useConnectionAttempt(
- connectToSandbox,
+ sandboxOnboardingUrl,
MESSAGES.SANDBOX_ERROR
);
@@ -142,11 +142,11 @@ export const useSandboxConnection = () => {
};
export const useProductionConnection = () => {
- const { connectToProduction } = CommonHooks.useProduction();
+ const { productionOnboardingUrl } = CommonHooks.useProduction();
const { withActivity } = CommonHooks.useBusyState();
const products = OnboardingHooks.useDetermineProducts();
const connectionAttempt = useConnectionAttempt(
- () => connectToProduction( products ),
+ () => productionOnboardingUrl( products ),
MESSAGES.PRODUCTION_ERROR
);
From 827e08d91fca63bce98edf023f9f5f82fbb24bb9 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:38:12 +0100
Subject: [PATCH 08/57] =?UTF-8?q?=E2=9C=A8=20Add=20a=20new=20local=20?=
=?UTF-8?q?=E2=80=9CisBusy=E2=80=9D=20flag=20for=20busy-wrapper?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../js/Components/ReusableComponents/BusyStateWrapper.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/BusyStateWrapper.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/BusyStateWrapper.js
index 959b71bfe..239a088b7 100644
--- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/BusyStateWrapper.js
+++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/BusyStateWrapper.js
@@ -24,6 +24,7 @@ const BusyContext = createContext( false );
* @param {boolean} props.busySpinner - Allows disabling the spinner in busy-state.
* @param {string} props.className - Additional class names for the wrapper.
* @param {Function} props.onBusy - Callback to process child props when busy.
+ * @param {boolean} props.isBusy - Optional. Additional condition to determine if the component is busy.
*/
const BusyStateWrapper = ( {
children,
@@ -31,11 +32,12 @@ const BusyStateWrapper = ( {
busySpinner = true,
className = '',
onBusy = () => ( { disabled: true } ),
+ isBusy = false,
} ) => {
- const { isBusy } = CommonHooks.useBusyState();
+ const { isBusy: globalIsBusy } = CommonHooks.useBusyState();
const hasBusyParent = useContext( BusyContext );
- const isBusyComponent = isBusy && enabled;
+ const isBusyComponent = ( isBusy || globalIsBusy ) && enabled;
const showSpinner = busySpinner && isBusyComponent && ! hasBusyParent;
const wrapperClassName = classNames( 'ppcp-r-busy-wrapper', className, {
From 1c44a8105ba1975e22f53b07b1aa18f2e8f53783 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:38:38 +0100
Subject: [PATCH 09/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20the=20PayPal?=
=?UTF-8?q?=20onboarding=20button?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Onboarding/Components/ConnectionButton.js | 82 +++++++---
.../js/hooks/useHandleConnections.js | 145 ++++++++----------
2 files changed, 127 insertions(+), 100 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
index ad6a7dcef..214da3211 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
@@ -1,15 +1,59 @@
import { Button } from '@wordpress/components';
-
+import { useEffect } from '@wordpress/element';
import classNames from 'classnames';
-
-import { CommonHooks } from '../../../../data';
import { openSignup } from '../../../ReusableComponents/Icons';
-import {
- useProductionConnection,
- useSandboxConnection,
-} from '../../../../hooks/useHandleConnections';
+import { useHandleOnboardingButton } from '../../../../hooks/useHandleConnections';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
+/**
+ * Button component that outputs a placeholder button when no onboardingUrl is present yet - the
+ * placeholder button looks identical to the working button, but has no href, target, or
+ * custom connection attributes.
+ *
+ * @param {Object} props
+ * @param {string} props.className
+ * @param {string} props.variant
+ * @param {boolean} props.showIcon
+ * @param {?string} props.href
+ * @param {Element} props.children
+ */
+const ButtonOrPlaceholder = ( {
+ className,
+ variant,
+ showIcon,
+ href,
+ children,
+} ) => {
+ if ( ! href ) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
const ConnectionButton = ( {
title,
isSandbox = false,
@@ -17,31 +61,29 @@ const ConnectionButton = ( {
showIcon = true,
className = '',
} ) => {
- const { handleSandboxConnect } = useSandboxConnection();
- const { handleProductionConnect } = useProductionConnection();
+ const { onboardingUrl, scriptLoaded } =
+ useHandleOnboardingButton( isSandbox );
const buttonClassName = classNames( 'ppcp-r-connection-button', className, {
'sandbox-mode': isSandbox,
'live-mode': ! isSandbox,
} );
- const handleConnectClick = async () => {
- if ( isSandbox ) {
- await handleSandboxConnect();
- } else {
- await handleProductionConnect();
+ useEffect( () => {
+ if ( scriptLoaded && onboardingUrl ) {
+ window.PAYPAL.apps.Signup.render();
}
- };
+ }, [ scriptLoaded, onboardingUrl ] );
return (
-
-
+
);
};
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 6686ab05f..0bd14cc17 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -1,9 +1,9 @@
import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data';
+import { useState, useEffect } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { CommonHooks, OnboardingHooks } from '../data';
-import { openPopup } from '../utils/window';
const MESSAGES = {
CONNECTED: __( 'Connected to PayPal', 'woocommerce-paypal-payments' ),
@@ -35,32 +35,77 @@ const ACTIVITIES = {
CONNECT_MANUAL: 'MANUAL_LOGIN',
};
-const handlePopupWithCompletion = ( url, onError ) => {
- return new Promise( ( resolve ) => {
- const popup = openPopup( url );
+export const useHandleOnboardingButton = ( isSandbox ) => {
+ const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
+ const { productionOnboardingUrl } = CommonHooks.useProduction();
+ const products = OnboardingHooks.useDetermineProducts();
+ const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
+ const [ scriptLoaded, setScriptLoaded ] = useState( false );
- if ( ! popup ) {
- onError( MESSAGES.POPUP_BLOCKED );
- resolve( false );
+ useEffect( () => {
+ const fetchOnboardingUrl = async () => {
+ let res;
+ if ( isSandbox ) {
+ res = await sandboxOnboardingUrl();
+ } else {
+ res = await productionOnboardingUrl( products );
+ }
+
+ if ( res.success && res.data ) {
+ setOnboardingUrl( res.data );
+ } else {
+ console.error( 'Failed to fetch onboarding URL' );
+ }
+ };
+
+ fetchOnboardingUrl();
+ }, [ isSandbox, productionOnboardingUrl, products, sandboxOnboardingUrl ] );
+
+ useEffect( () => {
+ /**
+ * The partner.js script initializes all onboarding buttons in the onload event.
+ * When no buttons are present, a JS error is displayed; i.e. we should load this script
+ * only when the button is ready (with a valid href and data-attributes).
+ */
+ if ( ! onboardingUrl ) {
return;
}
- // Check popup state every 500ms
- const checkPopup = setInterval( () => {
- if ( popup.closed ) {
- clearInterval( checkPopup );
- resolve( true );
- }
- }, 500 );
+ const script = document.createElement( 'script' );
+ script.id = 'partner-js';
+ script.src =
+ 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
+ script.onload = () => {
+ setScriptLoaded( true );
+ };
+ document.body.appendChild( script );
return () => {
- clearInterval( checkPopup );
+ /**
+ * When the component is unmounted, remove the partner.js script, as well as the
+ * dynamic scripts it loaded (signup-js and rampConfig-js)
+ *
+ * This is important, as the onboarding button is only initialized during the onload
+ * event of those scripts; i.e. we need to load the scripts again, when the button is
+ * rendered again.
+ */
+ const onboardingScripts = [
+ 'partner-js',
+ 'signup-js',
+ 'rampConfig-js',
+ ];
- if ( popup && ! popup.closed ) {
- popup.close();
- }
+ onboardingScripts.forEach( ( id ) => {
+ const el = document.querySelector( `script[id="${ id }"]` );
+
+ if ( el?.parentNode ) {
+ el.parentNode.removeChild( el );
+ }
+ } );
};
- } );
+ }, [ onboardingUrl ] );
+
+ return { onboardingUrl, scriptLoaded };
};
const useConnectionBase = () => {
@@ -92,75 +137,15 @@ const useConnectionBase = () => {
};
};
-const useConnectionAttempt = ( connectFn, errorMessage ) => {
- const { handleFailed, createErrorNotice, handleCompleted } =
- useConnectionBase();
-
- return async ( ...args ) => {
- const res = await connectFn( ...args );
-
- if ( ! res.success || ! res.data ) {
- handleFailed( res, errorMessage );
- return false;
- }
-
- const popupClosed = await handlePopupWithCompletion(
- res.data,
- createErrorNotice
- );
-
- if ( popupClosed ) {
- await handleCompleted();
- }
-
- return popupClosed;
- };
-};
-
export const useSandboxConnection = () => {
- const { sandboxOnboardingUrl, isSandboxMode, setSandboxMode } =
- CommonHooks.useSandbox();
- const { withActivity } = CommonHooks.useBusyState();
- const connectionAttempt = useConnectionAttempt(
- sandboxOnboardingUrl,
- MESSAGES.SANDBOX_ERROR
- );
-
- const handleSandboxConnect = async () => {
- return withActivity(
- ACTIVITIES.CONNECT_SANDBOX,
- 'Connecting to sandbox account',
- connectionAttempt
- );
- };
+ const { isSandboxMode, setSandboxMode } = CommonHooks.useSandbox();
return {
- handleSandboxConnect,
isSandboxMode,
setSandboxMode,
};
};
-export const useProductionConnection = () => {
- const { productionOnboardingUrl } = CommonHooks.useProduction();
- const { withActivity } = CommonHooks.useBusyState();
- const products = OnboardingHooks.useDetermineProducts();
- const connectionAttempt = useConnectionAttempt(
- () => productionOnboardingUrl( products ),
- MESSAGES.PRODUCTION_ERROR
- );
-
- const handleProductionConnect = async () => {
- return withActivity(
- ACTIVITIES.CONNECT_PRODUCTION,
- 'Connecting to production account',
- connectionAttempt
- );
- };
-
- return { handleProductionConnect };
-};
-
export const useManualConnection = () => {
const { handleFailed, handleCompleted, createErrorNotice } =
useConnectionBase();
From 2848347537b9ec092289161b3b8f52415e56c189 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:49:46 +0100
Subject: [PATCH 10/57] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unused=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Components/AdvancedOptionsForm.js | 4 --
.../Onboarding/Components/Navigation.js | 1 -
.../resources/js/utils/window.js | 42 -------------------
3 files changed, 47 deletions(-)
delete mode 100644 modules/ppcp-settings/resources/js/utils/window.js
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
index 6aabd15fd..5685cf014 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
@@ -7,18 +7,15 @@ import {
useMemo,
useCallback,
} from '@wordpress/element';
-
import classNames from 'classnames';
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
import Separator from '../../../ReusableComponents/Separator';
import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
-import { CommonHooks } from '../../../../data';
import {
useSandboxConnection,
useManualConnection,
} from '../../../../hooks/useHandleConnections';
-
import ConnectionButton from './ConnectionButton';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
@@ -41,7 +38,6 @@ const AdvancedOptionsForm = () => {
const [ clientValid, setClientValid ] = useState( false );
const [ secretValid, setSecretValid ] = useState( false );
- const { isBusy } = CommonHooks.useBusyState();
const { isSandboxMode, setSandboxMode } = useSandboxConnection();
const {
handleConnectViaIdAndSecret,
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/Navigation.js
index 3c12e1206..817b26f3e 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/Navigation.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/Navigation.js
@@ -1,7 +1,6 @@
import { Button, Icon } from '@wordpress/components';
import { chevronLeft } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
-
import classNames from 'classnames';
import { OnboardingHooks } from '../../../../data';
diff --git a/modules/ppcp-settings/resources/js/utils/window.js b/modules/ppcp-settings/resources/js/utils/window.js
deleted file mode 100644
index 165874302..000000000
--- a/modules/ppcp-settings/resources/js/utils/window.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * 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;
-};
From 2b5555c96247db39b66b64377b466738ef0bca69 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:54:25 +0100
Subject: [PATCH 11/57] =?UTF-8?q?=E2=9E=95=20Add=20the=20@wordpress/icons?=
=?UTF-8?q?=20dependency?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/package.json | 1 +
modules/ppcp-settings/yarn.lock | 23 +++++++++++++++++++++++
2 files changed, 24 insertions(+)
diff --git a/modules/ppcp-settings/package.json b/modules/ppcp-settings/package.json
index 47e69347c..5cdb7be5c 100644
--- a/modules/ppcp-settings/package.json
+++ b/modules/ppcp-settings/package.json
@@ -9,6 +9,7 @@
"devDependencies": {
"@wordpress/data": "^10.10.0",
"@wordpress/data-controls": "^4.10.0",
+ "@wordpress/icons": "^10.14.0",
"@wordpress/scripts": "^30.3.0",
"classnames": "^2.5.1"
},
diff --git a/modules/ppcp-settings/yarn.lock b/modules/ppcp-settings/yarn.lock
index 6b623d53a..62a5e4ac9 100644
--- a/modules/ppcp-settings/yarn.lock
+++ b/modules/ppcp-settings/yarn.lock
@@ -2919,6 +2919,15 @@
sprintf-js "^1.1.1"
tannin "^1.2.0"
+"@wordpress/icons@^10.14.0":
+ version "10.14.0"
+ resolved "https://registry.yarnpkg.com/@wordpress/icons/-/icons-10.14.0.tgz#a27298b438653a9a502eb4ee3b02b42ce516da2e"
+ integrity sha512-4S1AaBeqvTpsTC23y0+4WPiSyz7j+b7vJ4vQ4nqnPeBF7ZeC8J/UXWQnEuKY38n8TiutXljgagkEqGNC9pF2Mw==
+ dependencies:
+ "@babel/runtime" "7.25.7"
+ "@wordpress/element" "*"
+ "@wordpress/primitives" "*"
+
"@wordpress/is-shallow-equal@*":
version "5.11.0"
resolved "https://registry.yarnpkg.com/@wordpress/is-shallow-equal/-/is-shallow-equal-5.11.0.tgz#2f273d6d4de24a66a7a8316b770cf832d22bfc37"
@@ -2968,6 +2977,15 @@
resolved "https://registry.yarnpkg.com/@wordpress/prettier-config/-/prettier-config-4.11.0.tgz#6b3f9aa7e2698c0d78e644037c6778b5c1da12ce"
integrity sha512-Aoc8+xWOyiXekodjaEjS44z85XK877LzHZqsQuhC0kNgneDLrKkwI5qNgzwzAMbJ9jI58MPqVISCOX0bDLUPbw==
+"@wordpress/primitives@*":
+ version "4.14.0"
+ resolved "https://registry.yarnpkg.com/@wordpress/primitives/-/primitives-4.14.0.tgz#1769f45bc541fd48be2d57626a9f6bdece39942a"
+ integrity sha512-IZibRVbvWoIQ+uynH0N5bmfWz83hD8lJj6jJFhSFuALK+4U5mRGg6tl0ZV0YllR6cjheD9UhTmfrAcOx+gQAjA==
+ dependencies:
+ "@babel/runtime" "7.25.7"
+ "@wordpress/element" "*"
+ clsx "^2.1.1"
+
"@wordpress/priority-queue@*":
version "3.11.0"
resolved "https://registry.yarnpkg.com/@wordpress/priority-queue/-/priority-queue-3.11.0.tgz#01e1570a7a29372bb1d07cd22fd9cbc5b5d03b09"
@@ -3976,6 +3994,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.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
From 5b80e00c54c94f5ddb49945b6b2b6d530bc7ba77 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:54:46 +0100
Subject: [PATCH 12/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20the=20Con?=
=?UTF-8?q?nectionButton=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Onboarding/Components/ConnectionButton.js | 39 +++++++------------
1 file changed, 13 insertions(+), 26 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
index 214da3211..e100881b8 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
@@ -24,34 +24,21 @@ const ButtonOrPlaceholder = ( {
href,
children,
} ) => {
- if ( ! href ) {
- return (
-
- );
+ const buttonProps = {
+ className,
+ variant,
+ icon: showIcon ? openSignup : null,
+ };
+
+ if ( href ) {
+ buttonProps.href = href;
+ buttonProps.target = 'PPFrame';
+ buttonProps[ 'data-paypal-button' ] = 'true';
+ buttonProps[ 'data-paypal-onboard-complete' ] = 'onOnboardComplete';
+ buttonProps[ 'data-paypal-onboard-button' ] = 'true';
}
- return (
-
- );
+ return ;
};
const ConnectionButton = ( {
From 62dda0da3c9f165c8f74839efe1f0dadbf505a28 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 13:57:49 +0100
Subject: [PATCH 13/57] =?UTF-8?q?=E2=9C=A8=20Add=20Vaulting=20to=20onboard?=
=?UTF-8?q?ing=20products?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ppcp-settings/resources/js/data/onboarding/selectors.js | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/onboarding/selectors.js b/modules/ppcp-settings/resources/js/data/onboarding/selectors.js
index 2e0953437..9f3a7f35d 100644
--- a/modules/ppcp-settings/resources/js/data/onboarding/selectors.js
+++ b/modules/ppcp-settings/resources/js/data/onboarding/selectors.js
@@ -52,9 +52,6 @@ export const determineProducts = ( state ) => {
* The store uses the Express-checkout product.
*/
derivedProducts.push( 'EXPRESS_CHECKOUT' );
-
- // TODO: Add the "BCDC" product/feature
- // Requirement: "EXPRESS_CHECKOUT with BCDC"
} else {
/**
* Branch 3: Merchant is business, and can use CC payments.
@@ -64,8 +61,7 @@ export const determineProducts = ( state ) => {
}
if ( canUseVaulting ) {
- // TODO: Add the "Vaulting" product/feature
- // Requirement: "... with Vault"
+ derivedProducts.push( 'ADVANCED_VAULTING' );
}
return derivedProducts;
From 69f6cc2e7359154331f4d7f9f2fed61ced61bcf0 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 20 Dec 2024 16:09:54 +0100
Subject: [PATCH 14/57] =?UTF-8?q?=E2=9C=A8=20First=20steps=20to=20implemen?=
=?UTF-8?q?t=20merchant=20authentication?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Onboarding/Components/ConnectionButton.js | 23 +++++++--
.../js/hooks/useHandleConnections.js | 51 +++++++++++++++++--
2 files changed, 66 insertions(+), 8 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
index e100881b8..7cbc504e0 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ConnectionButton.js
@@ -34,7 +34,6 @@ const ButtonOrPlaceholder = ( {
buttonProps.href = href;
buttonProps.target = 'PPFrame';
buttonProps[ 'data-paypal-button' ] = 'true';
- buttonProps[ 'data-paypal-onboard-complete' ] = 'onOnboardComplete';
buttonProps[ 'data-paypal-onboard-button' ] = 'true';
}
@@ -48,18 +47,34 @@ const ConnectionButton = ( {
showIcon = true,
className = '',
} ) => {
- const { onboardingUrl, scriptLoaded } =
- useHandleOnboardingButton( isSandbox );
+ const {
+ onboardingUrl,
+ scriptLoaded,
+ setCompleteHandler,
+ removeCompleteHandler,
+ } = useHandleOnboardingButton( isSandbox );
const buttonClassName = classNames( 'ppcp-r-connection-button', className, {
'sandbox-mode': isSandbox,
'live-mode': ! isSandbox,
} );
+ const environment = isSandbox ? 'sandbox' : 'production';
useEffect( () => {
if ( scriptLoaded && onboardingUrl ) {
window.PAYPAL.apps.Signup.render();
+ setCompleteHandler( environment );
}
- }, [ scriptLoaded, onboardingUrl ] );
+
+ return () => {
+ removeCompleteHandler();
+ };
+ }, [
+ scriptLoaded,
+ onboardingUrl,
+ environment,
+ setCompleteHandler,
+ removeCompleteHandler,
+ ] );
return (
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 0bd14cc17..9d860e1c8 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -1,10 +1,13 @@
import { __ } from '@wordpress/i18n';
import { useDispatch } from '@wordpress/data';
-import { useState, useEffect } from '@wordpress/element';
+import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { CommonHooks, OnboardingHooks } from '../data';
+const PAYPAL_PARTNER_SDK_URL =
+ 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
+
const MESSAGES = {
CONNECTED: __( 'Connected to PayPal', 'woocommerce-paypal-payments' ),
POPUP_BLOCKED: __(
@@ -41,6 +44,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const products = OnboardingHooks.useDetermineProducts();
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
const [ scriptLoaded, setScriptLoaded ] = useState( false );
+ const timerRef = useRef( null );
useEffect( () => {
const fetchOnboardingUrl = async () => {
@@ -73,8 +77,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const script = document.createElement( 'script' );
script.id = 'partner-js';
- script.src =
- 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
+ script.src = PAYPAL_PARTNER_SDK_URL;
script.onload = () => {
setScriptLoaded( true );
};
@@ -105,7 +108,47 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
};
}, [ onboardingUrl ] );
- return { onboardingUrl, scriptLoaded };
+ const setCompleteHandler = useCallback( ( environment ) => {
+ const onComplete = ( authCode, shareId ) => {
+ // TODO -- finish this!
+ console.log(
+ `${ environment }-boarding complete - AUTH: `,
+ authCode
+ );
+ console.log(
+ `${ environment }-boarding complete - SHARE:`,
+ shareId
+ );
+ };
+
+ const addHandler = () => {
+ const MiniBrowser = window.PAYPAL?.apps?.Signup?.MiniBrowser;
+ if ( ! MiniBrowser || MiniBrowser.onOnboardComplete ) {
+ return;
+ }
+
+ MiniBrowser.onOnboardComplete = onComplete;
+ };
+
+ // Ensure the onComplete handler is not removed by a PayPal init script.
+ timerRef.current = setInterval( addHandler, 250 );
+ }, [] );
+
+ const removeCompleteHandler = useCallback( () => {
+ if ( timerRef.current ) {
+ clearInterval( timerRef.current );
+ timerRef.current = null;
+ }
+
+ delete window.PAYPAL?.apps?.Signup?.MiniBrowser?.onOnboardComplete;
+ }, [] );
+
+ return {
+ onboardingUrl,
+ scriptLoaded,
+ setCompleteHandler,
+ removeCompleteHandler,
+ };
};
const useConnectionBase = () => {
From f9809bdc5312b61c49edf8f4b4ef22d1edb4f766 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 12:26:40 +0100
Subject: [PATCH 15/57] =?UTF-8?q?=F0=9F=9A=A7=20Rename=20ConnectManual=20e?=
=?UTF-8?q?ndpoint?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This is a preparation to include the UI login authentication in the same endpoint as the manual connection logic
---
.../resources/js/data/common/constants.js | 4 ++--
modules/ppcp-settings/services.php | 6 +++---
...RestEndpoint.php => AuthenticationRestEndpoint.php} | 10 +++++-----
3 files changed, 10 insertions(+), 10 deletions(-)
rename modules/ppcp-settings/src/Endpoint/{ConnectManualRestEndpoint.php => AuthenticationRestEndpoint.php} (91%)
diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js
index c67b1fef0..e51e311a5 100644
--- a/modules/ppcp-settings/resources/js/data/common/constants.js
+++ b/modules/ppcp-settings/resources/js/data/common/constants.js
@@ -38,11 +38,11 @@ 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
+ * See: AuthenticateRestEndpoint.php
*
* @type {string}
*/
-export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual';
+export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/authenticate';
/**
* REST path to generate an ISU URL for the PayPal-login.
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index 6b676850d..7dde67edd 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -14,7 +14,7 @@ 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\AuthenticationRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
@@ -79,8 +79,8 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
- 'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint {
- return new ConnectManualRestEndpoint(
+ 'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : AuthenticationRestEndpoint {
+ return new AuthenticationRestEndpoint(
$container->get( 'settings.service.connection_manager' ),
);
},
diff --git a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
similarity index 91%
rename from modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php
rename to modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index efeda393d..f6310588e 100644
--- a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -1,6 +1,6 @@
Date: Thu, 2 Jan 2025 12:33:58 +0100
Subject: [PATCH 16/57] =?UTF-8?q?=F0=9F=92=A1=20Explain=20diff=20between?=
=?UTF-8?q?=20LoginLink=20&=20Authentication?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Endpoint/AuthenticationRestEndpoint.php | 9 ++++++++-
.../ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php | 9 ++++++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index f6310588e..037add444 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -23,7 +23,14 @@ use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
/**
- * REST controller for connection to a PayPal merchant account.
+ * REST controller for authenticating and connecting to a PayPal merchant account.
+ *
+ * This endpoint is responsible for verifying credentials and establishing
+ * a connection, regardless of whether they are provided via:
+ * 1. Direct login (clientId + secret)
+ * 2. UI-driven login (sharedId + authCode)
+ *
+ * It handles the actual authentication process after the login URL has been used.
*/
class AuthenticationRestEndpoint extends RestEndpoint {
/**
diff --git a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
index 8ed204383..9ce844f6d 100644
--- a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
@@ -15,7 +15,14 @@ use WP_REST_Request;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
/**
- * REST controller that generates merchant login URLs.
+ * REST controller that generates merchant login URLs for PayPal.
+ *
+ * This endpoint is responsible solely for generating a URL that initiates
+ * the PayPal login flow. It does not handle the authentication itself.
+ *
+ * The generated URL is typically used to redirect merchants to PayPal's login page.
+ * After successful login, the authentication process is completed via the
+ * AuthenticationRestEndpoint.
*/
class LoginLinkRestEndpoint extends RestEndpoint {
/**
From 5195748e7607f4d8e06879a7c55031d45195cbe6 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 12:58:33 +0100
Subject: [PATCH 17/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Slightly=20simplify?=
=?UTF-8?q?=20REST=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Endpoint/AuthenticationRestEndpoint.php | 2 --
.../src/Endpoint/CommonRestEndpoint.php | 24 +++++--------
.../src/Endpoint/LoginLinkRestEndpoint.php | 34 +++++++++----------
.../src/Endpoint/OnboardingRestEndpoint.php | 16 ++++-----
.../Endpoint/RefreshFeatureStatusEndpoint.php | 12 +++----
5 files changed, 36 insertions(+), 52 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 037add444..648da5d28 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -90,12 +90,10 @@ class AuthenticationRestEndpoint extends RestEndpoint {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
- array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connect_manual' ),
'permission_callback' => array( $this, 'check_permission' ),
- ),
)
);
}
diff --git a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
index 7524e7e31..90a1a5e8d 100644
--- a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
@@ -111,11 +111,9 @@ class CommonRestEndpoint extends RestEndpoint {
$this->namespace,
'/' . $this->rest_base,
array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_details' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_details' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
@@ -123,11 +121,9 @@ class CommonRestEndpoint extends RestEndpoint {
$this->namespace,
'/' . $this->rest_base,
array(
- array(
- 'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'update_details' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_details' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
@@ -135,11 +131,9 @@ class CommonRestEndpoint extends RestEndpoint {
$this->namespace,
"/$this->rest_base/merchant",
array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_merchant_details' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_merchant_details' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
}
diff --git a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
index 9ce844f6d..722a20be8 100644
--- a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
@@ -56,25 +56,23 @@ class LoginLinkRestEndpoint extends RestEndpoint {
$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 );
- },
+ '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 );
+ },
),
),
)
diff --git a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php
index d4273228f..018ab2dc2 100644
--- a/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/OnboardingRestEndpoint.php
@@ -101,11 +101,9 @@ class OnboardingRestEndpoint extends RestEndpoint {
$this->namespace,
'/' . $this->rest_base,
array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_details' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_details' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
@@ -113,11 +111,9 @@ class OnboardingRestEndpoint extends RestEndpoint {
$this->namespace,
'/' . $this->rest_base,
array(
- array(
- 'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'update_details' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_details' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
}
diff --git a/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php b/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php
index d8fc2760e..dfbfc3a3a 100644
--- a/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php
@@ -5,7 +5,7 @@
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
*/
-declare(strict_types=1);
+declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
@@ -87,11 +87,9 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
$this->namespace,
'/' . $this->rest_base,
array(
- array(
- 'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'refresh_status' ),
- 'permission_callback' => array( $this, 'check_permission' ),
- ),
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'refresh_status' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
)
);
}
@@ -102,7 +100,7 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
* @param WP_REST_Request $request Full data about the request.
* @return WP_REST_Response
*/
- public function refresh_status( WP_REST_Request $request ): WP_REST_Response {
+ public function refresh_status( WP_REST_Request $request ) : WP_REST_Response {
$now = time();
$last_request_time = $this->cache->get( self::CACHE_KEY ) ?: 0;
$seconds_missing = $last_request_time + self::TIMEOUT - $now;
From ff1df84ada6950fa32493b07d19eec457f72e2cf Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 14:33:42 +0100
Subject: [PATCH 18/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20serializati?=
=?UTF-8?q?on=20logic=20in=20manual=20login=20REST?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Endpoint/AuthenticationRestEndpoint.php | 59 +++++++++----------
.../src/Endpoint/RestEndpoint.php | 18 +++---
2 files changed, 38 insertions(+), 39 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 648da5d28..f112f6905 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -40,26 +40,6 @@ class AuthenticationRestEndpoint extends RestEndpoint {
*/
protected $rest_base = 'authenticate';
- /**
- * Field mapping for request.
- *
- * @var array
- */
- private array $field_map = array(
- 'client_id' => array(
- 'js_name' => 'clientId',
- 'sanitize' => 'sanitize_text_field',
- ),
- 'client_secret' => array(
- 'js_name' => 'clientSecret',
- 'sanitize' => 'sanitize_text_field',
- ),
- 'use_sandbox' => array(
- 'js_name' => 'useSandbox',
- 'sanitize' => 'to_boolean',
- ),
- );
-
/**
* Defines the JSON response format (when connection was successful).
*
@@ -90,10 +70,30 @@ class AuthenticationRestEndpoint extends RestEndpoint {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
- array(
- 'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'connect_manual' ),
- 'permission_callback' => array( $this, 'check_permission' ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'connect_manual' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
+ 'args' => array(
+ 'clientId' => array(
+ 'requires' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ 'minLength' => 80,
+ 'maxLength' => 80,
+ ),
+ 'clientSecret' => array(
+ 'requires' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ 'useSandbox' => array(
+ 'requires' => false,
+ 'type' => 'boolean',
+ 'default' => false,
+ 'sanitize_callback' => array( $this, 'to_boolean' ),
+ ),
+ ),
)
);
}
@@ -104,14 +104,9 @@ class AuthenticationRestEndpoint extends RestEndpoint {
* @param WP_REST_Request $request Full data about the request.
*/
public function connect_manual( WP_REST_Request $request ) : WP_REST_Response {
- $data = $this->sanitize_for_wordpress(
- $request->get_params(),
- $this->field_map
- );
-
- $client_id = $data['client_id'] ?? '';
- $client_secret = $data['client_secret'] ?? '';
- $use_sandbox = (bool) ( $data['use_sandbox'] ?? false );
+ $client_id = $request->get_param( 'clientId' );
+ $client_secret = $request->get_param( 'clientSecret' );
+ $use_sandbox = $request->get_param( 'useSandbox' );
try {
$this->connection_manager->validate_id_and_secret( $client_id, $client_secret );
diff --git a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
index 76626ac0c..850a70f87 100644
--- a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
@@ -81,7 +81,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
}
/**
- * Sanitizes parameters based on a field mapping.
+ * Sanitizes and renames input parameters, based on a field mapping.
*
* This method iterates through a field map, applying sanitization methods
* to the corresponding values in the input parameters array.
@@ -122,7 +122,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
}
/**
- * Sanitizes data for JavaScript based on a field mapping.
+ * Sanitizes and renames data for JavaScript, based on a field mapping.
*
* This method transforms the input data array according to the provided field map,
* renaming keys to their JavaScript equivalents as specified in the mapping.
@@ -151,9 +151,9 @@ abstract class RestEndpoint extends WC_REST_Controller {
}
/**
- * Convert a value to a boolean.
+ * Sanitation callback: Convert a value to a boolean.
*
- * @param mixed $value The value to convert.
+ * @param mixed $value The value to sanitize.
*
* @return bool|null The boolean value, or null if not set.
*/
@@ -162,13 +162,17 @@ abstract class RestEndpoint extends WC_REST_Controller {
}
/**
- * Convert a value to a number.
+ * Sanitation callback: Convert a value to a number.
*
- * @param mixed $value The value to convert.
+ * @param mixed $value The value to sanitize.
*
* @return int|float|null The numeric value, or null if not set.
*/
protected function to_number( $value ) {
- return $value !== null ? ( is_numeric( $value ) ? $value + 0 : null ) : null;
+ if ( $value !== null ) {
+ $value = is_numeric( $value ) ? $value + 0 : null;
+ }
+
+ return $value;
}
}
From ef0e7e756c9d303474d0564c36c850c77a9aaf83 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 14:35:24 +0100
Subject: [PATCH 19/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20REST=20endp?=
=?UTF-8?q?oint=20for=20manual=20login?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/constants.js | 5 +++--
.../resources/js/data/common/controls.js | 4 ++--
.../src/Endpoint/AuthenticationRestEndpoint.php | 11 +++++++----
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js
index e51e311a5..a94a62b33 100644
--- a/modules/ppcp-settings/resources/js/data/common/constants.js
+++ b/modules/ppcp-settings/resources/js/data/common/constants.js
@@ -35,14 +35,15 @@ export const REST_HYDRATE_MERCHANT_PATH = '/wc/v3/wc_paypal/common/merchant';
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common';
/**
- * REST path to perform the manual connection check, using client ID and secret,
+ * REST path to perform the manual connection authentication, using client ID and secret.
*
* Used by: Controls
* See: AuthenticateRestEndpoint.php
*
* @type {string}
*/
-export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/authenticate';
+export const REST_DIRECT_AUTHENTICATION_PATH =
+ '/wc/v3/wc_paypal/authenticate/direct';
/**
* REST path to generate an ISU URL for the PayPal-login.
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index 28e93eaf4..ffbc736b6 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -11,7 +11,7 @@ import apiFetch from '@wordpress/api-fetch';
import {
REST_PERSIST_PATH,
- REST_MANUAL_CONNECTION_PATH,
+ REST_DIRECT_AUTHENTICATION_PATH,
REST_CONNECTION_URL_PATH,
REST_HYDRATE_MERCHANT_PATH,
REST_REFRESH_FEATURES_PATH,
@@ -56,7 +56,7 @@ export const controls = {
} ) {
try {
return await apiFetch( {
- path: REST_MANUAL_CONNECTION_PATH,
+ path: REST_DIRECT_AUTHENTICATION_PATH,
method: 'POST',
data: {
clientId,
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index f112f6905..05abff8ee 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -69,10 +69,10 @@ class AuthenticationRestEndpoint extends RestEndpoint {
public function register_routes() {
register_rest_route(
$this->namespace,
- '/' . $this->rest_base,
+ '/' . $this->rest_base . '/direct',
array(
'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'connect_manual' ),
+ 'callback' => array( $this, 'connect_direct' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'clientId' => array(
@@ -99,11 +99,14 @@ class AuthenticationRestEndpoint extends RestEndpoint {
}
/**
- * Retrieves merchantId and email.
+ * Direct login: Retrieves merchantId and email using clientId and clientSecret.
+ *
+ * This is the "Manual Login" logic, when a merchant already knows their
+ * API credentials.
*
* @param WP_REST_Request $request Full data about the request.
*/
- public function connect_manual( WP_REST_Request $request ) : WP_REST_Response {
+ public function connect_direct( WP_REST_Request $request ) : WP_REST_Response {
$client_id = $request->get_param( 'clientId' );
$client_secret = $request->get_param( 'clientSecret' );
$use_sandbox = $request->get_param( 'useSandbox' );
From 565ee96bb6b463e10ea1e37126ec9efa071e84a0 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 14:43:33 +0100
Subject: [PATCH 20/57] =?UTF-8?q?=F0=9F=9A=A7=20First=20steps=20for=20the?=
=?UTF-8?q?=20final=20ISU=20authentication?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/constants.js | 10 ++++
.../Endpoint/AuthenticationRestEndpoint.php | 46 +++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js
index a94a62b33..853683555 100644
--- a/modules/ppcp-settings/resources/js/data/common/constants.js
+++ b/modules/ppcp-settings/resources/js/data/common/constants.js
@@ -45,6 +45,16 @@ export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common';
export const REST_DIRECT_AUTHENTICATION_PATH =
'/wc/v3/wc_paypal/authenticate/direct';
+/**
+ * REST path to perform the ISU authentication check, using shared ID and authCode.
+ *
+ * Used by: Controls
+ * See: AuthenticateRestEndpoint.php
+ *
+ * @type {string}
+ */
+export const REST_ISU_AUTHENTICATION_PATH = '/wc/v3/wc_paypal/authenticate/isu';
+
/**
* REST path to generate an ISU URL for the PayPal-login.
*
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 05abff8ee..39ea8f398 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -96,6 +96,34 @@ class AuthenticationRestEndpoint extends RestEndpoint {
),
)
);
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/isu',
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'connect_isu' ),
+ 'permission_callback' => array( $this, 'check_permission' ),
+ 'args' => array(
+ 'sharedId' => array(
+ 'requires' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ 'authCode' => array(
+ 'requires' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ 'useSandbox' => array(
+ 'requires' => false,
+ 'type' => 'boolean',
+ 'default' => false,
+ 'sanitize_callback' => array( $this, 'to_boolean' ),
+ ),
+ ),
+ )
+ );
}
/**
@@ -123,4 +151,22 @@ class AuthenticationRestEndpoint extends RestEndpoint {
return $this->return_success( $response );
}
+
+ /**
+ * ISU login: Retrieves clientId and clientSecret using a sharedId and authCode.
+ *
+ * This is the final step in the UI-driven login via the ISU popup, which
+ * is triggered by the LoginLinkRestEndpoint URL.
+ *
+ * @param WP_REST_Request $request Full data about the request.
+ */
+ public function connect_isu( WP_REST_Request $request ) : WP_REST_Response {
+ $shared_id = $request->get_param( 'sharedId' );
+ $auth_code = $request->get_param( 'authCode' );
+ $use_sandbox = $request->get_param( 'useSandbox' );
+
+ // TODO.
+
+ return $this->return_error( 'NOT IMPLEMENTED' );
+ }
}
From 084327c635cfe9a644d3f31fc1e41f08542dcf46 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 17:43:02 +0100
Subject: [PATCH 21/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20ambgious=20?=
=?UTF-8?q?hooks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two different hooks and one store property shared the same name. This commit resolves the ambiguity and makes all names unique
---
.../Screens/Onboarding/Components/AdvancedOptionsForm.js | 4 ++--
modules/ppcp-settings/resources/js/data/common/hooks.js | 2 +-
.../ppcp-settings/resources/js/hooks/useHandleConnections.js | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
index 5685cf014..0c5f1cebe 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
@@ -14,7 +14,7 @@ import Separator from '../../../ReusableComponents/Separator';
import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
import {
useSandboxConnection,
- useManualConnection,
+ useDirectAuthentication,
} from '../../../../hooks/useHandleConnections';
import ConnectionButton from './ConnectionButton';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
@@ -47,7 +47,7 @@ const AdvancedOptionsForm = () => {
setClientId,
clientSecret,
setClientSecret,
- } = useManualConnection();
+ } = useDirectAuthentication();
const refClientId = useRef( null );
const refClientSecret = useRef( null );
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 7057bbf2a..cfbf6adcc 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -97,7 +97,7 @@ export const useProduction = () => {
return { productionOnboardingUrl };
};
-export const useManualConnection = () => {
+export const useAuthentication = () => {
const {
isManualConnectionMode,
setManualConnectionMode,
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 9d860e1c8..09fc22787 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -189,7 +189,7 @@ export const useSandboxConnection = () => {
};
};
-export const useManualConnection = () => {
+export const useDirectAuthentication = () => {
const { handleFailed, handleCompleted, createErrorNotice } =
useConnectionBase();
const { withActivity } = CommonHooks.useBusyState();
@@ -201,7 +201,7 @@ export const useManualConnection = () => {
setClientId,
clientSecret,
setClientSecret,
- } = CommonHooks.useManualConnection();
+ } = CommonHooks.useAuthentication();
const handleConnectViaIdAndSecret = async ( { validation } = {} ) => {
return withActivity(
From 605275626891a8ad98c7e6b169e0d0c0b070dfb5 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 17:46:22 +0100
Subject: [PATCH 22/57] =?UTF-8?q?=F0=9F=9A=A7=20Prepare=20the=20ISU=20logi?=
=?UTF-8?q?n=20completion=20JS=20handler?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../js/hooks/useHandleConnections.js | 64 ++++++++++++-------
1 file changed, 42 insertions(+), 22 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 09fc22787..3c425f9c9 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -35,6 +35,7 @@ const MESSAGES = {
const ACTIVITIES = {
CONNECT_SANDBOX: 'ISU_LOGIN_SANDBOX',
CONNECT_PRODUCTION: 'ISU_LOGIN_PRODUCTION',
+ CONNECT_ISU: 'ISU_LOGIN',
CONNECT_MANUAL: 'MANUAL_LOGIN',
};
@@ -42,6 +43,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
const { productionOnboardingUrl } = CommonHooks.useProduction();
const products = OnboardingHooks.useDetermineProducts();
+ const { withActivity } = CommonHooks.useBusyState();
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
const [ scriptLoaded, setScriptLoaded ] = useState( false );
const timerRef = useRef( null );
@@ -108,31 +110,49 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
};
}, [ onboardingUrl ] );
- const setCompleteHandler = useCallback( ( environment ) => {
- const onComplete = ( authCode, shareId ) => {
- // TODO -- finish this!
- console.log(
- `${ environment }-boarding complete - AUTH: `,
- authCode
- );
- console.log(
- `${ environment }-boarding complete - SHARE:`,
- shareId
- );
- };
+ const setCompleteHandler = useCallback(
+ ( environment ) => {
+ const onComplete = async ( authCode, shareId ) => {
+ /**
+ * Until now, the full page is blocked by PayPal's semi-transparent, black overlay.
+ * But at this point, the overlay is removed, while we process the sharedId and
+ * authCode via a REST call.
+ *
+ * Note: The REST response is irrelevant, since PayPal will most likely refresh this
+ * frame before the REST endpoint returns a value. Using "withActivity" is more of a
+ * visual cue to the user that something is still processing in the background.
+ */
+ await withActivity(
+ ACTIVITIES.CONNECT_ISU,
+ 'Validating the connection details',
+ async () => {
+ // TODO -- finish this!
+ console.log(
+ `${ environment }-boarding complete - AUTH: `,
+ authCode
+ );
+ console.log(
+ `${ environment }-boarding complete - SHARE:`,
+ shareId
+ );
+ }
+ );
+ };
- const addHandler = () => {
- const MiniBrowser = window.PAYPAL?.apps?.Signup?.MiniBrowser;
- if ( ! MiniBrowser || MiniBrowser.onOnboardComplete ) {
- return;
- }
+ const addHandler = () => {
+ const MiniBrowser = window.PAYPAL?.apps?.Signup?.MiniBrowser;
+ if ( ! MiniBrowser || MiniBrowser.onOnboardComplete ) {
+ return;
+ }
- MiniBrowser.onOnboardComplete = onComplete;
- };
+ MiniBrowser.onOnboardComplete = onComplete;
+ };
- // Ensure the onComplete handler is not removed by a PayPal init script.
- timerRef.current = setInterval( addHandler, 250 );
- }, [] );
+ // Ensure the onComplete handler is not removed by a PayPal init script.
+ timerRef.current = setInterval( addHandler, 250 );
+ },
+ [ withActivity ]
+ );
const removeCompleteHandler = useCallback( () => {
if ( timerRef.current ) {
From 69d169533d2742c1978ef29669e8257cf99e03f0 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 17:59:33 +0100
Subject: [PATCH 23/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Unify=20function-=20?=
=?UTF-8?q?and=20hook=20names?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Screens/Onboarding/Components/AdvancedOptionsForm.js | 6 +++---
modules/ppcp-settings/resources/js/data/common/actions.js | 4 ++--
modules/ppcp-settings/resources/js/data/common/hooks.js | 8 ++++----
.../resources/js/hooks/useHandleConnections.js | 8 ++++----
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
index 0c5f1cebe..3ac56cc65 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
@@ -40,7 +40,7 @@ const AdvancedOptionsForm = () => {
const { isSandboxMode, setSandboxMode } = useSandboxConnection();
const {
- handleConnectViaIdAndSecret,
+ handleDirectAuthentication,
isManualConnectionMode,
setManualConnectionMode,
clientId,
@@ -83,10 +83,10 @@ const AdvancedOptionsForm = () => {
const handleManualConnect = useCallback(
() =>
- handleConnectViaIdAndSecret( {
+ handleDirectAuthentication( {
validation: validateManualConnectionForm,
} ),
- [ handleConnectViaIdAndSecret, validateManualConnectionForm ]
+ [ handleDirectAuthentication, validateManualConnectionForm ]
);
useEffect( () => {
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index 2b6dda353..a19697e16 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -173,11 +173,11 @@ export const productionOnboardingUrl = function* ( products = [] ) {
};
/**
- * Side effect. Initiates a manual connection attempt using the provided client ID and secret.
+ * Side effect. Initiates a direct connection attempt using the provided client ID and secret.
*
* @return {Action} The action.
*/
-export const connectViaIdAndSecret = function* () {
+export const connectViaSecret = function* () {
const { clientId, clientSecret, useSandbox } =
yield select( STORE_NAME ).persistentData();
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index cfbf6adcc..9e8101a76 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -33,7 +33,7 @@ const useHooks = () => {
setClientSecret,
sandboxOnboardingUrl,
productionOnboardingUrl,
- connectViaIdAndSecret,
+ connectViaSecret,
} = useDispatch( STORE_NAME );
// Transient accessors.
@@ -79,7 +79,7 @@ const useHooks = () => {
},
sandboxOnboardingUrl,
productionOnboardingUrl,
- connectViaIdAndSecret,
+ connectViaSecret,
merchant,
wooSettings,
};
@@ -105,7 +105,7 @@ export const useAuthentication = () => {
setClientId,
clientSecret,
setClientSecret,
- connectViaIdAndSecret,
+ connectViaSecret,
} = useHooks();
return {
@@ -115,7 +115,7 @@ export const useAuthentication = () => {
setClientId,
clientSecret,
setClientSecret,
- connectViaIdAndSecret,
+ connectViaSecret,
};
};
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 3c425f9c9..2d140e6d0 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -214,7 +214,7 @@ export const useDirectAuthentication = () => {
useConnectionBase();
const { withActivity } = CommonHooks.useBusyState();
const {
- connectViaIdAndSecret,
+ connectViaSecret,
isManualConnectionMode,
setManualConnectionMode,
clientId,
@@ -223,7 +223,7 @@ export const useDirectAuthentication = () => {
setClientSecret,
} = CommonHooks.useAuthentication();
- const handleConnectViaIdAndSecret = async ( { validation } = {} ) => {
+ const handleDirectAuthentication = async ( { validation } = {} ) => {
return withActivity(
ACTIVITIES.CONNECT_MANUAL,
'Connecting manually via Client ID and Secret',
@@ -237,7 +237,7 @@ export const useDirectAuthentication = () => {
}
}
- const res = await connectViaIdAndSecret();
+ const res = await connectViaSecret();
if ( res.success ) {
await handleCompleted();
@@ -251,7 +251,7 @@ export const useDirectAuthentication = () => {
};
return {
- handleConnectViaIdAndSecret,
+ handleDirectAuthentication,
isManualConnectionMode,
setManualConnectionMode,
clientId,
From 1246a02f07c88b2faed1263a4504d04f8bea8ed7 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 18:19:41 +0100
Subject: [PATCH 24/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Adjust=20naming=20to?=
=?UTF-8?q?=20new=20=E2=80=9Cauthentication=E2=80=9D=20pattern?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/resources/js/data/common/action-types.js | 2 +-
modules/ppcp-settings/resources/js/data/common/actions.js | 2 +-
modules/ppcp-settings/resources/js/data/common/controls.js | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js
index 5c9c83fe5..60db34c30 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -19,7 +19,7 @@ export default {
// Controls - always start with "DO_".
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
- DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION',
+ DO_MANUAL_AUTHENTICATION: 'COMMON:DO_MANUAL_AUTHENTICATION',
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index a19697e16..80edd13dc 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -182,7 +182,7 @@ export const connectViaSecret = function* () {
yield select( STORE_NAME ).persistentData();
return yield {
- type: ACTION_TYPES.DO_MANUAL_CONNECTION,
+ type: ACTION_TYPES.DO_MANUAL_AUTHENTICATION,
clientId,
clientSecret,
useSandbox,
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index ffbc736b6..075dfebe3 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -49,7 +49,7 @@ export const controls = {
}
},
- async [ ACTION_TYPES.DO_MANUAL_CONNECTION ]( {
+ async [ ACTION_TYPES.DO_MANUAL_AUTHENTICATION ]( {
clientId,
clientSecret,
useSandbox,
From 0d5832aa8b945796c44be2465db4ae435a4cfe6d Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 18:22:09 +0100
Subject: [PATCH 25/57] =?UTF-8?q?=E2=9C=A8=20Add=20React=20logic=20for=20f?=
=?UTF-8?q?inal=20ISU=20authentication?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/action-types.js | 1 +
.../resources/js/data/common/actions.js | 28 +++++++++++++++++++
.../resources/js/data/common/controls.js | 24 ++++++++++++++++
.../resources/js/data/common/hooks.js | 4 +++
.../js/hooks/useHandleConnections.js | 15 ++++------
5 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js
index 60db34c30..229a01c6d 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -20,6 +20,7 @@ export default {
// Controls - always start with "DO_".
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
DO_MANUAL_AUTHENTICATION: 'COMMON:DO_MANUAL_AUTHENTICATION',
+ DO_ISU_AUTHENTICATION: 'COMMON:DO_ISU_AUTHENTICATION',
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index 80edd13dc..dbf27a174 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -189,6 +189,34 @@ export const connectViaSecret = function* () {
};
};
+/**
+ * Side effect. Completes the ISU login by authenticating the user via the one time sharedId and
+ * authCode provided by PayPal.
+ *
+ * This action accepts parameters instead of fetching data from the Redux state because all
+ * parameters are dynamically generated during the authentication process, and not managed by our
+ * Redux store.
+ *
+ * @param {string} sharedId - One-time authentication ID that PayPal "shares" with us.
+ * @param {string} authCode - Matching one-time authentication code to validate the login.
+ * @param {string} environment - [production|sandbox].
+ * @return {Action} The action.
+ */
+export const connectViaAuthCode = function* (
+ sharedId,
+ authCode,
+ environment
+) {
+ const useSandbox = 'sandbox' === environment;
+
+ return yield {
+ type: ACTION_TYPES.DO_ISU_AUTHENTICATION,
+ sharedId,
+ authCode,
+ useSandbox,
+ };
+};
+
/**
* Side effect. Clears and refreshes the merchant data via a REST request.
*
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index 075dfebe3..870bca49d 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -15,6 +15,7 @@ import {
REST_CONNECTION_URL_PATH,
REST_HYDRATE_MERCHANT_PATH,
REST_REFRESH_FEATURES_PATH,
+ REST_ISU_AUTHENTICATION_PATH,
} from './constants';
import ACTION_TYPES from './action-types';
@@ -72,6 +73,29 @@ export const controls = {
}
},
+ async [ ACTION_TYPES.DO_ISU_AUTHENTICATION ]( {
+ sharedId,
+ authCode,
+ useSandbox,
+ } ) {
+ try {
+ return await apiFetch( {
+ path: REST_ISU_AUTHENTICATION_PATH,
+ method: 'POST',
+ data: {
+ sharedId,
+ authCode,
+ useSandbox,
+ },
+ } );
+ } catch ( e ) {
+ return {
+ success: false,
+ error: e,
+ };
+ }
+ },
+
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
try {
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 9e8101a76..32ab33e6f 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -34,6 +34,7 @@ const useHooks = () => {
sandboxOnboardingUrl,
productionOnboardingUrl,
connectViaSecret,
+ connectViaAuthCode,
} = useDispatch( STORE_NAME );
// Transient accessors.
@@ -80,6 +81,7 @@ const useHooks = () => {
sandboxOnboardingUrl,
productionOnboardingUrl,
connectViaSecret,
+ connectViaAuthCode,
merchant,
wooSettings,
};
@@ -106,6 +108,7 @@ export const useAuthentication = () => {
clientSecret,
setClientSecret,
connectViaSecret,
+ connectViaAuthCode,
} = useHooks();
return {
@@ -116,6 +119,7 @@ export const useAuthentication = () => {
clientSecret,
setClientSecret,
connectViaSecret,
+ connectViaAuthCode,
};
};
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 2d140e6d0..6ad4c8a90 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -44,6 +44,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const { productionOnboardingUrl } = CommonHooks.useProduction();
const products = OnboardingHooks.useDetermineProducts();
const { withActivity } = CommonHooks.useBusyState();
+ const { connectViaAuthCode } = CommonHooks.useAuthentication();
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
const [ scriptLoaded, setScriptLoaded ] = useState( false );
const timerRef = useRef( null );
@@ -112,7 +113,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const setCompleteHandler = useCallback(
( environment ) => {
- const onComplete = async ( authCode, shareId ) => {
+ const onComplete = async ( authCode, sharedId ) => {
/**
* Until now, the full page is blocked by PayPal's semi-transparent, black overlay.
* But at this point, the overlay is removed, while we process the sharedId and
@@ -126,14 +127,10 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
ACTIVITIES.CONNECT_ISU,
'Validating the connection details',
async () => {
- // TODO -- finish this!
- console.log(
- `${ environment }-boarding complete - AUTH: `,
- authCode
- );
- console.log(
- `${ environment }-boarding complete - SHARE:`,
- shareId
+ await connectViaAuthCode(
+ authCode,
+ sharedId,
+ environment
);
}
);
From de3ab2c479473df048a41af6120bfc2a9ecc7edf Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 18:22:53 +0100
Subject: [PATCH 26/57] =?UTF-8?q?=F0=9F=92=AC=20Add=20missing=20prefix=20t?=
=?UTF-8?q?o=20an=20unrelated=20action-type?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/resources/js/data/common/action-types.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js
index 229a01c6d..d607dc96e 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -23,5 +23,5 @@ export default {
DO_ISU_AUTHENTICATION: 'COMMON:DO_ISU_AUTHENTICATION',
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
- DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
+ DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
};
From 143bcd75a11e4105563240ce8fc7b50ce68dfabe Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 18:52:47 +0100
Subject: [PATCH 27/57] =?UTF-8?q?=F0=9F=92=A1=20Add=20comments=20to=20docu?=
=?UTF-8?q?ment=20auth=20flow?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/src/Service/ConnectionManager.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index 1cffb155c..1554d5045 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -87,6 +87,8 @@ class ConnectionManager {
/**
* Checks if the provided ID and secret have a valid format.
*
+ * Part of the "Direct Connection" (Manual Connection) flow.
+ *
* On failure, an Exception is thrown, while a successful check does not
* generate any return value.
*
@@ -113,6 +115,8 @@ class ConnectionManager {
* Disconnects the current merchant, and then attempts to connect to a
* PayPal account using a client ID and secret.
*
+ * Part of the "Direct Connection" (Manual Connection) flow.
+ *
* @param bool $use_sandbox Whether to use the sandbox mode.
* @param string $client_id The client ID.
* @param string $client_secret The client secret.
From 0cc451b66b907026bbd9c2f6ba924d3fc0dc3b0e Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 18:54:03 +0100
Subject: [PATCH 28/57] =?UTF-8?q?=F0=9F=9A=A7=20Start=20with=20server-side?=
=?UTF-8?q?=20ISU=20authentication?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Endpoint/AuthenticationRestEndpoint.php | 12 ++++-
.../src/Service/ConnectionManager.php | 51 +++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 39ea8f398..22cd6f472 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -165,8 +165,16 @@ class AuthenticationRestEndpoint extends RestEndpoint {
$auth_code = $request->get_param( 'authCode' );
$use_sandbox = $request->get_param( 'useSandbox' );
- // TODO.
+ try {
+ $this->connection_manager->validate_id_and_auth_code( $shared_id, $auth_code );
+ $this->connection_manager->connect_via_auth_code( $use_sandbox, $shared_id, $auth_code );
+ } catch ( Exception $exception ) {
+ return $this->return_error( $exception->getMessage() );
+ }
- return $this->return_error( 'NOT IMPLEMENTED' );
+ $account = $this->connection_manager->get_account_details();
+ $response = $this->sanitize_for_javascript( $this->response_map, $account );
+
+ return $this->return_success( $response );
}
}
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index 1554d5045..dbbf0d8fc 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -140,6 +140,57 @@ class ConnectionManager {
}
+ /**
+ * Checks if the provided ID and auth-code have a valid format.
+ *
+ * Part of the "ISU Connection" (login via Popup) flow.
+ *
+ * On failure, an Exception is thrown, while a successful check does not
+ * generate any return value. Note, that we did not find official documentation
+ * on those values, so we only check if they are non-empty strings.
+ *
+ * @param string $shared_id The shared onboarding ID.
+ * @param string $auth_code The authorization code.
+ * @return void
+ * @throws RuntimeException When invalid shared ID or auth provided.
+ */
+ public function validate_id_and_auth_code( string $shared_id, string $auth_code ) : void {
+ if ( empty( $shared_id ) ) {
+ throw new RuntimeException( 'No onboarding ID provided.' );
+ }
+
+ if ( empty( $auth_code ) ) {
+ throw new RuntimeException( 'No authorization code provided.' );
+ }
+ }
+
+ /**
+ * Disconnects the current merchant, and then attempts to connect to a
+ * PayPal account the onboarding ID and authorization ID.
+ *
+ * Part of the "ISU Connection" (login via Popup) flow.
+ *
+ * @param bool $use_sandbox Whether to use the sandbox mode.
+ * @param string $shared_id The shared onboarding ID.
+ * @param string $auth_code The authorization code.
+ * @return void
+ * @throws RuntimeException When failed to retrieve payee.
+ */
+ public function connect_via_auth_code( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
+ $this->disconnect();
+
+ $this->logger->info(
+ 'Attempting ISU login to PayPal...',
+ array(
+ 'sandbox' => $use_sandbox,
+ 'shared_id' => $shared_id,
+ )
+ );
+
+ // TODO ...
+ }
+
+
// ----------------------------------------------------------------------------
// Internal helper methods
From 67a3fe034bc43332a320f4a00f0e8e97cdb37061 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 20:39:16 +0100
Subject: [PATCH 29/57] =?UTF-8?q?=F0=9F=90=9B=20Fix=20REST=20argument=20sa?=
=?UTF-8?q?nitation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Endpoint/AuthenticationRestEndpoint.php | 13 ++++++-------
modules/ppcp-settings/src/Endpoint/RestEndpoint.php | 4 ++--
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 22cd6f472..f3c26c15f 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -76,19 +76,19 @@ class AuthenticationRestEndpoint extends RestEndpoint {
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'clientId' => array(
- 'requires' => true,
+ 'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'minLength' => 80,
'maxLength' => 80,
),
'clientSecret' => array(
- 'requires' => true,
+ 'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
'useSandbox' => array(
- 'requires' => false,
+ 'required' => false,
'type' => 'boolean',
'default' => false,
'sanitize_callback' => array( $this, 'to_boolean' ),
@@ -106,19 +106,18 @@ class AuthenticationRestEndpoint extends RestEndpoint {
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'sharedId' => array(
- 'requires' => true,
+ 'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
'authCode' => array(
- 'requires' => true,
+ 'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
'useSandbox' => array(
- 'requires' => false,
+ 'default' => 0,
'type' => 'boolean',
- 'default' => false,
'sanitize_callback' => array( $this, 'to_boolean' ),
),
),
diff --git a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
index 850a70f87..6f1eb0e4f 100644
--- a/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/RestEndpoint.php
@@ -157,7 +157,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
*
* @return bool|null The boolean value, or null if not set.
*/
- protected function to_boolean( $value ) : ?bool {
+ public function to_boolean( $value ) : ?bool {
return $value !== null ? (bool) $value : null;
}
@@ -168,7 +168,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
*
* @return int|float|null The numeric value, or null if not set.
*/
- protected function to_number( $value ) {
+ public function to_number( $value ) {
if ( $value !== null ) {
$value = is_numeric( $value ) ? $value + 0 : null;
}
From e2b169aff49d1c69175e0fbd3e994a1bb74d6548 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 2 Jan 2025 20:40:11 +0100
Subject: [PATCH 30/57] =?UTF-8?q?=F0=9F=9A=A7=20Authentication=20progress,?=
=?UTF-8?q?=20still=20non-functional?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 4 +
.../src/Service/ConnectionManager.php | 107 +++++++++++++++---
2 files changed, 96 insertions(+), 15 deletions(-)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index 7dde67edd..9bdc289c9 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -24,6 +24,7 @@ use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
+use WooCommerce\PayPalCommerce\Settings\Service\EnvironmentConfig;
return array(
'settings.url' => static function ( ContainerInterface $container ) : string {
@@ -195,6 +196,9 @@ return array(
$container->get( 'settings.data.common' ),
$container->get( 'api.paypal-host-production' ),
$container->get( 'api.paypal-host-sandbox' ),
+ $container->get( 'api.endpoint.login-seller-production' ),
+ $container->get( 'api.endpoint.login-seller-sandbox' ),
+ $container->get( 'api.repository.partner-referrals-data' ),
$container->get( 'woocommerce.logger.woocommerce' ),
);
},
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index dbbf0d8fc..c2d8292cc 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -11,10 +11,15 @@ namespace WooCommerce\PayPalCommerce\Settings\Service;
use Psr\Log\LoggerInterface;
use RuntimeException;
+use JsonException;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
+use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
+use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
+use Automattic\Jetpack\Partner;
/**
* Class that manages the connection to PayPal.
@@ -35,27 +40,58 @@ class ConnectionManager {
private LoggerInterface $logger;
/**
- * Base URLs for the manual connection attempt.
+ * Base URLs for the manual connection attempt, by environment.
*
* @var array
*/
private array $connection_hosts;
+ /**
+ * Login API handler instances, by environment.
+ *
+ * @var array
+ */
+ private array $login_endpoints;
+
+ /**
+ * Onboarding referrals data.
+ *
+ * @var PartnerReferralsData
+ */
+ private PartnerReferralsData $referrals_data;
+
/**
* Constructor.
*
- * @param CommonSettings $common_settings Data model that stores the connection details.
- * @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 Logging instance.
+ * @param CommonSettings $common_settings Data model that stores the connection
+ * details.
+ * @param string $live_host The API host for the live mode.
+ * @param string $sandbox_host The API host for the sandbox mode.
+ * @param LoginSeller $live_login_endpoint API handler to fetch live-merchant
+ * credentials.
+ * @param LoginSeller $sandbox_login_endpoint API handler to fetch sandbox-merchant
+ * credentials.
+ * @param PartnerReferralsData $referrals_data Partner referrals data.
+ * @param ?LoggerInterface $logger Logging instance.
*/
- public function __construct( CommonSettings $common_settings, string $live_host, string $sandbox_host, LoggerInterface $logger ) {
- $this->common_settings = $common_settings;
- $this->logger = $logger;
+ public function __construct(
+ CommonSettings $common_settings, string $live_host, string $sandbox_host,
+ LoginSeller $live_login_endpoint, LoginSeller $sandbox_login_endpoint,
+ PartnerReferralsData $referrals_data,
+ ?LoggerInterface $logger = null
+ ) {
+ $this->common_settings = $common_settings;
+ $this->logger = $logger ?: new NullLogger();
+
$this->connection_hosts = array(
'live' => $live_host,
'sandbox' => $sandbox_host,
);
+ $this->login_endpoints = array(
+ 'live' => $live_login_endpoint,
+ 'sandbox' => $sandbox_login_endpoint,
+ );
+ $this->referrals_data = $referrals_data;
}
/**
@@ -187,7 +223,10 @@ class ConnectionManager {
)
);
- // TODO ...
+ $credentials = $this->get_credentials( $shared_id, $auth_code, $use_sandbox );
+
+ // TODO.
+ // $this->update_connection_details( $use_sandbox, $payee['merchant_id'], $payee['email_address'] );
}
@@ -205,9 +244,21 @@ class ConnectionManager {
return $for_sandbox ? $this->connection_hosts['sandbox'] : $this->connection_hosts['live'];
}
+ /**
+ * Returns an API handler to fetch merchant credentials.
+ *
+ * @param bool $for_sandbox Whether to return the sandbox API handler.
+ * @return LoginSeller
+ */
+ private function get_login_endpoint( bool $for_sandbox = false ) : LoginSeller {
+ return $for_sandbox ? $this->login_endpoints['sandbox'] : $this->login_endpoints['live'];
+ }
+
/**
* Retrieves the payee object with the merchant data by creating a minimal PayPal order.
*
+ * Part of the "Direct Connection" (Manual Connection) flow.
+ *
* @param string $client_id The client ID.
* @param string $client_secret The client secret.
* @param bool $use_sandbox Whether to use the sandbox mode.
@@ -249,13 +300,16 @@ class ConnectionManager {
),
);
- $response = $orders->create( $request_body );
- $body = json_decode( $response['body'] );
+ try {
+ $response = $orders->create( $request_body );
+ $body = json_decode( $response['body'], false, 512, JSON_THROW_ON_ERROR );
+ $order_id = $body->id;
- $order_id = $body->id;
-
- $order_response = $orders->order( $order_id );
- $order_body = json_decode( $order_response['body'] );
+ $order_response = $orders->order( $order_id );
+ $order_body = json_decode( $order_response['body'], false, 512, JSON_THROW_ON_ERROR );
+ } catch ( JsonException $exception ) {
+ throw new RuntimeException( 'Could not decode JSON response: ' . $exception->getMessage() );
+ }
$pu = $order_body->purchase_units[0];
$payee = $pu->payee;
@@ -273,6 +327,28 @@ class ConnectionManager {
);
}
+ /**
+ * Fetches merchant API credentials using a shared onboarding ID and
+ * authorization code.
+ *
+ * Part of the "ISU Connection" (login via Popup) flow.
+ *
+ * @param string $shared_id The shared onboarding ID.
+ * @param string $auth_code The authorization code.
+ * @param bool $use_sandbox Whether to use the sandbox mode.
+ * @return array
+ */
+ private function get_credentials( string $shared_id, string $auth_code, bool $use_sandbox ) : array {
+ $login_handler = $this->get_login_endpoint( $use_sandbox );
+ $nonce = $this->referrals_data->nonce();
+
+ // TODO. Always throws the exception "No token found."
+ $response = $login_handler->credentials_for( $shared_id, $auth_code, $nonce );
+
+ // TODO.
+ return (array) $response;
+ }
+
/**
* Stores the provided details in the data model.
*
@@ -294,4 +370,5 @@ class ConnectionManager {
$this->common_settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
$this->common_settings->save();
}
+
}
From ed13064a169929d9cbd707bdcf7140b94d449c91 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 3 Jan 2025 12:12:25 +0100
Subject: [PATCH 31/57] =?UTF-8?q?=E2=9C=A8=20Create=20new=20environment-co?=
=?UTF-8?q?nfig-container?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Helper/EnvironmentConfig.php | 79 +++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php
diff --git a/modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php b/modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php
new file mode 100644
index 000000000..1542de783
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php
@@ -0,0 +1,79 @@
+production_value = $production_value;
+ $this->sandbox_value = $sandbox_value;
+ }
+
+ /**
+ * Factory method to create a validated EnvironmentConfig.
+ *
+ * @template U
+ * @param string $data_type Expected type for the values (class name or primitive type).
+ * @param U $production_value Value for production environment.
+ * @param U $sandbox_value Value for the sandbox environment.
+ * @return self
+ */
+ public static function create( string $data_type, $production_value, $sandbox_value ) : self {
+ assert(
+ gettype( $production_value ) === $data_type || $production_value instanceof $data_type,
+ "Production value must be of type '$data_type'"
+ );
+ assert(
+ gettype( $sandbox_value ) === $data_type || $sandbox_value instanceof $data_type,
+ "Sandbox value must be of type '$data_type'"
+ );
+
+ return new self( $production_value, $sandbox_value );
+ }
+
+ /**
+ * Get the value for the specified environment.
+ *
+ * @param bool $for_sandbox Whether to get the sandbox value.
+ * @return T The value for the specified environment.
+ */
+ public function get_value( bool $for_sandbox = false ) {
+ return $for_sandbox ? $this->sandbox_value : $this->production_value;
+ }
+}
From a1f80f1d3df4319091b94444ff17ddeda4895d57 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 3 Jan 2025 12:22:06 +0100
Subject: [PATCH 32/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20to=20use?=
=?UTF-8?q?=20the=20EnvironmentConfig?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-api-client/services.php | 17 ++++
modules/ppcp-settings/services.php | 6 +-
.../src/Service/ConnectionManager.php | 78 ++++++-------------
3 files changed, 42 insertions(+), 59 deletions(-)
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 280438b80..8665c3c75 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -79,6 +79,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
return array(
'api.host' => function( ContainerInterface $container ) : string {
@@ -879,4 +880,20 @@ return array(
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
},
+ 'api.env.paypal-host' => static function ( ContainerInterface $container ) : EnvironmentConfig {
+ /** @type EnvironmentConfig Configuration object */
+ return EnvironmentConfig::create(
+ 'string',
+ $container->get( 'api.paypal-host-production' ),
+ $container->get( 'api.paypal-host-sandbox' )
+ );
+ },
+ 'api.env.endpoint.login-seller' => static function ( ContainerInterface $container ) : EnvironmentConfig {
+ /** @type EnvironmentConfig Configuration object */
+ return EnvironmentConfig::create(
+ LoginSeller::class,
+ $container->get( 'api.endpoint.login-seller-production' ),
+ $container->get( 'api.endpoint.login-seller-sandbox' )
+ );
+ },
);
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index fc80dd6a0..98a37542b 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -201,10 +201,8 @@ return array(
'settings.service.connection_manager' => static function ( ContainerInterface $container ) : ConnectionManager {
return new ConnectionManager(
$container->get( 'settings.data.common' ),
- $container->get( 'api.paypal-host-production' ),
- $container->get( 'api.paypal-host-sandbox' ),
- $container->get( 'api.endpoint.login-seller-production' ),
- $container->get( 'api.endpoint.login-seller-sandbox' ),
+ $container->get( 'api.env.paypal-host' ),
+ $container->get( 'api.env.endpoint.login-seller' ),
$container->get( 'api.repository.partner-referrals-data' ),
$container->get( 'woocommerce.logger.woocommerce' ),
);
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index c2d8292cc..58303d2a6 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -9,17 +9,17 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Service;
+use JsonException;
use Psr\Log\LoggerInterface;
use RuntimeException;
-use JsonException;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
-use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
-use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
-use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
-use Automattic\Jetpack\Partner;
+use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
+use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
/**
* Class that manages the connection to PayPal.
@@ -42,16 +42,16 @@ class ConnectionManager {
/**
* Base URLs for the manual connection attempt, by environment.
*
- * @var array
+ * @var EnvironmentConfig
*/
- private array $connection_hosts;
+ private EnvironmentConfig $connection_host;
/**
* Login API handler instances, by environment.
*
- * @var array
+ * @var EnvironmentConfig
*/
- private array $login_endpoints;
+ private EnvironmentConfig $login_endpoint;
/**
* Onboarding referrals data.
@@ -63,35 +63,24 @@ class ConnectionManager {
/**
* Constructor.
*
- * @param CommonSettings $common_settings Data model that stores the connection
- * details.
- * @param string $live_host The API host for the live mode.
- * @param string $sandbox_host The API host for the sandbox mode.
- * @param LoginSeller $live_login_endpoint API handler to fetch live-merchant
- * credentials.
- * @param LoginSeller $sandbox_login_endpoint API handler to fetch sandbox-merchant
- * credentials.
- * @param PartnerReferralsData $referrals_data Partner referrals data.
- * @param ?LoggerInterface $logger Logging instance.
+ * @param CommonSettings $common_settings Data model that stores the connection details.
+ * @param EnvironmentConfig $connection_host API host for direct authentication.
+ * @param EnvironmentConfig $login_endpoint API handler to fetch merchant credentials.
+ * @param PartnerReferralsData $referrals_data Partner referrals data.
+ * @param ?LoggerInterface $logger Logging instance.
*/
public function __construct(
- CommonSettings $common_settings, string $live_host, string $sandbox_host,
- LoginSeller $live_login_endpoint, LoginSeller $sandbox_login_endpoint,
+ CommonSettings $common_settings,
+ EnvironmentConfig $connection_host,
+ EnvironmentConfig $login_endpoint,
PartnerReferralsData $referrals_data,
?LoggerInterface $logger = null
) {
$this->common_settings = $common_settings;
+ $this->connection_host = $connection_host;
+ $this->login_endpoint = $login_endpoint;
+ $this->referrals_data = $referrals_data;
$this->logger = $logger ?: new NullLogger();
-
- $this->connection_hosts = array(
- 'live' => $live_host,
- 'sandbox' => $sandbox_host,
- );
- $this->login_endpoints = array(
- 'live' => $live_login_endpoint,
- 'sandbox' => $sandbox_login_endpoint,
- );
- $this->referrals_data = $referrals_data;
}
/**
@@ -226,7 +215,6 @@ class ConnectionManager {
$credentials = $this->get_credentials( $shared_id, $auth_code, $use_sandbox );
// TODO.
- // $this->update_connection_details( $use_sandbox, $payee['merchant_id'], $payee['email_address'] );
}
@@ -234,26 +222,6 @@ class ConnectionManager {
// Internal helper methods
- /**
- * Returns the API host for the relevant environment.
- *
- * @param bool $for_sandbox Whether to return the sandbox API host.
- * @return string
- */
- private function get_host( bool $for_sandbox = false ) : string {
- return $for_sandbox ? $this->connection_hosts['sandbox'] : $this->connection_hosts['live'];
- }
-
- /**
- * Returns an API handler to fetch merchant credentials.
- *
- * @param bool $for_sandbox Whether to return the sandbox API handler.
- * @return LoginSeller
- */
- private function get_login_endpoint( bool $for_sandbox = false ) : LoginSeller {
- return $for_sandbox ? $this->login_endpoints['sandbox'] : $this->login_endpoints['live'];
- }
-
/**
* Retrieves the payee object with the merchant data by creating a minimal PayPal order.
*
@@ -271,7 +239,7 @@ class ConnectionManager {
string $client_secret,
bool $use_sandbox
) : array {
- $host = $this->get_host( $use_sandbox );
+ $host = $this->connection_host->get_value( $use_sandbox );
$bearer = new PayPalBearer(
new InMemoryCache(),
@@ -339,10 +307,10 @@ class ConnectionManager {
* @return array
*/
private function get_credentials( string $shared_id, string $auth_code, bool $use_sandbox ) : array {
- $login_handler = $this->get_login_endpoint( $use_sandbox );
+ $login_handler = $this->login_endpoint->get_value( $use_sandbox );
$nonce = $this->referrals_data->nonce();
- // TODO. Always throws the exception "No token found."
+ // TODO. Always throws the exception "No token found.".
$response = $login_handler->credentials_for( $shared_id, $auth_code, $nonce );
// TODO.
From 3f691bea012d327606e06032e204bd3a160cd81d Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 3 Jan 2025 13:48:26 +0100
Subject: [PATCH 33/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20EnvironmentCon?=
=?UTF-8?q?fig=20in=20ConnectionUrlGenerator?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-api-client/services.php | 8 +++
.../resources/js/data/common/actions.js | 4 +-
.../resources/js/data/common/controls.js | 4 +-
modules/ppcp-settings/services.php | 32 +++--------
.../src/Endpoint/LoginLinkRestEndpoint.php | 36 +++++-------
.../src/Service/ConnectionUrlGenerator.php | 55 +++++++------------
6 files changed, 54 insertions(+), 85 deletions(-)
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 8665c3c75..ab58194a7 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -896,4 +896,12 @@ return array(
$container->get( 'api.endpoint.login-seller-sandbox' )
);
},
+ 'api.env.endpoint.partner-referrals' => static function ( ContainerInterface $container ) : EnvironmentConfig {
+ /** @type EnvironmentConfig Configuration object */
+ return EnvironmentConfig::create(
+ PartnerReferrals::class,
+ $container->get( 'api.endpoint.partner-referrals-production' ),
+ $container->get( 'api.endpoint.partner-referrals-sandbox' )
+ );
+ },
);
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index 83e252d92..a8291787b 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -153,7 +153,7 @@ export const persist = function* () {
export const sandboxOnboardingUrl = function* () {
return yield {
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
- environment: 'sandbox',
+ useSandbox: true,
products: [ 'EXPRESS_CHECKOUT' ],
};
};
@@ -167,7 +167,7 @@ export const sandboxOnboardingUrl = function* () {
export const productionOnboardingUrl = function* ( products = [] ) {
return yield {
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
- environment: 'production',
+ useSandbox: false,
products,
};
};
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index cbe851982..dac31147c 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -36,13 +36,13 @@ export const controls = {
async [ ACTION_TYPES.DO_GENERATE_ONBOARDING_URL ]( {
products,
- environment,
+ useSandbox,
} ) {
try {
return apiFetch( {
path: REST_CONNECTION_URL_PATH,
method: 'POST',
- data: { environment, products },
+ data: { useSandbox, products },
} );
} catch ( e ) {
return {
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index 98a37542b..f19dc750f 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -87,7 +87,7 @@ return array(
},
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
return new LoginLinkRestEndpoint(
- $container->get( 'settings.service.connection-url-generators' ),
+ $container->get( 'settings.service.connection-url-generator' ),
);
},
'settings.rest.webhooks' => static function ( ContainerInterface $container ) : WebhookSettingsEndpoint {
@@ -172,31 +172,13 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
- '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' ),
- ),
+ 'settings.service.connection-url-generator' => static function ( ContainerInterface $container ) : ConnectionUrlGenerator {
+ return new ConnectionUrlGenerator(
+ $container->get( 'api.env.endpoint.partner-referrals' ),
+ $container->get( 'api.repository.partner-referrals-data' ),
+ $container->get( 'settings.service.onboarding-url-manager' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
);
-
- $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' ),
- $environment,
- $container->get( 'settings.service.onboarding-url-manager' ),
- $container->get( 'woocommerce.logger.woocommerce' )
- );
- }
-
- return $generators;
},
'settings.service.connection_manager' => static function ( ContainerInterface $container ) : ConnectionManager {
return new ConnectionManager(
diff --git a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
index 722a20be8..7ddee27a5 100644
--- a/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/LoginLinkRestEndpoint.php
@@ -33,25 +33,25 @@ class LoginLinkRestEndpoint extends RestEndpoint {
protected $rest_base = 'login_link';
/**
- * Link generator list, with environment name as array key.
+ * Login-URL generator.
*
- * @var ConnectionUrlGenerator[]
+ * @var ConnectionUrlGenerator
*/
- protected array $url_generators;
+ protected ConnectionUrlGenerator $url_generator;
/**
* Constructor.
*
- * @param ConnectionUrlGenerator[] $url_generators Array of environment-specific URL generators.
+ * @param ConnectionUrlGenerator $url_generator Login-URL generator.
*/
- public function __construct( array $url_generators ) {
- $this->url_generators = $url_generators;
+ public function __construct( ConnectionUrlGenerator $url_generator ) {
+ $this->url_generator = $url_generator;
}
/**
* Configure REST API routes.
*/
- public function register_routes() {
+ public function register_routes() : void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
@@ -60,11 +60,12 @@ class LoginLinkRestEndpoint extends RestEndpoint {
'callback' => array( $this, 'get_login_url' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
- 'environment' => array(
- 'required' => true,
- 'type' => 'string',
+ 'useSandbox' => array(
+ 'default' => 0,
+ 'type' => 'boolean',
+ 'sanitize_callback' => array( $this, 'to_boolean' ),
),
- 'products' => array(
+ 'products' => array(
'required' => true,
'type' => 'array',
'items' => array(
@@ -87,20 +88,11 @@ class LoginLinkRestEndpoint extends RestEndpoint {
* @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' );
+ $use_sandbox = $request->get_param( 'useSandbox' );
$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 );
+ $url = $this->url_generator->generate( $products, $use_sandbox );
return $this->return_success( $url );
} catch ( \Exception $e ) {
diff --git a/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php b/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
index b2483160a..62ee92dff 100644
--- a/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
+++ b/modules/ppcp-settings/src/Service/ConnectionUrlGenerator.php
@@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
// TODO: Replace the OnboardingUrl with a new implementation for this module.
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
@@ -25,9 +26,9 @@ class ConnectionUrlGenerator {
/**
* The partner referrals endpoint.
*
- * @var PartnerReferrals
+ * @var EnvironmentConfig
*/
- protected PartnerReferrals $partner_referrals;
+ protected EnvironmentConfig $partner_referrals;
/**
* The default partner referrals data.
@@ -43,13 +44,6 @@ class ConnectionUrlGenerator {
*/
protected OnboardingUrlManager $url_manager;
- /**
- * Which environment is used for the connection URL.
- *
- * @var string
- */
- protected string $environment = '';
-
/**
* The logger
*
@@ -62,36 +56,23 @@ class ConnectionUrlGenerator {
*
* Initializes the cache and logger properties of the class.
*
- * @param PartnerReferrals $partner_referrals PartnerReferrals for URL generation.
+ * @param EnvironmentConfig $partner_referrals PartnerReferrals for URL generation.
* @param PartnerReferralsData $referrals_data Default partner referrals data.
- * @param string $environment Environment that is used to generate the URL.
- * ['production'|'sandbox'].
* @param OnboardingUrlManager $url_manager Manages access to OnboardingUrl instances.
* @param ?LoggerInterface $logger The logger object for logging messages.
*/
public function __construct(
- PartnerReferrals $partner_referrals,
+ EnvironmentConfig $partner_referrals,
PartnerReferralsData $referrals_data,
- string $environment,
OnboardingUrlManager $url_manager,
?LoggerInterface $logger = null
) {
$this->partner_referrals = $partner_referrals;
$this->referrals_data = $referrals_data;
- $this->environment = $environment;
$this->url_manager = $url_manager;
$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.
*
@@ -99,13 +80,14 @@ class ConnectionUrlGenerator {
* 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.
+ * @param array $products An array of product identifiers to include in the sign-up process.
+ * These determine the PayPal onboarding experience.
+ * @param bool $use_sandbox Whether to generate a sandbox URL.
*
* @return string The generated PayPal onboarding URL.
*/
- public function generate( array $products = array() ) : string {
- $cache_key = $this->cache_key( $products );
+ public function generate( array $products = array(), bool $use_sandbox = false ) : string {
+ $cache_key = $this->cache_key( $products, $use_sandbox );
$user_id = get_current_user_id();
$onboarding_url = $this->url_manager->get( $cache_key, $user_id );
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
@@ -118,7 +100,7 @@ class ConnectionUrlGenerator {
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
- $url = $this->generate_new_url( $products, $onboarding_url, $cache_key );
+ $url = $this->generate_new_url( $use_sandbox, $products, $onboarding_url, $cache_key );
if ( $url ) {
$this->persist_url( $onboarding_url, $url );
@@ -130,15 +112,18 @@ class ConnectionUrlGenerator {
/**
* Generates a cache key from the environment and sorted product array.
*
- * @param array $products Product identifiers that are part of the cache key.
+ * @param array $products Product identifiers that are part of the cache key.
+ * @param bool $for_sandbox Whether the cache contains a sandbox URL.
*
* @return string The cache key, defining the product list and environment.
*/
- protected function cache_key( array $products = array() ) : string {
+ protected function cache_key( array $products, bool $for_sandbox ) : string {
+ $environment = $for_sandbox ? 'sandbox' : 'production';
+
// Sort products alphabetically, to improve cache implementation.
sort( $products );
- return $this->environment() . '-' . implode( '-', $products );
+ return $environment . '-' . implode( '-', $products );
}
/**
@@ -167,13 +152,14 @@ class ConnectionUrlGenerator {
/**
* Generates a new URL.
*
+ * @param bool $for_sandbox Whether to generate a sandbox 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 {
+ protected function generate_new_url( bool $for_sandbox, array $products, OnboardingUrl $onboarding_url, string $cache_key ) : string {
$query_args = array( 'displayMode' => 'minibrowser' );
$onboarding_url->init();
@@ -188,7 +174,8 @@ class ConnectionUrlGenerator {
$data = $this->prepare_referral_data( $products, $onboarding_token );
try {
- $url = $this->partner_referrals->signup_link( $data );
+ $referral = $this->partner_referrals->get_value( $for_sandbox );
+ $url = $referral->signup_link( $data );
} catch ( Exception $e ) {
$this->logger->warning( 'Could not generate an onboarding URL for: ' . $cache_key );
From e4c80d7dbc4f8d1f3e9d08cf6fe92cc02d253745 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 3 Jan 2025 18:26:01 +0100
Subject: [PATCH 34/57] =?UTF-8?q?=E2=9C=A8=20Prepare=20authentication=20ho?=
=?UTF-8?q?oks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Service/ConnectionManager.php | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index 58303d2a6..8ee63efea 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -11,7 +11,7 @@ namespace WooCommerce\PayPalCommerce\Settings\Service;
use JsonException;
use Psr\Log\LoggerInterface;
-use RuntimeException;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
@@ -107,6 +107,12 @@ class ConnectionManager {
$this->common_settings->reset_merchant_data();
$this->common_settings->save();
+
+ /**
+ * Broadcast, that the plugin disconnected from PayPal. This allows other
+ * modules to clean up merchant-related details, such as eligibility flags.
+ */
+ do_action( 'woocommerce_paypal_payments_merchant_disconnected' );
}
/**
@@ -305,6 +311,7 @@ class ConnectionManager {
* @param string $auth_code The authorization code.
* @param bool $use_sandbox Whether to use the sandbox mode.
* @return array
+ * @throws RuntimeException When failed to fetch credentials.
*/
private function get_credentials( string $shared_id, string $auth_code, bool $use_sandbox ) : array {
$login_handler = $this->login_endpoint->get_value( $use_sandbox );
@@ -337,6 +344,12 @@ class ConnectionManager {
$this->common_settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
$this->common_settings->save();
- }
+ /**
+ * Broadcast that the plugin connected to a new PayPal merchant account.
+ * This is the right time to initialize merchant relative flags for the
+ * first time.
+ */
+ do_action( 'woocommerce_paypal_payments_authenticated_merchant' );
+ }
}
From b86ae2b1c7f39e9fcf99bda9b773917e58177d9d Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 3 Jan 2025 18:31:52 +0100
Subject: [PATCH 35/57] =?UTF-8?q?=F0=9F=93=9D=20Add=20notes=20on=20WIP?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Service/ConnectionManager.php | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index 8ee63efea..96af4cb23 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -318,6 +318,21 @@ class ConnectionManager {
$nonce = $this->referrals_data->nonce();
// TODO. Always throws the exception "No token found.".
+ /*
+ * Maybe the problem is not with the `credentials_for()` call, but something
+ * that is wrong with the ConnectionUrl?
+ *
+ * The LoginSellerEndpoint class uses the same code as this class:
+ >> $credentials = $endpoint->credentials_for(
+ >> $data['sharedId'],
+ >> $data['authCode'],
+ >> $this->partner_referrals_data->nonce()
+ >> );
+ *
+ * PayPal's API response is 401, not because OAuth the onboarding ID and
+ * authorization code are invalid, but because they cannot be matched to
+ * this website/merchant. An intermediary step might be missing.
+ */
$response = $login_handler->credentials_for( $shared_id, $auth_code, $nonce );
// TODO.
From ca2aa4eff11a948be19411fe7ef30eddc0f11c86 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 7 Jan 2025 17:29:53 +0100
Subject: [PATCH 36/57] =?UTF-8?q?=F0=9F=A9=B9=20Add=20missing=20dependency?=
=?UTF-8?q?=20to=20useCallback?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ppcp-settings/resources/js/hooks/useHandleConnections.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 6ad4c8a90..b15c33b32 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -148,7 +148,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
// Ensure the onComplete handler is not removed by a PayPal init script.
timerRef.current = setInterval( addHandler, 250 );
},
- [ withActivity ]
+ [ connectViaAuthCode, withActivity ]
);
const removeCompleteHandler = useCallback( () => {
From 4e7d89fd2626b39e893b0ad01c76a158b903958c Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 13:21:30 +0100
Subject: [PATCH 37/57] =?UTF-8?q?=F0=9F=92=A1=20Improve=20a=20code=20comme?=
=?UTF-8?q?nt?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/resources/js/data/common/actions.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index a8291787b..c73719186 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -197,8 +197,8 @@ export const connectViaSecret = function* () {
* parameters are dynamically generated during the authentication process, and not managed by our
* Redux store.
*
- * @param {string} sharedId - One-time authentication ID that PayPal "shares" with us.
- * @param {string} authCode - Matching one-time authentication code to validate the login.
+ * @param {string} sharedId - OAuth client ID, provided via "sharedId" during onboarding.
+ * @param {string} authCode - OAuth authorization code provided during onboarding.
* @param {string} environment - [production|sandbox].
* @return {Action} The action.
*/
From 650586ce7ca41c48d2ff269776a27614d15f4248 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 13:21:46 +0100
Subject: [PATCH 38/57] =?UTF-8?q?=F0=9F=90=9B=20Fix=20the=20authentication?=
=?UTF-8?q?=20bug!?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ppcp-settings/resources/js/hooks/useHandleConnections.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index b15c33b32..111139638 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -128,8 +128,8 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
'Validating the connection details',
async () => {
await connectViaAuthCode(
- authCode,
sharedId,
+ authCode,
environment
);
}
From d4fbde1b139f356183b5333d7833b8871696869b Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 14:39:06 +0100
Subject: [PATCH 39/57] =?UTF-8?q?=F0=9F=9A=A7=20Clean=20up=20WIP=20code=20?=
=?UTF-8?q?in=20ConnectionManager?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Components/ManualConnectionForm.js | 0
.../Components/SandboxConnectionForm.js | 41 +++++++++++++++++++
.../src/Service/ConnectionManager.php | 27 ++++--------
3 files changed, 48 insertions(+), 20 deletions(-)
create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js
create mode 100644 modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
new file mode 100644
index 000000000..5323fb311
--- /dev/null
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
@@ -0,0 +1,41 @@
+import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
+import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
+import { __ } from '@wordpress/i18n';
+import ConnectionButton from './ConnectionButton';
+import { useSandboxConnection } from '../../../../hooks/useHandleConnections';
+
+const SandboxLoginSection = () => {
+ const { isSandboxMode, setSandboxMode } = useSandboxConnection();
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default SandboxLoginSection;
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index 96af4cb23..b773919bb 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -202,8 +202,8 @@ class ConnectionManager {
* Part of the "ISU Connection" (login via Popup) flow.
*
* @param bool $use_sandbox Whether to use the sandbox mode.
- * @param string $shared_id The shared onboarding ID.
- * @param string $auth_code The authorization code.
+ * @param string $shared_id The OAuth client ID.
+ * @param string $auth_code The OAuth authorization code.
* @return void
* @throws RuntimeException When failed to retrieve payee.
*/
@@ -317,26 +317,13 @@ class ConnectionManager {
$login_handler = $this->login_endpoint->get_value( $use_sandbox );
$nonce = $this->referrals_data->nonce();
- // TODO. Always throws the exception "No token found.".
- /*
- * Maybe the problem is not with the `credentials_for()` call, but something
- * that is wrong with the ConnectionUrl?
- *
- * The LoginSellerEndpoint class uses the same code as this class:
- >> $credentials = $endpoint->credentials_for(
- >> $data['sharedId'],
- >> $data['authCode'],
- >> $this->partner_referrals_data->nonce()
- >> );
- *
- * PayPal's API response is 401, not because OAuth the onboarding ID and
- * authorization code are invalid, but because they cannot be matched to
- * this website/merchant. An intermediary step might be missing.
- */
$response = $login_handler->credentials_for( $shared_id, $auth_code, $nonce );
- // TODO.
- return (array) $response;
+ return array(
+ 'client_id' => (string) ( $response->client_id ?? '' ),
+ 'client_secret' => (string) ( $response->client_secret ?? '' ),
+ 'merchant_id' => (string) ( $response->payer_id ?? '' ),
+ );
}
/**
From ac68aa79687e4e8384a7ec3696fd977f31606b7a Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 14:41:27 +0100
Subject: [PATCH 40/57] =?UTF-8?q?=E2=9C=A8=20New=20Redux=20properties=20fo?=
=?UTF-8?q?r=20manual=20connection=20data?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/onboarding/actions.js | 22 +++++++++++++++
.../resources/js/data/onboarding/hooks.js | 28 +++++++++++++++++++
.../resources/js/data/onboarding/reducer.js | 2 ++
3 files changed, 52 insertions(+)
diff --git a/modules/ppcp-settings/resources/js/data/onboarding/actions.js b/modules/ppcp-settings/resources/js/data/onboarding/actions.js
index dcf401995..e9bf8ed5f 100644
--- a/modules/ppcp-settings/resources/js/data/onboarding/actions.js
+++ b/modules/ppcp-settings/resources/js/data/onboarding/actions.js
@@ -47,6 +47,28 @@ export const setIsReady = ( isReady ) => ( {
payload: { isReady },
} );
+/**
+ * Transient. Sets the "manualClientId" value.
+ *
+ * @param {string} manualClientId
+ * @return {Action} The action.
+ */
+export const setManualClientId = ( manualClientId ) => ( {
+ type: ACTION_TYPES.SET_TRANSIENT,
+ payload: { manualClientId },
+} );
+
+/**
+ * Transient. Sets the "manualClientSecret" value.
+ *
+ * @param {string} manualClientSecret
+ * @return {Action} The action.
+ */
+export const setManualClientSecret = ( manualClientSecret ) => ( {
+ type: ACTION_TYPES.SET_TRANSIENT,
+ payload: { manualClientSecret },
+} );
+
/**
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
*
diff --git a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js
index e8582821e..c4308c0fa 100644
--- a/modules/ppcp-settings/resources/js/data/onboarding/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/onboarding/hooks.js
@@ -30,6 +30,8 @@ const useHooks = () => {
setStep,
setCompleted,
setIsCasualSeller,
+ setManualClientId,
+ setManualClientSecret,
setAreOptionalPaymentMethodsEnabled,
setProducts,
} = useDispatch( STORE_NAME );
@@ -43,6 +45,8 @@ const useHooks = () => {
// Transient accessors.
const isReady = useTransient( 'isReady' );
+ const manualClientId = useTransient( 'manualClientId' );
+ const manualClientSecret = useTransient( 'manualClientSecret' );
// Persistent accessors.
const step = usePersistent( 'step' );
@@ -73,6 +77,14 @@ const useHooks = () => {
setIsCasualSeller: ( value ) => {
return savePersistent( setIsCasualSeller, value );
},
+ manualClientId,
+ setManualClientId: ( value ) => {
+ return savePersistent( setManualClientId, value );
+ },
+ manualClientSecret,
+ setManualClientSecret: ( value ) => {
+ return savePersistent( setManualClientSecret, value );
+ },
areOptionalPaymentMethodsEnabled,
setAreOptionalPaymentMethodsEnabled: ( value ) => {
return savePersistent( setAreOptionalPaymentMethodsEnabled, value );
@@ -88,6 +100,22 @@ const useHooks = () => {
};
};
+export const useManualConnectionForm = () => {
+ const {
+ manualClientId,
+ setManualClientId,
+ manualClientSecret,
+ setManualClientSecret,
+ } = useHooks();
+
+ return {
+ manualClientId,
+ setManualClientId,
+ manualClientSecret,
+ setManualClientSecret,
+ };
+};
+
export const useBusiness = () => {
const { isCasualSeller, setIsCasualSeller } = useHooks();
diff --git a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js
index 2b16e2416..8d03f9fbf 100644
--- a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js
+++ b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js
@@ -14,6 +14,8 @@ import ACTION_TYPES from './action-types';
const defaultTransient = Object.freeze( {
isReady: false,
+ manualClientId: '',
+ manualClientSecret: '',
// Read only values, provided by the server.
flags: Object.freeze( {
From 1bf6e488a358b91e225a4981b214b7ff93937e7d Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 14:59:59 +0100
Subject: [PATCH 41/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20more=20accur?=
=?UTF-8?q?ate=20authentication=20naming?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- “authenticate” instead of “connect”
- “withCredentials” instead of “direct”
- “OAuth” instead of “ISU”
---
.../resources/js/data/common/action-types.js | 4 ++--
.../resources/js/data/common/actions.js | 10 +++++-----
.../resources/js/data/common/controls.js | 4 ++--
.../resources/js/data/common/hooks.js | 16 ++++++++--------
.../resources/js/hooks/useHandleConnections.js | 10 +++++-----
5 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js
index 76bfb66f0..8ae56b20c 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -19,8 +19,8 @@ export default {
// Controls - always start with "DO_".
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
- DO_MANUAL_AUTHENTICATION: 'COMMON:DO_MANUAL_AUTHENTICATION',
- DO_ISU_AUTHENTICATION: 'COMMON:DO_ISU_AUTHENTICATION',
+ DO_DIRECT_API_AUTHENTICATION: 'COMMON:DO_DIRECT_API_AUTHENTICATION',
+ DO_OAUTH_AUTHENTICATION: 'COMMON:DO_OAUTH_AUTHENTICATION',
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index c73719186..f559d53c7 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -177,12 +177,12 @@ export const productionOnboardingUrl = function* ( products = [] ) {
*
* @return {Action} The action.
*/
-export const connectViaSecret = function* () {
+export const authenticateWithCredentials = function* () {
const { clientId, clientSecret, useSandbox } =
yield select( STORE_NAME ).persistentData();
return yield {
- type: ACTION_TYPES.DO_MANUAL_AUTHENTICATION,
+ type: ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION,
clientId,
clientSecret,
useSandbox,
@@ -197,12 +197,12 @@ export const connectViaSecret = function* () {
* parameters are dynamically generated during the authentication process, and not managed by our
* Redux store.
*
- * @param {string} sharedId - OAuth client ID, provided via "sharedId" during onboarding.
+ * @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the API client ID.
* @param {string} authCode - OAuth authorization code provided during onboarding.
* @param {string} environment - [production|sandbox].
* @return {Action} The action.
*/
-export const connectViaAuthCode = function* (
+export const authenticateWithOAuth = function* (
sharedId,
authCode,
environment
@@ -210,7 +210,7 @@ export const connectViaAuthCode = function* (
const useSandbox = 'sandbox' === environment;
return yield {
- type: ACTION_TYPES.DO_ISU_AUTHENTICATION,
+ type: ACTION_TYPES.DO_OAUTH_AUTHENTICATION,
sharedId,
authCode,
useSandbox,
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
index dac31147c..62d4a8f84 100644
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ b/modules/ppcp-settings/resources/js/data/common/controls.js
@@ -52,7 +52,7 @@ export const controls = {
}
},
- async [ ACTION_TYPES.DO_MANUAL_AUTHENTICATION ]( {
+ async [ ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION ]( {
clientId,
clientSecret,
useSandbox,
@@ -75,7 +75,7 @@ export const controls = {
}
},
- async [ ACTION_TYPES.DO_ISU_AUTHENTICATION ]( {
+ async [ ACTION_TYPES.DO_OAUTH_AUTHENTICATION ]( {
sharedId,
authCode,
useSandbox,
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 715b42b65..844c375e4 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -32,8 +32,8 @@ const useHooks = () => {
setClientSecret,
sandboxOnboardingUrl,
productionOnboardingUrl,
- connectViaSecret,
- connectViaAuthCode,
+ authenticateWithCredentials,
+ authenticateWithOAuth,
startWebhookSimulation,
checkWebhookSimulationState,
} = useDispatch( STORE_NAME );
@@ -81,8 +81,8 @@ const useHooks = () => {
},
sandboxOnboardingUrl,
productionOnboardingUrl,
- connectViaSecret,
- connectViaAuthCode,
+ authenticateWithCredentials,
+ authenticateWithOAuth,
merchant,
wooSettings,
webhooks,
@@ -111,8 +111,8 @@ export const useAuthentication = () => {
setClientId,
clientSecret,
setClientSecret,
- connectViaSecret,
- connectViaAuthCode,
+ authenticateWithCredentials,
+ authenticateWithOAuth,
} = useHooks();
return {
@@ -122,8 +122,8 @@ export const useAuthentication = () => {
setClientId,
clientSecret,
setClientSecret,
- connectViaSecret,
- connectViaAuthCode,
+ authenticateWithCredentials,
+ authenticateWithOAuth,
};
};
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 111139638..66be18d24 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -44,7 +44,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const { productionOnboardingUrl } = CommonHooks.useProduction();
const products = OnboardingHooks.useDetermineProducts();
const { withActivity } = CommonHooks.useBusyState();
- const { connectViaAuthCode } = CommonHooks.useAuthentication();
+ const { authenticateWithOAuth } = CommonHooks.useAuthentication();
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
const [ scriptLoaded, setScriptLoaded ] = useState( false );
const timerRef = useRef( null );
@@ -127,7 +127,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
ACTIVITIES.CONNECT_ISU,
'Validating the connection details',
async () => {
- await connectViaAuthCode(
+ await authenticateWithOAuth(
sharedId,
authCode,
environment
@@ -148,7 +148,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
// Ensure the onComplete handler is not removed by a PayPal init script.
timerRef.current = setInterval( addHandler, 250 );
},
- [ connectViaAuthCode, withActivity ]
+ [ authenticateWithOAuth, withActivity ]
);
const removeCompleteHandler = useCallback( () => {
@@ -211,7 +211,7 @@ export const useDirectAuthentication = () => {
useConnectionBase();
const { withActivity } = CommonHooks.useBusyState();
const {
- connectViaSecret,
+ authenticateWithCredentials,
isManualConnectionMode,
setManualConnectionMode,
clientId,
@@ -234,7 +234,7 @@ export const useDirectAuthentication = () => {
}
}
- const res = await connectViaSecret();
+ const res = await authenticateWithCredentials();
if ( res.success ) {
await handleCompleted();
From 54b174e4470a465e00c3156a4f41dfcfb8428efd Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 15:01:40 +0100
Subject: [PATCH 42/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Decouple=20API=20aut?=
=?UTF-8?q?hentication=20from=20Redux?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/actions.js | 26 +++++++++------
.../js/hooks/useHandleConnections.js | 33 +++++++++++--------
2 files changed, 36 insertions(+), 23 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index f559d53c7..91ef41cfc 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -175,12 +175,20 @@ export const productionOnboardingUrl = function* ( products = [] ) {
/**
* Side effect. Initiates a direct connection attempt using the provided client ID and secret.
*
+ * This action accepts parameters instead of fetching data from the Redux state because the
+ * values (ID and secret) are not managed by a central redux store, but might come from private
+ * component state.
+ *
+ * @param {string} clientId - AP client ID (always 80-characters, starting with "A").
+ * @param {string} clientSecret - API client secret.
+ * @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
* @return {Action} The action.
*/
-export const authenticateWithCredentials = function* () {
- const { clientId, clientSecret, useSandbox } =
- yield select( STORE_NAME ).persistentData();
-
+export const authenticateWithCredentials = function* (
+ clientId,
+ clientSecret,
+ useSandbox
+) {
return yield {
type: ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION,
clientId,
@@ -197,18 +205,16 @@ export const authenticateWithCredentials = function* () {
* parameters are dynamically generated during the authentication process, and not managed by our
* Redux store.
*
- * @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the API client ID.
- * @param {string} authCode - OAuth authorization code provided during onboarding.
- * @param {string} environment - [production|sandbox].
+ * @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the API client ID.
+ * @param {string} authCode - OAuth authorization code provided during onboarding.
+ * @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
* @return {Action} The action.
*/
export const authenticateWithOAuth = function* (
sharedId,
authCode,
- environment
+ useSandbox
) {
- const useSandbox = 'sandbox' === environment;
-
return yield {
type: ACTION_TYPES.DO_OAUTH_AUTHENTICATION,
sharedId,
diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
index 66be18d24..f6837c488 100644
--- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
+++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js
@@ -130,7 +130,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
await authenticateWithOAuth(
sharedId,
authCode,
- environment
+ 'sandbox' === environment
);
}
);
@@ -214,27 +214,38 @@ export const useDirectAuthentication = () => {
authenticateWithCredentials,
isManualConnectionMode,
setManualConnectionMode,
- clientId,
- setClientId,
- clientSecret,
- setClientSecret,
} = CommonHooks.useAuthentication();
- const handleDirectAuthentication = async ( { validation } = {} ) => {
+ const handleDirectAuthentication = async ( connectionDetails ) => {
return withActivity(
ACTIVITIES.CONNECT_MANUAL,
'Connecting manually via Client ID and Secret',
async () => {
- if ( 'function' === typeof validation ) {
+ let data;
+
+ if ( 'function' === typeof connectionDetails ) {
try {
- validation();
+ data = connectionDetails();
} catch ( exception ) {
createErrorNotice( exception.message );
return;
}
+ } else if ( 'object' === typeof connectionDetails ) {
+ data = connectionDetails;
}
- const res = await authenticateWithCredentials();
+ if ( ! data || ! data.clientId || ! data.clientSecret ) {
+ createErrorNotice(
+ 'Invalid connection details (clientID or clientSecret missing)'
+ );
+ return;
+ }
+
+ const res = await authenticateWithCredentials(
+ data.clientId,
+ data.clientSecret,
+ !! data.isSandbox
+ );
if ( res.success ) {
await handleCompleted();
@@ -251,9 +262,5 @@ export const useDirectAuthentication = () => {
handleDirectAuthentication,
isManualConnectionMode,
setManualConnectionMode,
- clientId,
- setClientId,
- clientSecret,
- setClientSecret,
};
};
From 0752436f00dc6c16372e01b874ea124f4ef52d21 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 15:01:47 +0100
Subject: [PATCH 43/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Split=20the=20advanc?=
=?UTF-8?q?ed=20form=20into=202=20sub-components?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Components/AdvancedOptionsForm.js | 199 +-----------------
.../Components/ManualConnectionForm.js | 188 +++++++++++++++++
.../Components/SandboxConnectionForm.js | 9 +-
3 files changed, 197 insertions(+), 199 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
index 3ac56cc65..4d1891735 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/AdvancedOptionsForm.js
@@ -1,204 +1,13 @@
-import { __, sprintf } from '@wordpress/i18n';
-import { Button, TextControl } from '@wordpress/components';
-import {
- useRef,
- useState,
- useEffect,
- useMemo,
- useCallback,
-} from '@wordpress/element';
-import classNames from 'classnames';
-
-import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
import Separator from '../../../ReusableComponents/Separator';
-import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
-import {
- useSandboxConnection,
- useDirectAuthentication,
-} from '../../../../hooks/useHandleConnections';
-import ConnectionButton from './ConnectionButton';
-import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
-
-const FORM_ERRORS = {
- noClientId: __(
- 'Please enter your Client ID',
- 'woocommerce-paypal-payments'
- ),
- noClientSecret: __(
- 'Please enter your Secret Key',
- 'woocommerce-paypal-payments'
- ),
- invalidClientId: __(
- 'Please enter a valid Client ID',
- 'woocommerce-paypal-payments'
- ),
-};
+import SandboxConnectionForm from './SandboxConnectionForm';
+import ManualConnectionForm from './ManualConnectionForm';
const AdvancedOptionsForm = () => {
- const [ clientValid, setClientValid ] = useState( false );
- const [ secretValid, setSecretValid ] = useState( false );
-
- const { isSandboxMode, setSandboxMode } = useSandboxConnection();
- const {
- handleDirectAuthentication,
- isManualConnectionMode,
- setManualConnectionMode,
- clientId,
- setClientId,
- clientSecret,
- setClientSecret,
- } = useDirectAuthentication();
-
- const refClientId = useRef( null );
- const refClientSecret = useRef( null );
-
- const validateManualConnectionForm = useCallback( () => {
- const checks = [
- {
- ref: refClientId,
- valid: () => clientId,
- errorMessage: FORM_ERRORS.noClientId,
- },
- {
- ref: refClientId,
- valid: () => clientValid,
- errorMessage: FORM_ERRORS.invalidClientId,
- },
- {
- ref: refClientSecret,
- valid: () => clientSecret && secretValid,
- errorMessage: FORM_ERRORS.noClientSecret,
- },
- ];
-
- for ( const { ref, valid, errorMessage } of checks ) {
- if ( valid() ) {
- continue;
- }
-
- ref?.current?.focus();
- throw new Error( errorMessage );
- }
- }, [ clientId, clientSecret, clientValid, secretValid ] );
-
- const handleManualConnect = useCallback(
- () =>
- handleDirectAuthentication( {
- validation: validateManualConnectionForm,
- } ),
- [ handleDirectAuthentication, validateManualConnectionForm ]
- );
-
- useEffect( () => {
- setClientValid( ! clientId || /^A[\w-]{79}$/.test( clientId ) );
- setSecretValid( clientSecret && clientSecret.length > 0 );
- }, [ clientId, clientSecret ] );
-
- const clientIdLabel = useMemo(
- () =>
- isSandboxMode
- ? __( 'Sandbox Client ID', 'woocommerce-paypal-payments' )
- : __( 'Live Client ID', 'woocommerce-paypal-payments' ),
- [ isSandboxMode ]
- );
-
- const secretKeyLabel = useMemo(
- () =>
- isSandboxMode
- ? __( 'Sandbox Secret Key', 'woocommerce-paypal-payments' )
- : __( 'Live Secret Key', 'woocommerce-paypal-payments' ),
- [ isSandboxMode ]
- );
-
- const advancedUsersDescription = sprintf(
- // translators: %s: Link to PayPal REST application guide
- __(
- 'For advanced users: Connect a custom PayPal REST app for full control over your integration. For more information on creating a PayPal REST application, click here.',
- 'woocommerce-paypal-payments'
- ),
- 'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input'
- );
-
return (
<>
-
-
-
-
-
+
- ( {
- disabled: true,
- label: props.label + ' ...',
- } ) }
- >
-
-
- { clientValid || (
-
- { FORM_ERRORS.invalidClientId }
-
- ) }
-
-
-
-
+
>
);
};
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js
index e69de29bb..ca0257159 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/ManualConnectionForm.js
@@ -0,0 +1,188 @@
+import {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from '@wordpress/element';
+import { Button, TextControl } from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import classNames from 'classnames';
+
+import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
+import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
+import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
+import {
+ useDirectAuthentication,
+ useSandboxConnection,
+} from '../../../../hooks/useHandleConnections';
+import { OnboardingHooks } from '../../../../data';
+
+const FORM_ERRORS = {
+ noClientId: __(
+ 'Please enter your Client ID',
+ 'woocommerce-paypal-payments'
+ ),
+ noClientSecret: __(
+ 'Please enter your Secret Key',
+ 'woocommerce-paypal-payments'
+ ),
+ invalidClientId: __(
+ 'Please enter a valid Client ID',
+ 'woocommerce-paypal-payments'
+ ),
+};
+
+const ManualConnectionForm = () => {
+ const [ clientValid, setClientValid ] = useState( false );
+ const [ secretValid, setSecretValid ] = useState( false );
+ const { isSandboxMode } = useSandboxConnection();
+ const {
+ manualClientId,
+ setManualClientId,
+ manualClientSecret,
+ setManualClientSecret,
+ } = OnboardingHooks.useManualConnectionForm();
+ const {
+ handleDirectAuthentication,
+ isManualConnectionMode,
+ setManualConnectionMode,
+ } = useDirectAuthentication();
+ const refClientId = useRef( null );
+ const refClientSecret = useRef( null );
+
+ // Form data validation and sanitation.
+ const getManualConnectionDetails = useCallback( () => {
+ const checks = [
+ {
+ ref: refClientId,
+ valid: () => manualClientId,
+ errorMessage: FORM_ERRORS.noClientId,
+ },
+ {
+ ref: refClientId,
+ valid: () => clientValid,
+ errorMessage: FORM_ERRORS.invalidClientId,
+ },
+ {
+ ref: refClientSecret,
+ valid: () => manualClientSecret && secretValid,
+ errorMessage: FORM_ERRORS.noClientSecret,
+ },
+ ];
+
+ for ( const { ref, valid, errorMessage } of checks ) {
+ if ( valid() ) {
+ continue;
+ }
+
+ ref?.current?.focus();
+ throw new Error( errorMessage );
+ }
+
+ return {
+ clientId: manualClientId,
+ clientSecret: manualClientSecret,
+ isSandbox: isSandboxMode,
+ };
+ }, [
+ manualClientId,
+ manualClientSecret,
+ isSandboxMode,
+ clientValid,
+ secretValid,
+ ] );
+
+ // On-the-fly form validation.
+ useEffect( () => {
+ setClientValid(
+ ! manualClientId || /^A[\w-]{79}$/.test( manualClientId )
+ );
+ setSecretValid( manualClientSecret && manualClientSecret.length > 0 );
+ }, [ manualClientId, manualClientSecret ] );
+
+ // Environment-specific field labels.
+ const clientIdLabel = useMemo(
+ () =>
+ isSandboxMode
+ ? __( 'Sandbox Client ID', 'woocommerce-paypal-payments' )
+ : __( 'Live Client ID', 'woocommerce-paypal-payments' ),
+ [ isSandboxMode ]
+ );
+
+ const secretKeyLabel = useMemo(
+ () =>
+ isSandboxMode
+ ? __( 'Sandbox Secret Key', 'woocommerce-paypal-payments' )
+ : __( 'Live Secret Key', 'woocommerce-paypal-payments' ),
+ [ isSandboxMode ]
+ );
+
+ // Translations with placeholders.
+ const advancedUsersDescription = sprintf(
+ // translators: %s: Link to PayPal REST application guide
+ __(
+ 'For advanced users: Connect a custom PayPal REST app for full control over your integration. For more information on creating a PayPal REST application, click here.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input'
+ );
+
+ // Button click handler.
+ const handleManualConnect = useCallback(
+ () => handleDirectAuthentication( getManualConnectionDetails ),
+ [ handleDirectAuthentication, getManualConnectionDetails ]
+ );
+
+ return (
+ ( {
+ disabled: true,
+ label: props.label + ' ...',
+ } ) }
+ >
+
+
+ { clientValid || (
+
+ { FORM_ERRORS.invalidClientId }
+
+ ) }
+
+
+
+
+ );
+};
+
+export default ManualConnectionForm;
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
index 5323fb311..39b115b9d 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/Components/SandboxConnectionForm.js
@@ -1,10 +1,11 @@
+import { __ } from '@wordpress/i18n';
+
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
-import { __ } from '@wordpress/i18n';
-import ConnectionButton from './ConnectionButton';
import { useSandboxConnection } from '../../../../hooks/useHandleConnections';
+import ConnectionButton from './ConnectionButton';
-const SandboxLoginSection = () => {
+const SandboxConnectionForm = () => {
const { isSandboxMode, setSandboxMode } = useSandboxConnection();
return (
@@ -38,4 +39,4 @@ const SandboxLoginSection = () => {
);
};
-export default SandboxLoginSection;
+export default SandboxConnectionForm;
From 6167955374d83802b339f3733310c38e25c12792 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 15:07:37 +0100
Subject: [PATCH 44/57] =?UTF-8?q?=F0=9F=94=A5=20Remove=20setters=20for=20c?=
=?UTF-8?q?lientId/secret?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Those values should only be set by PHP after validating some authentication details
---
.../resources/js/data/common/actions.js | 22 -------------------
.../resources/js/data/common/hooks.js | 20 -----------------
.../resources/js/data/common/reducer.js | 4 ++--
3 files changed, 2 insertions(+), 44 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js
index 91ef41cfc..0cdec5d31 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions.js
@@ -112,28 +112,6 @@ export const setManualConnectionMode = ( useManualConnection ) => ( {
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.
*
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 844c375e4..8c22494ca 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -28,8 +28,6 @@ const useHooks = () => {
persist,
setSandboxMode,
setManualConnectionMode,
- setClientId,
- setClientSecret,
sandboxOnboardingUrl,
productionOnboardingUrl,
authenticateWithCredentials,
@@ -42,8 +40,6 @@ const useHooks = () => {
const isReady = useTransient( 'isReady' );
// Persistent accessors.
- const clientId = usePersistent( 'clientId' );
- const clientSecret = usePersistent( 'clientSecret' );
const isSandboxMode = usePersistent( 'useSandbox' );
const isManualConnectionMode = usePersistent( 'useManualConnection' );
const webhooks = usePersistent( 'webhooks' );
@@ -71,14 +67,6 @@ const useHooks = () => {
setManualConnectionMode: ( state ) => {
return savePersistent( setManualConnectionMode, state );
},
- clientId,
- setClientId: ( value ) => {
- return savePersistent( setClientId, value );
- },
- clientSecret,
- setClientSecret: ( value ) => {
- return savePersistent( setClientSecret, value );
- },
sandboxOnboardingUrl,
productionOnboardingUrl,
authenticateWithCredentials,
@@ -107,10 +95,6 @@ export const useAuthentication = () => {
const {
isManualConnectionMode,
setManualConnectionMode,
- clientId,
- setClientId,
- clientSecret,
- setClientSecret,
authenticateWithCredentials,
authenticateWithOAuth,
} = useHooks();
@@ -118,10 +102,6 @@ export const useAuthentication = () => {
return {
isManualConnectionMode,
setManualConnectionMode,
- clientId,
- setClientId,
- clientSecret,
- setClientSecret,
authenticateWithCredentials,
authenticateWithOAuth,
};
diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js
index 8b5cfb9b3..922db6985 100644
--- a/modules/ppcp-settings/resources/js/data/common/reducer.js
+++ b/modules/ppcp-settings/resources/js/data/common/reducer.js
@@ -22,6 +22,8 @@ const defaultTransient = Object.freeze( {
isSandbox: false,
id: '',
email: '',
+ clientId: '',
+ clientSecret: '',
} ),
wooSettings: Object.freeze( {
@@ -33,8 +35,6 @@ const defaultTransient = Object.freeze( {
const defaultPersistent = Object.freeze( {
useSandbox: false,
useManualConnection: false,
- clientId: '',
- clientSecret: '',
webhooks: [],
} );
From bd7bbb36b27df85a643679182cb621a210ad36ad Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 15:19:50 +0100
Subject: [PATCH 45/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Prevent=20upading=20?=
=?UTF-8?q?clientId/secret=20via=20REST?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/src/Data/CommonSettings.php | 4 ++--
.../src/Endpoint/CommonRestEndpoint.php | 14 ++++++--------
2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/modules/ppcp-settings/src/Data/CommonSettings.php b/modules/ppcp-settings/src/Data/CommonSettings.php
index 0935734b8..7d4c5a640 100644
--- a/modules/ppcp-settings/src/Data/CommonSettings.php
+++ b/modules/ppcp-settings/src/Data/CommonSettings.php
@@ -57,14 +57,14 @@ class CommonSettings extends AbstractDataModel {
return array(
'use_sandbox' => false,
'use_manual_connection' => false,
- 'client_id' => '',
- 'client_secret' => '',
// Details about connected merchant account.
'merchant_connected' => false,
'sandbox_merchant' => false,
'merchant_id' => '',
'merchant_email' => '',
+ 'client_id' => '',
+ 'client_secret' => '',
);
}
diff --git a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
index 74b66ff16..0be2b4ad3 100644
--- a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
@@ -50,14 +50,6 @@ class CommonRestEndpoint extends RestEndpoint {
'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',
- ),
'webhooks' => array(
'js_name' => 'webhooks',
),
@@ -81,6 +73,12 @@ class CommonRestEndpoint extends RestEndpoint {
'merchant_email' => array(
'js_name' => 'email',
),
+ 'client_id' => array(
+ 'js_name' => 'clientId',
+ ),
+ 'client_secret' => array(
+ 'js_name' => 'clientSecret',
+ ),
);
/**
From 08ff9e7380869d619e3ff55bdbbaa0ebaa249b28 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 16:15:49 +0100
Subject: [PATCH 46/57] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Introduce=20a=20D?=
=?UTF-8?q?TO=20to=20hold=20merchent=20credentials?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/DTO/MerchantConnectionDTO.php | 75 +++++++++++++++++++
.../ppcp-settings/src/Data/CommonSettings.php | 33 +++++---
.../src/Service/ConnectionManager.php | 42 ++++++++---
3 files changed, 126 insertions(+), 24 deletions(-)
create mode 100644 modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php
diff --git a/modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php b/modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php
new file mode 100644
index 000000000..f79a85d76
--- /dev/null
+++ b/modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php
@@ -0,0 +1,75 @@
+is_sandbox = $is_sandbox;
+ $this->client_id = $client_id;
+ $this->client_secret = $client_secret;
+ $this->merchant_id = $merchant_id;
+ $this->merchant_email = $merchant_email;
+ }
+}
diff --git a/modules/ppcp-settings/src/Data/CommonSettings.php b/modules/ppcp-settings/src/Data/CommonSettings.php
index 7d4c5a640..47470e35e 100644
--- a/modules/ppcp-settings/src/Data/CommonSettings.php
+++ b/modules/ppcp-settings/src/Data/CommonSettings.php
@@ -10,6 +10,7 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Data;
use RuntimeException;
+use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
/**
* Class CommonSettings
@@ -41,11 +42,16 @@ class CommonSettings extends AbstractDataModel {
*
* @param string $country WooCommerce store country.
* @param string $currency WooCommerce store currency.
+ *
+ * @throws RuntimeException When forgetting to define the OPTION_KEY in this class.
*/
public function __construct( string $country, string $currency ) {
parent::__construct();
+
$this->woo_settings['country'] = $country;
$this->woo_settings['currency'] = $currency;
+
+ $this->data['merchant_connected'] = $this->is_merchant_connected();
}
/**
@@ -154,19 +160,17 @@ class CommonSettings extends AbstractDataModel {
/**
* Setter to update details of the connected merchant account.
*
- * Those details cannot be changed individually.
- *
- * @param bool $is_sandbox Whether the details are for a sandbox account.
- * @param string $merchant_id The merchant ID.
- * @param string $merchant_email The merchant's email.
+ * @param MerchantConnectionDTO $connection Connection details.
*
* @return void
*/
- public function set_merchant_data( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
- $this->data['sandbox_merchant'] = $is_sandbox;
- $this->data['merchant_id'] = sanitize_text_field( $merchant_id );
- $this->data['merchant_email'] = sanitize_email( $merchant_email );
- $this->data['merchant_connected'] = true;
+ public function set_merchant_data( MerchantConnectionDTO $connection ) : void {
+ $this->data['sandbox_merchant'] = $connection->is_sandbox;
+ $this->data['merchant_id'] = sanitize_text_field( $connection->merchant_id );
+ $this->data['merchant_email'] = sanitize_email( $connection->merchant_email );
+ $this->data['client_id'] = sanitize_text_field( $connection->client_id );
+ $this->data['client_secret'] = sanitize_text_field( $connection->client_secret );
+ $this->data['merchant_connected'] = $this->is_merchant_connected();
}
/**
@@ -180,7 +184,9 @@ class CommonSettings extends AbstractDataModel {
$this->data['sandbox_merchant'] = $defaults['sandbox_merchant'];
$this->data['merchant_id'] = $defaults['merchant_id'];
$this->data['merchant_email'] = $defaults['merchant_email'];
- $this->data['merchant_connected'] = $defaults['merchant_connected'];
+ $this->data['client_id'] = $defaults['client_id'];
+ $this->data['client_secret'] = $defaults['client_secret'];
+ $this->data['merchant_connected'] = false;
}
/**
@@ -198,7 +204,10 @@ class CommonSettings extends AbstractDataModel {
* @return bool
*/
public function is_merchant_connected() : bool {
- return $this->data['merchant_connected'] && $this->data['merchant_id'] && $this->data['merchant_email'];
+ return $this->data['merchant_email']
+ && $this->data['merchant_id']
+ && $this->data['client_id']
+ && $this->data['client_secret'];
}
/**
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/ConnectionManager.php
index b773919bb..85d4af927 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/ConnectionManager.php
@@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
+use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
/**
* Class that manages the connection to PayPal.
@@ -167,7 +168,15 @@ class ConnectionManager {
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
- $this->update_connection_details( $use_sandbox, $payee['merchant_id'], $payee['email_address'] );
+ $connection = new MerchantConnectionDTO(
+ $use_sandbox,
+ $client_id,
+ $client_secret,
+ $payee['merchant_id'],
+ $payee['email_address']
+ );
+
+ $this->update_connection_details( $connection );
}
@@ -220,7 +229,22 @@ class ConnectionManager {
$credentials = $this->get_credentials( $shared_id, $auth_code, $use_sandbox );
- // TODO.
+ /**
+ * The merchant's email is set by `ConnectionListener`. That listener
+ * is invoked during the page reload, once the user clicks the blue
+ * "Return to Store" button in PayPal's login popup.
+ */
+ $empty_email = '';
+
+ $connection = new MerchantConnectionDTO(
+ $use_sandbox,
+ $credentials['client_id'],
+ $credentials['client_secret'],
+ $credentials['merchant_id'],
+ $empty_email
+ );
+
+ $this->update_connection_details( $connection );
}
@@ -329,22 +353,16 @@ class ConnectionManager {
/**
* Stores the provided details in the data model.
*
- * @param bool $is_sandbox Whether the details are for a sandbox account.
- * @param string $merchant_id PayPal's internal merchant ID.
- * @param string $merchant_email Email address associated with the PayPal account.
+ * @param MerchantConnectionDTO $connection Connection details to persist.
* @return void
*/
- private function update_connection_details( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
+ private function update_connection_details( MerchantConnectionDTO $connection ) : void {
$this->logger->info(
'Updating connection details',
- array(
- 'sandbox' => $is_sandbox,
- 'merchant_id' => $merchant_id,
- 'merchant_email' => $merchant_email,
- )
+ (array) $connection
);
- $this->common_settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
+ $this->common_settings->set_merchant_data( $connection );
$this->common_settings->save();
/**
From 33bd9ecce8ed5ece9880e2ddc6fe51f2467e7825 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 16:34:42 +0100
Subject: [PATCH 47/57] =?UTF-8?q?=E2=9C=A8=20Update=20merchant=20email=20a?=
=?UTF-8?q?fter=20OAuth=20completion?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Data/AbstractDataModel.php | 1 -
.../ppcp-settings/src/Data/CommonSettings.php | 55 ++++++-------------
.../src/Handler/ConnectionListener.php | 30 ++++++----
3 files changed, 35 insertions(+), 51 deletions(-)
diff --git a/modules/ppcp-settings/src/Data/AbstractDataModel.php b/modules/ppcp-settings/src/Data/AbstractDataModel.php
index 780ad40bd..070015af2 100644
--- a/modules/ppcp-settings/src/Data/AbstractDataModel.php
+++ b/modules/ppcp-settings/src/Data/AbstractDataModel.php
@@ -122,5 +122,4 @@ abstract class AbstractDataModel {
return $stripped_key ? "set_$stripped_key" : '';
}
-
}
diff --git a/modules/ppcp-settings/src/Data/CommonSettings.php b/modules/ppcp-settings/src/Data/CommonSettings.php
index 47470e35e..8e118ab89 100644
--- a/modules/ppcp-settings/src/Data/CommonSettings.php
+++ b/modules/ppcp-settings/src/Data/CommonSettings.php
@@ -61,8 +61,8 @@ class CommonSettings extends AbstractDataModel {
*/
protected function get_defaults() : array {
return array(
- 'use_sandbox' => false,
- 'use_manual_connection' => false,
+ 'use_sandbox' => false, // UI state, not a connection detail.
+ 'use_manual_connection' => false, // UI state, not a connection detail.
// Details about connected merchant account.
'merchant_connected' => false,
@@ -112,42 +112,6 @@ class CommonSettings extends AbstractDataModel {
$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 );
- }
-
/**
* Returns the list of read-only customization flags.
*
@@ -173,6 +137,21 @@ class CommonSettings extends AbstractDataModel {
$this->data['merchant_connected'] = $this->is_merchant_connected();
}
+ /**
+ * Returns the full merchant connection DTO for the current connection.
+ *
+ * @return MerchantConnectionDTO All connection details.
+ */
+ public function get_merchant_data() : MerchantConnectionDTO {
+ return new MerchantConnectionDTO(
+ $this->is_sandbox_merchant(),
+ $this->data['client_id'],
+ $this->data['client_secret'],
+ $this->data['merchant_id'],
+ $this->data['merchant_email']
+ );
+ }
+
/**
* Reset all connection details to the initial, disconnected state.
*
diff --git a/modules/ppcp-settings/src/Handler/ConnectionListener.php b/modules/ppcp-settings/src/Handler/ConnectionListener.php
index a24a82231..5ce3f14d6 100644
--- a/modules/ppcp-settings/src/Handler/ConnectionListener.php
+++ b/modules/ppcp-settings/src/Handler/ConnectionListener.php
@@ -10,10 +10,12 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Handler;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
-use Psr\Log\LoggerInterface;
/**
* Provides a listener that handles merchant-connection requests.
@@ -82,6 +84,8 @@ class ConnectionListener {
*
* @param int $user_id The current user ID.
* @param array $request Request details to process.
+ *
+ * @throws RuntimeException If the merchant ID does not match the ID previously set via OAuth.
*/
public function process( int $user_id, array $request ) : void {
$this->user_id = $user_id;
@@ -102,11 +106,15 @@ class ConnectionListener {
$this->logger->info( 'Found merchant data in request', $data );
- $this->store_data(
- $data['is_sandbox'],
- $data['merchant_id'],
- $data['merchant_email']
- );
+ $connection = $this->settings->get_merchant_data();
+
+ if ( $connection->merchant_id !== $data['merchant_id'] ) {
+ throw new RuntimeException( 'Unexpected merchant ID in request' );
+ }
+
+ $connection->merchant_email = $data['merchant_email'];
+
+ $this->store_data( $connection );
}
/**
@@ -169,14 +177,12 @@ class ConnectionListener {
/**
* Persist the merchant details to the database.
*
- * @param bool $is_sandbox Whether the details are for a sandbox account.
- * @param string $merchant_id The anonymized merchant ID.
- * @param string $merchant_email The merchant's email.
+ * @param MerchantConnectionDTO $connection Merchant connection details to store.
*/
- protected function store_data( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
- $this->logger->info( "Save merchant details to the DB: $merchant_email ($merchant_id)" );
+ protected function store_data( MerchantConnectionDTO $connection ) : void {
+ $this->logger->info( 'Save merchant details to the DB', (array) $connection );
- $this->settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
+ $this->settings->set_merchant_data( $connection );
$this->settings->save();
}
From 6591889079ac0fd32315bb22c813b05a84c1d7fb Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 16:50:38 +0100
Subject: [PATCH 48/57] =?UTF-8?q?=F0=9F=9A=A7=20Refactor=20the=20eligible-?=
=?UTF-8?q?feature=20REST=20response?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-applepay/src/ApplepayModule.php | 10 +++-------
modules/ppcp-googlepay/src/GooglepayModule.php | 10 +++-------
.../Components/Screens/Overview/TabOverview.js | 4 ++--
.../resources/js/data/common/hooks.js | 10 +++++++++-
.../resources/js/data/common/reducer.js | 18 +++++++++++++++++-
.../resources/js/data/common/selectors.js | 6 +++++-
.../src/Endpoint/CommonRestEndpoint.php | 10 ++++++----
.../ppcp-wc-gateway/src/WCGatewayModule.php | 12 ++++--------
8 files changed, 49 insertions(+), 31 deletions(-)
diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php
index aa9876069..dc7b3cf11 100644
--- a/modules/ppcp-applepay/src/ApplepayModule.php
+++ b/modules/ppcp-applepay/src/ApplepayModule.php
@@ -184,21 +184,17 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data',
- function( array $merchant_data ) use ( $c ): array {
- if ( ! isset( $merchant_data['features'] ) ) {
- $merchant_data['features'] = array();
- }
-
+ function( array $features ) use ( $c ): array {
$product_status = $c->get( 'applepay.apple-product-status' );
assert( $product_status instanceof AppleProductStatus );
$apple_pay_enabled = $product_status->is_active();
- $merchant_data['features']['apple_pay'] = array(
+ $features['apple_pay'] = array(
'enabled' => $apple_pay_enabled,
);
- return $merchant_data;
+ return $features;
}
);
diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php
index 01d5f8fae..dd7320011 100644
--- a/modules/ppcp-googlepay/src/GooglepayModule.php
+++ b/modules/ppcp-googlepay/src/GooglepayModule.php
@@ -234,21 +234,17 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data',
- function ( array $merchant_data ) use ( $c ): array {
- if ( ! isset( $merchant_data['features'] ) ) {
- $merchant_data['features'] = array();
- }
-
+ function ( array $features ) use ( $c ): array {
$product_status = $c->get( 'googlepay.helpers.apm-product-status' );
assert( $product_status instanceof ApmProductStatus );
$google_pay_enabled = $product_status->is_active();
- $merchant_data['features']['google_pay'] = array(
+ $features['google_pay'] = array(
'enabled' => $google_pay_enabled,
);
- return $merchant_data;
+ return $features;
}
);
diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js
index 07e70efea..f64095a8f 100644
--- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js
+++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js
@@ -16,11 +16,11 @@ const TabOverview = () => {
const [ todosData, setTodosData ] = useState( todosDataDefault );
const [ isRefreshing, setIsRefreshing ] = useState( false );
- const { merchant } = useMerchantInfo();
+ const { merchantFeatures } = useMerchantInfo();
const { refreshFeatureStatuses } = useDispatch( STORE_NAME );
const features = featuresDefault.map( ( feature ) => {
- const merchantFeature = merchant?.features?.[ feature.id ];
+ const merchantFeature = merchantFeatures?.[ feature.id ];
return {
...feature,
enabled: merchantFeature?.enabled ?? false,
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 8c22494ca..4a2429575 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -47,10 +47,16 @@ const useHooks = () => {
( select ) => select( STORE_NAME ).merchant(),
[]
);
+
+ // Read-only properties.
const wooSettings = useSelect(
( select ) => select( STORE_NAME ).wooSettings(),
[]
);
+ const features = useSelect(
+ ( select ) => select( STORE_NAME ).features(),
+ []
+ );
const savePersistent = async ( setter, value ) => {
setter( value );
@@ -73,6 +79,7 @@ const useHooks = () => {
authenticateWithOAuth,
merchant,
wooSettings,
+ features,
webhooks,
startWebhookSimulation,
checkWebhookSimulationState,
@@ -130,7 +137,7 @@ export const useWebhooks = () => {
};
};
export const useMerchantInfo = () => {
- const { merchant } = useHooks();
+ const { merchant, features } = useHooks();
const { refreshMerchantData } = useDispatch( STORE_NAME );
const verifyLoginStatus = useCallback( async () => {
@@ -146,6 +153,7 @@ export const useMerchantInfo = () => {
return {
merchant, // Merchant details
+ features, // Eligible merchant features
verifyLoginStatus, // Callback
};
};
diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js
index 922db6985..af5aece23 100644
--- a/modules/ppcp-settings/resources/js/data/common/reducer.js
+++ b/modules/ppcp-settings/resources/js/data/common/reducer.js
@@ -30,6 +30,21 @@ const defaultTransient = Object.freeze( {
storeCountry: '',
storeCurrency: '',
} ),
+
+ features: Object.freeze( {
+ save_paypal_and_venmo: {
+ enabled: false,
+ },
+ advanced_credit_and_debit_cards: {
+ enabled: false,
+ },
+ apple_pay: {
+ enabled: false,
+ },
+ google_pay: {
+ enabled: false,
+ },
+ } ),
} );
const defaultPersistent = Object.freeze( {
@@ -83,13 +98,14 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
[ ACTION_TYPES.DO_REFRESH_MERCHANT ]: ( state ) => ( {
...state,
merchant: Object.freeze( { ...defaultTransient.merchant } ),
+ features: Object.freeze( { ...defaultTransient.features } ),
} ),
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
const newState = setPersistent( state, payload.data );
// Populate read-only properties.
- [ 'wooSettings', 'merchant' ].forEach( ( key ) => {
+ [ 'wooSettings', 'merchant', 'features' ].forEach( ( key ) => {
if ( ! payload[ key ] ) {
return;
}
diff --git a/modules/ppcp-settings/resources/js/data/common/selectors.js b/modules/ppcp-settings/resources/js/data/common/selectors.js
index 96393942a..0e0ec781e 100644
--- a/modules/ppcp-settings/resources/js/data/common/selectors.js
+++ b/modules/ppcp-settings/resources/js/data/common/selectors.js
@@ -16,7 +16,7 @@ export const persistentData = ( state ) => {
};
export const transientData = ( state ) => {
- const { data, merchant, wooSettings, ...transientState } =
+ const { data, merchant, features, wooSettings, ...transientState } =
getState( state );
return transientState || EMPTY_OBJ;
};
@@ -30,6 +30,10 @@ export const merchant = ( state ) => {
return getState( state ).merchant || EMPTY_OBJ;
};
+export const features = ( state ) => {
+ return getState( state ).features || EMPTY_OBJ;
+};
+
export const wooSettings = ( state ) => {
return getState( state ).wooSettings || EMPTY_OBJ;
};
diff --git a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
index 0be2b4ad3..6f249006a 100644
--- a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php
@@ -201,10 +201,12 @@ class CommonRestEndpoint extends RestEndpoint {
$this->merchant_info_map
);
- $extra_data['merchant'] = apply_filters(
- 'woocommerce_paypal_payments_rest_common_merchant_data',
- $extra_data['merchant'],
- );
+ if ( $this->settings->is_merchant_connected() ) {
+ $extra_data['features'] = apply_filters(
+ 'woocommerce_paypal_payments_rest_common_merchant_data',
+ array(),
+ );
+ }
return $extra_data;
}
diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
index f754a3cbe..e4e13a91d 100644
--- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php
+++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
@@ -550,16 +550,12 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
add_filter(
'woocommerce_paypal_payments_rest_common_merchant_data',
- function( array $merchant_data ) use ( $c ): array {
- if ( ! isset( $merchant_data['features'] ) ) {
- $merchant_data['features'] = array();
- }
-
+ function( array $features ) use ( $c ): array {
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
$reference_transactions_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
- $merchant_data['features']['save_paypal_and_venmo'] = array(
+ $features['save_paypal_and_venmo'] = array(
'enabled' => $reference_transactions_enabled,
);
@@ -567,11 +563,11 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
assert( $dcc_product_status instanceof DCCProductStatus );
$dcc_enabled = $dcc_product_status->dcc_is_active();
- $merchant_data['features']['advanced_credit_and_debit_cards'] = array(
+ $features['advanced_credit_and_debit_cards'] = array(
'enabled' => $dcc_enabled,
);
- return $merchant_data;
+ return $features;
}
);
From 67a6d9e765af9cc1e9c927ccd4223bc1bbd1edfe Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 16:52:21 +0100
Subject: [PATCH 49/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Adjust=20=E2=80=9Cwe?=
=?UTF-8?q?bhooks=E2=80=9D=20property=20in=20Redux=20state?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/hooks.js | 5 +++-
.../resources/js/data/common/reducer.js | 23 +++++++++++--------
.../resources/js/data/common/selectors.js | 10 ++++++--
.../src/Endpoint/WebhookSettingsEndpoint.php | 2 +-
4 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js
index 4a2429575..e9a77b79b 100644
--- a/modules/ppcp-settings/resources/js/data/common/hooks.js
+++ b/modules/ppcp-settings/resources/js/data/common/hooks.js
@@ -42,7 +42,6 @@ const useHooks = () => {
// Persistent accessors.
const isSandboxMode = usePersistent( 'useSandbox' );
const isManualConnectionMode = usePersistent( 'useManualConnection' );
- const webhooks = usePersistent( 'webhooks' );
const merchant = useSelect(
( select ) => select( STORE_NAME ).merchant(),
[]
@@ -57,6 +56,10 @@ const useHooks = () => {
( select ) => select( STORE_NAME ).features(),
[]
);
+ const webhooks = useSelect(
+ ( select ) => select( STORE_NAME ).webhooks(),
+ []
+ );
const savePersistent = async ( setter, value ) => {
setter( value );
diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js
index af5aece23..019d29911 100644
--- a/modules/ppcp-settings/resources/js/data/common/reducer.js
+++ b/modules/ppcp-settings/resources/js/data/common/reducer.js
@@ -45,12 +45,13 @@ const defaultTransient = Object.freeze( {
enabled: false,
},
} ),
+
+ webhooks: Object.freeze( [] ),
} );
const defaultPersistent = Object.freeze( {
useSandbox: false,
useManualConnection: false,
- webhooks: [],
} );
// Reducer logic.
@@ -105,16 +106,18 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
const newState = setPersistent( state, payload.data );
// Populate read-only properties.
- [ 'wooSettings', 'merchant', 'features' ].forEach( ( key ) => {
- if ( ! payload[ key ] ) {
- return;
- }
+ [ 'wooSettings', 'merchant', 'features', 'webhooks' ].forEach(
+ ( key ) => {
+ if ( ! payload[ key ] ) {
+ return;
+ }
- newState[ key ] = Object.freeze( {
- ...newState[ key ],
- ...payload[ key ],
- } );
- } );
+ newState[ key ] = Object.freeze( {
+ ...newState[ key ],
+ ...payload[ key ],
+ } );
+ }
+ );
return newState;
},
diff --git a/modules/ppcp-settings/resources/js/data/common/selectors.js b/modules/ppcp-settings/resources/js/data/common/selectors.js
index 0e0ec781e..4716550bb 100644
--- a/modules/ppcp-settings/resources/js/data/common/selectors.js
+++ b/modules/ppcp-settings/resources/js/data/common/selectors.js
@@ -16,8 +16,14 @@ export const persistentData = ( state ) => {
};
export const transientData = ( state ) => {
- const { data, merchant, features, wooSettings, ...transientState } =
- getState( state );
+ const {
+ data,
+ merchant,
+ features,
+ wooSettings,
+ webhooks,
+ ...transientState
+ } = getState( state );
return transientState || EMPTY_OBJ;
};
diff --git a/modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php b/modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php
index c3116a1ed..69b346734 100644
--- a/modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/WebhookSettingsEndpoint.php
@@ -118,7 +118,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
try {
$webhook_list = ( $this->webhook_endpoint->list() )[0];
$webhook_events = array_map(
- function ( stdClass $webhook ) {
+ static function ( stdClass $webhook ) {
return strtolower( $webhook->name );
},
$webhook_list->event_types()
From 8ce7d6ca996b02f441851c2e41f22f76a689df30 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 17:18:52 +0100
Subject: [PATCH 50/57] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20ConnectionManager?=
=?UTF-8?q?=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 14 ++++-----
.../Endpoint/AuthenticationRestEndpoint.php | 29 ++++++++++++-------
...nManager.php => AuthenticationManager.php} | 2 +-
3 files changed, 26 insertions(+), 19 deletions(-)
rename modules/ppcp-settings/src/Service/{ConnectionManager.php => AuthenticationManager.php} (99%)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index f19dc750f..df50f886d 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -10,21 +10,21 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
+use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
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\AuthenticationRestEndpoint;
+use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
-use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint;
+use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
+use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
-use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
-use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
return array(
'settings.url' => static function ( ContainerInterface $container ) : string {
@@ -82,7 +82,7 @@ return array(
},
'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : AuthenticationRestEndpoint {
return new AuthenticationRestEndpoint(
- $container->get( 'settings.service.connection_manager' ),
+ $container->get( 'settings.service.authentication_manager' ),
);
},
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
@@ -180,8 +180,8 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
- 'settings.service.connection_manager' => static function ( ContainerInterface $container ) : ConnectionManager {
- return new ConnectionManager(
+ 'settings.service.authentication_manager' => static function ( ContainerInterface $container ) : AuthenticationManager {
+ return new AuthenticationManager(
$container->get( 'settings.data.common' ),
$container->get( 'api.env.paypal-host' ),
$container->get( 'api.env.endpoint.login-seller' ),
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index f3c26c15f..c11ccf1a8 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -20,7 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
-use WooCommerce\PayPalCommerce\Settings\Service\ConnectionManager;
+use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
/**
* REST controller for authenticating and connecting to a PayPal merchant account.
@@ -40,6 +40,13 @@ class AuthenticationRestEndpoint extends RestEndpoint {
*/
protected $rest_base = 'authenticate';
+ /**
+ * Authentication manager service.
+ *
+ * @var AuthenticationManager
+ */
+ private AuthenticationManager $authentication_manager;
+
/**
* Defines the JSON response format (when connection was successful).
*
@@ -57,16 +64,16 @@ class AuthenticationRestEndpoint extends RestEndpoint {
/**
* Constructor.
*
- * @param ConnectionManager $connection_manager The connection manager.
+ * @param AuthenticationManager $authentication_manager The authentication manager.
*/
- public function __construct( ConnectionManager $connection_manager ) {
- $this->connection_manager = $connection_manager;
+ public function __construct( AuthenticationManager $authentication_manager ) {
+ $this->authentication_manager = $authentication_manager;
}
/**
* Configure REST API routes.
*/
- public function register_routes() {
+ public function register_routes() : void {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/direct',
@@ -139,13 +146,13 @@ class AuthenticationRestEndpoint extends RestEndpoint {
$use_sandbox = $request->get_param( 'useSandbox' );
try {
- $this->connection_manager->validate_id_and_secret( $client_id, $client_secret );
- $this->connection_manager->connect_via_secret( $use_sandbox, $client_id, $client_secret );
+ $this->authentication_manager->validate_id_and_secret( $client_id, $client_secret );
+ $this->authentication_manager->connect_via_secret( $use_sandbox, $client_id, $client_secret );
} catch ( Exception $exception ) {
return $this->return_error( $exception->getMessage() );
}
- $account = $this->connection_manager->get_account_details();
+ $account = $this->authentication_manager->get_account_details();
$response = $this->sanitize_for_javascript( $this->response_map, $account );
return $this->return_success( $response );
@@ -165,13 +172,13 @@ class AuthenticationRestEndpoint extends RestEndpoint {
$use_sandbox = $request->get_param( 'useSandbox' );
try {
- $this->connection_manager->validate_id_and_auth_code( $shared_id, $auth_code );
- $this->connection_manager->connect_via_auth_code( $use_sandbox, $shared_id, $auth_code );
+ $this->authentication_manager->validate_id_and_auth_code( $shared_id, $auth_code );
+ $this->authentication_manager->connect_via_auth_code( $use_sandbox, $shared_id, $auth_code );
} catch ( Exception $exception ) {
return $this->return_error( $exception->getMessage() );
}
- $account = $this->connection_manager->get_account_details();
+ $account = $this->authentication_manager->get_account_details();
$response = $this->sanitize_for_javascript( $this->response_map, $account );
return $this->return_success( $response );
diff --git a/modules/ppcp-settings/src/Service/ConnectionManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
similarity index 99%
rename from modules/ppcp-settings/src/Service/ConnectionManager.php
rename to modules/ppcp-settings/src/Service/AuthenticationManager.php
index 85d4af927..68ba8dde0 100644
--- a/modules/ppcp-settings/src/Service/ConnectionManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -25,7 +25,7 @@ use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
/**
* Class that manages the connection to PayPal.
*/
-class ConnectionManager {
+class AuthenticationManager {
/**
* Data model that stores the connection details.
*
From ed8ae81297c882dd7cade6708fa5f41db93085d5 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 17:20:12 +0100
Subject: [PATCH 51/57] =?UTF-8?q?=F0=9F=8E=A8=20Minor=20code=20improvement?=
=?UTF-8?q?s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Endpoint/AuthenticationRestEndpoint.php | 7 -------
.../ppcp-settings/src/Service/AuthenticationManager.php | 7 ++++++-
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index c11ccf1a8..0259f1c02 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -10,16 +10,9 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use Exception;
-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\Settings\Data\GeneralSettings;
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
/**
diff --git a/modules/ppcp-settings/src/Service/AuthenticationManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
index 68ba8dde0..81db1ffec 100644
--- a/modules/ppcp-settings/src/Service/AuthenticationManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -10,6 +10,7 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Service;
use JsonException;
+use Throwable;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
@@ -306,7 +307,11 @@ class AuthenticationManager {
$order_response = $orders->order( $order_id );
$order_body = json_decode( $order_response['body'], false, 512, JSON_THROW_ON_ERROR );
} catch ( JsonException $exception ) {
+ // Cast JsonException to a RuntimeException.
throw new RuntimeException( 'Could not decode JSON response: ' . $exception->getMessage() );
+ } catch ( Throwable $exception ) {
+ // Cast any other Throwable to a RuntimeException.
+ throw new RuntimeException( $exception->getMessage() );
}
$pu = $order_body->purchase_units[0];
@@ -315,7 +320,7 @@ class AuthenticationManager {
if ( ! is_object( $payee ) ) {
throw new RuntimeException( 'Payee not found.' );
}
- if ( ! isset( $payee->merchant_id ) || ! isset( $payee->email_address ) ) {
+ if ( ! isset( $payee->merchant_id, $payee->email_address ) ) {
throw new RuntimeException( 'Payee info not found.' );
}
From 19a7986b561e43e5e58a2b3249dda46e7b8f1417 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 17:44:53 +0100
Subject: [PATCH 52/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Align=20function=20n?=
=?UTF-8?q?aming=20in=20PHP=20with=20JS?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Endpoint/AuthenticationRestEndpoint.php | 4 ++--
modules/ppcp-settings/src/Handler/ConnectionListener.php | 2 +-
modules/ppcp-settings/src/Service/AuthenticationManager.php | 6 +++---
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
index 0259f1c02..9d72ff88c 100644
--- a/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
+++ b/modules/ppcp-settings/src/Endpoint/AuthenticationRestEndpoint.php
@@ -140,7 +140,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
try {
$this->authentication_manager->validate_id_and_secret( $client_id, $client_secret );
- $this->authentication_manager->connect_via_secret( $use_sandbox, $client_id, $client_secret );
+ $this->authentication_manager->authenticate_via_direct_api( $use_sandbox, $client_id, $client_secret );
} catch ( Exception $exception ) {
return $this->return_error( $exception->getMessage() );
}
@@ -166,7 +166,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
try {
$this->authentication_manager->validate_id_and_auth_code( $shared_id, $auth_code );
- $this->authentication_manager->connect_via_auth_code( $use_sandbox, $shared_id, $auth_code );
+ $this->authentication_manager->authenticate_via_oauth( $use_sandbox, $shared_id, $auth_code );
} catch ( Exception $exception ) {
return $this->return_error( $exception->getMessage() );
}
diff --git a/modules/ppcp-settings/src/Handler/ConnectionListener.php b/modules/ppcp-settings/src/Handler/ConnectionListener.php
index 5ce3f14d6..f11c0bc20 100644
--- a/modules/ppcp-settings/src/Handler/ConnectionListener.php
+++ b/modules/ppcp-settings/src/Handler/ConnectionListener.php
@@ -104,7 +104,7 @@ class ConnectionListener {
return;
}
- $this->logger->info( 'Found merchant data in request', $data );
+ $this->logger->info( 'Found OAuth merchant data in request', $data );
$connection = $this->settings->get_merchant_data();
diff --git a/modules/ppcp-settings/src/Service/AuthenticationManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
index 81db1ffec..d44d029bb 100644
--- a/modules/ppcp-settings/src/Service/AuthenticationManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -156,7 +156,7 @@ class AuthenticationManager {
* @return void
* @throws RuntimeException When failed to retrieve payee.
*/
- public function connect_via_secret( bool $use_sandbox, string $client_id, string $client_secret ) : void {
+ public function authenticate_via_direct_api( bool $use_sandbox, string $client_id, string $client_secret ) : void {
$this->disconnect();
$this->logger->info(
@@ -217,11 +217,11 @@ class AuthenticationManager {
* @return void
* @throws RuntimeException When failed to retrieve payee.
*/
- public function connect_via_auth_code( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
+ public function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
$this->disconnect();
$this->logger->info(
- 'Attempting ISU login to PayPal...',
+ 'Attempting OAuth login to PayPal...',
array(
'sandbox' => $use_sandbox,
'shared_id' => $shared_id,
From b3e766d08a995d399245ccf6b45b7f50eb8e284e Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 17:47:37 +0100
Subject: [PATCH 53/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20OAuth=20logic?=
=?UTF-8?q?=20from=20Listner=20to=20Auth-Manager?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-settings/services.php | 2 +-
.../src/Handler/ConnectionListener.php | 65 ++++++++-----------
.../src/Service/AuthenticationManager.php | 28 ++++++++
3 files changed, 55 insertions(+), 40 deletions(-)
diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php
index df50f886d..080973039 100644
--- a/modules/ppcp-settings/services.php
+++ b/modules/ppcp-settings/services.php
@@ -158,8 +158,8 @@ return array(
return new ConnectionListener(
$page_id,
- $container->get( 'settings.data.common' ),
$container->get( 'settings.service.onboarding-url-manager' ),
+ $container->get( 'settings.service.authentication_manager' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
diff --git a/modules/ppcp-settings/src/Handler/ConnectionListener.php b/modules/ppcp-settings/src/Handler/ConnectionListener.php
index f11c0bc20..7b30b51a1 100644
--- a/modules/ppcp-settings/src/Handler/ConnectionListener.php
+++ b/modules/ppcp-settings/src/Handler/ConnectionListener.php
@@ -12,8 +12,7 @@ namespace WooCommerce\PayPalCommerce\Settings\Handler;
use Psr\Log\LoggerInterface;
use RuntimeException;
-use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
-use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
+use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
@@ -33,13 +32,6 @@ class ConnectionListener {
*/
private string $settings_page_id;
- /**
- * Access to connection settings.
- *
- * @var CommonSettings
- */
- private CommonSettings $settings;
-
/**
* Access to the onboarding URL manager.
*
@@ -47,6 +39,13 @@ class ConnectionListener {
*/
private OnboardingUrlManager $url_manager;
+ /**
+ * Authentication manager service, responsible to update connection details.
+ *
+ * @var AuthenticationManager
+ */
+ private AuthenticationManager $authentication_manager;
+
/**
* Logger instance, mainly used for debugging purposes.
*
@@ -64,16 +63,21 @@ class ConnectionListener {
/**
* Prepare the instance.
*
- * @param string $settings_page_id Current plugin settings page ID.
- * @param CommonSettings $settings Access to saved connection details.
- * @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
- * @param ?LoggerInterface $logger The logger, for debugging purposes.
+ * @param string $settings_page_id Current plugin settings page ID.
+ * @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
+ * @param AuthenticationManager $authentication_manager Authentication manager service.
+ * @param ?LoggerInterface $logger The logger, for debugging purposes.
*/
- public function __construct( string $settings_page_id, CommonSettings $settings, OnboardingUrlManager $url_manager, LoggerInterface $logger = null ) {
- $this->settings_page_id = $settings_page_id;
- $this->settings = $settings;
- $this->url_manager = $url_manager;
- $this->logger = $logger ?: new NullLogger();
+ public function __construct(
+ string $settings_page_id,
+ OnboardingUrlManager $url_manager,
+ AuthenticationManager $authentication_manager,
+ LoggerInterface $logger = null
+ ) {
+ $this->settings_page_id = $settings_page_id;
+ $this->url_manager = $url_manager;
+ $this->authentication_manager = $authentication_manager;
+ $this->logger = $logger ?: new NullLogger();
// Initialize as "guest", the real ID is provided via process().
$this->user_id = 0;
@@ -106,15 +110,11 @@ class ConnectionListener {
$this->logger->info( 'Found OAuth merchant data in request', $data );
- $connection = $this->settings->get_merchant_data();
-
- if ( $connection->merchant_id !== $data['merchant_id'] ) {
- throw new RuntimeException( 'Unexpected merchant ID in request' );
+ try {
+ $this->authentication_manager->finish_oauth_authentication( $data );
+ } catch ( \Exception $e ) {
+ $this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
}
-
- $connection->merchant_email = $data['merchant_email'];
-
- $this->store_data( $connection );
}
/**
@@ -168,24 +168,11 @@ class ConnectionListener {
}
return array(
- 'is_sandbox' => $this->settings->get_sandbox(),
'merchant_id' => $merchant_id,
'merchant_email' => $merchant_email,
);
}
- /**
- * Persist the merchant details to the database.
- *
- * @param MerchantConnectionDTO $connection Merchant connection details to store.
- */
- protected function store_data( MerchantConnectionDTO $connection ) : void {
- $this->logger->info( 'Save merchant details to the DB', (array) $connection );
-
- $this->settings->set_merchant_data( $connection );
- $this->settings->save();
- }
-
/**
* Returns the sanitized connection token from the incoming request.
*
diff --git a/modules/ppcp-settings/src/Service/AuthenticationManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
index d44d029bb..1eaef1aae 100644
--- a/modules/ppcp-settings/src/Service/AuthenticationManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -248,6 +248,34 @@ class AuthenticationManager {
$this->update_connection_details( $connection );
}
+ /**
+ * Verifies the merchant details in the final OAuth redirect and extracts
+ * missing credentials from the URL.
+ *
+ * @param array $request_data Array of request parameters to process.
+ * @return void
+ *
+ * @throws RuntimeException Missing or invalid credentials.
+ */
+ public function finish_oauth_authentication( array $request_data ) : void {
+ $merchant_id = $request_data['merchant_id'];
+ $merchant_email = $request_data['merchant_email'];
+
+ if ( empty( $merchant_id ) || empty( $merchant_email ) ) {
+ throw new RuntimeException( 'Missing merchant ID or email in request' );
+ }
+
+ $connection = $this->common_settings->get_merchant_data();
+
+ if ( $connection->merchant_id !== $merchant_id ) {
+ throw new RuntimeException( 'Unexpected merchant ID in request' );
+ }
+
+ $connection->merchant_email = $merchant_email;
+
+ $this->update_connection_details( $connection );
+ }
+
// ----------------------------------------------------------------------------
// Internal helper methods
From a9c2a8e8feea73dce063d2abf640301c7806db01 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 8 Jan 2025 17:49:04 +0100
Subject: [PATCH 54/57] =?UTF-8?q?=E2=9C=A8=20Sync=20onboarding=20completio?=
=?UTF-8?q?n=20with=20connection=20state?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Service/AuthenticationManager.php | 16 +++++++-----
modules/ppcp-settings/src/SettingsModule.php | 26 ++++++++++++++++++-
2 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/modules/ppcp-settings/src/Service/AuthenticationManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
index 1eaef1aae..b7d308669 100644
--- a/modules/ppcp-settings/src/Service/AuthenticationManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -398,11 +398,15 @@ class AuthenticationManager {
$this->common_settings->set_merchant_data( $connection );
$this->common_settings->save();
- /**
- * Broadcast that the plugin connected to a new PayPal merchant account.
- * This is the right time to initialize merchant relative flags for the
- * first time.
- */
- do_action( 'woocommerce_paypal_payments_authenticated_merchant' );
+ if ( $this->common_settings->is_merchant_connected() ) {
+ $this->logger->info( 'Merchant successfully connected to PayPal' );
+
+ /**
+ * Broadcast that the plugin connected to a new PayPal merchant account.
+ * This is the right time to initialize merchant relative flags for the
+ * first time.
+ */
+ do_action( 'woocommerce_paypal_payments_authenticated_merchant' );
+ }
}
}
diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php
index d104ce7a4..59f752545 100644
--- a/modules/ppcp-settings/src/SettingsModule.php
+++ b/modules/ppcp-settings/src/SettingsModule.php
@@ -9,8 +9,9 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
-use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
+use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
+use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
@@ -203,6 +204,29 @@ class SettingsModule implements ServiceModule, ExecutableModule {
}
);
+ add_action(
+ 'woocommerce_paypal_payments_merchant_disconnected',
+ static function () use ( $container ) : void {
+ $onboarding_profile = $container->get( 'settings.data.onboarding' );
+ assert( $onboarding_profile instanceof OnboardingProfile );
+
+ $onboarding_profile->set_completed( false );
+ $onboarding_profile->set_step( 0 );
+ $onboarding_profile->save();
+ }
+ );
+
+ add_action(
+ 'woocommerce_paypal_payments_authenticated_merchant',
+ static function () use ( $container ) : void {
+ $onboarding_profile = $container->get( 'settings.data.onboarding' );
+ assert( $onboarding_profile instanceof OnboardingProfile );
+
+ $onboarding_profile->set_completed( true );
+ $onboarding_profile->save();
+ }
+ );
+
return true;
}
From eee97757c37ddcef4676feef173435c4d493b8b9 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 9 Jan 2025 12:07:09 +0100
Subject: [PATCH 55/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20merchant=20?=
=?UTF-8?q?data=20instead=20of=20replacing=20it?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Service/AuthenticationManager.php | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/modules/ppcp-settings/src/Service/AuthenticationManager.php b/modules/ppcp-settings/src/Service/AuthenticationManager.php
index b7d308669..4b3f18786 100644
--- a/modules/ppcp-settings/src/Service/AuthenticationManager.php
+++ b/modules/ppcp-settings/src/Service/AuthenticationManager.php
@@ -235,15 +235,12 @@ class AuthenticationManager {
* is invoked during the page reload, once the user clicks the blue
* "Return to Store" button in PayPal's login popup.
*/
- $empty_email = '';
+ $connection = $this->common_settings->get_merchant_data();
- $connection = new MerchantConnectionDTO(
- $use_sandbox,
- $credentials['client_id'],
- $credentials['client_secret'],
- $credentials['merchant_id'],
- $empty_email
- );
+ $connection->is_sandbox = $use_sandbox;
+ $connection->client_id = $credentials['client_id'];
+ $connection->client_secret = $credentials['client_secret'];
+ $connection->merchant_id = $credentials['merchant_id'];
$this->update_connection_details( $connection );
}
@@ -267,10 +264,11 @@ class AuthenticationManager {
$connection = $this->common_settings->get_merchant_data();
- if ( $connection->merchant_id !== $merchant_id ) {
+ if ( $connection->merchant_id && $connection->merchant_id !== $merchant_id ) {
throw new RuntimeException( 'Unexpected merchant ID in request' );
}
+ $connection->merchant_id = $merchant_id;
$connection->merchant_email = $merchant_email;
$this->update_connection_details( $connection );
From eb4c9a63027efde309cb91d2a7c0f8cf080a32d6 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 9 Jan 2025 12:34:22 +0100
Subject: [PATCH 56/57] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20phpcs=20warnings?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-api-client/services.php | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index ab58194a7..349ba5577 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -881,7 +881,11 @@ return array(
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
},
'api.env.paypal-host' => static function ( ContainerInterface $container ) : EnvironmentConfig {
- /** @type EnvironmentConfig Configuration object */
+ /**
+ * Environment specific API host names.
+ *
+ * @type EnvironmentConfig
+ */
return EnvironmentConfig::create(
'string',
$container->get( 'api.paypal-host-production' ),
@@ -889,7 +893,11 @@ return array(
);
},
'api.env.endpoint.login-seller' => static function ( ContainerInterface $container ) : EnvironmentConfig {
- /** @type EnvironmentConfig Configuration object */
+ /**
+ * Environment specific LoginSeller API instances.
+ *
+ * @type EnvironmentConfig
+ */
return EnvironmentConfig::create(
LoginSeller::class,
$container->get( 'api.endpoint.login-seller-production' ),
@@ -897,7 +905,11 @@ return array(
);
},
'api.env.endpoint.partner-referrals' => static function ( ContainerInterface $container ) : EnvironmentConfig {
- /** @type EnvironmentConfig Configuration object */
+ /**
+ * Environment specific PartnerReferrals API instances.
+ *
+ * @type EnvironmentConfig
+ */
return EnvironmentConfig::create(
PartnerReferrals::class,
$container->get( 'api.endpoint.partner-referrals-production' ),
From f384d361cf6d4d730af63950ff010b52bda1fc7b Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 9 Jan 2025 12:36:05 +0100
Subject: [PATCH 57/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20code=20from?=
=?UTF-8?q?=20onboarding-=20to=20api-client-module?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
modules/ppcp-api-client/services.php | 14 ++++++++++++++
modules/ppcp-onboarding/services.php | 22 ----------------------
2 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 349ba5577..515968463 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -880,6 +880,20 @@ return array(
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
},
+ 'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
+ return new LoginSeller(
+ $container->get( 'api.paypal-host-production' ),
+ $container->get( 'api.partner_merchant_id-production' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
+ return new LoginSeller(
+ $container->get( 'api.paypal-host-sandbox' ),
+ $container->get( 'api.partner_merchant_id-sandbox' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
'api.env.paypal-host' => static function ( ContainerInterface $container ) : EnvironmentConfig {
/**
* Environment specific API host names.
diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php
index 56a49be8e..aca3bed0a 100644
--- a/modules/ppcp-onboarding/services.php
+++ b/modules/ppcp-onboarding/services.php
@@ -14,14 +14,12 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
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\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
-use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
return array(
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
@@ -144,26 +142,6 @@ return array(
);
},
- 'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
-
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new LoginSeller(
- $container->get( 'api.paypal-host-production' ),
- $container->get( 'api.partner_merchant_id-production' ),
- $logger
- );
- },
-
- 'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
-
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new LoginSeller(
- $container->get( 'api.paypal-host-sandbox' ),
- $container->get( 'api.partner_merchant_id-sandbox' ),
- $logger
- );
- },
-
'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint {
$request_data = $container->get( 'button.request-data' );