From 3b2f929df1bf5210716f8e0f0b80bc4168195eb7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 20 Dec 2022 11:10:22 +0200 Subject: [PATCH 01/49] Make SmartButton more reusable --- modules/ppcp-button/resources/js/button.js | 18 +------- .../js/modules/Helper/ScriptLoading.js | 24 +++++++++++ .../src/Assets/DisabledSmartButton.php | 23 +++++++--- .../ppcp-button/src/Assets/SmartButton.php | 42 ++++++++++++------- .../src/Assets/SmartButtonInterface.php | 18 ++++++-- 5 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index a118211a2..96ac2e630 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -6,7 +6,6 @@ import PayNowBootstrap from "./modules/ContextBootstrap/PayNowBootstrap"; import Renderer from './modules/Renderer/Renderer'; import ErrorHandler from './modules/ErrorHandler'; import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer"; -import dataClientIdAttributeHandler from "./modules/DataClientIdAttributeHandler"; import MessageRenderer from "./modules/Renderer/MessageRenderer"; import Spinner from "./modules/Helper/Spinner"; import { @@ -17,6 +16,7 @@ import { import {hide, setVisible, setVisibleByClass} from "./modules/Helper/Hiding"; import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; +import {loadPaypalScript} from "./modules/Helper/ScriptLoading"; // TODO: could be a good idea to have a separate spinner for each gateway, // but I think we care mainly about the script loading, so one spinner should be enough. @@ -240,24 +240,10 @@ document.addEventListener( hideOrderButtonIfPpcpGateway(); }); - const script = document.createElement('script'); - script.addEventListener('load', (event) => { + loadPaypalScript(PayPalCommerceGateway, () => { bootstrapped = true; bootstrap(); }); - script.setAttribute('src', PayPalCommerceGateway.button.url); - Object.entries(PayPalCommerceGateway.script_attributes).forEach( - (keyValue) => { - script.setAttribute(keyValue[0], keyValue[1]); - } - ); - - if (PayPalCommerceGateway.data_client_id.set_attribute) { - dataClientIdAttributeHandler(script, PayPalCommerceGateway.data_client_id); - return; - } - - document.body.appendChild(script); }, ); diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js new file mode 100644 index 000000000..c5742ab19 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -0,0 +1,24 @@ +import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler"; + +export const loadPaypalScript = (config, onLoaded) => { + if (typeof paypal !== 'undefined') { + onLoaded(); + return; + } + + const script = document.createElement('script'); + script.addEventListener('load', onLoaded); + script.setAttribute('src', config.url); + Object.entries(config.script_attributes).forEach( + (keyValue) => { + script.setAttribute(keyValue[0], keyValue[1]); + } + ); + + if (config.data_client_id.set_attribute) { + dataClientIdAttributeHandler(script, config.data_client_id); + return; + } + + document.body.appendChild(script); +} diff --git a/modules/ppcp-button/src/Assets/DisabledSmartButton.php b/modules/ppcp-button/src/Assets/DisabledSmartButton.php index 9fe537163..04cee3eab 100644 --- a/modules/ppcp-button/src/Assets/DisabledSmartButton.php +++ b/modules/ppcp-button/src/Assets/DisabledSmartButton.php @@ -24,12 +24,16 @@ class DisabledSmartButton implements SmartButtonInterface { } /** - * Enqueues necessary scripts. - * - * @return bool + * Whether the scripts should be loaded. */ - public function enqueue(): bool { - return true; + public function should_load(): bool { + return false; + } + + /** + * Enqueues necessary scripts. + */ + public function enqueue(): void { } /** @@ -41,4 +45,13 @@ class DisabledSmartButton implements SmartButtonInterface { return false; } + + /** + * The configuration for the smart buttons. + * + * @return array + */ + public function script_data(): array { + return array(); + } } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 265178906..2c770cfae 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -493,17 +493,25 @@ class SmartButton implements SmartButtonInterface { } /** - * Enqueues the script. - * - * @return bool - * @throws NotFoundException When a setting was not found. + * Whether the scripts should be loaded. */ - public function enqueue(): bool { + public function should_load(): bool { $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); if ( ! is_checkout() && ! $buttons_enabled ) { return false; } + return true; + } + + /** + * Enqueues the scripts. + */ + public function enqueue(): void { + if ( ! $this->should_load() ) { + return; + } + $load_script = false; if ( is_checkout() && $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) { $load_script = true; @@ -541,10 +549,9 @@ class SmartButton implements SmartButtonInterface { wp_localize_script( 'ppcp-smart-button', 'PayPalCommerceGateway', - $this->localize_script() + $this->script_data() ); } - return true; } /** @@ -738,18 +745,22 @@ class SmartButton implements SmartButtonInterface { } /** - * The localized data for the smart button. + * The configuration for the smart buttons. * * @return array * @throws NotFoundException If a setting hasn't been found. */ - private function localize_script(): array { + public function script_data(): array { global $wp; $is_free_trial_cart = $this->is_free_trial_cart(); + $url_params = $this->url_params(); + $this->request_data->enqueue_nonce_fix(); $localize = array( + 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ), + 'url_params' => $url_params, 'script_attributes' => $this->attributes(), 'data_client_id' => array( 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(), @@ -788,7 +799,6 @@ class SmartButton implements SmartButtonInterface { 'wrapper' => '#ppc-button-' . PayPalGateway::ID, 'mini_cart_wrapper' => '#ppc-button-minicart', 'cancel_wrapper' => '#ppcp-cancel', - 'url' => $this->url(), 'mini_cart_style' => array( 'layout' => $this->style_for_context( 'layout', 'mini-cart' ), 'color' => $this->style_for_context( 'color', 'mini-cart' ), @@ -894,12 +904,12 @@ class SmartButton implements SmartButtonInterface { } /** - * The JavaScript SDK url to load. + * The JavaScript SDK url parameters. * - * @return string + * @return array * @throws NotFoundException If a setting was not found. */ - private function url(): string { + private function url_params(): array { $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; $product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent; $other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent; @@ -971,8 +981,7 @@ class SmartButton implements SmartButtonInterface { $params['enable-funding'] = implode( ',', array_unique( $enable_funding ) ); } - $smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' ); - return $smart_button_url; + return $params; } /** @@ -1075,7 +1084,8 @@ class SmartButton implements SmartButtonInterface { if ( is_cart() ) { $context = 'cart'; } - if ( is_checkout() && ! $this->is_paypal_continuation() ) { + // TODO: refactor. + if ( is_checkout() && ! $this->is_paypal_continuation() || did_action( 'woocommerce_blocks_checkout_enqueue_data' ) ) { $context = 'checkout'; } if ( is_checkout_pay_page() ) { diff --git a/modules/ppcp-button/src/Assets/SmartButtonInterface.php b/modules/ppcp-button/src/Assets/SmartButtonInterface.php index a574bc649..be5f5d015 100644 --- a/modules/ppcp-button/src/Assets/SmartButtonInterface.php +++ b/modules/ppcp-button/src/Assets/SmartButtonInterface.php @@ -22,11 +22,14 @@ interface SmartButtonInterface { public function render_wrapper(): bool; /** - * Enqueues the necessary scripts. - * - * @return bool + * Whether the scripts should be loaded. */ - public function enqueue(): bool; + public function should_load(): bool; + + /** + * Enqueues the necessary scripts. + */ + public function enqueue(): void; /** * Whether the running installation could save vault tokens or not. @@ -34,4 +37,11 @@ interface SmartButtonInterface { * @return bool */ public function can_save_vault_token(): bool; + + /** + * The configuration for the smart buttons. + * + * @return array + */ + public function script_data(): array; } From 2012548fc2c4f1684bfaa7a5cc3d6ce24e3d08bf Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 20 Dec 2022 11:11:53 +0200 Subject: [PATCH 02/49] Fix phpdoc --- modules/ppcp-button/src/Assets/SmartButton.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 2c770cfae..82a94f8c3 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -694,7 +694,6 @@ class SmartButton implements SmartButtonInterface { * Whether we can store vault tokens or not. * * @return bool - * @throws NotFoundException If a setting hasn't been found. */ public function can_save_vault_token(): bool { @@ -748,7 +747,6 @@ class SmartButton implements SmartButtonInterface { * The configuration for the smart buttons. * * @return array - * @throws NotFoundException If a setting hasn't been found. */ public function script_data(): array { global $wp; @@ -907,7 +905,6 @@ class SmartButton implements SmartButtonInterface { * The JavaScript SDK url parameters. * * @return array - * @throws NotFoundException If a setting was not found. */ private function url_params(): array { $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; @@ -1118,7 +1115,6 @@ class SmartButton implements SmartButtonInterface { * Whether DCC is enabled or not. * * @return bool - * @throws NotFoundException If a setting has not been found. */ private function dcc_is_enabled(): bool { if ( ! is_checkout() ) { @@ -1147,7 +1143,6 @@ class SmartButton implements SmartButtonInterface { * @param string $context The context. * * @return string - * @throws NotFoundException When a setting hasn't been found. */ private function style_for_context( string $style, string $context ): string { $defaults = array( From 8bb5f464b4ce9650edd46f97502e3c250deca048 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 10 Jan 2023 11:22:32 +0200 Subject: [PATCH 03/49] Fix order response --- modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php index 7554f1731..a56176717 100644 --- a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php @@ -180,7 +180,7 @@ class ApproveOrderEndpoint implements EndpointInterface { ); } $this->session_handler->replace_order( $order ); - wp_send_json_success( $order ); + wp_send_json_success( $order->to_array() ); } if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) { @@ -198,7 +198,7 @@ class ApproveOrderEndpoint implements EndpointInterface { $this->session_handler->replace_funding_source( $funding_source ); $this->session_handler->replace_order( $order ); - wp_send_json_success( $order ); + wp_send_json_success( $order->to_array() ); return true; } catch ( Exception $error ) { $this->logger->error( 'Order approve failed: ' . $error->getMessage() ); From 0bf18d67c5c428a918b6bb41b25f44d833428b97 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 11 Jan 2023 17:44:30 +0200 Subject: [PATCH 04/49] Pass user_action --- modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php | 6 ++++-- .../src/Repository/ApplicationContextRepository.php | 7 +++++-- tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index 6438ba95f..7c0db1189 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -191,6 +191,7 @@ class OrderEndpoint { * @param PaymentToken|null $payment_token The payment token. * @param PaymentMethod|null $payment_method The payment method. * @param string $paypal_request_id The paypal request id. + * @param string $user_action The user action. * * @return Order * @throws RuntimeException If the request fails. @@ -201,7 +202,8 @@ class OrderEndpoint { Payer $payer = null, PaymentToken $payment_token = null, PaymentMethod $payment_method = null, - string $paypal_request_id = '' + string $paypal_request_id = '', + string $user_action = ApplicationContext::USER_ACTION_CONTINUE ): Order { $bearer = $this->bearer->bearer(); $data = array( @@ -213,7 +215,7 @@ class OrderEndpoint { $items ), 'application_context' => $this->application_context_repository - ->current_context( $shipping_preference )->to_array(), + ->current_context( $shipping_preference, $user_action )->to_array(), ); if ( $payer && ! empty( $payer->email_address() ) ) { $data['payer'] = $payer->to_array(); diff --git a/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php b/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php index 03c619495..acfb4a6cf 100644 --- a/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php +++ b/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php @@ -38,11 +38,13 @@ class ApplicationContextRepository { * Returns the current application context. * * @param string $shipping_preferences The shipping preferences. + * @param string $user_action The user action. * * @return ApplicationContext */ public function current_context( - string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING + string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, + string $user_action = ApplicationContext::USER_ACTION_CONTINUE ): ApplicationContext { $brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : ''; @@ -55,7 +57,8 @@ class ApplicationContextRepository { (string) $brand_name, $locale, (string) $landingpage, - $shipping_preferences + $shipping_preferences, + $user_action ); return $context; } diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php index 01972386e..44c9f7da9 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php @@ -936,7 +936,7 @@ class OrderEndpointTest extends TestCase $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); $applicationContextRepository ->expects('current_context') - ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING)) + ->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE) ->andReturn($applicationContext); $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class); $paypalRequestIdRepository @@ -1049,7 +1049,7 @@ class OrderEndpointTest extends TestCase $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); $applicationContextRepository ->expects('current_context') - ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE)) + ->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE) ->andReturn($applicationContext); $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class); $paypalRequestIdRepository @@ -1139,7 +1139,7 @@ class OrderEndpointTest extends TestCase $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); $applicationContextRepository ->expects('current_context') - ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING)) + ->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE) ->andReturn($applicationContext); $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class); @@ -1232,7 +1232,7 @@ class OrderEndpointTest extends TestCase $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); $applicationContextRepository ->expects('current_context') - ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE)) + ->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE) ->andReturn($applicationContext); $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class); From ea3e5bbeb61efb80564531755f8c7082a53f701f Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 20 Dec 2022 16:04:11 +0200 Subject: [PATCH 05/49] Add express checkout block (wip) --- .psalm/wcblocks.php | 370 +++ composer.lock | 40 +- modules.php | 1 + modules/ppcp-blocks/.babelrc | 14 + modules/ppcp-blocks/.gitignore | 3 + modules/ppcp-blocks/composer.json | 17 + modules/ppcp-blocks/extensions.php | 12 + modules/ppcp-blocks/module.php | 16 + modules/ppcp-blocks/package.json | 34 + .../resources/js/Helper/Address.js | 84 + .../resources/js/checkout-block.js | 151 ++ modules/ppcp-blocks/services.php | 34 + modules/ppcp-blocks/src/BlocksModule.php | 53 + .../ppcp-blocks/src/PayPalPaymentMethod.php | 100 + modules/ppcp-blocks/webpack.config.js | 38 + modules/ppcp-blocks/yarn.lock | 2233 +++++++++++++++++ .../ppcp-button/src/Assets/SmartButton.php | 3 +- .../src/Endpoint/CreateOrderEndpoint.php | 7 +- .../src/Processor/OrderProcessor.php | 9 +- package.json | 3 + phpcs.xml.dist | 1 + psalm.xml.dist | 1 + 22 files changed, 3199 insertions(+), 25 deletions(-) create mode 100644 .psalm/wcblocks.php create mode 100644 modules/ppcp-blocks/.babelrc create mode 100644 modules/ppcp-blocks/.gitignore create mode 100644 modules/ppcp-blocks/composer.json create mode 100644 modules/ppcp-blocks/extensions.php create mode 100644 modules/ppcp-blocks/module.php create mode 100644 modules/ppcp-blocks/package.json create mode 100644 modules/ppcp-blocks/resources/js/Helper/Address.js create mode 100644 modules/ppcp-blocks/resources/js/checkout-block.js create mode 100644 modules/ppcp-blocks/services.php create mode 100644 modules/ppcp-blocks/src/BlocksModule.php create mode 100644 modules/ppcp-blocks/src/PayPalPaymentMethod.php create mode 100644 modules/ppcp-blocks/webpack.config.js create mode 100644 modules/ppcp-blocks/yarn.lock diff --git a/.psalm/wcblocks.php b/.psalm/wcblocks.php new file mode 100644 index 000000000..d99fcbcf8 --- /dev/null +++ b/.psalm/wcblocks.php @@ -0,0 +1,370 @@ + $instance` pairs. + * + * @var IntegrationInterface[] + */ + protected $registered_integrations = []; + + /** + * Initializes all registered integrations. + * + * Integration identifier is used to construct hook names and is given when the integration registry is initialized. + * + * @param string $registry_identifier Identifier for this registry. + */ + public function initialize($registry_identifier = '') + { + } + + /** + * Registers an integration. + * + * @param IntegrationInterface $integration An instance of IntegrationInterface. + * + * @return boolean True means registered successfully. + */ + public function register(IntegrationInterface $integration) + { + } + + /** + * Checks if an integration is already registered. + * + * @param string $name Integration name. + * @return bool True if the integration is registered, false otherwise. + */ + public function is_registered($name) + { + } + + /** + * Un-register an integration. + * + * @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance. + * @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully. + */ + public function unregister($name) + { + } + + /** + * Retrieves a registered Integration by name. + * + * @param string $name Integration name. + * @return IntegrationInterface|null The registered integration, or null if it is not registered. + */ + public function get_registered($name) + { + } + + /** + * Retrieves all registered integrations. + * + * @return IntegrationInterface[] + */ + public function get_all_registered() + { + } + + /** + * Gets an array of all registered integration's script handles for the editor. + * + * @return string[] + */ + public function get_all_registered_editor_script_handles() + { + } + + /** + * Gets an array of all registered integration's script handles. + * + * @return string[] + */ + public function get_all_registered_script_handles() + { + } + + /** + * Gets an array of all registered integration's script data. + * + * @return array + */ + public function get_all_registered_script_data() + { + } + } +} + +namespace Automattic\WooCommerce\Blocks\Payments { + use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; + + /** + * Class used for interacting with payment method types. + * + * @since 2.6.0 + */ + final class PaymentMethodRegistry extends IntegrationRegistry + { + /** + * Integration identifier is used to construct hook names and is given when the integration registry is initialized. + * + * @var string + */ + protected $registry_identifier = 'payment_method_type'; + + /** + * Retrieves all registered payment methods that are also active. + * + * @return PaymentMethodTypeInterface[] + */ + public function get_all_active_registered() + { + } + + /** + * Gets an array of all registered payment method script handles, but only for active payment methods. + * + * @return string[] + */ + public function get_all_active_payment_method_script_dependencies() + { + } + + /** + * Gets an array of all registered payment method script data, but only for active payment methods. + * + * @return array + */ + public function get_all_registered_script_data() + { + } + } +} diff --git a/composer.lock b/composer.lock index e377dddce..a46a52e72 100644 --- a/composer.lock +++ b/composer.lock @@ -1430,30 +1430,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -1480,7 +1480,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -1496,7 +1496,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -3792,16 +3792,16 @@ }, { "name": "symfony/console", - "version": "v5.4.16", + "version": "v5.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8e9b9c8dfb33af6057c94e1b44846bee700dc5ef" + "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8e9b9c8dfb33af6057c94e1b44846bee700dc5ef", - "reference": "8e9b9c8dfb33af6057c94e1b44846bee700dc5ef", + "url": "https://api.github.com/repos/symfony/console/zipball/58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", + "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", "shasum": "" }, "require": { @@ -3871,7 +3871,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.16" + "source": "https://github.com/symfony/console/tree/v5.4.17" }, "funding": [ { @@ -3887,7 +3887,7 @@ "type": "tidelift" } ], - "time": "2022-11-25T14:09:27+00:00" + "time": "2022-12-28T14:15:31+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4450,16 +4450,16 @@ }, { "name": "symfony/string", - "version": "v5.4.15", + "version": "v5.4.17", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed" + "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/571334ce9f687e3e6af72db4d3b2a9431e4fd9ed", - "reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed", + "url": "https://api.github.com/repos/symfony/string/zipball/55733a8664b8853b003e70251c58bc8cb2d82a6b", + "reference": "55733a8664b8853b003e70251c58bc8cb2d82a6b", "shasum": "" }, "require": { @@ -4516,7 +4516,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.15" + "source": "https://github.com/symfony/string/tree/v5.4.17" }, "funding": [ { @@ -4532,7 +4532,7 @@ "type": "tidelift" } ], - "time": "2022-10-05T15:16:54+00:00" + "time": "2022-12-12T15:54:21+00:00" }, { "name": "theseer/tokenizer", diff --git a/modules.php b/modules.php index 6bbca541d..9bda3f6a4 100644 --- a/modules.php +++ b/modules.php @@ -26,6 +26,7 @@ return function ( string $root_dir ): iterable { ( require "$modules_dir/ppcp-vaulting/module.php" )(), ( require "$modules_dir/ppcp-order-tracking/module.php" )(), ( require "$modules_dir/ppcp-uninstall/module.php" )(), + ( require "$modules_dir/ppcp-blocks/module.php" )(), ); return $modules; diff --git a/modules/ppcp-blocks/.babelrc b/modules/ppcp-blocks/.babelrc new file mode 100644 index 000000000..822778e6c --- /dev/null +++ b/modules/ppcp-blocks/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "usage", + "corejs": "3.25.0" + } + ], + [ + "@babel/preset-react" + ] + ] +} diff --git a/modules/ppcp-blocks/.gitignore b/modules/ppcp-blocks/.gitignore new file mode 100644 index 000000000..0bd2b9f58 --- /dev/null +++ b/modules/ppcp-blocks/.gitignore @@ -0,0 +1,3 @@ +node_modules +assets/js +assets/css diff --git a/modules/ppcp-blocks/composer.json b/modules/ppcp-blocks/composer.json new file mode 100644 index 000000000..f71b084a2 --- /dev/null +++ b/modules/ppcp-blocks/composer.json @@ -0,0 +1,17 @@ +{ + "name": "woocommerce/ppcp-blocks", + "type": "dhii-mod", + "description": "Blocks module for PPCP", + "license": "GPL-2.0", + "require": { + "php": "^7.2 | ^8.0", + "dhii/module-interface": "^0.3.0-alpha1" + }, + "autoload": { + "psr-4": { + "WooCommerce\\PayPalCommerce\\Blocks\\": "src" + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php new file mode 100644 index 000000000..522ac202c --- /dev/null +++ b/modules/ppcp-blocks/extensions.php @@ -0,0 +1,12 @@ + 0.5%", + "Safari >= 8", + "Chrome >= 41", + "Firefox >= 43", + "Edge >= 14" + ], + "dependencies": { + "@paypal/react-paypal-js": "^7.8.2", + "core-js": "^3.25.0" + }, + "devDependencies": { + "@babel/core": "^7.19", + "@babel/preset-env": "^7.19", + "@babel/preset-react": "^7.18.6", + "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0", + "babel-loader": "^8.2", + "cross-env": "^7.0.3", + "file-loader": "^6.2.0", + "sass": "^1.42.1", + "sass-loader": "^12.1.0", + "webpack": "^5.74", + "webpack-cli": "^4.10" + }, + "scripts": { + "build": "cross-env BABEL_ENV=default NODE_ENV=production webpack", + "watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch", + "dev": "cross-env BABEL_ENV=default webpack --watch" + } +} diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js new file mode 100644 index 000000000..aaebac4f6 --- /dev/null +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -0,0 +1,84 @@ +/** + * @param {String} fullName + * @returns {Array} + */ +export const splitFullName = (fullName) => { + fullName = fullName.trim() + if (!fullName.includes(' ')) { + return [fullName, '']; + } + const parts = fullName.split(' '); + const firstName = parts[0]; + parts.shift(); + const lastName = parts.join(' '); + return [firstName, lastName]; +} + +/** + * @param {Object} address + * @returns {Object} + */ +export const paypalAddressToWc = (address) => { + const map = { + country_code: 'country', + address_line_1: 'address_1', + address_line_2: 'address_2', + admin_area_1: 'state', + admin_area_2: 'city', + postal_code: 'postcode', + }; + const result = {}; + Object.entries(map).forEach(([paypalKey, wcKey]) => { + if (address[paypalKey]) { + result[wcKey] = address[paypalKey]; + } + }) + + return result; +} + +/** + * @param {Object} shipping + * @returns {Object} + */ +export const paypalShippingToWc = (shipping) => { + const [firstName, lastName] = splitFullName(shipping.name.full_name); + return { + first_name: firstName, + last_name: lastName, + ...paypalAddressToWc(shipping.address), + } +} + +/** + * @param {Object} payer + * @returns {Object} + */ +export const paypalPayerToWc = (payer) => { + const firstName = payer.name.given_name; + const lastName = payer.name.surname; + const address = payer.address ? paypalAddressToWc(payer.address) : {}; + return { + first_name: firstName, + last_name: lastName, + email: payer.email_address, + ...address, + } +} + +/** + * @param {Object} order + * @returns {Object} + */ +export const paypalOrderToWcShippingAddress = (order) => { + const res = paypalShippingToWc(order.purchase_units[0].shipping); + + // use the name from billing if the same, to avoid possible mistakes when splitting full_name + const billingAddress = paypalPayerToWc(order.payer); + if (`${res.first_name} ${res.last_name}` === `${billingAddress.first_name} ${billingAddress.last_name}`) { + res.first_name = billingAddress.first_name; + res.last_name = billingAddress.last_name; + } + + return res; +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js new file mode 100644 index 000000000..cb643452b --- /dev/null +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -0,0 +1,151 @@ +import {useEffect, useState} from '@wordpress/element'; +import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; +import {PayPalScriptProvider, PayPalButtons} from "@paypal/react-paypal-js"; +import {paypalOrderToWcShippingAddress, paypalPayerToWc} from "./Helper/Address"; + +const config = wc.wcSettings.getSetting('ppcp-gateway_data'); + +const PayPalComponent = ({ + onClick, + onClose, + onSubmit, + eventRegistration, + emitResponse, + setExpressPaymentError, +}) => { + const {onPaymentProcessing} = eventRegistration; + const {responseTypes} = emitResponse; + + const [paypalOrder, setPaypalOrder] = useState(null); + + const createOrder = async () => { + try { + const res = await fetch(config.scriptData.ajax.create_order.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + nonce: config.scriptData.ajax.create_order.nonce, + bn_code: '', + context: 'express', + order_id: config.scriptData.order_id, + payment_method: 'ppcp-gateway', + funding_source: 'paypal', + createaccount: false + }), + }); + + const json = await res.json(); + + if (!json.success) { + if (json.data?.details?.length > 0) { + throw new Error(json.data.details.map(d => `${d.issue} ${d.description}`).join('
')); + } else if (json.data?.message) { + throw new Error(json.data.message); + } + + throw new Error(config.scriptData.labels.error.generic); + } + return json.data.id; + } catch (err) { + console.error(err); + + setExpressPaymentError(err.message); + + onClose(); + + throw err; + } + }; + + const handleApprove = async (data, actions) => { + try { + const res = await fetch(config.scriptData.ajax.approve_order.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + nonce: config.scriptData.ajax.approve_order.nonce, + order_id: data.orderID, + //funding_source: , + }) + }); + + const json = await res.json(); + + if (!json.success) { + if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') { + return actions.restart(); + } + if (json.data?.message) { + throw new Error(json.data.message); + } + + throw new Error(config.scriptData.labels.error.generic) + } + + setPaypalOrder(json.data); + + onSubmit(); + } catch (err) { + console.error(err); + + setExpressPaymentError(err.message); + + onClose(); + + throw err; + } + }; + + const handleClick = () => { + setExpressPaymentError(''); + + onClick(); + }; + + useEffect(() => { + const unsubscribeProcessing = onPaymentProcessing(() => { + const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); + const billingAddress = paypalPayerToWc(paypalOrder.payer); + + return { + type: responseTypes.SUCCESS, + meta: { + paymentMethodData: { + 'paypal_order_id': paypalOrder.id, + }, + shippingData: {address: shippingAddress}, + billingAddress, + billingData: billingAddress, + }, + }; + }); + return () => { + unsubscribeProcessing(); + }; + }, [onPaymentProcessing, paypalOrder]); + + return ( + + + + ); +} + +registerExpressPaymentMethod({ + name: config.id, + label:
, + content: , + edit: TODO: editing, + ariaLabel: config.title, + canMakePayment: () => true, + supports: { + features: ['products'], + }, +}); diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php new file mode 100644 index 000000000..43b60cd38 --- /dev/null +++ b/modules/ppcp-blocks/services.php @@ -0,0 +1,34 @@ + static function ( ContainerInterface $container ): string { + /** + * The path cannot be false. + * + * @psalm-suppress PossiblyFalseArgument + */ + return plugins_url( + '/modules/ppcp-blocks/', + dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' + ); + }, + 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod { + return new PayPalPaymentMethod( + $container->get( 'blocks.url' ), + $container->get( 'ppcp.asset-version' ), + $container->get( 'button.smart-button' ), + $container->get( 'wcgateway.paypal-gateway' ) + ); + }, +); diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php new file mode 100644 index 000000000..8658b2c4c --- /dev/null +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -0,0 +1,53 @@ +register( $c->get( 'blocks.method' ) ); + } + ); + } + } + + /** + * Returns the key for the module. + * + * @return string|void + */ + public function getKey() { + } +} diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php new file mode 100644 index 000000000..892624b79 --- /dev/null +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -0,0 +1,100 @@ +name = PayPalGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->smart_button = $smart_button; + $this->gateway = $gateway; + } + + /** + * {@inheritDoc} + */ + public function initialize() { } + + /** + * {@inheritDoc} + */ + public function get_payment_method_script_handles() { + wp_register_script( + 'ppcp-checkout-block', + trailingslashit( $this->module_url ) . 'assets/js/checkout-block.js', + array(), + $this->version, + true + ); + + return array( 'ppcp-checkout-block' ); + } + + /** + * {@inheritDoc} + */ + public function get_payment_method_data() { + return array( + 'id' => $this->gateway->id, + 'title' => $this->gateway->title, + 'description' => $this->gateway->description, + 'scriptData' => $this->smart_button->script_data(), + ); + } +} diff --git a/modules/ppcp-blocks/webpack.config.js b/modules/ppcp-blocks/webpack.config.js new file mode 100644 index 000000000..4695769ed --- /dev/null +++ b/modules/ppcp-blocks/webpack.config.js @@ -0,0 +1,38 @@ +const path = require('path'); +const isProduction = process.env.NODE_ENV === 'production'; + +const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' ); + +module.exports = { + devtool: isProduction ? 'source-map' : 'eval-source-map', + mode: isProduction ? 'production' : 'development', + target: 'web', + plugins: [ new DependencyExtractionWebpackPlugin() ], + entry: { + 'checkout-block': path.resolve('./resources/js/checkout-block.js') + }, + output: { + path: path.resolve(__dirname, 'assets/'), + filename: 'js/[name].js', + }, + module: { + rules: [{ + test: /\.js?$/, + exclude: /node_modules/, + loader: 'babel-loader', + }, + { + test: /\.scss$/, + exclude: /node_modules/, + use: [ + { + loader: 'file-loader', + options: { + name: 'css/[name].css', + } + }, + {loader:'sass-loader'} + ] + }] + } +}; diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock new file mode 100644 index 000000000..3133116cd --- /dev/null +++ b/modules/ppcp-blocks/yarn.lock @@ -0,0 +1,2233 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733" + integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g== + +"@babel/core@^7.19": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113" + integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-module-transforms" "^7.20.2" + "@babel/helpers" "^7.20.5" + "@babel/parser" "^7.20.5" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" + integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== + dependencies: + "@babel/types" "^7.20.5" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== + dependencies: + "@babel/compat-data" "^7.20.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06" + integrity sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" + integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.2.1" + +"@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712" + integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.1" + "@babel/types" "^7.20.2" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + +"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== + dependencies: + "@babel/types" "^7.20.0" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.9": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" + integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + +"@babel/helpers@^7.20.5": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763" + integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.18.10", "@babel/parser@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + +"@babel/plugin-proposal-async-generator-functions@^7.20.1": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" + integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" + integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.1" + +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135" + integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoping@^7.20.2": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz#401215f9dc13dc5262940e2e527c9536b3d7f237" + integrity sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-classes@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" + integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" + integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-amd@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" + integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== + dependencies: + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-transform-modules-commonjs@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" + integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== + dependencies: + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-simple-access" "^7.19.4" + +"@babel/plugin-transform-modules-systemjs@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" + integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-identifier" "^7.19.1" + +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" + integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + +"@babel/plugin-transform-parameters@^7.20.1": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz#f8f9186c681d10c3de7620c916156d893c8a019e" + integrity sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + +"@babel/plugin-transform-react-jsx@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9" + integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.19.0" + +"@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" + integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + regenerator-transform "^0.15.1" + +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@^7.19": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" + integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.20.2" + "@babel/plugin-transform-classes" "^7.20.2" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.19.6" + "@babel/plugin-transform-modules-commonjs" "^7.19.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.20.2" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + +"@babel/runtime@^7.8.4": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" + integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.5" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.5" + "@babel/types" "^7.20.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.4.4": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" + integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@paypal/paypal-js@^5.1.4": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-5.1.4.tgz#aff45a27bb542dc331853577094aee78d5233b75" + integrity sha512-5ktge4DFOMu3rvFR4YWDiPm4wDUzOmghtCM9ZxgJyJCOJP+61IVYdrENysMay91T4GOZ10k2ywWcBN5shsGhLw== + dependencies: + promise-polyfill "^8.2.3" + +"@paypal/react-paypal-js@^7.8.2": + version "7.8.2" + resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-7.8.2.tgz#df24d0e50aebce9d9ac467c53a66f45ed93beca1" + integrity sha512-H6/qpAVkf57spCgvQPO7jNhtD58KdiywqKYmgmJm7Xnwu2wH03XfOsBjlINi1zM1VnzcyKhcliyBh9+uTAzI0g== + dependencies: + "@paypal/paypal-js" "^5.1.4" + "@paypal/sdk-constants" "^1.0.122" + +"@paypal/sdk-constants@^1.0.122": + version "1.0.128" + resolved "https://registry.yarnpkg.com/@paypal/sdk-constants/-/sdk-constants-1.0.128.tgz#210ed1da43f3129042a8d0e0f4c7e8407b01e27f" + integrity sha512-6DB6M+mnf4B9PQR/dutI1aNMnXTbw9N+VklUbO0FkGzF7k0vr8lflfitDAKWh1hFJf8XzFCe+KbknMSpJMqiDw== + dependencies: + hi-base32 "^0.5.0" + +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.10" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb" + integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/node@*": + version "18.11.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc" + integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g== + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" + integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== + +"@webpack-cli/info@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" + integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" + integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== + +"@woocommerce/dependency-extraction-webpack-plugin@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz#230d674a67585bc32e31bc28485bec99b41dbd1f" + integrity sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A== + dependencies: + "@wordpress/dependency-extraction-webpack-plugin" "^3.3.0" + +"@wordpress/dependency-extraction-webpack-plugin@^3.3.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-3.7.0.tgz#e52ef31f66b8c4add3d773a87e11007375127b04" + integrity sha512-SHyp88D1ICSaRVMfs/kKEicjKXWf1y2wecUeZIiMtkfAi8Bnk3JsnUo11LH7drJIXfjmDoer2B2rrBMZmRm8VA== + dependencies: + json2php "^0.0.4" + webpack-sources "^3.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn@^8.5.0, acorn@^8.7.1: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +babel-loader@^8.2: + version "8.3.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^2.0.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-polyfill-corejs2@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.3" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" + +babel-plugin-polyfill-regenerator@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +caniuse-lite@^1.0.30001400: + version "1.0.30001436" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz#22d7cbdbbbb60cdc4ca1030ccd6dea9f5de4848b" + integrity sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +"chokidar@>=3.0.0 <4.0.0": + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +colorette@^2.0.14: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +core-js-compat@^3.25.1: + version "3.26.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df" + integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A== + dependencies: + browserslist "^4.21.4" + +core-js@^3.25.0: + version "3.26.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e" + integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^5.10.0: + version "5.12.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" + integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hi-base32@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/hi-base32/-/hi-base32-0.5.1.tgz#1279f2ddae2673219ea5870c2121d2a33132857e" + integrity sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA== + +immutable@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" + integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json2php@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/json2php/-/json2php-0.0.4.tgz#6bd85a1dda6a5dd7e91022bb24403cc1b7c2ee34" + integrity sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g== + +json5@^2.1.2, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +promise-polyfill@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6" + integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc" + integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.14.2, resolve@^1.9.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sass-loader@^12.1.0: + version "12.6.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" + integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== + dependencies: + klona "^2.0.4" + neo-async "^2.6.2" + +sass@^1.42.1: + version "1.56.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7" + integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +"source-map-js@>=0.6.2 <2.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.1.3: + version "5.3.6" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.14" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.14.1" + +terser@^5.14.1: + version "5.16.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.1.tgz#5af3bc3d0f24241c7fb2024199d5c461a1075880" + integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@^4.10: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" + integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.2.0" + "@webpack-cli/info" "^1.5.0" + "@webpack-cli/serve" "^1.7.0" + colorette "^2.0.14" + commander "^7.0.0" + cross-spawn "^7.0.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.8.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^3.2.2, webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.74: + version "5.75.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" + integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 82a94f8c3..ddf3d3f40 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1081,8 +1081,7 @@ class SmartButton implements SmartButtonInterface { if ( is_cart() ) { $context = 'cart'; } - // TODO: refactor. - if ( is_checkout() && ! $this->is_paypal_continuation() || did_action( 'woocommerce_blocks_checkout_enqueue_data' ) ) { + if ( is_checkout() && ! $this->is_paypal_continuation() || has_block( 'woocommerce/checkout' ) ) { $context = 'checkout'; } if ( is_checkout_pay_page() ) { diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 205505c5c..df2977982 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -382,6 +382,9 @@ class CreateOrderEndpoint implements EndpointInterface { $funding_source ); + $action = in_array( $this->parsed_request_data['context'], array( 'checkout', 'express' ), true ) ? + ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE; + if ( 'card' === $funding_source ) { if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) { if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) { @@ -407,7 +410,9 @@ class CreateOrderEndpoint implements EndpointInterface { $shipping_preference, $payer, null, - $this->payment_method() + $this->payment_method(), + '', + $action ); } catch ( PayPalApiException $exception ) { // Looks like currently there is no proper way to validate the shipping address for PayPal, diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php index ce8d48583..6cc9c7ce4 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php @@ -162,8 +162,13 @@ class OrderProcessor { * * @return bool */ - public function process( WC_Order $wc_order ): bool { - $order = $this->session_handler->order(); + public function process( \WC_Order $wc_order ): bool { + // phpcs:ignore WordPress.Security.NonceVerification + $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ?: wc_clean( wp_unslash( $_POST['paypal_order_id'] ?? '' ) ); + $order = $this->session_handler->order(); + if ( ! $order && is_string( $order_id ) ) { + $order = $this->order_endpoint->order( $order_id ); + } if ( ! $order ) { $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); if ( ! $order_id ) { diff --git a/package.json b/package.json index 49a410f3f..31f1539ec 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "postinstall": "run-s install:modules:* && run-s build:modules", + "install:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn install", "install:modules:ppcp-button": "cd modules/ppcp-button && yarn install", "install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install", "install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install", @@ -17,6 +18,7 @@ "install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install", "install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install", + "build:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run build", "build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build", "build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build", "build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build", @@ -27,6 +29,7 @@ "build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build", "build:modules": "run-p build:modules:*", + "watch:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run watch", "watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch", "watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch", "watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index d12770d98..69420f158 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -52,4 +52,5 @@ */vendor/* ./tests/* */resources/* + *.asset.php diff --git a/psalm.xml.dist b/psalm.xml.dist index fffe2fa98..ed1e42065 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -29,6 +29,7 @@ + From b824de4e33d914d026c37078aa94d20b8c24380b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 21 Mar 2023 10:27:02 +0200 Subject: [PATCH 06/49] Handle missing billing address for express block --- modules/ppcp-blocks/resources/js/checkout-block.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index cb643452b..3eee02221 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -105,7 +105,11 @@ const PayPalComponent = ({ useEffect(() => { const unsubscribeProcessing = onPaymentProcessing(() => { const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); - const billingAddress = paypalPayerToWc(paypalOrder.payer); + let billingAddress = paypalPayerToWc(paypalOrder.payer); + // no billing address, such as if billing address retrieval is not allowed in the merchant account + if (!billingAddress.address_line_1) { + billingAddress = {...shippingAddress, ...paypalPayerToWc(paypalOrder.payer)}; + } return { type: responseTypes.SUCCESS, From 02bec2b611f427fa9e3e512526623eb535ae65a1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Mar 2023 09:02:17 +0200 Subject: [PATCH 07/49] Handle missing shipping address for express block (virtual products) --- modules/ppcp-blocks/resources/js/Helper/Address.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index aaebac4f6..d545d0787 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -71,7 +71,12 @@ export const paypalPayerToWc = (payer) => { * @returns {Object} */ export const paypalOrderToWcShippingAddress = (order) => { - const res = paypalShippingToWc(order.purchase_units[0].shipping); + const shipping = order.purchase_units[0].shipping; + if (!shipping) { + return {}; + } + + const res = paypalShippingToWc(shipping); // use the name from billing if the same, to avoid possible mistakes when splitting full_name const billingAddress = paypalPayerToWc(order.payer); From 10cf1b940878f0d0c63a5fccbafc1373f3e991bd Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Mar 2023 09:03:17 +0200 Subject: [PATCH 08/49] Add express block playwright test --- .env.e2e.example | 1 + tests/playwright/place-order.spec.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.env.e2e.example b/.env.e2e.example index c8023a9cb..632f3ced8 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -3,6 +3,7 @@ PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress BASEURL="https://woocommerce-paypal-payments.ddev.site" PRODUCT_URL="/product/prod" +PRODUCT_ID=123 CUSTOMER_EMAIL="customer@example.com" CUSTOMER_PASSWORD="password" diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index c1701fef3..7109c5ec7 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -7,6 +7,7 @@ const { CREDIT_CARD_EXPIRATION, CREDIT_CARD_CVV, PRODUCT_URL, + PRODUCT_ID, } = process.env; async function fillCheckoutForm(page) { @@ -99,3 +100,21 @@ test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', asy const title = await page.locator('.entry-title'); await expect(title).toHaveText('Order received'); }); + +test('PayPal express block', async ({page}) => { + + await page.goto('/cart?add-to-cart=' + PRODUCT_ID); + + await page.goto('/blocks-checkout') + + const popup = await openPaypalPopup(page); + + await loginIntoPaypal(popup); + + await popup.locator('#payment-submit-btn').click(); + + await page.waitForNavigation(); + + const title = await page.locator('.entry-title'); + await expect(title).toHaveText('Order received'); +}); From e4b9cb33982cebfa97f43267c4b180ed97a7aead Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Mar 2023 10:52:19 +0200 Subject: [PATCH 09/49] Set missing address fields to empty for express block Fixes compatibility with latest wc blocks version --- .../resources/js/Helper/Address.js | 21 +++++++++++++++---- .../resources/js/checkout-block.js | 3 +-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index d545d0787..b7ecc1645 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -32,9 +32,22 @@ export const paypalAddressToWc = (address) => { if (address[paypalKey]) { result[wcKey] = address[paypalKey]; } - }) + }); - return result; + const defaultAddress = { + first_name: '', + last_name: '', + company: '', + address_1: '', + address_2: '', + city: '', + state: '', + postcode: '', + country: '', + phone: '', + }; + + return {...defaultAddress, ...result}; } /** @@ -44,9 +57,9 @@ export const paypalAddressToWc = (address) => { export const paypalShippingToWc = (shipping) => { const [firstName, lastName] = splitFullName(shipping.name.full_name); return { + ...paypalAddressToWc(shipping.address), first_name: firstName, last_name: lastName, - ...paypalAddressToWc(shipping.address), } } @@ -59,10 +72,10 @@ export const paypalPayerToWc = (payer) => { const lastName = payer.name.surname; const address = payer.address ? paypalAddressToWc(payer.address) : {}; return { + ...address, first_name: firstName, last_name: lastName, email: payer.email_address, - ...address, } } diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 3eee02221..e9b6d1cc6 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -117,9 +117,8 @@ const PayPalComponent = ({ paymentMethodData: { 'paypal_order_id': paypalOrder.id, }, - shippingData: {address: shippingAddress}, + shippingAddress, billingAddress, - billingData: billingAddress, }, }; }); From e990eb2a6ee6eb2f5112e0264f02837dc41f196b Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Mar 2023 11:59:15 +0200 Subject: [PATCH 10/49] Use new event name --- modules/ppcp-blocks/resources/js/checkout-block.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index e9b6d1cc6..068a9e472 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -13,7 +13,7 @@ const PayPalComponent = ({ emitResponse, setExpressPaymentError, }) => { - const {onPaymentProcessing} = eventRegistration; + const {onPaymentSetup} = eventRegistration; const {responseTypes} = emitResponse; const [paypalOrder, setPaypalOrder] = useState(null); @@ -103,7 +103,7 @@ const PayPalComponent = ({ }; useEffect(() => { - const unsubscribeProcessing = onPaymentProcessing(() => { + const unsubscribeProcessing = onPaymentSetup(() => { const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); let billingAddress = paypalPayerToWc(paypalOrder.payer); // no billing address, such as if billing address retrieval is not allowed in the merchant account @@ -125,7 +125,7 @@ const PayPalComponent = ({ return () => { unsubscribeProcessing(); }; - }, [onPaymentProcessing, paypalOrder]); + }, [onPaymentSetup, paypalOrder]); return ( From d887135951ca06f6fbdcf607ec28540fc94c9ee6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Mar 2023 15:54:24 +0200 Subject: [PATCH 11/49] Use onError instead of deprecated setExpressPaymentError --- modules/ppcp-blocks/resources/js/checkout-block.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 068a9e472..d9f6252f8 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -9,9 +9,9 @@ const PayPalComponent = ({ onClick, onClose, onSubmit, + onError, eventRegistration, emitResponse, - setExpressPaymentError, }) => { const {onPaymentSetup} = eventRegistration; const {responseTypes} = emitResponse; @@ -49,7 +49,7 @@ const PayPalComponent = ({ } catch (err) { console.error(err); - setExpressPaymentError(err.message); + onError(err.message); onClose(); @@ -88,7 +88,7 @@ const PayPalComponent = ({ } catch (err) { console.error(err); - setExpressPaymentError(err.message); + onError(err.message); onClose(); @@ -97,8 +97,6 @@ const PayPalComponent = ({ }; const handleClick = () => { - setExpressPaymentError(''); - onClick(); }; From 5261484f11d47e9be1df350d075eaa0590f15422 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 24 Mar 2023 10:11:53 +0200 Subject: [PATCH 12/49] Use react component from the standard sdk --- modules/ppcp-blocks/package.json | 1 - .../resources/js/checkout-block.js | 35 +++++++++++++------ modules/ppcp-blocks/yarn.lock | 32 ----------------- 3 files changed, 24 insertions(+), 44 deletions(-) diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 83bae49ad..c74af1d92 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,7 +10,6 @@ "Edge >= 14" ], "dependencies": { - "@paypal/react-paypal-js": "^7.8.2", "core-js": "^3.25.0" }, "devDependencies": { diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index d9f6252f8..a9b8b21dc 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,7 +1,7 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; -import {PayPalScriptProvider, PayPalButtons} from "@paypal/react-paypal-js"; import {paypalOrderToWcShippingAddress, paypalPayerToWc} from "./Helper/Address"; +import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' const config = wc.wcSettings.getSetting('ppcp-gateway_data'); @@ -18,6 +18,15 @@ const PayPalComponent = ({ const [paypalOrder, setPaypalOrder] = useState(null); + const [loaded, setLoaded] = useState(false); + useEffect(() => { + if (!loaded) { + loadPaypalScript(config.scriptData, () => { + setLoaded(true); + }); + } + }, [loaded]); + const createOrder = async () => { try { const res = await fetch(config.scriptData.ajax.create_order.endpoint, { @@ -125,17 +134,21 @@ const PayPalComponent = ({ }; }, [onPaymentSetup, paypalOrder]); + if (!loaded) { + return null; + } + + const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM }); + return ( - - - + ); } diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index 3133116cd..d39357666 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1005,28 +1005,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@paypal/paypal-js@^5.1.4": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-5.1.4.tgz#aff45a27bb542dc331853577094aee78d5233b75" - integrity sha512-5ktge4DFOMu3rvFR4YWDiPm4wDUzOmghtCM9ZxgJyJCOJP+61IVYdrENysMay91T4GOZ10k2ywWcBN5shsGhLw== - dependencies: - promise-polyfill "^8.2.3" - -"@paypal/react-paypal-js@^7.8.2": - version "7.8.2" - resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-7.8.2.tgz#df24d0e50aebce9d9ac467c53a66f45ed93beca1" - integrity sha512-H6/qpAVkf57spCgvQPO7jNhtD58KdiywqKYmgmJm7Xnwu2wH03XfOsBjlINi1zM1VnzcyKhcliyBh9+uTAzI0g== - dependencies: - "@paypal/paypal-js" "^5.1.4" - "@paypal/sdk-constants" "^1.0.122" - -"@paypal/sdk-constants@^1.0.122": - version "1.0.128" - resolved "https://registry.yarnpkg.com/@paypal/sdk-constants/-/sdk-constants-1.0.128.tgz#210ed1da43f3129042a8d0e0f4c7e8407b01e27f" - integrity sha512-6DB6M+mnf4B9PQR/dutI1aNMnXTbw9N+VklUbO0FkGzF7k0vr8lflfitDAKWh1hFJf8XzFCe+KbknMSpJMqiDw== - dependencies: - hi-base32 "^0.5.0" - "@types/eslint-scope@^3.7.3": version "3.7.4" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" @@ -1621,11 +1599,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hi-base32@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/hi-base32/-/hi-base32-0.5.1.tgz#1279f2ddae2673219ea5870c2121d2a33132857e" - integrity sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA== - immutable@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" @@ -1867,11 +1840,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -promise-polyfill@^8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6" - integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg== - punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" From 21bba80f490d213f0faad22c30613401738c936a Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 27 Mar 2023 10:59:08 +0300 Subject: [PATCH 13/49] Check the current method in block --- modules/ppcp-blocks/resources/js/checkout-block.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index a9b8b21dc..1afbc9e1b 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -12,6 +12,7 @@ const PayPalComponent = ({ onError, eventRegistration, emitResponse, + activePaymentMethod, }) => { const {onPaymentSetup} = eventRegistration; const {responseTypes} = emitResponse; @@ -110,6 +111,10 @@ const PayPalComponent = ({ }; useEffect(() => { + if (activePaymentMethod !== config.id) { + return; + } + const unsubscribeProcessing = onPaymentSetup(() => { const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); let billingAddress = paypalPayerToWc(paypalOrder.payer); @@ -132,7 +137,7 @@ const PayPalComponent = ({ return () => { unsubscribeProcessing(); }; - }, [onPaymentSetup, paypalOrder]); + }, [onPaymentSetup, paypalOrder, activePaymentMethod]); if (!loaded) { return null; From dc3fde5abf2796257a3cd2d15fc3070590331ddf Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 29 Mar 2023 20:12:39 +0300 Subject: [PATCH 14/49] Refactor SmartButton script loading checks --- .../src/Assets/DisabledSmartButton.php | 2 +- .../ppcp-button/src/Assets/SmartButton.php | 131 ++++++++---------- .../src/Assets/SmartButtonInterface.php | 6 +- modules/ppcp-button/src/ButtonModule.php | 12 +- 4 files changed, 68 insertions(+), 83 deletions(-) diff --git a/modules/ppcp-button/src/Assets/DisabledSmartButton.php b/modules/ppcp-button/src/Assets/DisabledSmartButton.php index 04cee3eab..71889c7cc 100644 --- a/modules/ppcp-button/src/Assets/DisabledSmartButton.php +++ b/modules/ppcp-button/src/Assets/DisabledSmartButton.php @@ -26,7 +26,7 @@ class DisabledSmartButton implements SmartButtonInterface { /** * Whether the scripts should be loaded. */ - public function should_load(): bool { + public function should_load_ppcp_script(): bool { return false; } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index d44639f4b..193ebdd2e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -508,31 +508,63 @@ class SmartButton implements SmartButtonInterface { } /** - * Whether the scripts should be loaded. + * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded. */ - public function should_load(): bool { + public function should_load_ppcp_script(): bool { $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); - if ( ! is_checkout() && ! $buttons_enabled ) { + if ( ! $buttons_enabled ) { return false; } - return true; + return $this->should_load_buttons() || $this->can_render_dcc(); } /** - * Enqueues the scripts. + * Determines whether the button component should be loaded. */ - public function enqueue(): void { - if ( ! $this->should_load() ) { - return; + public function should_load_buttons() : bool { + $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); + if ( ! $buttons_enabled ) { + return false; } - $load_script = false; - if ( is_checkout() && $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) { - $load_script = true; + $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() ); + $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); + $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() ); + + switch ( $this->context() ) { + case 'checkout': + case 'cart': + case 'pay-now': + return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location; + case 'product': + return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart; + default: + return $smart_button_enabled_for_mini_cart; } - if ( $this->load_button_component() ) { - $load_script = true; + } + + /** + * Whether DCC fields can be rendered. + */ + public function can_render_dcc() : bool { + return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) + && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) + && $this->dcc_applies->for_country_currency() + && in_array( $this->context(), array( 'checkout', 'pay-now' ), true ); + } + + /** + * Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout) + */ + public function enqueue(): void { + if ( $this->can_render_dcc() ) { + wp_enqueue_style( + 'ppcp-hosted-fields', + untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css', + array(), + $this->version + ); } if ( in_array( $this->context(), array( 'pay-now', 'checkout' ), true ) ) { @@ -542,31 +574,21 @@ class SmartButton implements SmartButtonInterface { array(), $this->version ); - - if ( $this->can_render_dcc() ) { - wp_enqueue_style( - 'ppcp-hosted-fields', - untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css', - array(), - $this->version - ); - } } - if ( $load_script ) { - wp_enqueue_script( - 'ppcp-smart-button', - untrailingslashit( $this->module_url ) . '/assets/js/button.js', - array( 'jquery' ), - $this->version, - true - ); - wp_localize_script( - 'ppcp-smart-button', - 'PayPalCommerceGateway', - $this->script_data() - ); - } + wp_enqueue_script( + 'ppcp-smart-button', + untrailingslashit( $this->module_url ) . '/assets/js/button.js', + array( 'jquery' ), + $this->version, + true + ); + + wp_localize_script( + 'ppcp-smart-button', + 'PayPalCommerceGateway', + $this->script_data() + ); } /** @@ -665,18 +687,6 @@ class SmartButton implements SmartButtonInterface { } - /** - * Whether DCC fields can be rendered. - * - * @return bool - * @throws NotFoundException When a setting was not found. - */ - private function can_render_dcc() : bool { - - $can_render = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) && $this->dcc_applies->for_country_currency(); - return $can_render; - } - /** * Renders the HTML for the DCC fields. */ @@ -1056,7 +1066,7 @@ class SmartButton implements SmartButtonInterface { private function components(): array { $components = array(); - if ( $this->load_button_component() ) { + if ( $this->should_load_buttons() ) { $components[] = 'buttons'; $components[] = 'funding-eligibility'; } @@ -1072,29 +1082,6 @@ class SmartButton implements SmartButtonInterface { return $components; } - /** - * Determines whether the button component should be loaded. - * - * @return bool - * @throws NotFoundException If a setting has not been found. - */ - private function load_button_component() : bool { - $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() ); - $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); - $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() ); - - switch ( $this->context() ) { - case 'checkout': - case 'cart': - case 'pay-now': - return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location; - case 'product': - return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart; - default: - return $smart_button_enabled_for_mini_cart; - } - } - /** * Whether DCC is enabled or not. * diff --git a/modules/ppcp-button/src/Assets/SmartButtonInterface.php b/modules/ppcp-button/src/Assets/SmartButtonInterface.php index be5f5d015..644fa271a 100644 --- a/modules/ppcp-button/src/Assets/SmartButtonInterface.php +++ b/modules/ppcp-button/src/Assets/SmartButtonInterface.php @@ -22,12 +22,12 @@ interface SmartButtonInterface { public function render_wrapper(): bool; /** - * Whether the scripts should be loaded. + * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded. */ - public function should_load(): bool; + public function should_load_ppcp_script(): bool; /** - * Enqueues the necessary scripts. + * Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout) */ public function enqueue(): void; diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index db3e67245..581d99374 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -63,14 +63,12 @@ class ButtonModule implements ModuleInterface { add_action( 'wp_enqueue_scripts', static function () use ( $c ) { - $smart_button = $c->get( 'button.smart-button' ); - /** - * The Smart Button. - * - * @var SmartButtonInterface $smart_button - */ - $smart_button->enqueue(); + assert( $smart_button instanceof SmartButtonInterface ); + + if ( $smart_button->should_load_ppcp_script() ) { + $smart_button->enqueue(); + } } ); From bfaf05a96e912f6683ac4221419607ace152ca42 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 29 Mar 2023 20:15:42 +0300 Subject: [PATCH 15/49] Add locations for block, do not load non-block scripts on block pages --- modules/ppcp-blocks/extensions.php | 24 ++++++++- .../resources/js/checkout-block.js | 2 +- modules/ppcp-blocks/services.php | 2 + .../ppcp-blocks/src/PayPalPaymentMethod.php | 49 ++++++++++++++++--- .../ppcp-button/src/Assets/SmartButton.php | 7 +++ .../ppcp-button/src/Helper/ContextTrait.php | 11 ++++- .../src/Helper/SettingsStatus.php | 3 ++ 7 files changed, 89 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 522ac202c..817fd2d6b 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -9,4 +9,26 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; -return array(); +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; + +return array( + 'wcgateway.button.locations' => function ( ContainerInterface $container, array $locations ): array { + return array_merge( + $locations, + array( + 'checkout-block-express' => _x( 'Block Express Checkout', 'Name of Buttons Location', 'woocommerce-paypal-payments' ), + 'cart-block' => _x( 'Block Cart', 'Name of Buttons Location', 'woocommerce-paypal-payments' ), + ) + ); + }, + 'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array { + unset( $locations['checkout-block-express'] ); + unset( $locations['cart-block'] ); + return $locations; + }, + 'wcgateway.settings.pay-later.button-locations' => function ( ContainerInterface $container, array $locations ): array { + unset( $locations['checkout-block-express'] ); + unset( $locations['cart-block'] ); + return $locations; + }, +); diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 1afbc9e1b..f51ddfb94 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -163,7 +163,7 @@ registerExpressPaymentMethod({ content: , edit: TODO: editing, ariaLabel: config.title, - canMakePayment: () => true, + canMakePayment: () => config.enabled, supports: { features: ['products'], }, diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index 43b60cd38..afc7d368c 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -28,6 +28,8 @@ return array( $container->get( 'blocks.url' ), $container->get( 'ppcp.asset-version' ), $container->get( 'button.smart-button' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'wcgateway.settings.status' ), $container->get( 'wcgateway.paypal-gateway' ) ); }, diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 892624b79..7cae8c914 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class PayPalPaymentMethod @@ -38,6 +40,20 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ private $smart_button; + /** + * The settings. + * + * @var Settings + */ + private $plugin_settings; + + /** + * The Settings status helper. + * + * @var SettingsStatus + */ + protected $settings_status; + /** * The WC gateway. * @@ -51,19 +67,25 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { * @param string $module_url The url of this module. * @param string $version The assets version. * @param SmartButtonInterface $smart_button The smart button script loading handler. + * @param Settings $plugin_settings The settings. + * @param SettingsStatus $settings_status The Settings status helper. * @param PayPalGateway $gateway The WC gateway. */ public function __construct( string $module_url, string $version, SmartButtonInterface $smart_button, + Settings $plugin_settings, + SettingsStatus $settings_status, PayPalGateway $gateway ) { - $this->name = PayPalGateway::ID; - $this->module_url = $module_url; - $this->version = $version; - $this->smart_button = $smart_button; - $this->gateway = $gateway; + $this->name = PayPalGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->smart_button = $smart_button; + $this->plugin_settings = $plugin_settings; + $this->settings_status = $settings_status; + $this->gateway = $gateway; } /** @@ -71,6 +93,18 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ public function initialize() { } + /** + * {@inheritDoc} + */ + public function is_active() { + // Do not load when definitely not needed, + // but we still need to check the locations later and handle in JS + // because has_block cannot be called here (too early). + return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' ) + && ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) || + $this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) ); + } + /** * {@inheritDoc} */ @@ -90,11 +124,14 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { * {@inheritDoc} */ public function get_payment_method_data() { + $script_data = $this->smart_button->script_data(); + return array( 'id' => $this->gateway->id, 'title' => $this->gateway->title, 'description' => $this->gateway->description, - 'scriptData' => $this->smart_button->script_data(), + 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), + 'scriptData' => $script_data, ); } } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 193ebdd2e..8cf7360b4 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -516,6 +516,10 @@ class SmartButton implements SmartButtonInterface { return false; } + if ( in_array( $this->context(), array( 'checkout-block', 'cart-block' ), true ) ) { + return false; + } + return $this->should_load_buttons() || $this->can_render_dcc(); } @@ -537,6 +541,9 @@ class SmartButton implements SmartButtonInterface { case 'cart': case 'pay-now': return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location; + case 'checkout-block': + case 'cart-block': + return $smart_button_enabled_for_current_location; case 'product': return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart; default: diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 2718d84c1..2417464f6 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -22,11 +22,20 @@ trait ContextTrait { $context = 'product'; } + // has_block may not work if called too early, such as during the block registration. + if ( has_block( 'woocommerce/cart' ) ) { + $context = 'cart-block'; + } + if ( is_cart() ) { $context = 'cart'; } - if ( ( is_checkout() || has_block( 'woocommerce/checkout' ) ) && ! $this->is_paypal_continuation() ) { + if ( has_block( 'woocommerce/checkout' ) ) { + $context = 'checkout-block'; + } + + if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) { $context = 'checkout'; } diff --git a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php index 02f5e7082..d1f848d19 100644 --- a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php @@ -98,6 +98,9 @@ class SettingsStatus { if ( 'pay-now' === $location ) { $location = 'checkout'; } + if ( 'checkout-block' === $location ) { + $location = 'checkout-block-express'; + } return $location; } From ccb5391d816edde81a090ce798daa0f9c674b033 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 5 Apr 2023 17:55:58 +0300 Subject: [PATCH 16/49] Allow pay later button in block --- modules/ppcp-blocks/extensions.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 817fd2d6b..c28feb5f9 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -26,9 +26,4 @@ return array( unset( $locations['cart-block'] ); return $locations; }, - 'wcgateway.settings.pay-later.button-locations' => function ( ContainerInterface $container, array $locations ): array { - unset( $locations['checkout-block-express'] ); - unset( $locations['cart-block'] ); - return $locations; - }, ); From 7148948a59435ea47b7959a7ce3f385807fda312 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 5 Apr 2023 20:08:39 +0300 Subject: [PATCH 17/49] Add flag/filter/env var for enabling blocks --- .ddev/config.yaml | 1 + modules.php | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 251638bc4..7381c8c23 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -27,6 +27,7 @@ web_environment: - ADMIN_PASS=admin - ADMIN_EMAIL=admin@example.com - WC_VERSION=6.1.0 + - PCP_BLOCKS_ENABLED=1 # Key features of ddev's config.yaml: diff --git a/modules.php b/modules.php index 9bda3f6a4..2962988ce 100644 --- a/modules.php +++ b/modules.php @@ -26,8 +26,14 @@ return function ( string $root_dir ): iterable { ( require "$modules_dir/ppcp-vaulting/module.php" )(), ( require "$modules_dir/ppcp-order-tracking/module.php" )(), ( require "$modules_dir/ppcp-uninstall/module.php" )(), - ( require "$modules_dir/ppcp-blocks/module.php" )(), ); + if ( apply_filters( + 'woocommerce_paypal_payments_blocks_enabled', + getenv( 'PCP_BLOCKS_ENABLED' ) === '1' + ) ) { + $modules[] = ( require "$modules_dir/ppcp-blocks/module.php" )(); + } + return $modules; }; From b0a0eea752448c9a244048cafacc74b5da7827fd Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 7 Apr 2023 15:57:52 +0300 Subject: [PATCH 18/49] Refactor cancellation --- .../src/Cancellation/CancelController.php | 12 +++++++----- modules/ppcp-session/src/Cancellation/CancelView.php | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-session/src/Cancellation/CancelController.php b/modules/ppcp-session/src/Cancellation/CancelController.php index 3c23983f5..79dbd4dcf 100644 --- a/modules/ppcp-session/src/Cancellation/CancelController.php +++ b/modules/ppcp-session/src/Cancellation/CancelController.php @@ -16,6 +16,8 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler; */ class CancelController { + public const NONCE = 'ppcp-cancel'; + /** * The Session handler. * @@ -49,12 +51,11 @@ class CancelController { * Runs the controller. */ public function run() { - $param_name = 'ppcp-cancel'; - $nonce = 'ppcp-cancel-' . get_current_user_id(); + $param_name = self::NONCE; if ( isset( $_GET[ $param_name ] ) && // Input var ok. wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ $param_name ] ) ), // Input var ok. - $nonce + self::NONCE ) ) { // Input var ok. $this->session_handler->destroy_session_data(); @@ -74,11 +75,12 @@ class CancelController { return; // Ignore for card buttons. } - $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() ); + $url = add_query_arg( array( $param_name => wp_create_nonce( self::NONCE ) ), wc_get_checkout_url() ); add_action( 'woocommerce_review_order_after_submit', function () use ( $url ) { - $this->view->render_session_cancellation( $url, $this->session_handler->funding_source() ); + // phpcs:ignore WordPress.Security.EscapeOutput + echo $this->view->render_session_cancellation( $url, $this->session_handler->funding_source() ); } ); } diff --git a/modules/ppcp-session/src/Cancellation/CancelView.php b/modules/ppcp-session/src/Cancellation/CancelView.php index cdaf9b41e..2242b15c1 100644 --- a/modules/ppcp-session/src/Cancellation/CancelView.php +++ b/modules/ppcp-session/src/Cancellation/CancelView.php @@ -50,7 +50,8 @@ class CancelView { * @param string $url The URL. * @param string|null $funding_source The ID of the funding source, such as 'venmo'. */ - public function render_session_cancellation( string $url, ?string $funding_source ) { + public function render_session_cancellation( string $url, ?string $funding_source ): string { + ob_start(); ?>

Date: Fri, 7 Apr 2023 15:58:59 +0300 Subject: [PATCH 19/49] Handle continuation mode in blocks --- .psalm/wcblocks.php | 11 +++++ .../resources/js/checkout-block.js | 32 +++++++++++-- modules/ppcp-blocks/services.php | 4 +- modules/ppcp-blocks/src/BlocksModule.php | 16 +++++++ .../ppcp-blocks/src/PayPalPaymentMethod.php | 47 +++++++++++++++---- .../ppcp-button/src/Assets/SmartButton.php | 9 ++++ 6 files changed, 107 insertions(+), 12 deletions(-) diff --git a/.psalm/wcblocks.php b/.psalm/wcblocks.php index d99fcbcf8..4c8274398 100644 --- a/.psalm/wcblocks.php +++ b/.psalm/wcblocks.php @@ -368,3 +368,14 @@ namespace Automattic\WooCommerce\Blocks\Payments { } } } + +/** + * Registers and validates payment requirements callbacks. + * + * @see Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::register_payment_requirements() + * + * @param array $args Args to pass to register_payment_requirements. + * @returns boolean|\WP_Error True on success, WP_Error on fail. + */ +function woocommerce_store_api_register_payment_requirements( $args ) { +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index f51ddfb94..4ea221ac0 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,5 +1,5 @@ import {useEffect, useState} from '@wordpress/element'; -import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; +import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; import {paypalOrderToWcShippingAddress, paypalPayerToWc} from "./Helper/Address"; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' @@ -116,6 +116,17 @@ const PayPalComponent = ({ } const unsubscribeProcessing = onPaymentSetup(() => { + if (config.scriptData.continuation) { + return { + type: responseTypes.SUCCESS, + meta: { + paymentMethodData: { + 'paypal_order_id': config.scriptData.continuation.order_id, + }, + }, + }; + } + const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); let billingAddress = paypalPayerToWc(paypalOrder.payer); // no billing address, such as if billing address retrieval is not allowed in the merchant account @@ -139,6 +150,14 @@ const PayPalComponent = ({ }; }, [onPaymentSetup, paypalOrder, activePaymentMethod]); + if (config.scriptData.continuation) { + return ( +
+ +
+ ) + } + if (!loaded) { return null; } @@ -157,7 +176,14 @@ const PayPalComponent = ({ ); } -registerExpressPaymentMethod({ +const features = ['products']; +let registerMethod = registerExpressPaymentMethod; +if (config.scriptData.continuation) { + features.push('ppcp_continuation'); + registerMethod = registerPaymentMethod; +} + +registerMethod({ name: config.id, label:
, content: , @@ -165,6 +191,6 @@ registerExpressPaymentMethod({ ariaLabel: config.title, canMakePayment: () => config.enabled, supports: { - features: ['products'], + features: features, }, }); diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index afc7d368c..895fe5277 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -30,7 +30,9 @@ return array( $container->get( 'button.smart-button' ), $container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings.status' ), - $container->get( 'wcgateway.paypal-gateway' ) + $container->get( 'wcgateway.paypal-gateway' ), + $container->get( 'session.cancellation.view' ), + $container->get( 'session.handler' ) ); }, ); diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 8658b2c4c..eb1ccc01b 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WooCommerce\PayPalCommerce\Button\Assets\SmartButton; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -40,6 +41,21 @@ class BlocksModule implements ModuleInterface { $payment_method_registry->register( $c->get( 'blocks.method' ) ); } ); + + woocommerce_store_api_register_payment_requirements( + array( + 'data_callback' => function() use ( $c ): array { + $smart_button = $c->get( 'button.smart-button' ); + assert( $smart_button instanceof SmartButton ); + + if ( isset( $smart_button->script_data()['continuation'] ) ) { + return array( 'ppcp_continuation' ); + } + + return array(); + }, + ) + ); } } diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 7cae8c914..0aeff9fc5 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -11,6 +11,9 @@ namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; +use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController; +use WooCommerce\PayPalCommerce\Session\Cancellation\CancelView; +use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -61,6 +64,20 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ private $gateway; + /** + * The cancellation view. + * + * @var CancelView + */ + private $cancellation_view; + + /** + * The Session handler. + * + * @var SessionHandler + */ + private $session_handler; + /** * Assets constructor. * @@ -70,6 +87,8 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { * @param Settings $plugin_settings The settings. * @param SettingsStatus $settings_status The Settings status helper. * @param PayPalGateway $gateway The WC gateway. + * @param CancelView $cancellation_view The cancellation view. + * @param SessionHandler $session_handler The Session handler. */ public function __construct( string $module_url, @@ -77,15 +96,19 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { SmartButtonInterface $smart_button, Settings $plugin_settings, SettingsStatus $settings_status, - PayPalGateway $gateway + PayPalGateway $gateway, + CancelView $cancellation_view, + SessionHandler $session_handler ) { - $this->name = PayPalGateway::ID; - $this->module_url = $module_url; - $this->version = $version; - $this->smart_button = $smart_button; - $this->plugin_settings = $plugin_settings; - $this->settings_status = $settings_status; - $this->gateway = $gateway; + $this->name = PayPalGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->smart_button = $smart_button; + $this->plugin_settings = $plugin_settings; + $this->settings_status = $settings_status; + $this->gateway = $gateway; + $this->cancellation_view = $cancellation_view; + $this->session_handler = $session_handler; } /** @@ -126,6 +149,14 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { public function get_payment_method_data() { $script_data = $this->smart_button->script_data(); + if ( isset( $script_data['continuation'] ) ) { + $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() ); + + $script_data['continuation']['cancel'] = array( + 'html' => $this->cancellation_view->render_session_cancellation( $url, $this->session_handler->funding_source() ), + ); + } + return array( 'id' => $this->gateway->id, 'title' => $this->gateway->title, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 8cf7360b4..ab06a46f7 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -927,6 +927,15 @@ class SmartButton implements SmartButtonInterface { $localize['button']['style']['tagline'] = false; } + if ( $this->is_paypal_continuation() ) { + $order = $this->session_handler->order(); + assert( $order !== null ); + + $localize['continuation'] = array( + 'order_id' => $order->id(), + ); + } + $this->request_data->dequeue_nonce_fix(); return $localize; } From 26e0e9b828dddd06bd3e63c880863bf39af5f8c5 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 08:10:49 +0300 Subject: [PATCH 20/49] Refactor test --- tests/playwright/place-order.spec.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index 7109c5ec7..7beea8d4e 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -52,6 +52,11 @@ async function loginIntoPaypal(popup) { await popup.locator('#btnLogin').click(); } +async function expectOrderReceivedPage(page) { + const title = await page.locator('.entry-title'); + await expect(title).toHaveText('Order received'); +} + test('PayPal button place order from Product page', async ({page}) => { await page.goto(PRODUCT_URL); @@ -69,8 +74,7 @@ test('PayPal button place order from Product page', async ({page}) => { page.locator('#place_order').click(), ]); - const title = await page.locator('.entry-title'); - await expect(title).toHaveText('Order received'); + await expectOrderReceivedPage(page); }); test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', async ({page}) => { @@ -97,8 +101,7 @@ test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', asy page.locator('.ppcp-dcc-order-button').click(), ]); - const title = await page.locator('.entry-title'); - await expect(title).toHaveText('Order received'); + await expectOrderReceivedPage(page); }); test('PayPal express block', async ({page}) => { From e032edcc8cd8a89b9a2b950bb0f18069ef358c15 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 08:12:13 +0300 Subject: [PATCH 21/49] Fix test when popup starts with login --- tests/playwright/place-order.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index 7beea8d4e..b2bfc7c02 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -45,7 +45,11 @@ async function openPaypalPopup(page) { } async function loginIntoPaypal(popup) { - await popup.click("text=Log in"); + await Promise.any([ + popup.locator('[name="login_email"]'), + popup.click("text=Log in"), + ]); + await popup.fill('[name="login_email"]', CUSTOMER_EMAIL); await popup.locator('#btnNext').click(); await popup.fill('[name="login_password"]', CUSTOMER_PASSWORD); From 19f004becd3ddf9bd4e3471d2228bb9616eaf1e1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 08:14:58 +0300 Subject: [PATCH 22/49] Retry popup openning sometimes checkout refreshes and the buttons becomes inactive for some time --- tests/playwright/place-order.spec.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index b2bfc7c02..576f629cd 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -31,17 +31,24 @@ async function fillCheckoutForm(page) { } } -async function openPaypalPopup(page) { - await page.locator('.component-frame').scrollIntoViewIfNeeded(); +async function openPaypalPopup(page, retry = true) { + try { + await page.locator('.component-frame').scrollIntoViewIfNeeded(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.frameLocator('.component-frame').locator('[data-funding-source="paypal"]').click(), - ]); + const [popup] = await Promise.all([ + page.waitForEvent('popup', {timeout: 5000}), + page.frameLocator('.component-frame').locator('[data-funding-source="paypal"]').click(), + ]); - await popup.waitForLoadState(); + await popup.waitForLoadState(); - return popup; + return popup; + } catch (err) { + if (retry) { + return openPaypalPopup(page, false); + } + throw err; + } } async function loginIntoPaypal(popup) { From b444c1ef555f6fb6752b0fd61ee3ef199ec190a0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 10:23:13 +0300 Subject: [PATCH 23/49] Fix block context --- modules/ppcp-button/src/Helper/ContextTrait.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 2417464f6..7ce05ddbe 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -17,33 +17,32 @@ trait ContextTrait { * @return string */ protected function context(): string { - $context = 'mini-cart'; if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) { - $context = 'product'; + return 'product'; } // has_block may not work if called too early, such as during the block registration. if ( has_block( 'woocommerce/cart' ) ) { - $context = 'cart-block'; + return 'cart-block'; } if ( is_cart() ) { - $context = 'cart'; + return 'cart'; } if ( has_block( 'woocommerce/checkout' ) ) { - $context = 'checkout-block'; + return 'checkout-block'; } if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) { - $context = 'checkout'; + return 'checkout'; } if ( is_checkout_pay_page() ) { - $context = 'pay-now'; + return 'pay-now'; } - return $context; + return 'mini-cart'; } /** From 211c57b938bbada047ac6a2cd624afa812187b07 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 10:23:52 +0300 Subject: [PATCH 24/49] Add final review for blocks --- .../resources/js/checkout-block.js | 36 +++++++++---------- .../ppcp-button/src/Assets/SmartButton.php | 9 ++--- .../src/Endpoint/CreateOrderEndpoint.php | 2 +- tests/playwright/place-order.spec.js | 17 ++++++--- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 4ea221ac0..a21abb7ec 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -92,9 +92,23 @@ const PayPalComponent = ({ throw new Error(config.scriptData.labels.error.generic) } - setPaypalOrder(json.data); + const order = json.data; - onSubmit(); + setPaypalOrder(order); + + const shippingAddress = paypalOrderToWcShippingAddress(order); + let billingAddress = paypalPayerToWc(order.payer); + // no billing address, such as if billing address retrieval is not allowed in the merchant account + if (!billingAddress.address_line_1) { + billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)}; + } + + await wp.data.dispatch('wc/store/cart').updateCustomerData({ + billing_address: billingAddress, + shipping_address: shippingAddress, + }); + + location.href = config.scriptData.redirect; } catch (err) { console.error(err); @@ -126,24 +140,6 @@ const PayPalComponent = ({ }, }; } - - const shippingAddress = paypalOrderToWcShippingAddress(paypalOrder); - let billingAddress = paypalPayerToWc(paypalOrder.payer); - // no billing address, such as if billing address retrieval is not allowed in the merchant account - if (!billingAddress.address_line_1) { - billingAddress = {...shippingAddress, ...paypalPayerToWc(paypalOrder.payer)}; - } - - return { - type: responseTypes.SUCCESS, - meta: { - paymentMethodData: { - 'paypal_order_id': paypalOrder.id, - }, - shippingAddress, - billingAddress, - }, - }; }); return () => { unsubscribeProcessing(); diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ab06a46f7..07ad5351a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -960,6 +960,7 @@ class SmartButton implements SmartButtonInterface { * @return array */ private function url_params(): array { + $context = $this->context(); $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture'; $product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent; $other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent; @@ -970,8 +971,8 @@ class SmartButton implements SmartButtonInterface { 'integration-date' => PAYPAL_INTEGRATION_DATE, 'components' => implode( ',', $this->components() ), 'vault' => $this->can_save_vault_token() ? 'true' : 'false', - 'commit' => is_checkout() ? 'true' : 'false', - 'intent' => $this->context() === 'product' ? $product_intent : $other_context_intent, + 'commit' => in_array( $context, array( 'checkout', 'pay-now' ), true ) ? 'true' : 'false', + 'intent' => $context === 'product' ? $product_intent : $other_context_intent, ); if ( $this->environment->current_environment_is( Environment::SANDBOX ) @@ -1012,8 +1013,8 @@ class SmartButton implements SmartButtonInterface { $enable_funding = array( 'venmo' ); - if ( $this->settings_status->is_pay_later_button_enabled_for_location( $this->context() ) || - $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() ) + if ( $this->settings_status->is_pay_later_button_enabled_for_location( $context ) || + $this->settings_status->is_pay_later_messaging_enabled_for_location( $context ) ) { $enable_funding[] = 'paylater'; } else { diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index df2977982..81398a88d 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -382,7 +382,7 @@ class CreateOrderEndpoint implements EndpointInterface { $funding_source ); - $action = in_array( $this->parsed_request_data['context'], array( 'checkout', 'express' ), true ) ? + $action = in_array( $this->parsed_request_data['context'], array( 'checkout' ), true ) ? ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE; if ( 'card' === $funding_source ) { diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index 576f629cd..c9778f54c 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -68,6 +68,18 @@ async function expectOrderReceivedPage(page) { await expect(title).toHaveText('Order received'); } +async function completeBlockContinuation(page) { + await expect(page.locator('#radio-control-wc-payment-method-options-ppcp-gateway')).toBeChecked(); + + await expect(page.locator('.component-frame')).toHaveCount(0); + + await page.locator('.wc-block-components-checkout-place-order-button').click(); + + await page.waitForNavigation(); + + await expectOrderReceivedPage(page); +} + test('PayPal button place order from Product page', async ({page}) => { await page.goto(PRODUCT_URL); @@ -127,8 +139,5 @@ test('PayPal express block', async ({page}) => { await popup.locator('#payment-submit-btn').click(); - await page.waitForNavigation(); - - const title = await page.locator('.entry-title'); - await expect(title).toHaveText('Order received'); + await completeBlockContinuation(page); }); From 0d8f86eefaeadfe2d707cd3ce639f53494a9aa5a Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 17 Apr 2023 17:15:37 +0300 Subject: [PATCH 25/49] Fix block cart continuation --- modules/ppcp-blocks/resources/js/checkout-block.js | 7 ++++++- tests/playwright/place-order.spec.js | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index a21abb7ec..e1b4b8f52 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -108,7 +108,12 @@ const PayPalComponent = ({ shipping_address: shippingAddress, }); - location.href = config.scriptData.redirect; + const checkoutUrl = new URL(config.scriptData.redirect); + // sometimes some browsers may load some kind of cached version of the page, + // so adding a parameter to avoid that + checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString()); + + location.href = checkoutUrl.toString(); } catch (err) { console.error(err); diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index c9778f54c..0eb3490cf 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -141,3 +141,16 @@ test('PayPal express block', async ({page}) => { await completeBlockContinuation(page); }); + +test('PayPal express block cart', async ({page}) => { + + await page.goto('/cart-block?add-to-cart=' + PRODUCT_ID) + + const popup = await openPaypalPopup(page); + + await loginIntoPaypal(popup); + + await popup.locator('#payment-submit-btn').click(); + + await completeBlockContinuation(page); +}); From f435796cbef6728e7fb4778f908b1214f2abafbc Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 18 Apr 2023 16:55:01 +0300 Subject: [PATCH 26/49] Add final review checkbox --- modules/ppcp-blocks/extensions.php | 27 +++++++++++++++++++ .../resources/js/gateway-settings.js | 11 ++++++++ 2 files changed, 38 insertions(+) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index c28feb5f9..92555dae3 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; +use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( @@ -26,4 +27,30 @@ return array( unset( $locations['cart-block'] ); return $locations; }, + + 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + $insert_after = function( array $array, string $key, array $new ): array { + $keys = array_keys( $array ); + $index = array_search( $key, $keys, true ); + $pos = false === $index ? count( $array ) : $index + 1; + + return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) ); + }; + + return $insert_after( + $fields, + 'smart_button_enable_styling_per_location', + array( + 'blocks_no_final_review' => array( + 'title' => __( 'Block Express payments Final Review', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'Require customers to confirm Block Express payments on the Checkout page. If disabled, the Checkout page is skipped for Block Express payments.', 'woocommerce-paypal-payments' ), + 'default' => true, + 'screens' => array( State::STATE_START, State::STATE_ONBOARDED ), + 'requirements' => array(), + 'gateway' => 'paypal', + ), + ) + ); + }, ); diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 01ac034c8..9e9622f56 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -19,6 +19,17 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo const form = jQuery('#mainform'); + const noReviewChk = document.querySelector('#ppcp-blocks_no_final_review'); + if (noReviewChk) { + noReviewChk.addEventListener('click', (e) => { + if (!noReviewChk.checked) { + if (!window.confirm('Are you sure you want to disable the final review on the Checkout page for Block Express payments?')) { + noReviewChk.checked = true; + } + } + }); + } + const payLaterButtonInput = document.querySelector('#ppcp-pay_later_button_enabled'); if (payLaterButtonInput) { From 29e1b0cbd0a9d5e197a691a20aae502548f22606 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 19 Apr 2023 11:10:35 +0300 Subject: [PATCH 27/49] Show notice if cannot init blocks --- modules/ppcp-blocks/src/BlocksModule.php | 56 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index eb1ccc01b..2c38f9b5a 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -34,29 +34,49 @@ class BlocksModule implements ModuleInterface { * {@inheritDoc} */ public function run( ContainerInterface $c ): void { - if ( class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) { + if ( + ! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) + || ! function_exists( 'woocommerce_store_api_register_payment_requirements' ) + ) { add_action( - 'woocommerce_blocks_payment_method_type_registration', - function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void { - $payment_method_registry->register( $c->get( 'blocks.method' ) ); + 'admin_notices', + function () { + printf( + '

%1$s

', + wp_kses_post( + __( + 'PayPal checkout block initialization failed, possibly old WooCommerce version or disabled WooCommerce Blocks plugin.', + 'woocommerce-paypal-payments' + ) + ) + ); } ); - woocommerce_store_api_register_payment_requirements( - array( - 'data_callback' => function() use ( $c ): array { - $smart_button = $c->get( 'button.smart-button' ); - assert( $smart_button instanceof SmartButton ); - - if ( isset( $smart_button->script_data()['continuation'] ) ) { - return array( 'ppcp_continuation' ); - } - - return array(); - }, - ) - ); + return; } + + add_action( + 'woocommerce_blocks_payment_method_type_registration', + function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void { + $payment_method_registry->register( $c->get( 'blocks.method' ) ); + } + ); + + woocommerce_store_api_register_payment_requirements( + array( + 'data_callback' => function() use ( $c ): array { + $smart_button = $c->get( 'button.smart-button' ); + assert( $smart_button instanceof SmartButton ); + + if ( isset( $smart_button->script_data()['continuation'] ) ) { + return array( 'ppcp_continuation' ); + } + + return array(); + }, + ) + ); } /** From 8f3e9c4554e176a5a607a8e33541df5f2c3687fc Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 21 Apr 2023 09:46:28 +0300 Subject: [PATCH 28/49] Disable card button for blocks not working well with express, cannot fill fields --- modules/ppcp-button/src/Assets/SmartButton.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 07ad5351a..45d021c67 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1003,6 +1003,10 @@ class SmartButton implements SmartButtonInterface { } } + if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) { + $disable_funding[] = 'card'; + } + if ( $this->is_free_trial_cart() ) { $all_sources = array_keys( $this->all_funding_sources ); if ( $is_dcc_enabled || $is_separate_card_enabled ) { From 98f6d0de4513c01c124d31159eee5dfcded827d1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 21 Apr 2023 11:12:32 +0300 Subject: [PATCH 29/49] Save funding source for block orders --- modules/ppcp-blocks/resources/js/checkout-block.js | 10 +++++++--- modules/ppcp-blocks/src/PayPalPaymentMethod.php | 11 ++++++----- modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 8 +++++++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index e1b4b8f52..def53bd7a 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -5,6 +5,8 @@ import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper const config = wc.wcSettings.getSetting('ppcp-gateway_data'); +window.ppcpFundingSource = config.fundingSource; + const PayPalComponent = ({ onClick, onClose, @@ -39,7 +41,6 @@ const PayPalComponent = ({ context: 'express', order_id: config.scriptData.order_id, payment_method: 'ppcp-gateway', - funding_source: 'paypal', createaccount: false }), }); @@ -75,7 +76,7 @@ const PayPalComponent = ({ body: JSON.stringify({ nonce: config.scriptData.ajax.approve_order.nonce, order_id: data.orderID, - //funding_source: , + funding_source: window.ppcpFundingSource ?? 'paypal', }) }); @@ -125,7 +126,9 @@ const PayPalComponent = ({ } }; - const handleClick = () => { + const handleClick = (data, actions) => { + window.ppcpFundingSource = data.fundingSource; + onClick(); }; @@ -141,6 +144,7 @@ const PayPalComponent = ({ meta: { paymentMethodData: { 'paypal_order_id': config.scriptData.continuation.order_id, + 'funding_source': window.ppcpFundingSource ?? 'paypal', }, }, }; diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 0aeff9fc5..3c792868e 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -158,11 +158,12 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { } return array( - 'id' => $this->gateway->id, - 'title' => $this->gateway->title, - 'description' => $this->gateway->description, - 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), - 'scriptData' => $script_data, + 'id' => $this->gateway->id, + 'title' => $this->gateway->title, + 'description' => $this->gateway->description, + 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), + 'fundingSource' => $this->session_handler->funding_source(), + 'scriptData' => $script_data, ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 944d7b67b..58037e60e 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -424,7 +424,13 @@ class PayPalGateway extends \WC_Payment_Gateway { } // phpcs:ignore WordPress.Security.NonceVerification.Missing - $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) ); + $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? ( $_POST['funding_source'] ?? '' ) ) ); + + if ( $funding_source ) { + $wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) ); + $wc_order->save(); + } + if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) { $user_id = (int) $wc_order->get_customer_id(); $tokens = $this->payment_token_repository->all_for_user_id( $user_id ); From 57414cff9f867e37ad475f56059b2dd6c01cac9d Mon Sep 17 00:00:00 2001 From: Alex P Date: Sat, 22 Apr 2023 13:16:42 +0300 Subject: [PATCH 30/49] Fix request params --- modules/ppcp-blocks/resources/js/checkout-block.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index def53bd7a..c0ac40409 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -38,8 +38,7 @@ const PayPalComponent = ({ body: JSON.stringify({ nonce: config.scriptData.ajax.create_order.nonce, bn_code: '', - context: 'express', - order_id: config.scriptData.order_id, + context: config.scriptData.context, payment_method: 'ppcp-gateway', createaccount: false }), From 37959ad2d866ba2411f60a34b3d7f660fe4340be Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 24 Apr 2023 08:12:48 +0300 Subject: [PATCH 31/49] Handle final review option --- modules/ppcp-blocks/extensions.php | 2 +- .../resources/js/Helper/Address.js | 16 ++++++ .../resources/js/checkout-block.js | 49 ++++++++++++------- modules/ppcp-blocks/services.php | 13 ++++- .../ppcp-blocks/src/PayPalPaymentMethod.php | 41 ++++++++++------ .../resources/js/gateway-settings.js | 11 +++-- 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 92555dae3..522248023 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -41,7 +41,7 @@ return array( $fields, 'smart_button_enable_styling_per_location', array( - 'blocks_no_final_review' => array( + 'blocks_final_review_enabled' => array( 'title' => __( 'Block Express payments Final Review', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'label' => __( 'Require customers to confirm Block Express payments on the Checkout page. If disabled, the Checkout page is skipped for Block Express payments.', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index b7ecc1645..5f72d3add 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -100,3 +100,19 @@ export const paypalOrderToWcShippingAddress = (order) => { return res; } + +/** + * + * @param order + * @returns {{shippingAddress: Object, billingAddress: Object}} + */ +export const paypalOrderToWcAddresses = (order) => { + const shippingAddress = paypalOrderToWcShippingAddress(order); + let billingAddress = paypalPayerToWc(order.payer); + // no billing address, such as if billing address retrieval is not allowed in the merchant account + if (!billingAddress.address_line_1) { + billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)}; + } + + return {billingAddress, shippingAddress}; +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index c0ac40409..c808fe948 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,6 +1,6 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; -import {paypalOrderToWcShippingAddress, paypalPayerToWc} from "./Helper/Address"; +import {paypalOrderToWcAddresses} from "./Helper/Address"; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' const config = wc.wcSettings.getSetting('ppcp-gateway_data'); @@ -96,24 +96,22 @@ const PayPalComponent = ({ setPaypalOrder(order); - const shippingAddress = paypalOrderToWcShippingAddress(order); - let billingAddress = paypalPayerToWc(order.payer); - // no billing address, such as if billing address retrieval is not allowed in the merchant account - if (!billingAddress.address_line_1) { - billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)}; + if (config.finalReviewEnabled) { + const addresses = paypalOrderToWcAddresses(order); + + await wp.data.dispatch('wc/store/cart').updateCustomerData({ + billing_address: addresses.billingAddress, + shipping_address: addresses.shippingAddress, + }); + const checkoutUrl = new URL(config.scriptData.redirect); + // sometimes some browsers may load some kind of cached version of the page, + // so adding a parameter to avoid that + checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString()); + + location.href = checkoutUrl.toString(); + } else { + onSubmit(); } - - await wp.data.dispatch('wc/store/cart').updateCustomerData({ - billing_address: billingAddress, - shipping_address: shippingAddress, - }); - - const checkoutUrl = new URL(config.scriptData.redirect); - // sometimes some browsers may load some kind of cached version of the page, - // so adding a parameter to avoid that - checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString()); - - location.href = checkoutUrl.toString(); } catch (err) { console.error(err); @@ -144,10 +142,23 @@ const PayPalComponent = ({ paymentMethodData: { 'paypal_order_id': config.scriptData.continuation.order_id, 'funding_source': window.ppcpFundingSource ?? 'paypal', - }, + } }, }; } + + const addresses = paypalOrderToWcAddresses(paypalOrder); + + return { + type: responseTypes.SUCCESS, + meta: { + paymentMethodData: { + 'paypal_order_id': paypalOrder.id, + 'funding_source': window.ppcpFundingSource ?? 'paypal', + }, + ...addresses, + }, + }; }); return () => { unsubscribeProcessing(); diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index 895fe5277..5c765b538 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -12,7 +12,7 @@ namespace WooCommerce\PayPalCommerce\Blocks; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'blocks.url' => static function ( ContainerInterface $container ): string { + 'blocks.url' => static function ( ContainerInterface $container ): string { /** * The path cannot be false. * @@ -23,7 +23,7 @@ return array( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod { + 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod { return new PayPalPaymentMethod( $container->get( 'blocks.url' ), $container->get( 'ppcp.asset-version' ), @@ -31,8 +31,17 @@ return array( $container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings.status' ), $container->get( 'wcgateway.paypal-gateway' ), + $container->get( 'blocks.settings.final_review_enabled' ), $container->get( 'session.cancellation.view' ), $container->get( 'session.handler' ) ); }, + 'blocks.settings.final_review_enabled' => static function ( ContainerInterface $container ): bool { + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof ContainerInterface ); + + return $settings->has( 'blocks_final_review_enabled' ) ? + (bool) $settings->get( 'blocks_final_review_enabled' ) : + true; + }, ); diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 3c792868e..b573a124b 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -64,6 +64,13 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ private $gateway; + /** + * Whether the final review is enabled. + * + * @var bool + */ + private $final_review_enabled; + /** * The cancellation view. * @@ -87,6 +94,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { * @param Settings $plugin_settings The settings. * @param SettingsStatus $settings_status The Settings status helper. * @param PayPalGateway $gateway The WC gateway. + * @param bool $final_review_enabled Whether the final review is enabled. * @param CancelView $cancellation_view The cancellation view. * @param SessionHandler $session_handler The Session handler. */ @@ -97,18 +105,20 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { Settings $plugin_settings, SettingsStatus $settings_status, PayPalGateway $gateway, + bool $final_review_enabled, CancelView $cancellation_view, SessionHandler $session_handler ) { - $this->name = PayPalGateway::ID; - $this->module_url = $module_url; - $this->version = $version; - $this->smart_button = $smart_button; - $this->plugin_settings = $plugin_settings; - $this->settings_status = $settings_status; - $this->gateway = $gateway; - $this->cancellation_view = $cancellation_view; - $this->session_handler = $session_handler; + $this->name = PayPalGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->smart_button = $smart_button; + $this->plugin_settings = $plugin_settings; + $this->settings_status = $settings_status; + $this->gateway = $gateway; + $this->final_review_enabled = $final_review_enabled; + $this->cancellation_view = $cancellation_view; + $this->session_handler = $session_handler; } /** @@ -158,12 +168,13 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { } return array( - 'id' => $this->gateway->id, - 'title' => $this->gateway->title, - 'description' => $this->gateway->description, - 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), - 'fundingSource' => $this->session_handler->funding_source(), - 'scriptData' => $script_data, + 'id' => $this->gateway->id, + 'title' => $this->gateway->title, + 'description' => $this->gateway->description, + 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), + 'fundingSource' => $this->session_handler->funding_source(), + 'finalReviewEnabled' => $this->final_review_enabled, + 'scriptData' => $script_data, ); } } diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 9e9622f56..910e21395 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -19,12 +19,13 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo const form = jQuery('#mainform'); - const noReviewChk = document.querySelector('#ppcp-blocks_no_final_review'); - if (noReviewChk) { - noReviewChk.addEventListener('click', (e) => { - if (!noReviewChk.checked) { + // TODO: maybe move to a separate JS file in blocks module, when we need more JS for block settings + const finalReviewCheckbox = document.querySelector('#ppcp-blocks_final_review_enabled'); + if (finalReviewCheckbox) { + finalReviewCheckbox.addEventListener('click', () => { + if (!finalReviewCheckbox.checked) { if (!window.confirm('Are you sure you want to disable the final review on the Checkout page for Block Express payments?')) { - noReviewChk.checked = true; + finalReviewCheckbox.checked = true; } } }); From 8efa0e6da38769c3403243e14755b209a8044d6c Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 24 Apr 2023 08:15:22 +0300 Subject: [PATCH 32/49] Fix pay now button not sure if the action parameter in order creation request affects anything, but the docs say that it should match the js sdk commit parameter --- modules/ppcp-blocks/extensions.php | 10 ++++++++++ modules/ppcp-button/services.php | 5 +++++ modules/ppcp-button/src/Assets/SmartButton.php | 12 +++++++++++- .../ppcp-button/src/Endpoint/CreateOrderEndpoint.php | 12 +++++++++++- .../Button/Endpoint/CreateOrderEndpointTest.php | 1 + 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 522248023..9a48a57fb 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -53,4 +53,14 @@ return array( ) ); }, + + 'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array { + $contexts[] = 'checkout-block'; + + if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) { + $contexts[] = 'cart-block'; + } + + return $contexts; + }, ); diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 53e01f193..ee6d0033c 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -110,6 +110,7 @@ return array( $container->get( 'wcgateway.all-funding-sources' ), $container->get( 'button.basic-checkout-validation-enabled' ), $container->get( 'button.early-wc-checkout-validation-enabled' ), + $container->get( 'button.pay-now-contexts' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, @@ -119,6 +120,9 @@ return array( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, + 'button.pay-now-contexts' => static function ( ContainerInterface $container ): array { + return array( 'checkout', 'pay-now' ); + }, 'button.request-data' => static function ( ContainerInterface $container ): RequestData { return new RequestData(); }, @@ -156,6 +160,7 @@ return array( $registration_needed, $container->get( 'wcgateway.settings.card_billing_data_mode' ), $container->get( 'button.early-wc-checkout-validation-enabled' ), + $container->get( 'button.pay-now-contexts' ), $logger ); }, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 45d021c67..3ea10a1bb 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -165,6 +165,13 @@ class SmartButton implements SmartButtonInterface { */ protected $early_validation_enabled; + /** + * The contexts that should have the Pay Now button. + * + * @var string[] + */ + private $pay_now_contexts; + /** * The logger. * @@ -199,6 +206,7 @@ class SmartButton implements SmartButtonInterface { * @param array $all_funding_sources All existing funding sources. * @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled. * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. + * @param string[] $pay_now_contexts The contexts that should have the Pay Now button. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -219,6 +227,7 @@ class SmartButton implements SmartButtonInterface { array $all_funding_sources, bool $basic_checkout_validation_enabled, bool $early_validation_enabled, + array $pay_now_contexts, LoggerInterface $logger ) { @@ -239,6 +248,7 @@ class SmartButton implements SmartButtonInterface { $this->all_funding_sources = $all_funding_sources; $this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled; $this->early_validation_enabled = $early_validation_enabled; + $this->pay_now_contexts = $pay_now_contexts; $this->logger = $logger; } @@ -971,7 +981,7 @@ class SmartButton implements SmartButtonInterface { 'integration-date' => PAYPAL_INTEGRATION_DATE, 'components' => implode( ',', $this->components() ), 'vault' => $this->can_save_vault_token() ? 'true' : 'false', - 'commit' => in_array( $context, array( 'checkout', 'pay-now' ), true ) ? 'true' : 'false', + 'commit' => in_array( $context, $this->pay_now_contexts, true ) ? 'true' : 'false', 'intent' => $context === 'product' ? $product_intent : $other_context_intent, ); if ( diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 4abd7dad6..930ccd59c 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -138,6 +138,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ protected $early_validation_enabled; + /** + * The contexts that should have the Pay Now button. + * + * @var string[] + */ + private $pay_now_contexts; + /** * The logger. * @@ -159,6 +166,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param bool $registration_needed Whether a new user must be registered during checkout. * @param string $card_billing_data_mode The value of card_billing_data_mode from the settings. * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. + * @param string[] $pay_now_contexts The contexts that should have the Pay Now button. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -173,6 +181,7 @@ class CreateOrderEndpoint implements EndpointInterface { bool $registration_needed, string $card_billing_data_mode, bool $early_validation_enabled, + array $pay_now_contexts, LoggerInterface $logger ) { @@ -187,6 +196,7 @@ class CreateOrderEndpoint implements EndpointInterface { $this->registration_needed = $registration_needed; $this->card_billing_data_mode = $card_billing_data_mode; $this->early_validation_enabled = $early_validation_enabled; + $this->pay_now_contexts = $pay_now_contexts; $this->logger = $logger; } @@ -385,7 +395,7 @@ class CreateOrderEndpoint implements EndpointInterface { $funding_source ); - $action = in_array( $this->parsed_request_data['context'], array( 'checkout' ), true ) ? + $action = in_array( $this->parsed_request_data['context'], $this->pay_now_contexts, true ) ? ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE; if ( 'card' === $funding_source ) { diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index 4e6d82a6c..d884ee59f 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -167,6 +167,7 @@ class CreateOrderEndpointTest extends TestCase false, CardBillingMode::MINIMAL_INPUT, false, + ['checkout'], new NullLogger() ); return array($payer_factory, $testee); From 9d638a57e33bd1914953ea457aafd47e360488cb Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 25 Apr 2023 15:06:24 +0300 Subject: [PATCH 33/49] Show shipping methods in paypal if express without review --- modules/ppcp-api-client/services.php | 12 +- .../ppcp-api-client/src/Entity/Shipping.php | 35 ++++- .../src/Entity/ShippingOption.php | 139 ++++++++++++++++++ .../src/Factory/PurchaseUnitFactory.php | 5 +- .../src/Factory/ShippingFactory.php | 29 +++- .../src/Factory/ShippingOptionFactory.php | 111 ++++++++++++++ modules/ppcp-blocks/extensions.php | 4 + .../resources/js/checkout-block.js | 12 ++ modules/ppcp-button/services.php | 9 ++ .../src/Endpoint/CreateOrderEndpoint.php | 12 +- .../Factory/PurchaseUnitFactoryTest.php | 2 +- .../Endpoint/CreateOrderEndpointTest.php | 1 + 12 files changed, 355 insertions(+), 16 deletions(-) create mode 100644 modules/ppcp-api-client/src/Entity/ShippingOption.php create mode 100644 modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 1b1eb3da0..603d9cbc4 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; @@ -289,12 +290,19 @@ return array( ); }, 'api.factory.shipping' => static function ( ContainerInterface $container ): ShippingFactory { - $address_factory = $container->get( 'api.factory.address' ); - return new ShippingFactory( $address_factory ); + return new ShippingFactory( + $container->get( 'api.factory.address' ), + $container->get( 'api.factory.shipping-option' ) + ); }, 'api.factory.shipping-preference' => static function ( ContainerInterface $container ): ShippingPreferenceFactory { return new ShippingPreferenceFactory(); }, + 'api.factory.shipping-option' => static function ( ContainerInterface $container ): ShippingOptionFactory { + return new ShippingOptionFactory( + $container->get( 'api.factory.money' ) + ); + }, 'api.factory.amount' => static function ( ContainerInterface $container ): AmountFactory { $item_factory = $container->get( 'api.factory.item' ); return new AmountFactory( diff --git a/modules/ppcp-api-client/src/Entity/Shipping.php b/modules/ppcp-api-client/src/Entity/Shipping.php index 244d876f3..eeb4001dc 100644 --- a/modules/ppcp-api-client/src/Entity/Shipping.php +++ b/modules/ppcp-api-client/src/Entity/Shipping.php @@ -28,15 +28,24 @@ class Shipping { */ private $address; + /** + * Shipping methods. + * + * @var ShippingOption[] + */ + private $options; + /** * Shipping constructor. * - * @param string $name The name. - * @param Address $address The address. + * @param string $name The name. + * @param Address $address The address. + * @param ShippingOption[] $options Shipping methods. */ - public function __construct( string $name, Address $address ) { + public function __construct( string $name, Address $address, array $options = array() ) { $this->name = $name; $this->address = $address; + $this->options = $options; } /** @@ -57,17 +66,35 @@ class Shipping { return $this->address; } + /** + * Returns the shipping methods. + * + * @return ShippingOption[] + */ + public function options(): array { + return $this->options; + } + /** * Returns the object as array. * * @return array */ public function to_array(): array { - return array( + $result = array( 'name' => array( 'full_name' => $this->name(), ), 'address' => $this->address()->to_array(), ); + if ( $this->options ) { + $result['options'] = array_map( + function ( ShippingOption $opt ): array { + return $opt->to_array(); + }, + $this->options + ); + } + return $result; } } diff --git a/modules/ppcp-api-client/src/Entity/ShippingOption.php b/modules/ppcp-api-client/src/Entity/ShippingOption.php new file mode 100644 index 000000000..40a7b5ef7 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/ShippingOption.php @@ -0,0 +1,139 @@ +id = $id; + $this->label = $label; + $this->selected = $selected; + $this->amount = $amount; + $this->type = $type; + } + + /** + * The name. + * + * @return string + */ + public function id(): string { + return $this->id; + } + + /** + * The label. + * + * @return string + */ + public function label(): string { + return $this->label; + } + + /** + * Whether the method is selected by default. + * + * @return bool + */ + public function selected(): bool { + return $this->selected; + } + + /** + * Sets whether the method is selected by default. + * + * @param bool $selected The value to be set. + */ + public function set_selected( bool $selected ): void { + $this->selected = $selected; + } + + /** + * The price. + * + * @return Money + */ + public function amount(): Money { + return $this->amount; + } + + /** + * SHIPPING or PICKUP. + * + * @return string + */ + public function type(): string { + return $this->type; + } + + /** + * Returns the object as array. + * + * @return array + */ + public function to_array(): array { + return array( + 'id' => $this->id, + 'label' => $this->label, + 'selected' => $this->selected, + 'amount' => $this->amount->to_array(), + 'type' => $this->type, + ); + } +} diff --git a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php index c2667fc92..9f8f12c5e 100644 --- a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php +++ b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php @@ -153,10 +153,11 @@ class PurchaseUnitFactory { * Creates a PurchaseUnit based off a WooCommerce cart. * * @param \WC_Cart|null $cart The cart. + * @param bool $with_shipping_options Include WC shipping methods. * * @return PurchaseUnit */ - public function from_wc_cart( ?\WC_Cart $cart = null ): PurchaseUnit { + public function from_wc_cart( ?\WC_Cart $cart = null, bool $with_shipping_options = false ): PurchaseUnit { if ( ! $cart ) { $cart = WC()->cart ?? new \WC_Cart(); } @@ -172,7 +173,7 @@ class PurchaseUnitFactory { $shipping = null; $customer = \WC()->customer; if ( $this->shipping_needed( ... array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) { - $shipping = $this->shipping_factory->from_wc_customer( \WC()->customer ); + $shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options ); if ( 2 !== strlen( $shipping->address()->country_code() ) || ( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) ) diff --git a/modules/ppcp-api-client/src/Factory/ShippingFactory.php b/modules/ppcp-api-client/src/Factory/ShippingFactory.php index dcebb9b9f..b85259394 100644 --- a/modules/ppcp-api-client/src/Factory/ShippingFactory.php +++ b/modules/ppcp-api-client/src/Factory/ShippingFactory.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; +use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; /** @@ -24,23 +25,33 @@ class ShippingFactory { */ private $address_factory; + /** + * The shipping option factory. + * + * @var ShippingOptionFactory + */ + private $shipping_option_factory; + /** * ShippingFactory constructor. * - * @param AddressFactory $address_factory The address factory. + * @param AddressFactory $address_factory The address factory. + * @param ShippingOptionFactory $shipping_option_factory The shipping option factory. */ - public function __construct( AddressFactory $address_factory ) { - $this->address_factory = $address_factory; + public function __construct( AddressFactory $address_factory, ShippingOptionFactory $shipping_option_factory ) { + $this->address_factory = $address_factory; + $this->shipping_option_factory = $shipping_option_factory; } /** * Creates a shipping object based off a WooCommerce customer. * * @param \WC_Customer $customer The WooCommerce customer. + * @param bool $with_shipping_options Include WC shipping methods. * * @return Shipping */ - public function from_wc_customer( \WC_Customer $customer ): Shipping { + public function from_wc_customer( \WC_Customer $customer, bool $with_shipping_options = false ): Shipping { // Replicates the Behavior of \WC_Order::get_formatted_shipping_full_name(). $full_name = sprintf( // translators: %1$s is the first name and %2$s is the second name. wc translation. @@ -51,7 +62,8 @@ class ShippingFactory { $address = $this->address_factory->from_wc_customer( $customer ); return new Shipping( $full_name, - $address + $address, + $with_shipping_options ? $this->shipping_option_factory->from_wc_cart() : array() ); } @@ -91,9 +103,14 @@ class ShippingFactory { ); } $address = $this->address_factory->from_paypal_response( $data->address ); + $options = array_map( + array( $this->shipping_option_factory, 'from_paypal_response' ), + $data->options ?? array() + ); return new Shipping( $data->name->full_name, - $address + $address, + $options ); } } diff --git a/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php b/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php new file mode 100644 index 000000000..08af8c79e --- /dev/null +++ b/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php @@ -0,0 +1,111 @@ +money_factory = $money_factory; + } + + /** + * Creates an array of ShippingOption objects for the shipping methods available in the cart. + * + * @param WC_Cart|null $cart The cart. + * @return ShippingOption[] + */ + public function from_wc_cart( ?WC_Cart $cart = null ): array { + if ( ! $cart ) { + $cart = WC()->cart ?? new WC_Cart(); + } + + $cart->calculate_shipping(); + + $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() ); + if ( ! is_array( $chosen_shipping_methods ) ) { + $chosen_shipping_methods = array(); + } + + $packages = WC()->shipping()->get_packages(); + $options = array(); + foreach ( $packages as $package ) { + $rates = $package['rates'] ?? array(); + foreach ( $rates as $rate ) { + if ( ! $rate instanceof \WC_Shipping_Rate ) { + continue; + } + + $options[] = new ShippingOption( + $rate->get_id(), + $rate->get_label(), + in_array( $rate->get_id(), $chosen_shipping_methods, true ), + new Money( + (float) $rate->get_cost(), + get_woocommerce_currency() + ), + ShippingOption::TYPE_SHIPPING + ); + } + } + + if ( ! $chosen_shipping_methods && $options ) { + $options[0]->set_selected( true ); + } + + return $options; + } + + /** + * Creates a ShippingOption object from the PayPal JSON object. + * + * @param stdClass $data The JSON object. + * + * @return ShippingOption + * @throws RuntimeException When JSON object is malformed. + */ + public function from_paypal_response( stdClass $data ): ShippingOption { + if ( ! isset( $data->id ) ) { + throw new RuntimeException( 'No id was given for shipping option.' ); + } + if ( ! isset( $data->amount ) ) { + throw new RuntimeException( 'Shipping option amount not found' ); + } + + $amount = $this->money_factory->from_paypal_response( $data->amount ); + return new ShippingOption( + $data->id, + $data->label ?? '', + isset( $data->selected ) ? (bool) $data->selected : false, + $amount, + $data->type ?? ShippingOption::TYPE_SHIPPING + ); + } +} diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 9a48a57fb..df6b90ec2 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -63,4 +63,8 @@ return array( return $contexts; }, + + 'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool { + return ! $container->get( 'blocks.settings.final_review_enabled' ); + }, ); diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index c808fe948..60cd4beb4 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -15,6 +15,7 @@ const PayPalComponent = ({ eventRegistration, emitResponse, activePaymentMethod, + shippingData, }) => { const {onPaymentSetup} = eventRegistration; const {responseTypes} = emitResponse; @@ -129,6 +130,16 @@ const PayPalComponent = ({ onClick(); }; + let handleShippingChange = null; + if (shippingData.needsShipping && !config.finalReviewEnabled) { + handleShippingChange = (data) => { + console.log(data) + + const shippingOptionId = data.selected_shipping_option.id; + shippingData.setSelectedRates(shippingOptionId) + }; + } + useEffect(() => { if (activePaymentMethod !== config.id) { return; @@ -187,6 +198,7 @@ const PayPalComponent = ({ onError={onClose} createOrder={createOrder} onApprove={handleApprove} + onShippingChange={handleShippingChange} /> ); } diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index ee6d0033c..5cf16a535 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -161,6 +161,7 @@ return array( $container->get( 'wcgateway.settings.card_billing_data_mode' ), $container->get( 'button.early-wc-checkout-validation-enabled' ), $container->get( 'button.pay-now-contexts' ), + $container->get( 'button.handle-shipping-in-paypal' ), $logger ); }, @@ -271,4 +272,12 @@ return array( 'button.validation.wc-checkout-validator' => static function ( ContainerInterface $container ): CheckoutFormValidator { return new CheckoutFormValidator(); }, + + /** + * If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup. + * May result in slower popup performance, additional loading. + */ + 'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool { + return false; + }, ); diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 930ccd59c..a88cb9875 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -145,6 +145,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $pay_now_contexts; + /** + * If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup. + * + * @var bool + */ + private $handle_shipping_in_paypal; + /** * The logger. * @@ -167,6 +174,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param string $card_billing_data_mode The value of card_billing_data_mode from the settings. * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. * @param string[] $pay_now_contexts The contexts that should have the Pay Now button. + * @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup. * @param LoggerInterface $logger The logger. */ public function __construct( @@ -182,6 +190,7 @@ class CreateOrderEndpoint implements EndpointInterface { string $card_billing_data_mode, bool $early_validation_enabled, array $pay_now_contexts, + bool $handle_shipping_in_paypal, LoggerInterface $logger ) { @@ -197,6 +206,7 @@ class CreateOrderEndpoint implements EndpointInterface { $this->card_billing_data_mode = $card_billing_data_mode; $this->early_validation_enabled = $early_validation_enabled; $this->pay_now_contexts = $pay_now_contexts; + $this->handle_shipping_in_paypal = $handle_shipping_in_paypal; $this->logger = $logger; } @@ -236,7 +246,7 @@ class CreateOrderEndpoint implements EndpointInterface { } $this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); } else { - $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart(); + $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->handle_shipping_in_paypal ); // The cart does not have any info about payment method, so we must handle free trial here. if ( ( diff --git a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php index 72c664c91..54be500fc 100644 --- a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php @@ -294,7 +294,7 @@ class PurchaseUnitFactoryTest extends TestCase $shippingFactory = Mockery::mock(ShippingFactory::class); $shippingFactory ->expects('from_wc_customer') - ->with($wcCustomer) + ->with($wcCustomer, false) ->andReturn($shipping); $paymentsFacory = Mockery::mock(PaymentsFactory::class); $testee = new PurchaseUnitFactory( diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index d884ee59f..d2fc1a4fa 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -168,6 +168,7 @@ class CreateOrderEndpointTest extends TestCase CardBillingMode::MINIMAL_INPUT, false, ['checkout'], + false, new NullLogger() ); return array($payer_factory, $testee); From 47d69332882cfc173f07676c4275e991ce96178c Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 25 Apr 2023 16:12:50 +0300 Subject: [PATCH 34/49] Replace no final review alert with description --- modules/ppcp-blocks/extensions.php | 7 ++++++- .../ppcp-wc-gateway/resources/js/gateway-settings.js | 12 ------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index df6b90ec2..758e8ecb1 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -44,7 +44,12 @@ return array( 'blocks_final_review_enabled' => array( 'title' => __( 'Block Express payments Final Review', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => __( 'Require customers to confirm Block Express payments on the Checkout page. If disabled, the Checkout page is skipped for Block Express payments.', 'woocommerce-paypal-payments' ), + 'label' => __( + 'Require customers to confirm Block Express payments on the Checkout page. +If disabled, the Checkout page is skipped for Block Express payments. +

This can cause issues and poor UX if the server is not handling the requests fast enough.

', + 'woocommerce-paypal-payments' + ), 'default' => true, 'screens' => array( State::STATE_START, State::STATE_ONBOARDED ), 'requirements' => array(), diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 910e21395..01ac034c8 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -19,18 +19,6 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo const form = jQuery('#mainform'); - // TODO: maybe move to a separate JS file in blocks module, when we need more JS for block settings - const finalReviewCheckbox = document.querySelector('#ppcp-blocks_final_review_enabled'); - if (finalReviewCheckbox) { - finalReviewCheckbox.addEventListener('click', () => { - if (!finalReviewCheckbox.checked) { - if (!window.confirm('Are you sure you want to disable the final review on the Checkout page for Block Express payments?')) { - finalReviewCheckbox.checked = true; - } - } - }); - } - const payLaterButtonInput = document.querySelector('#ppcp-pay_later_button_enabled'); if (payLaterButtonInput) { From f396870610b8c631561d0f1307d5b6b98e6c53b1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 26 Apr 2023 16:41:47 +0300 Subject: [PATCH 35/49] Refactor patch request --- .../src/Endpoint/OrderEndpoint.php | 37 ++++++++++--------- modules/ppcp-api-client/src/Entity/Patch.php | 2 +- .../src/Factory/PatchCollectionFactory.php | 10 ++++- .../ApiClient/Endpoint/OrderEndpointTest.php | 4 +- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index c08b463bb..c0179d8e7 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; @@ -514,13 +515,22 @@ class OrderEndpoint { return $order_to_update; } + $this->patch( $order_to_update->id(), $patches ); + + $new_order = $this->order( $order_to_update->id() ); + return $new_order; + } + + /** + * Patches an order. + * + * @param string $order_id The PayPal order ID. + * @param PatchCollection $patches The patches. + * + * @throws RuntimeException If the request fails. + */ + public function patch( string $order_id, PatchCollection $patches ): void { $patches_array = $patches->to_array(); - if ( ! isset( $patches_array[0]['value']['shipping'] ) ) { - $shipping = isset( $order_to_update->purchase_units()[0] ) && null !== $order_to_update->purchase_units()[0]->shipping() ? $order_to_update->purchase_units()[0]->shipping() : null; - if ( $shipping ) { - $patches_array[0]['value']['shipping'] = $shipping->to_array(); - } - } /** * The filter can be used to modify the order patching request body data (the final prices, items). @@ -528,7 +538,7 @@ class OrderEndpoint { $patches_array = apply_filters( 'ppcp_patch_order_request_body_data', $patches_array ); $bearer = $this->bearer->bearer(); - $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_id; $args = array( 'method' => 'PATCH', 'headers' => array( @@ -544,11 +554,8 @@ class OrderEndpoint { $response = $this->request( $url, $args ); if ( is_wp_error( $response ) ) { - $error = new RuntimeException( - __( 'Could not retrieve order.', 'woocommerce-paypal-payments' ) - ); - $this->logger->log( - 'warning', + $error = new RuntimeException( 'Could not patch order.' ); + $this->logger->warning( $error->getMessage(), array( 'args' => $args, @@ -564,8 +571,7 @@ class OrderEndpoint { $json, $status_code ); - $this->logger->log( - 'warning', + $this->logger->warning( $error->getMessage(), array( 'args' => $args, @@ -574,9 +580,6 @@ class OrderEndpoint { ); throw $error; } - - $new_order = $this->order( $order_to_update->id() ); - return $new_order; } /** diff --git a/modules/ppcp-api-client/src/Entity/Patch.php b/modules/ppcp-api-client/src/Entity/Patch.php index 4350565cf..8814532ea 100644 --- a/modules/ppcp-api-client/src/Entity/Patch.php +++ b/modules/ppcp-api-client/src/Entity/Patch.php @@ -83,8 +83,8 @@ class Patch { public function to_array(): array { return array( 'op' => $this->op(), - 'value' => $this->value(), 'path' => $this->path(), + 'value' => $this->value(), ); } diff --git a/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php b/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php index 3be240393..d6ae8b7fc 100644 --- a/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php +++ b/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php @@ -71,7 +71,15 @@ class PatchCollectionFactory { ); $operation = $purchase_unit_from ? 'replace' : 'add'; $value = $purchase_unit_to->to_array(); - $patches[] = new Patch( + + if ( ! isset( $value['shipping'] ) ) { + $shipping = $purchase_unit_from && null !== $purchase_unit_from->shipping() ? $purchase_unit_from->shipping() : null; + if ( $shipping ) { + $value['shipping'] = $shipping->to_array(); + } + } + + $patches[] = new Patch( $operation, $path . "/@reference_id=='" . $purchase_unit_to->reference_id() . "'", $value diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php index fee542879..5041bf16a 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php @@ -647,7 +647,7 @@ class OrderEndpointTest extends TestCase $intent = 'CAPTURE'; $logger = Mockery::mock(LoggerInterface::class); - $logger->shouldReceive('log'); + $logger->shouldReceive('warning'); $logger->shouldReceive('debug'); $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class); @@ -742,7 +742,7 @@ class OrderEndpointTest extends TestCase $intent = 'CAPTURE'; $logger = Mockery::mock(LoggerInterface::class); - $logger->shouldReceive('log'); + $logger->shouldReceive('warning'); $logger->shouldReceive('debug'); $applicationContextRepository = Mockery::mock(ApplicationContextRepository::class); From efb86acb1f90043a1391efb8f588cbfc846736d6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 09:06:38 +0300 Subject: [PATCH 36/49] Handle shipping address/methods update --- .../resources/js/Helper/Address.js | 10 +- .../resources/js/checkout-block.js | 37 ++++- modules/ppcp-blocks/services.php | 10 ++ modules/ppcp-blocks/src/BlocksModule.php | 11 ++ .../src/Endpoint/UpdateShippingEndpoint.php | 133 ++++++++++++++++++ .../ppcp-blocks/src/PayPalPaymentMethod.php | 8 ++ 6 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js index 5f72d3add..d522d208e 100644 --- a/modules/ppcp-blocks/resources/js/Helper/Address.js +++ b/modules/ppcp-blocks/resources/js/Helper/Address.js @@ -19,7 +19,7 @@ export const splitFullName = (fullName) => { * @returns {Object} */ export const paypalAddressToWc = (address) => { - const map = { + let map = { country_code: 'country', address_line_1: 'address_1', address_line_2: 'address_2', @@ -27,6 +27,14 @@ export const paypalAddressToWc = (address) => { admin_area_2: 'city', postal_code: 'postcode', }; + if (address.city) { // address not from API, such as onShippingChange + map = { + country_code: 'country', + state: 'state', + city: 'city', + postal_code: 'postcode', + }; + } const result = {}; Object.entries(map).forEach(([paypalKey, wcKey]) => { if (address[paypalKey]) { diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 60cd4beb4..af5b2ab2b 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,6 +1,6 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; -import {paypalOrderToWcAddresses} from "./Helper/Address"; +import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address"; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' const config = wc.wcSettings.getSetting('ppcp-gateway_data'); @@ -132,11 +132,40 @@ const PayPalComponent = ({ let handleShippingChange = null; if (shippingData.needsShipping && !config.finalReviewEnabled) { - handleShippingChange = (data) => { + handleShippingChange = async (data, actions) => { console.log(data) - const shippingOptionId = data.selected_shipping_option.id; - shippingData.setSelectedRates(shippingOptionId) + try { + const shippingOptionId = data.selected_shipping_option?.id; + if (shippingOptionId) { + await shippingData.setSelectedRates(shippingOptionId); + } + + const address = paypalAddressToWc(data.shipping_address); + + await wp.data.dispatch('wc/store/cart').updateCustomerData({ + shipping_address: address, + }); + + const res = await fetch(config.ajax.update_shipping.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + nonce: config.ajax.update_shipping.nonce, + order_id: data.orderID, + }) + }); + + const json = await res.json(); + + if (!json.success) { + throw new Error(json.data.message); + } + } catch (e) { + console.error(e); + + actions.reject(); + } }; } diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php index 5c765b538..5b5a5159b 100644 --- a/modules/ppcp-blocks/services.php +++ b/modules/ppcp-blocks/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; +use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( @@ -44,4 +45,13 @@ return array( (bool) $settings->get( 'blocks_final_review_enabled' ) : true; }, + + 'blocks.endpoint.update-shipping' => static function ( ContainerInterface $container ): UpdateShippingEndpoint { + return new UpdateShippingEndpoint( + $container->get( 'button.request-data' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'api.factory.purchase-unit' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, ); diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 2c38f9b5a..a46c0ae16 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Assets\SmartButton; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; @@ -77,6 +78,16 @@ class BlocksModule implements ModuleInterface { }, ) ); + + add_action( + 'wc_ajax_' . UpdateShippingEndpoint::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'blocks.endpoint.update-shipping' ); + assert( $endpoint instanceof UpdateShippingEndpoint ); + + $endpoint->handle_request(); + } + ); } /** diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php new file mode 100644 index 000000000..f9322700d --- /dev/null +++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php @@ -0,0 +1,133 @@ +request_data = $request_data; + $this->order_endpoint = $order_endpoint; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->logger = $logger; + } + + /** + * Returns the nonce. + * + * @return string + */ + public static function nonce(): string { + return self::ENDPOINT; + } + + /** + * Handles the request. + * + * @return bool + */ + public function handle_request(): bool { + try { + $data = $this->request_data->read_request( $this->nonce() ); + + $order_id = $data['order_id']; + + $pu = $this->purchase_unit_factory->from_wc_cart( null, true ); + $pu_data = $pu->to_array(); + + if ( ! isset( $pu_data['shipping']['options'] ) ) { + wp_send_json_error( + array( + 'message' => 'No shipping methods.', + ) + ); + return false; + } + + // TODO: maybe should patch only if methods changed. + // But it seems a bit difficult to detect, + // e.g. ->order($id) may not have Shipping because we drop it when address or name are missing. + // Also may consider patching only amount and options instead of the whole PU, though not sure if it makes any difference. + $patches = new PatchCollection( + new Patch( + 'replace', + "/purchase_units/@reference_id=='{$pu->reference_id()}'", + $pu_data + ) + ); + + $this->order_endpoint->patch( $order_id, $patches ); + + wp_send_json_success(); + return true; + } catch ( Exception $error ) { + wp_send_json_error( + array( + 'message' => $error->getMessage(), + ) + ); + return false; + } + } +} diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index b573a124b..1da58914d 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType; +use WC_AJAX; +use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController; use WooCommerce\PayPalCommerce\Session\Cancellation\CancelView; @@ -174,6 +176,12 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ), 'fundingSource' => $this->session_handler->funding_source(), 'finalReviewEnabled' => $this->final_review_enabled, + 'ajax' => array( + 'update_shipping' => array( + 'endpoint' => WC_AJAX::get_endpoint( UpdateShippingEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( UpdateShippingEndpoint::nonce() ), + ), + ), 'scriptData' => $script_data, ); } From f285728ba94e6bc59c329365cf1beebe4fdc45a0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 09:07:37 +0300 Subject: [PATCH 37/49] Fix ddev:e2e-tests cp returns non-success code when file exists --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09ba7323a..1b236e61c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "ddev:watch-js": "ddev yarn watch:modules", "ddev:composer-update": "ddev composer update && ddev composer update --lock", "ddev:unit-tests": "ddev exec phpunit", - "ddev:e2e-tests": "cp -n .env.e2e.example .env.e2e && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist", + "ddev:e2e-tests": "(cp -n .env.e2e.example .env.e2e || true) && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist", "ddev:pw-install": "ddev yarn playwright install --with-deps", "ddev:pw-tests": "ddev yarn playwright test", "ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests && yarn run ddev:pw-tests", From 45986d39615824126a88a10cd00cc6832524014d Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 09:07:54 +0300 Subject: [PATCH 38/49] Fix phpcs --- .../src/Gateway/PayUponInvoice/PayUponInvoice.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 17a9b9df7..b9193910f 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -440,6 +440,7 @@ class PayUponInvoice { } if ( + // phpcs:ignore WordPress.Security.NonceVerification isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true' && ! $this->pui_helper->is_pay_for_order_ready_for_pui() ) { From 885d7cab19e93c3dcc4aa640ef2f0a984609e2df Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 09:29:37 +0300 Subject: [PATCH 39/49] Fix checkout address update Looks like update* only sends to server, and set* fills UI without immediately sending to the server --- modules/ppcp-blocks/resources/js/checkout-block.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index af5b2ab2b..c6013d255 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -133,8 +133,6 @@ const PayPalComponent = ({ let handleShippingChange = null; if (shippingData.needsShipping && !config.finalReviewEnabled) { handleShippingChange = async (data, actions) => { - console.log(data) - try { const shippingOptionId = data.selected_shipping_option?.id; if (shippingOptionId) { @@ -147,6 +145,8 @@ const PayPalComponent = ({ shipping_address: address, }); + await shippingData.setShippingAddress(address); + const res = await fetch(config.ajax.update_shipping.endpoint, { method: 'POST', credentials: 'same-origin', From 825ee0add668b331e41c8435c7973834788ecd08 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 09:50:32 +0300 Subject: [PATCH 40/49] Refactor tests --- .env.e2e.example | 5 +++++ tests/playwright/place-order.spec.js | 27 +++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.env.e2e.example b/.env.e2e.example index 632f3ced8..33fbf3091 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -2,6 +2,11 @@ PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress BASEURL="https://woocommerce-paypal-payments.ddev.site" +CHECKOUT_URL="/checkout" +CART_URL="/cart" +BLOCK_CHECKOUT_URL="/checkout-block" +BLOCK_CART_URL="/cart-block" + PRODUCT_URL="/product/prod" PRODUCT_ID=123 diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index 0eb3490cf..c5ce22154 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -8,6 +8,10 @@ const { CREDIT_CARD_CVV, PRODUCT_URL, PRODUCT_ID, + CHECKOUT_URL, + CART_URL, + BLOCK_CHECKOUT_URL, + BLOCK_CART_URL, } = process.env; async function fillCheckoutForm(page) { @@ -63,6 +67,13 @@ async function loginIntoPaypal(popup) { await popup.locator('#btnLogin').click(); } +async function completePaypalPayment(popup) { + await Promise.all([ + popup.waitForEvent('close', {timeout: 20000}), + popup.click('#payment-submit-btn'), + ]); +} + async function expectOrderReceivedPage(page) { const title = await page.locator('.entry-title'); await expect(title).toHaveText('Order received'); @@ -88,7 +99,7 @@ test('PayPal button place order from Product page', async ({page}) => { await loginIntoPaypal(popup); - await popup.locator('#payment-submit-btn').click(); + await completePaypalPayment(popup); await fillCheckoutForm(page); @@ -105,7 +116,7 @@ test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', asy await page.goto(PRODUCT_URL); await page.locator('.single_add_to_cart_button').click(); - await page.goto('/checkout/'); + await page.goto(CHECKOUT_URL); await fillCheckoutForm(page); await page.click("text=Credit Cards"); @@ -127,30 +138,30 @@ test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', asy await expectOrderReceivedPage(page); }); -test('PayPal express block', async ({page}) => { +test('PayPal express block checkout', async ({page}) => { - await page.goto('/cart?add-to-cart=' + PRODUCT_ID); + await page.goto('?add-to-cart=' + PRODUCT_ID); - await page.goto('/blocks-checkout') + await page.goto(BLOCK_CHECKOUT_URL) const popup = await openPaypalPopup(page); await loginIntoPaypal(popup); - await popup.locator('#payment-submit-btn').click(); + await completePaypalPayment(popup); await completeBlockContinuation(page); }); test('PayPal express block cart', async ({page}) => { - await page.goto('/cart-block?add-to-cart=' + PRODUCT_ID) + await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) const popup = await openPaypalPopup(page); await loginIntoPaypal(popup); - await popup.locator('#payment-submit-btn').click(); + await completePaypalPayment(popup); await completeBlockContinuation(page); }); From 694aafd3461c9e430cebce89b9142a86235d02bc Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 12:49:26 +0300 Subject: [PATCH 41/49] Use "Pay now" label only if no review --- modules/ppcp-blocks/extensions.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 758e8ecb1..60ac44cd9 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -60,9 +60,8 @@ If disabled, the Checkout page is skipped for Block Express payments. }, 'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array { - $contexts[] = 'checkout-block'; - if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) { + $contexts[] = 'checkout-block'; $contexts[] = 'cart-block'; } From 68758500fa9aacdc0beb8a950e8cda52d4a3ae8a Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 13:06:02 +0300 Subject: [PATCH 42/49] Fix pay-order page context --- modules/ppcp-button/src/Helper/ContextTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 7ce05ddbe..daddbb0a9 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -30,6 +30,10 @@ trait ContextTrait { return 'cart'; } + if ( is_checkout_pay_page() ) { + return 'pay-now'; + } + if ( has_block( 'woocommerce/checkout' ) ) { return 'checkout-block'; } @@ -38,10 +42,6 @@ trait ContextTrait { return 'checkout'; } - if ( is_checkout_pay_page() ) { - return 'pay-now'; - } - return 'mini-cart'; } From e2e28977140ea2d1088800a0c7c902c1fb77d9f9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Apr 2023 15:45:39 +0300 Subject: [PATCH 43/49] Allow only paylater, venmo apm in express block --- modules/ppcp-button/src/Assets/SmartButton.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 3ea10a1bb..6dfaf79f7 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1014,7 +1014,10 @@ class SmartButton implements SmartButtonInterface { } if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) { - $disable_funding[] = 'card'; + $disable_funding = array_diff( + array_keys( $this->all_funding_sources ), + array( 'venmo', 'paylater' ) + ); } if ( $this->is_free_trial_cart() ) { From 54e740e396cb7fbbb16876131ef35133c9df30a2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 2 May 2023 10:34:47 +0300 Subject: [PATCH 44/49] Set checkout page before block/classic tests --- .env.e2e.example | 2 + package.json | 2 +- tests/playwright/place-order.spec.js | 123 +++++++++++++++------------ tests/playwright/utils/server.js | 27 ++++++ wp-cli.yml | 1 + 5 files changed, 98 insertions(+), 57 deletions(-) create mode 100644 tests/playwright/utils/server.js create mode 100644 wp-cli.yml diff --git a/.env.e2e.example b/.env.e2e.example index 33fbf3091..4bdfc1399 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -3,8 +3,10 @@ PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress BASEURL="https://woocommerce-paypal-payments.ddev.site" CHECKOUT_URL="/checkout" +CHECKOUT_PAGE_ID=7 CART_URL="/cart" BLOCK_CHECKOUT_URL="/checkout-block" +BLOCK_CHECKOUT_PAGE_ID=22 BLOCK_CART_URL="/cart-block" PRODUCT_URL="/product/prod" diff --git a/package.json b/package.json index 165c6e80f..f7deecb41 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "prearchive": "rm -rf $npm_package_name.zip", "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" + "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index c5ce22154..271f00215 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -1,4 +1,5 @@ const {test, expect} = require('@playwright/test'); +const {serverExec} = require("./utils/server"); const { CUSTOMER_EMAIL, @@ -9,8 +10,10 @@ const { PRODUCT_URL, PRODUCT_ID, CHECKOUT_URL, + CHECKOUT_PAGE_ID, CART_URL, BLOCK_CHECKOUT_URL, + BLOCK_CHECKOUT_PAGE_ID, BLOCK_CART_URL, } = process.env; @@ -91,77 +94,85 @@ async function completeBlockContinuation(page) { await expectOrderReceivedPage(page); } -test('PayPal button place order from Product page', async ({page}) => { +test.describe('Classic checkout', () => { + test.beforeAll(async ({ browser }) => { + await serverExec('wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID); + }); - await page.goto(PRODUCT_URL); + test('PayPal button place order from Product page', async ({page}) => { + await page.goto(PRODUCT_URL); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup(page); - await loginIntoPaypal(popup); + await loginIntoPaypal(popup); - await completePaypalPayment(popup); + await completePaypalPayment(popup); - await fillCheckoutForm(page); + await fillCheckoutForm(page); - await Promise.all([ - page.waitForNavigation(), - page.locator('#place_order').click(), - ]); + await Promise.all([ + page.waitForNavigation(), + page.locator('#place_order').click(), + ]); - await expectOrderReceivedPage(page); + await expectOrderReceivedPage(page); + }); + + test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', async ({page}) => { + await page.goto(PRODUCT_URL); + await page.locator('.single_add_to_cart_button').click(); + + await page.goto(CHECKOUT_URL); + await fillCheckoutForm(page); + + await page.click("text=Credit Cards"); + + const creditCardNumber = page.frameLocator('#braintree-hosted-field-number').locator('#credit-card-number'); + await creditCardNumber.fill(CREDIT_CARD_NUMBER); + + const expirationDate = page.frameLocator('#braintree-hosted-field-expirationDate').locator('#expiration'); + await expirationDate.fill(CREDIT_CARD_EXPIRATION); + + const cvv = page.frameLocator('#braintree-hosted-field-cvv').locator('#cvv'); + await cvv.fill(CREDIT_CARD_CVV); + + await Promise.all([ + page.waitForNavigation(), + page.locator('.ppcp-dcc-order-button').click(), + ]); + + await expectOrderReceivedPage(page); + }); }); -test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', async ({page}) => { +test.describe('Block checkout', () => { + test.beforeAll(async ({browser}) => { + await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID); + }); - await page.goto(PRODUCT_URL); - await page.locator('.single_add_to_cart_button').click(); + test('PayPal express block checkout', async ({page}) => { + await page.goto('?add-to-cart=' + PRODUCT_ID); - await page.goto(CHECKOUT_URL); - await fillCheckoutForm(page); + await page.goto(BLOCK_CHECKOUT_URL) - await page.click("text=Credit Cards"); + const popup = await openPaypalPopup(page); - const creditCardNumber = page.frameLocator('#braintree-hosted-field-number').locator('#credit-card-number'); - await creditCardNumber.fill(CREDIT_CARD_NUMBER); + await loginIntoPaypal(popup); - const expirationDate = page.frameLocator('#braintree-hosted-field-expirationDate').locator('#expiration'); - await expirationDate.fill(CREDIT_CARD_EXPIRATION); + await completePaypalPayment(popup); - const cvv = page.frameLocator('#braintree-hosted-field-cvv').locator('#cvv'); - await cvv.fill(CREDIT_CARD_CVV); + await completeBlockContinuation(page); + }); - await Promise.all([ - page.waitForNavigation(), - page.locator('.ppcp-dcc-order-button').click(), - ]); + test('PayPal express block cart', async ({page}) => { + await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) - await expectOrderReceivedPage(page); -}); - -test('PayPal express block checkout', async ({page}) => { - - await page.goto('?add-to-cart=' + PRODUCT_ID); - - await page.goto(BLOCK_CHECKOUT_URL) - - const popup = await openPaypalPopup(page); - - await loginIntoPaypal(popup); - - await completePaypalPayment(popup); - - await completeBlockContinuation(page); -}); - -test('PayPal express block cart', async ({page}) => { - - await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) - - const popup = await openPaypalPopup(page); - - await loginIntoPaypal(popup); - - await completePaypalPayment(popup); - - await completeBlockContinuation(page); + const popup = await openPaypalPopup(page); + + await loginIntoPaypal(popup); + + await completePaypalPayment(popup); + + await completeBlockContinuation(page); + }); }); diff --git a/tests/playwright/utils/server.js b/tests/playwright/utils/server.js new file mode 100644 index 000000000..351f7dfb8 --- /dev/null +++ b/tests/playwright/utils/server.js @@ -0,0 +1,27 @@ +const { exec } = require('node:child_process'); + +/** + * Executes the command on the server (inside DDEV). Can be called inside and outside DDEV. + */ +export const serverExec = async (cmd) => { + const isDdev = process.env.IS_DDEV_PROJECT === 'true'; + if (!isDdev) { + cmd = 'ddev exec ' + cmd; + } + + console.log(cmd); + + return new Promise((resolve) => exec(cmd, (error, stdout, stderr) => { + if (stderr) { + console.error(stderr); + } + if (stdout) { + console.log(stdout); + } + if (error) { + throw error; + } else { + resolve(stdout); + } + })) +} diff --git a/wp-cli.yml b/wp-cli.yml new file mode 100644 index 000000000..d07f2be00 --- /dev/null +++ b/wp-cli.yml @@ -0,0 +1 @@ +path: .ddev/wordpress/ From 071dbe145e0ca3b1fde5faedf5fb0c4505032eae Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 2 May 2023 12:40:35 +0300 Subject: [PATCH 45/49] Add WP-CLI command for updating settings --- .psalm/wpcli.php | 651 ++++++++++++++++++ modules/ppcp-wc-gateway/services.php | 6 + .../src/Cli/SettingsCommand.php | 70 ++ .../ppcp-wc-gateway/src/WCGatewayModule.php | 7 + psalm.xml.dist | 1 + 5 files changed, 735 insertions(+) create mode 100644 .psalm/wpcli.php create mode 100644 modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php diff --git a/.psalm/wpcli.php b/.psalm/wpcli.php new file mode 100644 index 000000000..4b2ea7ed8 --- /dev/null +++ b/.psalm/wpcli.php @@ -0,0 +1,651 @@ + ['color' => 'yellow'], + * * %g => ['color' => 'green'], + * * %b => ['color' => 'blue'], + * * %r => ['color' => 'red'], + * * %p => ['color' => 'magenta'], + * * %m => ['color' => 'magenta'], + * * %c => ['color' => 'cyan'], + * * %w => ['color' => 'grey'], + * * %k => ['color' => 'black'], + * * %n => ['color' => 'reset'], + * * %Y => ['color' => 'yellow', 'style' => 'bright'], + * * %G => ['color' => 'green', 'style' => 'bright'], + * * %B => ['color' => 'blue', 'style' => 'bright'], + * * %R => ['color' => 'red', 'style' => 'bright'], + * * %P => ['color' => 'magenta', 'style' => 'bright'], + * * %M => ['color' => 'magenta', 'style' => 'bright'], + * * %C => ['color' => 'cyan', 'style' => 'bright'], + * * %W => ['color' => 'grey', 'style' => 'bright'], + * * %K => ['color' => 'black', 'style' => 'bright'], + * * %N => ['color' => 'reset', 'style' => 'bright'], + * * %3 => ['background' => 'yellow'], + * * %2 => ['background' => 'green'], + * * %4 => ['background' => 'blue'], + * * %1 => ['background' => 'red'], + * * %5 => ['background' => 'magenta'], + * * %6 => ['background' => 'cyan'], + * * %7 => ['background' => 'grey'], + * * %0 => ['background' => 'black'], + * * %F => ['style' => 'blink'], + * * %U => ['style' => 'underline'], + * * %8 => ['style' => 'inverse'], + * * %9 => ['style' => 'bright'], + * * %_ => ['style' => 'bright'] + * + * @access public + * @category Output + * + * @param string $string String to colorize for output, with color tokens. + * @return string Colorized string. + */ + public static function colorize( $string ) { + } + + /** + * Schedule a callback to be executed at a certain point. + * + * Hooks conceptually are very similar to WordPress actions. WP-CLI hooks + * are typically called before WordPress is loaded. + * + * WP-CLI hooks include: + * + * * `before_add_command:` - Before the command is added. + * * `after_add_command:` - After the command was added. + * * `before_invoke:` (1) - Just before a command is invoked. + * * `after_invoke:` (1) - Just after a command is invoked. + * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run. + * * `before_registering_contexts` (1) - Before the contexts are registered. + * * `before_wp_load` - Just before the WP load process begins. + * * `before_wp_config_load` - After wp-config.php has been located. + * * `after_wp_config_load` - After wp-config.php has been loaded into scope. + * * `after_wp_load` - Just after the WP load process has completed. + * * `before_run_command` (3) - Just before the command is executed. + * + * The parentheses behind the hook name denote the number of arguments + * being passed into the hook. For such hooks, the callback should return + * the first argument again, making them work like a WP filter. + * + * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`. + * + * If additional arguments are passed through the `WP_CLI::do_hook()` call, + * these will be passed on to the callback provided by `WP_CLI::add_hook()`. + * + * ``` + * # `wp network meta` confirms command is executing in multisite context. + * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array( + * 'before_invoke' => function ( $name ) { + * if ( !is_multisite() ) { + * WP_CLI::error( 'This is not a multisite installation.' ); + * } + * } + * ) ); + * ``` + * + * @access public + * @category Registration + * + * @param string $when Identifier for the hook. + * @param mixed $callback Callback to execute when hook is called. + * @return null + */ + public static function add_hook( $when, $callback ) { + } + + /** + * Execute callbacks registered to a given hook. + * + * See `WP_CLI::add_hook()` for details on WP-CLI's internal hook system. + * Commands can provide and call their own hooks. + * + * @access public + * @category Registration + * + * @param string $when Identifier for the hook. + * @param mixed ...$args Optional. Arguments that will be passed onto the + * callback provided by `WP_CLI::add_hook()`. + * @return null|mixed Returns the first optional argument if optional + * arguments were passed, otherwise returns null. + */ + public static function do_hook( $when, ...$args ) { + } + + /** + * Add a callback to a WordPress action or filter. + * + * `add_action()` without needing access to `add_action()`. If WordPress is + * already loaded though, you should use `add_action()` (and `add_filter()`) + * instead. + * + * @access public + * @category Registration + * + * @param string $tag Named WordPress action or filter. + * @param mixed $function_to_add Callable to execute when the action or filter is evaluated. + * @param integer $priority Priority to add the callback as. + * @param integer $accepted_args Number of arguments to pass to callback. + * @return true + */ + public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { + } + + /** + * Register a command to WP-CLI. + * + * WP-CLI supports using any callable class, function, or closure as a + * command. `WP_CLI::add_command()` is used for both internal and + * third-party command registration. + * + * Command arguments are parsed from PHPDoc by default, but also can be + * supplied as an optional third argument during registration. + * + * ``` + * # Register a custom 'foo' command to output a supplied positional param. + * # + * # $ wp foo bar --append=qux + * # Success: bar qux + * + * /** + * * My awesome closure command + * * + * * + * * : An awesome message to display + * * + * * --append= + * * : An awesome message to append to the original message. + * * + * * @when before_wp_load + * *\/ + * $foo = function( $args, $assoc_args ) { + * WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] ); + * }; + * WP_CLI::add_command( 'foo', $foo ); + * ``` + * + * @access public + * @category Registration + * + * @param string $name Name for the command (e.g. "post list" or "site empty"). + * @param callable $callable Command implementation as a class, function or closure. + * @param array $args { + * Optional. An associative array with additional registration parameters. + * + * @type callable $before_invoke Callback to execute before invoking the command. + * @type callable $after_invoke Callback to execute after invoking the command. + * @type string $shortdesc Short description (80 char or less) for the command. + * @type string $longdesc Description of arbitrary length for examples, etc. + * @type string $synopsis The synopsis for the command (string or array). + * @type string $when Execute callback on a named WP-CLI hook (e.g. before_wp_load). + * @type bool $is_deferred Whether the command addition had already been deferred. + * } + * @return bool True on success, false if deferred, hard error if registration failed. + */ + public static function add_command( $name, $callable, $args = [] ) { + } + + /** + * Get the list of outstanding deferred command additions. + * + * @return array Array of outstanding command additions. + */ + public static function get_deferred_additions() { + } + + /** + * Remove a command addition from the list of outstanding deferred additions. + */ + public static function remove_deferred_addition( $name ) { + } + + /** + * Display informational message without prefix, and ignore `--quiet`. + * + * Message is written to STDOUT. `WP_CLI::log()` is typically recommended; + * `WP_CLI::line()` is included for historical compat. + * + * @access public + * @category Output + * + * @param string $message Message to display to the end user. + * @return null + */ + public static function line( $message = '' ) { + } + + /** + * Display informational message without prefix. + * + * Message is written to STDOUT, or discarded when `--quiet` flag is supplied. + * + * ``` + * # `wp cli update` lets user know of each step in the update process. + * WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) ); + * ``` + * + * @access public + * @category Output + * + * @param string $message Message to write to STDOUT. + */ + public static function log( $message ) { + } + + /** + * Display success message prefixed with "Success: ". + * + * Success message is written to STDOUT. + * + * Typically recommended to inform user of successful script conclusion. + * + * ``` + * # wp rewrite flush expects 'rewrite_rules' option to be set after flush. + * flush_rewrite_rules( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) ); + * if ( ! get_option( 'rewrite_rules' ) ) { + * WP_CLI::warning( "Rewrite rules are empty." ); + * } else { + * WP_CLI::success( 'Rewrite rules flushed.' ); + * } + * ``` + * + * @access public + * @category Output + * + * @param string $message Message to write to STDOUT. + * @return null + */ + public static function success( $message ) { + } + + /** + * Display debug message prefixed with "Debug: " when `--debug` is used. + * + * Debug message is written to STDERR, and includes script execution time. + * + * Helpful for optionally showing greater detail when needed. Used throughout + * WP-CLI bootstrap process for easier debugging and profiling. + * + * ``` + * # Called in `WP_CLI\Runner::set_wp_root()`. + * private static function set_wp_root( $path ) { + * define( 'ABSPATH', Utils\trailingslashit( $path ) ); + * WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH ); + * $_SERVER['DOCUMENT_ROOT'] = realpath( $path ); + * } + * + * # Debug details only appear when `--debug` is used. + * # $ wp --debug + * # [...] + * # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s) + * ``` + * + * @access public + * @category Output + * + * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. + * @param string|bool $group Organize debug message to a specific group. + * Use `false` to not group the message. + * @return null + */ + public static function debug( $message, $group = false ) { + } + + /** + * Display warning message prefixed with "Warning: ". + * + * Warning message is written to STDERR. + * + * Use instead of `WP_CLI::debug()` when script execution should be permitted + * to continue. + * + * ``` + * # `wp plugin activate` skips activation when plugin is network active. + * $status = $this->get_status( $plugin->file ); + * // Network-active is the highest level of activation status + * if ( 'active-network' === $status ) { + * WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." ); + * continue; + * } + * ``` + * + * @access public + * @category Output + * + * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. + * @return null + */ + public static function warning( $message ) { + } + + /** + * Display error message prefixed with "Error: " and exit script. + * + * Error message is written to STDERR. Defaults to halting script execution + * with return code 1. + * + * Use `WP_CLI::warning()` instead when script execution should be permitted + * to continue. + * + * ``` + * # `wp cache flush` considers flush failure to be a fatal error. + * if ( false === wp_cache_flush() ) { + * WP_CLI::error( 'The object cache could not be flushed.' ); + * } + * ``` + * + * @access public + * @category Output + * + * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. + * @param boolean|integer $exit True defaults to exit(1). + * @return null + */ + public static function error( $message, $exit = true ) { + } + + /** + * Halt script execution with a specific return code. + * + * Permits script execution to be overloaded by `WP_CLI::runcommand()` + * + * @access public + * @category Output + * + * @param integer $return_code + * @return never + */ + public static function halt( $return_code ) { + } + + /** + * Display a multi-line error message in a red box. Doesn't exit script. + * + * Error message is written to STDERR. + * + * @access public + * @category Output + * + * @param array $message_lines Multi-line error message to be displayed. + */ + public static function error_multi_line( $message_lines ) { + } + + /** + * Ask for confirmation before running a destructive operation. + * + * If 'y' is provided to the question, the script execution continues. If + * 'n' or any other response is provided to the question, script exits. + * + * ``` + * # `wp db drop` asks for confirmation before dropping the database. + * + * WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args ); + * ``` + * + * @access public + * @category Input + * + * @param string $question Question to display before the prompt. + * @param array $assoc_args Skips prompt if 'yes' is provided. + */ + public static function confirm( $question, $assoc_args = [] ) { + } + + /** + * Read value from a positional argument or from STDIN. + * + * @param array $args The list of positional arguments. + * @param int $index At which position to check for the value. + * + * @return string + */ + public static function get_value_from_arg_or_stdin( $args, $index ) { + } + + /** + * Read a value, from various formats. + * + * @access public + * @category Input + * + * @param mixed $raw_value + * @param array $assoc_args + */ + public static function read_value( $raw_value, $assoc_args = [] ) { + } + + /** + * Display a value, in various formats + * + * @param mixed $value Value to display. + * @param array $assoc_args Arguments passed to the command, determining format. + */ + public static function print_value( $value, $assoc_args = [] ) { + } + + /** + * Convert a WP_Error or Exception into a string + * + * @param string|WP_Error|Exception|Throwable $errors + * @throws InvalidArgumentException + * + * @return string + */ + public static function error_to_string( $errors ) { + } + + /** + * Launch an arbitrary external process that takes over I/O. + * + * ``` + * # `wp core download` falls back to the `tar` binary when PharData isn't available + * if ( ! class_exists( 'PharData' ) ) { + * $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball"; + * WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) ); + * return; + * } + * ``` + * + * @access public + * @category Execution + * + * @param string $command External process to launch. + * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code. + * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results. + * @return int|ProcessRun The command exit status, or a ProcessRun object for full details. + */ + public static function launch( $command, $exit_on_error = true, $return_detailed = false ) { + } + + /** + * Run a WP-CLI command in a new process reusing the current runtime arguments. + * + * Use `WP_CLI::runcommand()` instead, which is easier to use and works better. + * + * Note: While this command does persist a limited set of runtime arguments, + * it *does not* persist environment variables. Practically speaking, WP-CLI + * packages won't be loaded when using WP_CLI::launch_self() because the + * launched process doesn't have access to the current process $HOME. + * + * @access public + * @category Execution + * + * @param string $command WP-CLI command to call. + * @param array $args Positional arguments to include when calling the command. + * @param array $assoc_args Associative arguments to include when calling the command. + * @param bool $exit_on_error Whether to exit if the command returns an elevated return code. + * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results. + * @param array $runtime_args Override one or more global args (path,url,user,allow-root) + * @return int|ProcessRun The command exit status, or a ProcessRun instance + */ + public static function launch_self( $command, $args = [], $assoc_args = [], $exit_on_error = true, $return_detailed = false, $runtime_args = [] ) { + } + + /** + * Get the path to the PHP binary used when executing WP-CLI. + * + * Environment values permit specific binaries to be indicated. + * + * Note: moved to Utils, left for BC. + * + * @access public + * @category System + * + * @return string + */ + public static function get_php_binary() { + } + + /** + * Confirm that a global configuration parameter does exist. + * + * @access public + * @category Input + * + * @param string $key Config parameter key to check. + * + * @return bool + */ + public static function has_config( $key ) { + } + + /** + * Get values of global configuration parameters. + * + * Provides access to `--path=`, `--url=`, and other values of + * the [global configuration parameters](https://wp-cli.org/config/). + * + * ``` + * WP_CLI::log( 'The --url= value is: ' . WP_CLI::get_config( 'url' ) ); + * ``` + * + * @access public + * @category Input + * + * @param string $key Get value for a specific global configuration parameter. + * @return mixed + */ + public static function get_config( $key = null ) { + } + + /** + * Run a WP-CLI command. + * + * Launches a new child process to run a specified WP-CLI command. + * Optionally: + * + * * Run the command in an existing process. + * * Prevent halting script execution on error. + * * Capture and return STDOUT, or full details about command execution. + * * Parse JSON output if the command rendered it. + * + * ``` + * $options = array( + * 'return' => true, // Return 'STDOUT'; use 'all' for full object. + * 'parse' => 'json', // Parse captured STDOUT to JSON array. + * 'launch' => false, // Reuse the current process. + * 'exit_error' => true, // Halt script execution on error. + * ); + * $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options ); + * ``` + * + * @access public + * @category Execution + * + * @param string $command WP-CLI command to run, including arguments. + * @param array $options Configuration options for command execution. + * @return mixed + */ + public static function runcommand( $command, $options = [] ) { + } + + /** + * Run a given command within the current process using the same global + * parameters. + * + * Use `WP_CLI::runcommand()` instead, which is easier to use and works better. + * + * To run a command using a new process with the same global parameters, + * use WP_CLI::launch_self(). To run a command using a new process with + * different global parameters, use WP_CLI::launch(). + * + * ``` + * ob_start(); + * WP_CLI::run_command( array( 'cli', 'cmd-dump' ) ); + * $ret = ob_get_clean(); + * ``` + * + * @access public + * @category Execution + * + * @param array $args Positional arguments including command name. + * @param array $assoc_args + */ + public static function run_command( $args, $assoc_args = [] ) { + } +} diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 6a95c98a2..96e18f25c 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -32,6 +32,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderAuthorizeAction; use WooCommerce\PayPalCommerce\WcGateway\Assets\FraudNetAssets; use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset; use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; +use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; @@ -1348,4 +1349,9 @@ return array( $container->get( 'wcgateway.is-fraudnet-enabled' ) ); }, + 'wcgateway.cli.settings.command' => function( ContainerInterface $container ) : SettingsCommand { + return new SettingsCommand( + $container->get( 'wcgateway.settings' ) + ); + }, ); diff --git a/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php b/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php new file mode 100644 index 000000000..f67c7c6f1 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php @@ -0,0 +1,70 @@ +settings = $settings; + } + + /** + * Updates the specified settings. + * + * ## OPTIONS + * + * + * : The setting key. + * + * + * : The setting value. + * + * ## EXAMPLES + * + * wp pcp settings update description "Pay via PayPal." + * wp pcp settings update vault_enabled true + * wp pcp settings update vault_enabled false + * + * @param array $args Positional args. + * @param array $assoc_args Option args. + */ + public function update( array $args, array $assoc_args ): void { + $key = (string) $args[0]; + $value = $args[1]; + + if ( 'true' === strtolower( $value ) ) { + $value = true; + } elseif ( 'false' === strtolower( $value ) ) { + $value = false; + } + + $this->settings->set( $key, $value ); + $this->settings->persist(); + + WP_CLI::success( "Updated '{$key}' to '{$value}'." ); + } +} diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index f19a244e6..d7167359c 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -380,6 +380,13 @@ class WCGatewayModule implements ModuleInterface { 10, 3 ); + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + \WP_CLI::add_command( + 'pcp settings', + $c->get( 'wcgateway.cli.settings.command' ) + ); + } } /** diff --git a/psalm.xml.dist b/psalm.xml.dist index a510b4dab..092361998 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -35,6 +35,7 @@ + From 0816495226859722bb0685418a56cb64e6a63b1b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 2 May 2023 12:41:47 +0300 Subject: [PATCH 46/49] Add playwright tests for block without final review --- tests/playwright/place-order.spec.js | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/playwright/place-order.spec.js b/tests/playwright/place-order.spec.js index 271f00215..a09ede7b5 100644 --- a/tests/playwright/place-order.spec.js +++ b/tests/playwright/place-order.spec.js @@ -70,6 +70,10 @@ async function loginIntoPaypal(popup) { await popup.locator('#btnLogin').click(); } +async function waitForPaypalShippingList(popup) { + await expect(popup.locator('#shippingMethodsDropdown')).toBeVisible({timeout: 15000}); +} + async function completePaypalPayment(popup) { await Promise.all([ popup.waitForEvent('close', {timeout: 20000}), @@ -148,6 +152,7 @@ test.describe('Classic checkout', () => { test.describe('Block checkout', () => { test.beforeAll(async ({browser}) => { await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID); + await serverExec('wp pcp settings update blocks_final_review_enabled true'); }); test('PayPal express block checkout', async ({page}) => { @@ -175,4 +180,40 @@ test.describe('Block checkout', () => { await completeBlockContinuation(page); }); + + test.describe('Without review', () => { + test.beforeAll(async ({browser}) => { + await serverExec('wp pcp settings update blocks_final_review_enabled false'); + }); + + test('PayPal express block checkout', async ({page}) => { + await page.goto('?add-to-cart=' + PRODUCT_ID); + + await page.goto(BLOCK_CHECKOUT_URL) + + const popup = await openPaypalPopup(page); + + await loginIntoPaypal(popup); + + await waitForPaypalShippingList(popup); + + await completePaypalPayment(popup); + + await expectOrderReceivedPage(page); + }); + + test('PayPal express block cart', async ({page}) => { + await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) + + const popup = await openPaypalPopup(page); + + await loginIntoPaypal(popup); + + await waitForPaypalShippingList(popup); + + await completePaypalPayment(popup); + + await expectOrderReceivedPage(page); + }); + }); }); From 703bedf810df9eb402bb16bd5df24a7f2e51db60 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 8 May 2023 15:04:25 +0300 Subject: [PATCH 47/49] Update final review checkbox --- modules/ppcp-blocks/extensions.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 60ac44cd9..31f90576d 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -39,15 +39,15 @@ return array( return $insert_after( $fields, - 'smart_button_enable_styling_per_location', + 'smart_button_locations', array( 'blocks_final_review_enabled' => array( - 'title' => __( 'Block Express payments Final Review', 'woocommerce-paypal-payments' ), + 'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'label' => __( - 'Require customers to confirm Block Express payments on the Checkout page. -If disabled, the Checkout page is skipped for Block Express payments. -

This can cause issues and poor UX if the server is not handling the requests fast enough.

', + 'Require customers to confirm express payments from the Block Cart and Block Express Checkout on the checkout page. +

If this setting is not enabled, payment confirmation on the checkout will be skipped. +Skipping the final confirmation on the checkout page may impact the buyer experience during the PayPal payment process.

', 'woocommerce-paypal-payments' ), 'default' => true, From b7ef769aa7826a1d9bf3783283376e47ae44e828 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 May 2023 11:26:45 +0300 Subject: [PATCH 48/49] Use cart/checkout styles for blocks --- modules/ppcp-button/src/Assets/SmartButton.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 6dfaf79f7..5b9f9fe63 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1150,6 +1150,9 @@ class SmartButton implements SmartButtonInterface { * @return string */ private function style_for_context( string $style, string $context ): string { + // Use the cart/checkout styles for blocks. + $context = str_replace( '-block', '', $context ); + $defaults = array( 'layout' => 'vertical', 'size' => 'responsive', From 84459c446124965f563df29225e25eab070c71c9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 May 2023 14:52:17 +0300 Subject: [PATCH 49/49] Do not send shipping options when not using paypal shipping --- .../ppcp-api-client/src/Endpoint/OrderEndpoint.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index c0179d8e7..cf22e8106 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -200,8 +200,15 @@ class OrderEndpoint { $data = array( 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent, 'purchase_units' => array_map( - static function ( PurchaseUnit $item ): array { - return $item->to_array(); + static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { + $data = $item->to_array(); + + if ( $shipping_preference !== ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) { + // Shipping options are not allowed to be sent when not getting the address from PayPal. + unset( $data['shipping']['options'] ); + } + + return $data; }, $items ),