From a31ae1da7706017eeb9ffa57e75769312fc98db4 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Wed, 6 Nov 2024 09:15:53 +0200 Subject: [PATCH] Retrieve merchant data by creating order --- .../src/Helper/InMemoryCache.php | 66 ++++++++ .../Screens/Onboarding/StepWelcome.js | 2 + modules/ppcp-settings/services.php | 6 +- .../Endpoint/ConnectManualRestEndpoint.php | 152 +++++++++++++++++- 4 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 modules/ppcp-api-client/src/Helper/InMemoryCache.php diff --git a/modules/ppcp-api-client/src/Helper/InMemoryCache.php b/modules/ppcp-api-client/src/Helper/InMemoryCache.php new file mode 100644 index 000000000..53dc90d1a --- /dev/null +++ b/modules/ppcp-api-client/src/Helper/InMemoryCache.php @@ -0,0 +1,66 @@ + + */ + private array $data = array(); + + /** + * InMemoryCache constructor + */ + public function __construct() { + parent::__construct( '' ); + } + + /** + * Gets a value. + * + * @param string $key The key under which the value is stored. + * + * @return mixed + */ + public function get( string $key ) { + if ( ! array_key_exists( $key, $this->data ) ) { + return false; + } + return $this->data[ $key ]; + } + + /** + * Deletes a cache. + * + * @param string $key The key. + */ + public function delete( string $key ): void { + unset( $this->data[ $key ] ); + } + + /** + * Caches a value. + * + * @param string $key The key under which the value should be cached. + * @param mixed $value The value to cache. + * @param int $expiration Unused. + * + * @return bool + */ + public function set( string $key, $value, int $expiration = 0 ): bool { + $this->data[ $key ] = $value; + return true; + } +} diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/StepWelcome.js b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/StepWelcome.js index 2f51f605a..d7638ff75 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/StepWelcome.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Onboarding/StepWelcome.js @@ -100,6 +100,8 @@ const WelcomeForm = ( { setCompleted } ) => { throw new Error( 'Request failed.' ); } + console.log(`Merchant ID: ${res.merchantId}, email: ${res.email}`); + setCompleted( true ); } catch ( exc ) { console.error( exc ); diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index bab12ffb8..80df22370 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -47,7 +47,11 @@ return array( return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); }, 'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint { - return new ConnectManualRestEndpoint(); + return new ConnectManualRestEndpoint( + $container->get( 'api.paypal-host-production' ), + $container->get( 'api.paypal-host-sandbox' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); }, 'settings.casual-selling.supported-countries' => static function ( ContainerInterface $container ) : array { return array( diff --git a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php index 904f415d4..af81b90ca 100644 --- a/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/ConnectManualRestEndpoint.php @@ -9,6 +9,15 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Endpoint; +use Exception; +use Psr\Log\LoggerInterface; +use RuntimeException; +use stdClass; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; +use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WP_REST_Server; use WP_REST_Response; use WP_REST_Request; @@ -17,6 +26,28 @@ use WP_REST_Request; * 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. * @@ -44,6 +75,24 @@ class ConnectManualRestEndpoint extends RestEndpoint { ), ); + /** + * 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. + */ + public function __construct( + string $live_host, + string $sandbox_host, + LoggerInterface $logger + ) { + + $this->live_host = $live_host; + $this->sandbox_host = $sandbox_host; + $this->logger = $logger; + } + /** * Configure REST API routes. */ @@ -72,21 +121,116 @@ class ConnectManualRestEndpoint extends RestEndpoint { $this->field_map ); - if ( ! isset( $data['client_id'] ) || empty( $data['client_id'] ) - || ! isset( $data['client_secret'] ) || empty( $data['client_secret'] ) ) { + $client_id = $data['client_id'] ?? ''; + $client_secret = $data['client_secret'] ?? ''; + $use_sandbox = (bool) ( $data['use_sandbox'] ?? false ); + + if ( empty( $client_id ) || empty( $client_secret ) ) { return rest_ensure_response( array( 'success' => false, + 'message' => 'No client ID or secret provided.', ) ); } + try { + $payee = $this->request_payee( $client_id, $client_secret, $use_sandbox ); + } catch ( Exception $exception ) { + return rest_ensure_response( + array( + 'success' => false, + 'message' => $exception->getMessage(), + ) + ); + + } + $result = array( - 'merchantId' => 'bt_us@woocommerce.com', - 'email' => 'AT45V2DGMKLRY', + 'merchantId' => $payee->merchant_id, + 'email' => $payee->email_address, 'success' => true, ); return rest_ensure_response( $result ); } + + /** + * Retrieves the payee object with the merchant data + * by creating a minimal PayPal order. + * + * @param string $client_id The client ID. + * @param string $client_secret The client secret. + * @param bool $use_sandbox Whether to use the sandbox mode. + * @return stdClass The payee object. + * @throws Exception When failed to retrieve payee. + * + * phpcs:disable Squiz.Commenting + * phpcs:disable Generic.Commenting + */ + private function request_payee( + string $client_id, + string $client_secret, + bool $use_sandbox + ) : stdClass { + + $host = $use_sandbox ? $this->sandbox_host : $this->live_host; + + $empty_settings = new class() implements ContainerInterface + { + public function get( string $id ) { + throw new NotFoundException(); + } + + public function has( string $id ) { + return false; + } + }; + + $bearer = new PayPalBearer( + new InMemoryCache(), + $host, + $client_id, + $client_secret, + $this->logger, + $empty_settings + ); + + $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; + } }