From 56b510e8fcfe07fae78962b5ee17e9a332355888 Mon Sep 17 00:00:00 2001 From: David Remer Date: Thu, 13 Aug 2020 14:20:47 +0300 Subject: [PATCH 1/2] load data-client-id via endpoint when needed --- .../ppcp-button/resources/js/button.js | 12 +++-- .../modules/DataClientIdAttributeHandler.js | 52 +++++++++++++++++++ modules.local/ppcp-button/services.php | 9 ++++ .../ppcp-button/src/Assets/SmartButton.php | 22 +++----- .../ppcp-button/src/ButtonModule.php | 12 +++++ .../src/Endpoint/DataClientIdEndpoint.php | 48 +++++++++++++++++ 6 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 modules.local/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js create mode 100644 modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php diff --git a/modules.local/ppcp-button/resources/js/button.js b/modules.local/ppcp-button/resources/js/button.js index 5ca11fa4c..9c9be8cf3 100644 --- a/modules.local/ppcp-button/resources/js/button.js +++ b/modules.local/ppcp-button/resources/js/button.js @@ -5,6 +5,7 @@ import CheckoutBootstap from './modules/ContextBootstrap/CheckoutBootstap'; import Renderer from './modules/Renderer/Renderer'; import ErrorHandler from './modules/ErrorHandler'; import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer"; +import dataClientIdAttributeHandler from "./modules/DataClientIdAttributeHandler"; const bootstrap = () => { const errorHandler = new ErrorHandler(PayPalCommerceGateway.labels.error.generic); @@ -57,15 +58,20 @@ document.addEventListener( } const script = document.createElement('script'); + script.addEventListener('load', (event) => { + bootstrap(); + }); script.setAttribute('src', PayPalCommerceGateway.button.url); Object.entries(PayPalCommerceGateway.script_attributes).forEach( (keyValue) => { script.setAttribute(keyValue[0], keyValue[1]); } ); - script.addEventListener('load', (event) => { - bootstrap(); - }); + + if (PayPalCommerceGateway.data_client_id.set_attribute) { + dataClientIdAttributeHandler(script, PayPalCommerceGateway.data_client_id); + return; + } document.body.append(script); }, diff --git a/modules.local/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js b/modules.local/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js new file mode 100644 index 000000000..8ddecbed6 --- /dev/null +++ b/modules.local/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js @@ -0,0 +1,52 @@ +const storageKey = 'ppcp-data-client-id'; + +const validateToken = (token, user) => { + if (! token) { + return false; + } + if (token.user !== user) { + return false; + } + const currentTime = new Date().getTime(); + const isExpired = currentTime >= token.expiration * 1000; + return ! isExpired; +} + +const storedTokenForUser = (user) => { + const token = JSON.parse(sessionStorage.getItem(storageKey)); + if (validateToken(token, user)) { + return token.token; + } + return null; +} + +const storeToken = (token) => { + sessionStorage.setItem(storageKey, JSON.stringify(token)); +} + +const dataClientIdAttributeHandler = (script, config) => { + const token = storedTokenForUser(config.user); + if (token) { + script.setAttribute('data-client-token', token); + document.body.append(script); + return; + } + fetch(config.endpoint, { + method: 'POST', + body: JSON.stringify({ + nonce: config.nonce + }) + }).then((res)=>{ + return res.json(); + }).then((data)=>{ + const isValid = validateToken(data, config.user); + if (!isValid) { + return; + } + storeToken(data); + script.setAttribute('data-client-token', data.token); + document.body.append(script); + }); +} + +export default dataClientIdAttributeHandler; \ No newline at end of file diff --git a/modules.local/ppcp-button/services.php b/modules.local/ppcp-button/services.php index 68d20a4f3..4ad4dcfd2 100644 --- a/modules.local/ppcp-button/services.php +++ b/modules.local/ppcp-button/services.php @@ -11,6 +11,7 @@ use Inpsyde\PayPalCommerce\Button\Assets\SmartButtonInterface; use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; +use Inpsyde\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\RequestData; use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException; use Inpsyde\PayPalCommerce\Button\Helper\ThreeDSecure; @@ -106,6 +107,14 @@ return [ $threeDSecure = $container->get('button.helper.three-d-secure'); return new ApproveOrderEndpoint($requestData, $apiClient, $sessionHandler, $threeDSecure); }, + 'button.endpoint.data-client-id' => static function(ContainerInterface $container) : DataClientIdEndpoint { + $requestData = $container->get('button.request-data'); + $tokenEndpoint = $container->get('api.endpoint.identity-token'); + return new DataClientIdEndpoint( + $requestData, + $tokenEndpoint + ); + }, 'button.helper.three-d-secure' => static function (ContainerInterface $container): ThreeDSecure { return new ThreeDSecure(); }, diff --git a/modules.local/ppcp-button/src/Assets/SmartButton.php b/modules.local/ppcp-button/src/Assets/SmartButton.php index 42fa094db..ebaa3fa14 100644 --- a/modules.local/ppcp-button/src/Assets/SmartButton.php +++ b/modules.local/ppcp-button/src/Assets/SmartButton.php @@ -12,6 +12,7 @@ use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository; use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; +use Inpsyde\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\RequestData; use Inpsyde\PayPalCommerce\Session\SessionHandler; use Inpsyde\PayPalCommerce\Subscription\Helper\SubscriptionHelper; @@ -305,6 +306,12 @@ class SmartButton implements SmartButtonInterface $this->requestData->enqueueNonceFix(); $localize = [ 'script_attributes' => $this->attributes(), + 'data_client_id' => [ + 'set_attribute' => $this->dccIsEnabled() || $this->canSaveVaultToken(), + 'endpoint' => home_url(\WC_AJAX::get_endpoint(DataClientIdEndpoint::ENDPOINT)), + 'nonce' => wp_create_nonce(DataClientIdEndpoint::nonce()), + 'user' => get_current_user_id(), + ], 'redirect' => wc_get_checkout_url(), 'context' => $this->context(), 'ajax' => [ @@ -408,22 +415,9 @@ class SmartButton implements SmartButtonInterface private function attributes(): array { - $attributes = [ + return [ 'data-partner-attribution-id' => $this->bnCodeForContext($this->context()), ]; - try { - if (!is_user_logged_in()) { - return $attributes; - } - if (! $this->dccIsEnabled() && ! $this->canSaveVaultToken()) { - return $attributes; - } - $clientToken = $this->identityToken->generateForCustomer((int) get_current_user_id()); - $attributes['data-client-token'] = $clientToken->token(); - return $attributes; - } catch (RuntimeException $exception) { - return $attributes; - } } /** diff --git a/modules.local/ppcp-button/src/ButtonModule.php b/modules.local/ppcp-button/src/ButtonModule.php index 1bdf0d569..7e09c60d0 100644 --- a/modules.local/ppcp-button/src/ButtonModule.php +++ b/modules.local/ppcp-button/src/ButtonModule.php @@ -10,6 +10,7 @@ use Inpsyde\PayPalCommerce\Button\Assets\SmartButton; use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; +use Inpsyde\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use Inpsyde\PayPalCommerce\Button\Endpoint\RequestData; use Interop\Container\ServiceProviderInterface; use Psr\Container\ContainerInterface; @@ -49,6 +50,17 @@ class ButtonModule implements ModuleInterface $smartButton->enqueue(); }); + add_action( + 'wc_ajax_' . DataClientIdEndpoint::ENDPOINT, + static function () use ($container) { + $endpoint = $container->get('button.endpoint.data-client-id'); + /** + * @var DataClientIdEndpoint $endpoint + */ + $endpoint->handleRequest(); + } + ); + add_action( 'wc_ajax_' . ChangeCartEndpoint::ENDPOINT, static function () use ($container) { diff --git a/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php b/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php new file mode 100644 index 000000000..37cca7eb4 --- /dev/null +++ b/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php @@ -0,0 +1,48 @@ +requestData = $requestData; + $this->identityToken = $identityToken; + } + + public static function nonce(): string + { + return self::ENDPOINT; + } + + public function handleRequest(): bool + { + try { + $this->requestData->readRequest($this->nonce()); + $userId = get_current_user_id(); + $token = $this->identityToken->generateForCustomer($userId); + wp_send_json([ + 'token' => $token->token(), + 'expiration' => $token->expirationTimestamp(), + 'user' => get_current_user_id(), + ]); + return true; + } catch (RuntimeException $error) { + wp_send_json_error($error->getMessage()); + return false; + } + } +} From 6630c9555727eeba382afbd6c56ae999aa7d94c2 Mon Sep 17 00:00:00 2001 From: David Remer Date: Thu, 13 Aug 2020 14:30:12 +0300 Subject: [PATCH 2/2] prefix endpoint to avoid possible conflicts --- modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php b/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php index 37cca7eb4..904553a05 100644 --- a/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php +++ b/modules.local/ppcp-button/src/Endpoint/DataClientIdEndpoint.php @@ -10,7 +10,7 @@ use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException; class DataClientIdEndpoint implements EndpointInterface { - public const ENDPOINT = 'data-client-id'; + public const ENDPOINT = 'ppc-data-client-id'; private $requestData; private $identityToken;