From ce5214970c268b989e8fbcf4f92e75b9cea808ee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 16 Jul 2024 14:33:03 +0200 Subject: [PATCH 001/105] AXO: Add whitespace stripping on submission --- modules/ppcp-axo/resources/js/AxoManager.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-axo/resources/js/AxoManager.js b/modules/ppcp-axo/resources/js/AxoManager.js index b0837bcd6..34b8d24c5 100644 --- a/modules/ppcp-axo/resources/js/AxoManager.js +++ b/modules/ppcp-axo/resources/js/AxoManager.js @@ -709,6 +709,8 @@ class AxoManager { }` ); + this.emailInput.value = this.stripSpaces( this.emailInput.value ); + this.$( this.el.paymentContainer.selector + '-detail' ).html( '' ); this.$( this.el.paymentContainer.selector + '-form' ).html( '' ); @@ -1134,6 +1136,10 @@ class AxoManager { return emailPattern.test( value ); } + stripSpaces( str ) { + return str.replace( /\s+/g, '' ); + } + validateEmail( billingEmail ) { const billingEmailSelector = document.querySelector( billingEmail ); const value = document.querySelector( billingEmail + ' input' ).value; From 0d7b557fa8e9df05b5e72d974558914ab8ae4e09 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 17 Jul 2024 19:06:40 +0200 Subject: [PATCH 002/105] AXO: Add warning when shipping config is not compatible --- modules/ppcp-axo/extensions.php | 12 ++++++++++++ modules/ppcp-axo/services.php | 21 +++++++++++++++++++-- modules/ppcp-axo/src/AxoModule.php | 10 ++++++++++ modules/ppcp-axo/src/Helper/ApmApplies.php | 15 --------------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 8d8bf4378..5fd342741 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -126,6 +126,18 @@ return array( 'requirements' => array( 'dcc', 'axo' ), 'gateway' => array( 'dcc', 'axo' ), ), + 'axo_shipping_config_notice' => array( + 'heading' => '', + 'html' => $container->get( 'axo.shipping-config-notice' ), + 'type' => 'ppcp-html', + 'classes' => array( 'ppcp-field-indent' ), + 'class' => array(), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array( 'dcc', 'axo' ), + 'gateway' => array( 'dcc', 'axo' ), + ), 'axo_gateway_title' => array( 'title' => __( 'Gateway Title', 'woocommerce-paypal-payments' ), 'type' => 'text', diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 9d4e6d5fe..399a8b3e3 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -25,7 +25,7 @@ return array( $apm_applies = $container->get( 'axo.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency() && $apm_applies->for_settings(); + return $apm_applies->for_country_currency(); }, 'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { @@ -202,7 +202,6 @@ return array( return '

' . $notice_content . '

'; }, - 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); @@ -230,6 +229,24 @@ return array( return '

' . $notice_content . '

'; }, + 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { + $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); + + if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Shipping destination settings page. */ + __( + 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', + 'woocommerce-paypal-payments' + ), + esc_url( $shipping_settings_link ) + ); + } else { + return ''; + } + + return '

' . $notice_content . '

'; + }, 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint { return new FrontendLoggerEndpoint( $container->get( 'button.request-data' ), diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..6350b5adc 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -341,6 +341,7 @@ class AxoModule implements ModuleInterface { return ! is_user_logged_in() && CartCheckoutDetector::has_classic_checkout() + && $this->is_compatible_shipping_config() && $is_axo_enabled && $is_dcc_enabled && ! $this->is_excluded_endpoint(); @@ -396,4 +397,13 @@ class AxoModule implements ModuleInterface { // Exclude the Order Pay endpoint. return is_wc_endpoint_url( 'order-pay' ); } + + /** + * Condition to evaluate if the shipping configuration is compatible. + * + * @return bool + */ + private function is_compatible_shipping_config(): bool { + return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() ); + } } diff --git a/modules/ppcp-axo/src/Helper/ApmApplies.php b/modules/ppcp-axo/src/Helper/ApmApplies.php index c5fe645a9..c8a709f55 100644 --- a/modules/ppcp-axo/src/Helper/ApmApplies.php +++ b/modules/ppcp-axo/src/Helper/ApmApplies.php @@ -64,19 +64,4 @@ class ApmApplies { } return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); } - - /** - * Returns whether the settings are compatible with AXO. - * - * @return bool - */ - public function for_settings(): bool { - if ( get_option( 'woocommerce_ship_to_destination' ) === 'billing_only' ) { // Force shipping to the customer billing address. - return false; - } - return true; - } - - - } From e9a71a9237a08e36fcb63a20954ec53b47c8b8c9 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 18 Jul 2024 00:27:35 +0200 Subject: [PATCH 003/105] Refactor the checkout and shipping notice logic to minimize the markup and make the code more readable --- modules/ppcp-axo/extensions.php | 16 +--- modules/ppcp-axo/services.php | 73 ++++----------- .../src/Helper/SettingsNoticeGenerator.php | 92 +++++++++++++++++++ 3 files changed, 110 insertions(+), 71 deletions(-) create mode 100644 modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5fd342741..5c8f694aa 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -114,21 +114,9 @@ return array( ), 'classes' => array( 'ppcp-valign-label-middle', 'ppcp-align-label-center' ), ), - 'axo_checkout_config_notice' => array( + 'axo_main_notice' => array( 'heading' => '', - 'html' => $container->get( 'axo.checkout-config-notice' ), - 'type' => 'ppcp-html', - 'classes' => array( 'ppcp-field-indent' ), - 'class' => array(), - 'screens' => array( - State::STATE_ONBOARDED, - ), - 'requirements' => array( 'dcc', 'axo' ), - 'gateway' => array( 'dcc', 'axo' ), - ), - 'axo_shipping_config_notice' => array( - 'heading' => '', - 'html' => $container->get( 'axo.shipping-config-notice' ), + 'html' => $container->get( 'axo.shipping-config-notice' ) . $container->get( 'axo.checkout-config-notice' ), 'type' => 'ppcp-html', 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 399a8b3e3..e3a45bb05 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Axo; use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies; +use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -36,6 +37,10 @@ return array( ); }, + 'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator { + return new SettingsNoticeGenerator(); + }, + // If AXO is configured and onboarded. 'axo.available' => static function ( ContainerInterface $container ): bool { return true; @@ -160,48 +165,19 @@ return array( }, 'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { - $checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' ); - $block_checkout_docs_link = __( - 'https://woocommerce.com/document/cart-checkout-blocks-status/#reverting-to-the-cart-and-checkout-shortcodes', - 'woocommerce-paypal-payments' - ); + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); - if ( CartCheckoutDetector::has_elementor_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store currently uses the Elementor Checkout widget. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } elseif ( CartCheckoutDetector::has_block_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store currently uses the WooCommerce Checkout block. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } elseif ( ! CartCheckoutDetector::has_classic_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store does not seem to be properly configured or uses an incompatible third-party Checkout solution. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } else { - return ''; - } - - return '

' . $notice_content . '

'; + return $settings_notice_generator->generate_checkout_notice(); }, + + 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + return $settings_notice_generator->generate_shipping_notice(); + }, + 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); @@ -229,24 +205,7 @@ return array( return '

' . $notice_content . '

'; }, - 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { - $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); - if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Shipping destination settings page. */ - __( - 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', - 'woocommerce-paypal-payments' - ), - esc_url( $shipping_settings_link ) - ); - } else { - return ''; - } - - return '

' . $notice_content . '

'; - }, 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint { return new FrontendLoggerEndpoint( $container->get( 'button.request-data' ), diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php new file mode 100644 index 000000000..4f835d549 --- /dev/null +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -0,0 +1,92 @@ +Warning: The Checkout page of your store currently uses the Elementor Checkout widget. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } elseif ( CartCheckoutDetector::has_block_checkout() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ + __( + 'Warning: The Checkout page of your store currently uses the WooCommerce Checkout block. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } elseif ( ! CartCheckoutDetector::has_classic_checkout() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ + __( + 'Warning: The Checkout page of your store does not seem to be properly configured or uses an incompatible third-party Checkout solution. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } + + return $notice_content ? '

' . $notice_content . '

' : ''; + } + + /** + * Generates the shipping notice. + * + * @return string + */ + public function generate_shipping_notice(): string { + $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); + + $notice_content = ''; + + if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Shipping destination settings page. */ + __( + 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', + 'woocommerce-paypal-payments' + ), + esc_url( $shipping_settings_link ) + ); + } + + return $notice_content ? '

' . $notice_content . '

' : ''; + } +} From 78fda07e8becadf6be6694c3e9de775617954c6b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 18 Jul 2024 12:11:11 +0200 Subject: [PATCH 004/105] AXO: Ensure Fastlane scripts do not load when PayPal is disabled --- modules/ppcp-axo/src/AxoModule.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..d9ac8f8c2 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -77,9 +77,10 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); + $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; - if ( ! $is_dcc_enabled ) { + if ( ! $is_paypal_enabled || ! $is_dcc_enabled ) { return $methods; } @@ -144,11 +145,17 @@ class AxoModule implements ModuleInterface { function () use ( $c ) { $module = $this; + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); // Check if the module is applicable, correct country, currency, ... etc. - if ( ! $c->get( 'axo.eligible' ) + if ( ! $is_paypal_enabled + || ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) || $subscription_helper->cart_contains_subscription() ) { return; From c3d46d89af1c1cb54cf511fc465cb0dedf153464 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:15:02 +0400 Subject: [PATCH 005/105] Add shipping option validation --- .../src/Helper/WooCommerceOrderCreator.php | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..0c989e6a0 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -9,8 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button\Helper; +use Exception; use RuntimeException; use WC_Cart; +use WC_Data_Exception; use WC_Order; use WC_Order_Item_Product; use WC_Order_Item_Shipping; @@ -83,17 +85,22 @@ class WooCommerceOrderCreator { throw new RuntimeException( 'Problem creating WC order.' ); } - $payer = $order->payer(); - $shipping = $order->purchase_units()[0]->shipping(); + try { + $payer = $order->payer(); + $shipping = $order->purchase_units()[0]->shipping(); - $this->configure_payment_source( $wc_order ); - $this->configure_customer( $wc_order ); - $this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping ); - $this->configure_shipping( $wc_order, $payer, $shipping ); - $this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() ); + $this->configure_payment_source( $wc_order ); + $this->configure_customer( $wc_order ); + $this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping ); + $this->configure_shipping( $wc_order, $payer, $shipping, $wc_cart ); + $this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() ); - $wc_order->calculate_totals(); - $wc_order->save(); + $wc_order->calculate_totals(); + $wc_order->save(); + } catch ( Exception $exception ) { + $wc_order->delete( true ); + throw new RuntimeException( 'Failed to create WooCommerce order: ' . $exception->getMessage() ); + } return $wc_order; } @@ -172,9 +179,11 @@ class WooCommerceOrderCreator { * @param WC_Order $wc_order The WC order. * @param Payer|null $payer The payer. * @param Shipping|null $shipping The shipping. + * @param WC_Cart $wc_cart The Cart. * @return void + * @throws WC_Data_Exception|RuntimeException When failing to configure shipping. */ - protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping ): void { + protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping, WC_Cart $wc_cart ): void { $shipping_address = null; $billing_address = null; $shipping_options = null; @@ -212,6 +221,10 @@ class WooCommerceOrderCreator { $shipping_options = $shipping->options()[0] ?? ''; } + if ( $wc_cart->needs_shipping() && empty( $shipping_options ) ) { + throw new RuntimeException( 'No shipping method has been selected.' ); + } + if ( $shipping_address ) { $wc_order->set_shipping_address( $shipping_address ); } From 15250e5a83f32cdf79e5f704f91c356f2834deec Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:15:39 +0400 Subject: [PATCH 006/105] Redirect to continuation when error happens --- .../js/modules/OnApproveHandler/onApproveForContinue.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js index c60c163fd..54f4e123a 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js @@ -20,10 +20,7 @@ const onApprove = ( context, errorHandler ) => { } ) .then( ( data ) => { if ( ! data.success ) { - errorHandler.genericError(); - return actions.restart().catch( ( err ) => { - errorHandler.genericError(); - } ); + location.href = context.config.redirect; } const orderReceivedUrl = data.data?.order_received_url; From fe046ea48c1ee57051ff8720ad9342f774d8d859 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:19:33 +0400 Subject: [PATCH 007/105] Add the missing argument --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 0c989e6a0..ed1d4b82f 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -154,7 +154,7 @@ class WooCommerceOrderCreator { $item->set_total( $subscription_total ); $subscription->add_product( $product ); - $this->configure_shipping( $subscription, $payer, $shipping ); + $this->configure_shipping( $subscription, $payer, $shipping, $wc_cart ); $this->configure_payment_source( $subscription ); $this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() ); From 112b98875bbd0e87f8e20dab474d867c6bca1363 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 23 Jul 2024 15:38:07 +0200 Subject: [PATCH 008/105] Refactor save payment method js logic --- .../resources/js/Configuration.js | 190 +++++++++ .../resources/js/RenderCardFields.js | 55 +++ .../resources/js/add-payment-method.js | 368 ++++-------------- 3 files changed, 331 insertions(+), 282 deletions(-) create mode 100644 modules/ppcp-save-payment-methods/resources/js/Configuration.js create mode 100644 modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js new file mode 100644 index 000000000..366314de1 --- /dev/null +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -0,0 +1,190 @@ +import { + getCurrentPaymentMethod, + PaymentMethods, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; + +class Configuration { + constructor( ppcp_add_payment_method, errorHandler ) { + this.ppcp_add_payment_method = ppcp_add_payment_method; + this.errorHandler = errorHandler; + } + + buttonConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_payment_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + window.location.href = + this.ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + }; + } + + cardFieldsConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + payment_method: PaymentMethods.CARDS, + verification_method: + this.ppcp_add_payment_method + .verification_method, + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_payment_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + if ( + this.ppcp_add_payment_method + .is_subscription_change_payment_page + ) { + const subscriptionId = + this.ppcp_add_payment_method + .subscription_id_to_change_payment; + if ( subscriptionId && result.data ) { + const req = await fetch( + this.ppcp_add_payment_method.ajax + .subscription_change_payment_method + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .subscription_change_payment_method + .nonce, + subscription_id: subscriptionId, + payment_method: + getCurrentPaymentMethod(), + wc_payment_token_id: result.data, + } ), + } + ); + + const res = await req.json(); + if ( res.success === true ) { + window.location.href = `${ this.ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; + return; + } + } + + return; + } + + window.location.href = + this.ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + }; + } +} + +export default Configuration; diff --git a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js new file mode 100644 index 000000000..00c37ca6d --- /dev/null +++ b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js @@ -0,0 +1,55 @@ +import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; + +class RenderCardFields { + constructor( cardFields ) { + this.cardFields = cardFields; + } + + render() { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + this.cardFields + .NameField( { style: { input: styles } } ) + .render( nameField.parentNode ); + nameField.hidden = true; + } + + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + this.cardFields + .NumberField( { style: { input: styles } } ) + .render( numberField.parentNode ); + numberField.hidden = true; + } + + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + this.cardFields + .ExpiryField( { style: { input: styles } } ) + .render( expiryField.parentNode ); + expiryField.hidden = true; + } + + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + this.cardFields + .CVVField( { style: { input: styles } } ) + .render( cvvField.parentNode ); + cvvField.hidden = true; + } + } +} + +export default RenderCardFields; diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index 659d515f4..684de0eb2 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -4,303 +4,107 @@ import { PaymentMethods, } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { loadScript } from '@paypal/paypal-js'; +import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; + +import Configuration from './Configuration'; +import RenderCardFields from './RenderCardFields'; import { setVisible, setVisibleByClass, } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; -import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; -const errorHandler = new ErrorHandler( - ppcp_add_payment_method.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) -); - -const init = () => { - setVisibleByClass( - ORDER_BUTTON_SELECTOR, - getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, - 'ppcp-hidden' - ); - setVisible( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`, - getCurrentPaymentMethod() === PaymentMethods.PAYPAL - ); -}; - -document.addEventListener( 'DOMContentLoaded', () => { - jQuery( document.body ).on( - 'click init_add_payment_method', - '.payment_methods input.input-radio', - function () { - init(); - } - ); - - if ( ppcp_add_payment_method.is_subscription_change_payment_page ) { - const saveToAccount = document.querySelector( - '#wc-ppcp-credit-card-gateway-new-payment-method' - ); - if ( saveToAccount ) { - saveToAccount.checked = true; - saveToAccount.disabled = true; - } - } - - setTimeout( () => { - loadScript( { - clientId: ppcp_add_payment_method.client_id, - merchantId: ppcp_add_payment_method.merchant_id, - dataUserIdToken: ppcp_add_payment_method.id_token, - components: 'buttons,card-fields', - } ).then( ( paypal ) => { - errorHandler.clear(); - - const paypalButtonContainer = document.querySelector( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` - ); - if ( paypalButtonContainer ) { - paypal - .Buttons( { - createVaultSetupToken: async () => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - ppcp_add_payment_method.ajax - .create_payment_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { - window.location.href = - ppcp_add_payment_method.payment_methods_page; - return; - } - - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - } ) - .render( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` - ); +( function ( { ppcp_add_payment_method, jQuery } ) { + document.addEventListener( 'DOMContentLoaded', () => { + jQuery( document.body ).on( + 'click init_add_payment_method', + '.payment_methods input.input-radio', + function () { + setVisibleByClass( + ORDER_BUTTON_SELECTOR, + getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, + 'ppcp-hidden' + ); + setVisible( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`, + getCurrentPaymentMethod() === PaymentMethods.PAYPAL + ); } + ); - const cardField = paypal.CardFields( { - createVaultSetupToken: async () => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: PaymentMethods.CARDS, - verification_method: - ppcp_add_payment_method.verification_method, - } ), - } - ); + // TODO move to wc subscriptions module + if ( ppcp_add_payment_method.is_subscription_change_payment_page ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } + setTimeout( () => { + loadScript( { + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token, + components: 'buttons,card-fields', + } ).then( ( paypal ) => { + const errorHandler = new ErrorHandler( + ppcp_add_payment_method.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + errorHandler.clear(); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - payment_method: PaymentMethods.CARDS, - } ), - } - ); + const configuration = new Configuration( + ppcp_add_payment_method, + errorHandler + ); - const result = await response.json(); - if ( result.success === true ) { + const paypalButtonContainer = document.querySelector( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + + if ( paypalButtonContainer ) { + paypal + .Buttons( configuration.buttonConfiguration() ) + .render( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + } + + const cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + + if ( cardFields.isEligible() ) { + const renderCardFields = new RenderCardFields( cardFields ); + renderCardFields.render(); + } + + document + .querySelector( '#place_order' ) + ?.addEventListener( 'click', ( event ) => { + const cardPaymentToken = document.querySelector( + 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' + )?.value; if ( - ppcp_add_payment_method.is_subscription_change_payment_page + getCurrentPaymentMethod() !== + 'ppcp-credit-card-gateway' || + ( cardPaymentToken && cardPaymentToken !== 'new' ) ) { - const subscriptionId = - ppcp_add_payment_method.subscription_id_to_change_payment; - if ( subscriptionId && result.data ) { - const req = await fetch( - ppcp_add_payment_method.ajax - .subscription_change_payment_method - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .subscription_change_payment_method - .nonce, - subscription_id: subscriptionId, - payment_method: - getCurrentPaymentMethod(), - wc_payment_token_id: result.data, - } ), - } - ); - - const res = await req.json(); - if ( res.success === true ) { - window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; - return; - } - } - return; } - window.location.href = - ppcp_add_payment_method.payment_methods_page; - return; - } + event.preventDefault(); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - } ); - - if ( cardField.isEligible() ) { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - cardField - .NameField( { style: { input: styles } } ) - .render( nameField.parentNode ); - nameField.hidden = true; - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - cardField - .NumberField( { style: { input: styles } } ) - .render( numberField.parentNode ); - numberField.hidden = true; - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - cardField - .ExpiryField( { style: { input: styles } } ) - .render( expiryField.parentNode ); - expiryField.hidden = true; - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - cardField - .CVVField( { style: { input: styles } } ) - .render( cvvField.parentNode ); - cvvField.hidden = true; - } - } - - document - .querySelector( '#place_order' ) - ?.addEventListener( 'click', ( event ) => { - const cardPaymentToken = document.querySelector( - 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' - )?.value; - if ( - getCurrentPaymentMethod() !== - 'ppcp-credit-card-gateway' || - ( cardPaymentToken && cardPaymentToken !== 'new' ) - ) { - return; - } - - event.preventDefault(); - - cardField.submit().catch( ( error ) => { - console.error( error ); + cardFields.submit().catch( ( error ) => { + console.error( error ); + } ); } ); - } ); - } ); - }, 1000 ); + } ); + }, 1000 ); + } ); +} )( { + ppcp_add_payment_method: window.ppcp_add_payment_method, + jQuery: window.jQuery, } ); From efbd36025d24e69408779053e935b28f2da613e8 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 24 Jul 2024 16:00:55 +0200 Subject: [PATCH 009/105] Add card payment token for free trial subscriptions (WIP) --- modules/ppcp-button/resources/js/button.js | 21 +++-- .../Renderer/CardFieldsFreeTrialRenderer.js | 91 +++++++++++++++++++ .../resources/js/Configuration.js | 9 ++ .../src/Endpoint/CreatePaymentToken.php | 5 + .../src/Gateway/CreditCardGateway.php | 14 +++ 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 9f5485b38..d252c4392 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -7,6 +7,7 @@ import Renderer from './modules/Renderer/Renderer'; import ErrorHandler from './modules/ErrorHandler'; import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer'; import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer'; +import CardFieldsFreeTrialRenderer from './modules/Renderer/CardFieldsFreeTrialRenderer'; import MessageRenderer from './modules/Renderer/MessageRenderer'; import Spinner from './modules/Helper/Spinner'; import { @@ -215,12 +216,20 @@ const bootstrap = () => { spinner ); if ( typeof paypal.CardFields !== 'undefined' ) { - creditCardRenderer = new CardFieldsRenderer( - PayPalCommerceGateway, - errorHandler, - spinner, - onCardFieldsBeforeSubmit - ); + if ( PayPalCommerceGateway.is_free_trial_cart ) { + creditCardRenderer = new CardFieldsFreeTrialRenderer( + PayPalCommerceGateway, + errorHandler, + spinner + ); + } else { + creditCardRenderer = new CardFieldsRenderer( + PayPalCommerceGateway, + errorHandler, + spinner, + onCardFieldsBeforeSubmit + ); + } } const renderer = new Renderer( diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js new file mode 100644 index 000000000..c813e7fc7 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -0,0 +1,91 @@ +import { show } from '../Helper/Hiding'; +import ErrorHandler from '../ErrorHandler'; +import RenderCardFields from '../../../../../ppcp-save-payment-methods/resources/js/RenderCardFields'; +import Configuration from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; + +class CardFieldsFreeTrialRenderer { + constructor( defaultConfig, errorHandler, spinner ) { + this.defaultConfig = defaultConfig; + this.errorHandler = errorHandler; + this.spinner = spinner; + } + + render( wrapper, contextConfig ) { + if ( + ( this.defaultConfig.context !== 'checkout' && + this.defaultConfig.context !== 'pay-now' ) || + wrapper === null || + document.querySelector( wrapper ) === null + ) { + return; + } + + const buttonSelector = wrapper + ' button'; + + const gateWayBox = document.querySelector( + '.payment_box.payment_method_ppcp-credit-card-gateway' + ); + if ( ! gateWayBox ) { + return; + } + + const oldDisplayStyle = gateWayBox.style.display; + gateWayBox.style.display = 'block'; + + const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' ); + if ( hideDccGateway ) { + hideDccGateway.parentNode.removeChild( hideDccGateway ); + } + + const errorHandler = new ErrorHandler( + this.defaultConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + errorHandler.clear(); + + const configuration = new Configuration( + this.defaultConfig, + errorHandler + ); + + const cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + + if ( cardFields.isEligible() ) { + const renderCardFields = new RenderCardFields( cardFields ); + renderCardFields.render(); + } + + gateWayBox.style.display = oldDisplayStyle; + + show( buttonSelector ); + + if ( this.defaultConfig.cart_contains_subscription ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } + + document + .querySelector( buttonSelector ) + ?.addEventListener( 'click', ( event ) => { + event.preventDefault(); + this.spinner.block(); + this.errorHandler.clear(); + + cardFields.submit().catch( ( error ) => { + console.error( error ); + } ); + } ); + } + + disableFields() {} + enableFields() {} +} + +export default CardFieldsFreeTrialRenderer; diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index 366314de1..e779dbdbc 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -108,6 +108,8 @@ class Configuration { ); }, onApprove: async ( { vaultSetupToken } ) => { + const isFreeTrialCart = + this.ppcp_add_payment_method?.is_free_trial_cart ?? false; const response = await fetch( this.ppcp_add_payment_method.ajax.create_payment_token .endpoint, @@ -122,12 +124,19 @@ class Configuration { .create_payment_token.nonce, vault_setup_token: vaultSetupToken, payment_method: PaymentMethods.CARDS, + is_free_trial_cart: isFreeTrialCart, } ), } ); const result = await response.json(); if ( result.success === true ) { + const context = this.ppcp_add_payment_method?.context ?? ''; + if ( context === 'checkout' ) { + document.querySelector( '#place_order' ).click(); + return; + } + if ( this.ppcp_add_payment_method .is_subscription_change_payment_page diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index 4fea1f188..acd4f988f 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -115,6 +115,11 @@ class CreatePaymentToken implements EndpointInterface { if ( isset( $result->payment_source->card ) ) { $wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result ); + + $is_free_trial_cart = $data['is_free_trial_cart'] ?? ''; + if($is_free_trial_cart === '1') { + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', $wc_token_id ); + } } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index b2ce4cbc5..319510b68 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -426,12 +426,26 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, WC_Order::class ) ) { + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); + return $this->handle_payment_failure( null, new GatewayGenericException( new Exception( 'WC order was not found.' ) ) ); } + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); + if($card_payment_token_for_free_trial) { + $tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() ); + foreach ( $tokens as $token ) { + if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) { + $wc_order->payment_complete(); + return $this->handle_payment_success( $wc_order ); + } + } + } + // phpcs:ignore WordPress.Security.NonceVerification.Missing $card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) ); From 29e195e7f3e0af2871b00e0c915c8e47588bff11 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 24 Jul 2024 17:00:58 +0200 Subject: [PATCH 010/105] Ensure saved card payment use default payment flow for free trial subscriptions --- modules/ppcp-button/resources/js/button.js | 5 ++++- modules/ppcp-button/src/Assets/SmartButton.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index d252c4392..505fa60af 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -216,7 +216,10 @@ const bootstrap = () => { spinner ); if ( typeof paypal.CardFields !== 'undefined' ) { - if ( PayPalCommerceGateway.is_free_trial_cart ) { + if ( + PayPalCommerceGateway.is_free_trial_cart && + PayPalCommerceGateway.user?.has_wc_card_payment_tokens !== true + ) { creditCardRenderer = new CardFieldsFreeTrialRenderer( PayPalCommerceGateway, errorHandler, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 4b309818e..43acaa81a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets; use Exception; use Psr\Log\LoggerInterface; use WC_Order; +use WC_Payment_Tokens; use WC_Product; use WC_Product_Variation; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; @@ -1293,6 +1294,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, 'user' => array( 'is_logged' => is_user_logged_in(), + 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens(get_current_user_id()), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), @@ -2132,4 +2134,19 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages return $location; } } + + /** + * Whether the given user has WC card payment tokens. + * + * @param int $user_id + * @return bool + */ + private function user_has_wc_card_payment_tokens(int $user_id): bool { + $tokens = WC_Payment_Tokens::get_customer_tokens( $user_id, CreditCardGateway::ID ); + if($tokens) { + return true; + } + + return false; + } } From 8535601810a7605b9f1721d4ac03110903510cee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 25 Jul 2024 00:18:15 +0200 Subject: [PATCH 011/105] AXO: Add a notice informing about potentially conflicting plugins being active --- modules/ppcp-axo/extensions.php | 9 ++++- modules/ppcp-axo/services.php | 7 ++++ .../src/Helper/SettingsNoticeGenerator.php | 38 +++++++++++++++++++ .../ppcp-wc-gateway/resources/css/common.scss | 5 +++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5c8f694aa..3bee1d391 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -116,7 +116,14 @@ return array( ), 'axo_main_notice' => array( 'heading' => '', - 'html' => $container->get( 'axo.shipping-config-notice' ) . $container->get( 'axo.checkout-config-notice' ), + 'html' => implode( + '', + array( + $container->get( 'axo.shipping-config-notice' ), + $container->get( 'axo.checkout-config-notice' ), + $container->get( 'axo.incompatible-plugins-notice' ), + ) + ), 'type' => 'ppcp-html', 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index e3a45bb05..31e448bfd 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -178,6 +178,13 @@ return array( return $settings_notice_generator->generate_shipping_notice(); }, + 'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + return $settings_notice_generator->generate_incompatible_plugins_notice(); + }, + 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php index 4f835d549..f375e2943 100644 --- a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -89,4 +89,42 @@ class SettingsNoticeGenerator { return $notice_content ? '

' . $notice_content . '

' : ''; } + + /** + * Generates the incompatible plugins notice. + * + * @return string + */ + public function generate_incompatible_plugins_notice(): string { + $incompatible_plugins = array( + 'Elementor' => did_action( 'elementor/loaded' ), + 'CheckoutWC' => defined( 'CFW_NAME' ), + ); + + $active_plugins_list = array_filter( $incompatible_plugins ); + + if ( empty( $active_plugins_list ) ) { + return ''; + } + + $incompatible_plugin_items = array_map( + function ( $plugin ) { + return "
  • {$plugin}
  • "; + }, + array_keys( $active_plugins_list ) + ); + + $plugins_settings_link = esc_url( admin_url( 'plugins.php' ) ); + $notice_content = sprintf( + /* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */ + __( + 'Note: The accelerated guest buyer experience provided by Fastlane may not be fully compatible with some of the following active plugins:
      %2$s
    ', + 'woocommerce-paypal-payments' + ), + $plugins_settings_link, + implode( '', $incompatible_plugin_items ) + ); + + return '

    ' . $notice_content . '

    '; + } } diff --git a/modules/ppcp-wc-gateway/resources/css/common.scss b/modules/ppcp-wc-gateway/resources/css/common.scss index b694547c8..ac4bcab32 100644 --- a/modules/ppcp-wc-gateway/resources/css/common.scss +++ b/modules/ppcp-wc-gateway/resources/css/common.scss @@ -133,6 +133,11 @@ $background-ident-color: #fbfbfb; } } + .ppcp-notice-list { + list-style-type: disc; + padding-left: 20px; + } + th, td { border-top: 1px solid $border-color; } From 40c81683d926630290b4c595f54c111446ef24e2 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Thu, 25 Jul 2024 14:08:45 +0400 Subject: [PATCH 012/105] Add DHL DE plugin compatibility --- modules/ppcp-compat/services.php | 3 + modules/ppcp-order-tracking/services.php | 6 + .../Integration/DhlShipmentIntegration.php | 114 ++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index d27091162..6b48e0e3c 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -77,6 +77,9 @@ return array( 'compat.ywot.is_supported_plugin_version_active' => function (): bool { return function_exists( 'yith_ywot_init' ); }, + 'compat.dhl.is_supported_plugin_version_active' => function (): bool { + return function_exists( 'PR_DHL' ); + }, 'compat.shipstation.is_supported_plugin_version_active' => function (): bool { return function_exists( 'woocommerce_shipstation_init' ); }, diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php index 7eda7c68b..ad7aaa3ea 100644 --- a/modules/ppcp-order-tracking/services.php +++ b/modules/ppcp-order-tracking/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\OrderTracking; +use WooCommerce\PayPalCommerce\OrderTracking\Integration\DhlShipmentIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\GermanizedShipmentIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipmentTrackingIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipStationIntegration; @@ -118,6 +119,7 @@ return array( $is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' ); $is_wc_shipment_active = $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' ); $is_yith_ywot_active = $container->get( 'compat.ywot.is_supported_plugin_version_active' ); + $is_dhl_de_active = $container->get( 'compat.dhl.is_supported_plugin_version_active' ); $is_ship_station_active = $container->get( 'compat.shipstation.is_supported_plugin_version_active' ); $is_wc_shipping_tax_active = $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' ); @@ -135,6 +137,10 @@ return array( $integrations[] = new YithShipmentIntegration( $shipment_factory, $logger, $endpoint ); } + if ( $is_dhl_de_active ) { + $integrations[] = new DhlShipmentIntegration( $shipment_factory, $logger, $endpoint ); + } + if ( $is_ship_station_active ) { $integrations[] = new ShipStationIntegration( $shipment_factory, $logger, $endpoint ); } diff --git a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php new file mode 100644 index 000000000..1e8961fcb --- /dev/null +++ b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php @@ -0,0 +1,114 @@ +shipment_factory = $shipment_factory; + $this->logger = $logger; + $this->endpoint = $endpoint; + } + + /** + * {@inheritDoc} + */ + public function integrate(): void { + add_action( + 'pr_save_dhl_label_tracking', + function( int $order_id, array $tracking_details ) { + try { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return; + } + + $foo = $tracking_details; + + $paypal_order = ppcp_get_paypal_order( $wc_order ); + $capture_id = $this->get_paypal_order_transaction_id( $paypal_order ); + $tracking_number = $tracking_details['tracking_number']; + $carrier = $tracking_details['carrier']; + + if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) { + return; + } + + $ppcp_shipment = $this->shipment_factory->create_shipment( + $order_id, + $capture_id, + $tracking_number, + 'SHIPPED', + 'DE_DHL', + $carrier, + array() + ); + + $tracking_information = $this->endpoint->get_tracking_information( $order_id, $tracking_number ); + + $tracking_information + ? $this->endpoint->update_tracking_information( $ppcp_shipment, $order_id ) + : $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id ); + + } catch ( Exception $exception ) { + return; + } + }, + 600, + 2 + ); + } +} From c4232e3957c56f11a348f7839a83b47e817a29b2 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 25 Jul 2024 12:08:59 +0200 Subject: [PATCH 013/105] Add card payment for guest free trial (WIP) --- .../Renderer/CardFieldsFreeTrialRenderer.js | 9 ++- .../resources/js/Configuration.js | 59 +++++++++++++++++++ .../src/Gateway/CreditCardGateway.php | 16 +++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js index c813e7fc7..d2700aadb 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -48,9 +48,14 @@ class CardFieldsFreeTrialRenderer { errorHandler ); - const cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + let cardFields = paypal.CardFields( + configuration.addPaymentMethodConfiguration() ); + if ( this.defaultConfig.user.is_logged ) { + cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + } if ( cardFields.isEligible() ) { const renderCardFields = new RenderCardFields( cardFields ); diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index e779dbdbc..42ad02a77 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -194,6 +194,65 @@ class Configuration { }, }; } + + addPaymentMethodConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + payment_method: getCurrentPaymentMethod(), + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + console.error( result ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax + .create_payment_token_for_guest.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + document.querySelector( '#place_order' ).click(); + return; + } + + console.error( result ); + }, + onError: ( error ) => { + console.error( error ); + }, + }; + } } export default Configuration; diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 319510b68..393328660 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -434,6 +434,22 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { ); } + $guest_card_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null; + WC()->session->get( 'ppcp_guest_payment_for_free_trial', null ); + if($guest_card_payment_for_free_trial) { + $customer_id = $guest_card_payment_for_free_trial->customer->id ?? ''; + if ( $customer_id ) { + update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id ); + } + + if ( isset( $guest_card_payment_for_free_trial->payment_source->card ) ) { + $this->wc_payment_tokens->create_payment_token_card( $wc_order->get_customer_id(), $guest_card_payment_for_free_trial ); + + $wc_order->payment_complete(); + return $this->handle_payment_success( $wc_order ); + } + } + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); if($card_payment_token_for_free_trial) { From 2da1fa0323062b0f9f22a3b2e4be2d704a9e7c41 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Fri, 26 Jul 2024 16:20:08 +0400 Subject: [PATCH 014/105] Automatically update PayPal Package Tracking Status metabox --- .../resources/js/tracking-compat.js | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js index 589e747b5..a907ddc8f 100644 --- a/modules/ppcp-compat/resources/js/tracking-compat.js +++ b/modules/ppcp-compat/resources/js/tracking-compat.js @@ -15,6 +15,8 @@ document.addEventListener( 'DOMContentLoaded', () => { ); const wcShipmentTaxBuyLabelButtonSelector = '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button'; + const dhlGenerateLabelButton = + document.getElementById( 'dhl-label-button' ); const toggleLoaderVisibility = function () { const loader = document.querySelector( '.ppcp-tracking-loader' ); @@ -44,6 +46,20 @@ document.addEventListener( 'DOMContentLoaded', () => { } }; + const waitForButtonRemoval = function ( button ) { + if ( document.body.contains( button ) ) { + setTimeout( () => waitForButtonRemoval( button ), 100 ); + } else { + jQuery( orderTrackingContainerSelector ).load( + loadLocation, + '', + function () { + toggleLoaderVisibility(); + } + ); + } + }; + if ( gzdSyncEnabled && typeof gzdSaveButton !== 'undefined' && @@ -66,10 +82,19 @@ document.addEventListener( 'DOMContentLoaded', () => { } ); } + if ( + typeof dhlGenerateLabelButton !== 'undefined' && + dhlGenerateLabelButton != null + ) { + dhlGenerateLabelButton.addEventListener( 'click', function ( event ) { + toggleLoaderVisibility(); + waitForButtonRemoval( dhlGenerateLabelButton ); + } ); + } + if ( wcShippingTaxSyncEnabled && - typeof wcShippingTaxSyncEnabled !== 'undefined' && - wcShippingTaxSyncEnabled != null + typeof wcShippingTaxSyncEnabled !== 'undefined' ) { document.addEventListener( 'click', function ( event ) { const wcShipmentTaxBuyLabelButton = event.target.closest( From 9f18f2e899345fc0e2b5248409a7df34b16130bf Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 26 Jul 2024 15:39:25 +0200 Subject: [PATCH 015/105] Refactor: extract logic to corresponding modules --- .../ActionHandler/CheckoutActionHandler.js | 55 - .../ContextBootstrap/CheckoutBootstap.js | 3 +- .../Renderer/CardFieldsFreeTrialRenderer.js | 26 +- .../js/modules/Renderer/CardFieldsRenderer.js | 81 +- modules/ppcp-card-fields/package.json | 31 + .../resources/js}/CardFieldsHelper.js | 0 .../ppcp-card-fields/resources/js/Render.js | 47 + modules/ppcp-card-fields/webpack.config.js | 38 + modules/ppcp-card-fields/yarn.lock | 2194 +++++++++++++++++ .../resources/js/Configuration.js | 442 ++-- .../resources/js/RenderCardFields.js | 55 - .../resources/js/add-payment-method.js | 25 +- package.json | 3 + 13 files changed, 2549 insertions(+), 451 deletions(-) create mode 100644 modules/ppcp-card-fields/package.json rename modules/{ppcp-button/resources/js/modules/Helper => ppcp-card-fields/resources/js}/CardFieldsHelper.js (100%) create mode 100644 modules/ppcp-card-fields/resources/js/Render.js create mode 100644 modules/ppcp-card-fields/webpack.config.js create mode 100644 modules/ppcp-card-fields/yarn.lock delete mode 100644 modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index a93a24212..4c2fa9b2d 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -173,61 +173,6 @@ class CheckoutActionHandler { }, }; } - - addPaymentMethodConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.config.ajax.create_setup_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.config.ajax.create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - console.error( result ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.config.ajax.create_payment_token_for_guest.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.config.ajax - .create_payment_token_for_guest.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { - document.querySelector( '#place_order' ).click(); - return; - } - - console.error( result ); - }, - onError: ( error ) => { - console.error( error ); - }, - }; - } } export default CheckoutActionHandler; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 33d1ecfd3..916758eb4 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -7,6 +7,7 @@ import { PaymentMethods, } from '../Helper/CheckoutMethodState'; import BootstrapHelper from '../Helper/BootstrapHelper'; +import { addPaymentMethodConfiguration } from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; class CheckoutBootstap { constructor( gateway, renderer, spinner, errorHandler ) { @@ -160,7 +161,7 @@ class CheckoutBootstap { PayPalCommerceGateway.vault_v3_enabled ) { this.renderer.render( - actionHandler.addPaymentMethodConfiguration(), + addPaymentMethodConfiguration( PayPalCommerceGateway ), {}, actionHandler.configuration() ); diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js index d2700aadb..b97569762 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -1,7 +1,9 @@ import { show } from '../Helper/Hiding'; -import ErrorHandler from '../ErrorHandler'; -import RenderCardFields from '../../../../../ppcp-save-payment-methods/resources/js/RenderCardFields'; -import Configuration from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; +import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render'; +import { + addPaymentMethodConfiguration, + cardFieldsConfiguration, +} from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; class CardFieldsFreeTrialRenderer { constructor( defaultConfig, errorHandler, spinner ) { @@ -37,29 +39,19 @@ class CardFieldsFreeTrialRenderer { hideDccGateway.parentNode.removeChild( hideDccGateway ); } - const errorHandler = new ErrorHandler( - this.defaultConfig.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) - ); - errorHandler.clear(); - - const configuration = new Configuration( - this.defaultConfig, - errorHandler - ); + this.errorHandler.clear(); let cardFields = paypal.CardFields( - configuration.addPaymentMethodConfiguration() + addPaymentMethodConfiguration( this.defaultConfig ) ); if ( this.defaultConfig.user.is_logged ) { cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + cardFieldsConfiguration( this.defaultConfig, this.errorHandler ) ); } if ( cardFields.isEligible() ) { - const renderCardFields = new RenderCardFields( cardFields ); - renderCardFields.render(); + renderFields( cardFields ); } gateWayBox.style.display = oldDisplayStyle; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index b3e70011f..4a0ec09f2 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -1,5 +1,5 @@ import { show } from '../Helper/Hiding'; -import { cardFieldStyles } from '../Helper/CardFieldsHelper'; +import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render'; class CardFieldsRenderer { constructor( @@ -45,7 +45,7 @@ class CardFieldsRenderer { hideDccGateway.parentNode.removeChild( hideDccGateway ); } - const cardField = paypal.CardFields( { + const cardFields = paypal.CardFields( { createOrder: contextConfig.createOrder, onApprove( data ) { return contextConfig.onApprove( data ); @@ -56,79 +56,8 @@ class CardFieldsRenderer { }, } ); - if ( cardField.isEligible() ) { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( nameField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - nameField.getAttribute( 'placeholder' ); - } - cardField - .NameField( fieldOptions ) - .render( nameField.parentNode ); - nameField.remove(); - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( numberField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - numberField.getAttribute( 'placeholder' ); - } - cardField - .NumberField( fieldOptions ) - .render( numberField.parentNode ); - numberField.remove(); - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( expiryField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - expiryField.getAttribute( 'placeholder' ); - } - cardField - .ExpiryField( fieldOptions ) - .render( expiryField.parentNode ); - expiryField.remove(); - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( cvvField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - cvvField.getAttribute( 'placeholder' ); - } - cardField - .CVVField( fieldOptions ) - .render( cvvField.parentNode ); - cvvField.remove(); - } - + if ( cardFields.isEligible() ) { + renderFields( cardFields ); document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) ); } @@ -169,7 +98,7 @@ class CardFieldsRenderer { return; } - cardField.submit().catch( ( error ) => { + cardFields.submit().catch( ( error ) => { this.spinner.unblock(); console.error( error ); this.errorHandler.message( diff --git a/modules/ppcp-card-fields/package.json b/modules/ppcp-card-fields/package.json new file mode 100644 index 000000000..20ea98a53 --- /dev/null +++ b/modules/ppcp-card-fields/package.json @@ -0,0 +1,31 @@ +{ + "name": "ppcp-card-fields", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "browserslist": [ + "> 0.5%", + "Safari >= 8", + "Chrome >= 41", + "Firefox >= 43", + "Edge >= 14" + ], + "dependencies": { + "core-js": "^3.25.0" + }, + "devDependencies": { + "@babel/core": "^7.19", + "@babel/preset-env": "^7.19", + "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.76", + "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-button/resources/js/modules/Helper/CardFieldsHelper.js b/modules/ppcp-card-fields/resources/js/CardFieldsHelper.js similarity index 100% rename from modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js rename to modules/ppcp-card-fields/resources/js/CardFieldsHelper.js diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js new file mode 100644 index 000000000..8b46f17d7 --- /dev/null +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -0,0 +1,47 @@ +import { cardFieldStyles } from './CardFieldsHelper'; + +export function renderFields( cardFields ) { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + cardFields + .NameField( { style: { input: styles } } ) + .render( nameField.parentNode ); + nameField.hidden = true; + } + + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + cardFields + .NumberField( { style: { input: styles } } ) + .render( numberField.parentNode ); + numberField.hidden = true; + } + + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + cardFields + .ExpiryField( { style: { input: styles } } ) + .render( expiryField.parentNode ); + expiryField.hidden = true; + } + + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + cardFields + .CVVField( { style: { input: styles } } ) + .render( cvvField.parentNode ); + cvvField.hidden = true; + } +} diff --git a/modules/ppcp-card-fields/webpack.config.js b/modules/ppcp-card-fields/webpack.config.js new file mode 100644 index 000000000..a952f7de0 --- /dev/null +++ b/modules/ppcp-card-fields/webpack.config.js @@ -0,0 +1,38 @@ +const path = require( 'path' ); +const isProduction = process.env.NODE_ENV === 'production'; + +module.exports = { + devtool: isProduction ? 'source-map' : 'eval-source-map', + mode: isProduction ? 'production' : 'development', + target: 'web', + entry: { + render: path.resolve( './resources/js/Render.js' ), + helper: path.resolve( './resources/js/CardFieldsHelper.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-card-fields/yarn.lock b/modules/ppcp-card-fields/yarn.lock new file mode 100644 index 000000000..42b77f28b --- /dev/null +++ b/modules/ppcp-card-fields/yarn.lock @@ -0,0 +1,2194 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.8": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.9.tgz#53eee4e68f1c1d0282aa0eb05ddb02d033fc43a0" + integrity sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng== + +"@babel/core@^7.19": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" + integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.9" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-module-transforms" "^7.24.9" + "@babel/helpers" "^7.24.8" + "@babel/parser" "^7.24.8" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.9" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.8", "@babel/generator@^7.24.9": + version "7.24.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.10.tgz#a4ab681ec2a78bbb9ba22a3941195e28a81d8e76" + integrity sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg== + dependencies: + "@babel/types" "^7.24.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" + integrity sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" + integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== + dependencies: + "@babel/compat-data" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz#47f546408d13c200c0867f9d935184eaa0851b09" + integrity sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" + integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-member-expression-to-functions@^7.24.7", "@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" + integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== + dependencies: + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.24.9": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz#e13d26306b89eea569180868e652e7f514de9d29" + integrity sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== + dependencies: + "@babel/types" "^7.24.7" + +"@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.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + +"@babel/helper-remap-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" + integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-wrap-function" "^7.24.7" + +"@babel/helper-replace-supers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" + integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + +"@babel/helper-wrap-function@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" + integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== + dependencies: + "@babel/helper-function-name" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helpers@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.8.tgz#2820d64d5d6686cca8789dd15b074cd862795873" + integrity sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.8" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.7", "@babel/parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.8.tgz#58a4dbbcad7eb1d48930524a3fd93d93e9084c6f" + integrity sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" + integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": + version "7.24.7" + 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.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" + integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" + integrity sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.7" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" + integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@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.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz#2a0b406b5871a20a841240586b1300ce2088a778" + integrity sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" + integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@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-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-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" + integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-async-generator-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" + integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" + integrity sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + +"@babel/plugin-transform-block-scoped-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" + integrity sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-block-scoping@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" + integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-class-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" + integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-class-static-block@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" + integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz#ad23301fe5bc153ca4cf7fb572a9bc8b0b711cf7" + integrity sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" + integrity sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/template" "^7.24.7" + +"@babel/plugin-transform-destructuring@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz#c828e814dbe42a2718a838c2a2e16a408e055550" + integrity sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-dotall-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" + integrity sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-duplicate-keys@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" + integrity sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-dynamic-import@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" + integrity sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" + integrity sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-export-namespace-from@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" + integrity sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" + integrity sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" + integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-json-strings@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" + integrity sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" + integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" + integrity sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" + integrity sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-amd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" + integrity sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-commonjs@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" + integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== + dependencies: + "@babel/helper-module-transforms" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-simple-access" "^7.24.7" + +"@babel/plugin-transform-modules-systemjs@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" + integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== + dependencies: + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/plugin-transform-modules-umd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" + integrity sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" + integrity sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-new-target@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" + integrity sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" + integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" + integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" + integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.7" + +"@babel/plugin-transform-object-super@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" + integrity sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + +"@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" + integrity sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" + integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" + integrity sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-private-methods@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e" + integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" + integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" + integrity sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-regenerator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" + integrity sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" + integrity sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" + integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" + integrity sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" + integrity sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-template-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" + integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-typeof-symbol@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz#383dab37fb073f5bfe6e60c654caac309f92ba1c" + integrity sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-unicode-escapes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" + integrity sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-property-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" + integrity sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" + integrity sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-sets-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9" + integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/preset-env@^7.19": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.8.tgz#e0db94d7f17d6f0e2564e8d29190bc8cdacec2d1" + integrity sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ== + dependencies: + "@babel/compat-data" "^7.24.8" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@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.24.7" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@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-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.24.7" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoped-functions" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.24.7" + "@babel/plugin-transform-class-properties" "^7.24.7" + "@babel/plugin-transform-class-static-block" "^7.24.7" + "@babel/plugin-transform-classes" "^7.24.8" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" + "@babel/plugin-transform-dotall-regex" "^7.24.7" + "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-dynamic-import" "^7.24.7" + "@babel/plugin-transform-exponentiation-operator" "^7.24.7" + "@babel/plugin-transform-export-namespace-from" "^7.24.7" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.24.7" + "@babel/plugin-transform-json-strings" "^7.24.7" + "@babel/plugin-transform-literals" "^7.24.7" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-member-expression-literals" "^7.24.7" + "@babel/plugin-transform-modules-amd" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-modules-systemjs" "^7.24.7" + "@babel/plugin-transform-modules-umd" "^7.24.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-new-target" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-object-super" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-property-literals" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-reserved-words" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-template-literals" "^7.24.7" + "@babel/plugin-transform-typeof-symbol" "^7.24.8" + "@babel/plugin-transform-unicode-escapes" "^7.24.7" + "@babel/plugin-transform-unicode-property-regex" "^7.24.7" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.7" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.37.1" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.8.4": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.8.tgz#6c14ed5232b7549df3371d820fbd9abfcd7dfab7" + integrity sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.8" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.8" + "@babel/types" "^7.24.8" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.4.4": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.9.tgz#228ce953d7b0d16646e755acf204f4cf3d08cc73" + integrity sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + 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.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.0.tgz#51d4fe4d0316da9e9f2c80884f2c20ed5fb022ff" + integrity sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "20.14.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" + integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== + dependencies: + undici-types "~5.26.4" + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.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== + +"@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-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +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.4.10: + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + +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.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.21.10, browserslist@^4.23.0, browserslist@^4.23.1: + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== + dependencies: + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" + node-releases "^2.0.14" + update-browserslist-db "^1.1.0" + +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.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== + +chalk@^2.4.2: + 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.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + 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.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +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.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +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@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.36.1, core-js-compat@^3.37.1: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" + integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== + dependencies: + browserslist "^4.23.0" + +core-js@^3.25.0: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9" + integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw== + +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, debug@^4.3.1: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.820: + version "1.5.1" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz#24640bd4dcfaccb6d82bb4c3f4c7311503241581" + integrity sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w== + +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.17.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.13.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" + integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +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.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + 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" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +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.11, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +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== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +immutable@^4.0.0: + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + 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.13.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + +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== + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +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.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +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== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +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.14: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +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, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +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" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +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.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + 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.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +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.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.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.77.8" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd" + integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ== + 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.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + 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.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +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.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.26.0: + version "5.31.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" + integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + 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" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +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.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +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.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== + 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.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +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.76: + version "5.93.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" + integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + 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.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + 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.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index 42ad02a77..a602d4d32 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -3,256 +3,228 @@ import { PaymentMethods, } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; -class Configuration { - constructor( ppcp_add_payment_method, errorHandler ) { - this.ppcp_add_payment_method = ppcp_add_payment_method; - this.errorHandler = errorHandler; - } - - buttonConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; +export function buttonConfiguration( ppcp_add_payment_method, errorHandler ) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - const result = await response.json(); - if ( result.success === true ) { - window.location.href = - this.ppcp_add_payment_method.payment_methods_page; - return; + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_payment_token + .nonce, + vault_setup_token: vaultSetupToken, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - }; - } + const result = await response.json(); + if ( result.success === true ) { + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } - cardFieldsConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: PaymentMethods.CARDS, - verification_method: - this.ppcp_add_payment_method - .verification_method, - } ), - } - ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + }; +} - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; +export function cardFieldsConfiguration( + ppcp_add_payment_method, + errorHandler +) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + payment_method: PaymentMethods.CARDS, + verification_method: + ppcp_add_payment_method.verification_method, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const isFreeTrialCart = - this.ppcp_add_payment_method?.is_free_trial_cart ?? false; - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - payment_method: PaymentMethods.CARDS, - is_free_trial_cart: isFreeTrialCart, - } ), - } - ); + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - const result = await response.json(); - if ( result.success === true ) { - const context = this.ppcp_add_payment_method?.context ?? ''; - if ( context === 'checkout' ) { - document.querySelector( '#place_order' ).click(); - return; - } - - if ( - this.ppcp_add_payment_method - .is_subscription_change_payment_page - ) { - const subscriptionId = - this.ppcp_add_payment_method - .subscription_id_to_change_payment; - if ( subscriptionId && result.data ) { - const req = await fetch( - this.ppcp_add_payment_method.ajax - .subscription_change_payment_method - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .subscription_change_payment_method - .nonce, - subscription_id: subscriptionId, - payment_method: - getCurrentPaymentMethod(), - wc_payment_token_id: result.data, - } ), - } - ); - - const res = await req.json(); - if ( res.success === true ) { - window.location.href = `${ this.ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; - return; - } - } - - return; - } - - window.location.href = - this.ppcp_add_payment_method.payment_methods_page; - return; + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const isFreeTrialCart = + ppcp_add_payment_method?.is_free_trial_cart ?? false; + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_payment_token + .nonce, + vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS, + is_free_trial_cart: isFreeTrialCart, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - }; - } - - addPaymentMethodConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: getCurrentPaymentMethod(), - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - console.error( result ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.ppcp_add_payment_method.ajax - .create_payment_token_for_guest.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token_for_guest.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { + const result = await response.json(); + if ( result.success === true ) { + const context = ppcp_add_payment_method?.context ?? ''; + if ( context === 'checkout' ) { document.querySelector( '#place_order' ).click(); return; } - console.error( result ); - }, - onError: ( error ) => { - console.error( error ); - }, - }; - } + if ( + ppcp_add_payment_method.is_subscription_change_payment_page + ) { + const subscriptionId = + ppcp_add_payment_method.subscription_id_to_change_payment; + if ( subscriptionId && result.data ) { + const req = await fetch( + ppcp_add_payment_method.ajax + .subscription_change_payment_method.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .subscription_change_payment_method + .nonce, + subscription_id: subscriptionId, + payment_method: getCurrentPaymentMethod(), + wc_payment_token_id: result.data, + } ), + } + ); + + const res = await req.json(); + if ( res.success === true ) { + window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; + return; + } + } + + return; + } + + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + }; } -export default Configuration; +export function addPaymentMethodConfiguration( ppcp_add_payment_method ) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + payment_method: getCurrentPaymentMethod(), + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + console.error( result ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token_for_guest + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + document.querySelector( '#place_order' ).click(); + return; + } + + console.error( result ); + }, + onError: ( error ) => { + console.error( error ); + }, + }; +} diff --git a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js deleted file mode 100644 index 00c37ca6d..000000000 --- a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js +++ /dev/null @@ -1,55 +0,0 @@ -import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; - -class RenderCardFields { - constructor( cardFields ) { - this.cardFields = cardFields; - } - - render() { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - this.cardFields - .NameField( { style: { input: styles } } ) - .render( nameField.parentNode ); - nameField.hidden = true; - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - this.cardFields - .NumberField( { style: { input: styles } } ) - .render( numberField.parentNode ); - numberField.hidden = true; - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - this.cardFields - .ExpiryField( { style: { input: styles } } ) - .render( expiryField.parentNode ); - expiryField.hidden = true; - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - this.cardFields - .CVVField( { style: { input: styles } } ) - .render( cvvField.parentNode ); - cvvField.hidden = true; - } - } -} - -export default RenderCardFields; diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index 684de0eb2..8e75e3ad7 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -5,9 +5,8 @@ import { } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { loadScript } from '@paypal/paypal-js'; import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; - -import Configuration from './Configuration'; -import RenderCardFields from './RenderCardFields'; +import { buttonConfiguration, cardFieldsConfiguration } from './Configuration'; +import { renderFields } from '../../../ppcp-card-fields/resources/js/Render'; import { setVisible, setVisibleByClass, @@ -55,30 +54,32 @@ import { ); errorHandler.clear(); - const configuration = new Configuration( - ppcp_add_payment_method, - errorHandler - ); - const paypalButtonContainer = document.querySelector( `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` ); if ( paypalButtonContainer ) { paypal - .Buttons( configuration.buttonConfiguration() ) + .Buttons( + buttonConfiguration( + ppcp_add_payment_method, + errorHandler + ) + ) .render( `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` ); } const cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + cardFieldsConfiguration( + ppcp_add_payment_method, + errorHandler + ) ); if ( cardFields.isEligible() ) { - const renderCardFields = new RenderCardFields( cardFields ); - renderCardFields.render(); + renderFields( cardFields ); } document diff --git a/package.json b/package.json index 61c625ca9..d9f9c6b51 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install", "install:modules:ppcp-axo": "cd modules/ppcp-axo && yarn install", "install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install", + "install:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn install", "install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install", "install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install", "build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build", @@ -37,6 +38,7 @@ "build:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run build", "build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build", "build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build", + "build:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run build", "build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build", "build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build", "build:modules": "run-p build:modules:*", @@ -54,6 +56,7 @@ "watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch", "watch:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run watch", "watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch", + "watch:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run watch", "watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch", "watch:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run watch", "watch:modules": "run-p watch:modules:*", From 6fbdb9417b922138218414a304c2785e656a3be3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 26 Jul 2024 18:50:35 +0200 Subject: [PATCH 016/105] Fix card add payment method test --- tests/Playwright/playwright.config.js | 134 ++++---- tests/Playwright/tests/place-order.spec.js | 301 ++++++++++-------- .../tests/save-payment-methods.spec.js | 138 ++++---- 3 files changed, 311 insertions(+), 262 deletions(-) diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js index dbe3173da..7273db2a0 100644 --- a/tests/Playwright/playwright.config.js +++ b/tests/Playwright/playwright.config.js @@ -1,78 +1,78 @@ // @ts-check -const { defineConfig, devices } = require('@playwright/test'); +const { defineConfig, devices } = require( '@playwright/test' ); -require('dotenv').config({ path: '.env' }); +require( 'dotenv' ).config( { path: '.env' } ); /** * @see https://playwright.dev/docs/test-configuration */ -module.exports = defineConfig({ - timeout: 60000, - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [ - [process.env.CI ? 'github' : 'list'], - ['html', {open: 'never'}], - ], - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.BASEURL, - ignoreHTTPSErrors: true, - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, +module.exports = defineConfig( { + timeout: 60000, + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !! process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + [ process.env.CI ? 'github' : 'list' ], + [ 'html', { open: 'never' } ], + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.BASEURL, + ignoreHTTPSErrors: true, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices[ 'Desktop Chrome' ] }, + }, - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, -}); + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +} ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js index 4a9f7523f..086d8e9c7 100644 --- a/tests/Playwright/tests/place-order.spec.js +++ b/tests/Playwright/tests/place-order.spec.js @@ -1,207 +1,242 @@ -const {test, expect} = require('@playwright/test'); -const {serverExec} = require("./utils/server"); -const {fillCheckoutForm, expectOrderReceivedPage, acceptTerms} = require("./utils/checkout"); -const {openPaypalPopup, loginIntoPaypal, waitForPaypalShippingList, completePaypalPayment} = require("./utils/paypal-popup"); +const { test, expect } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, + acceptTerms, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + loginIntoPaypal, + waitForPaypalShippingList, + completePaypalPayment, +} = require( './utils/paypal-popup' ); const { - CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, - CREDIT_CARD_CVV, - PRODUCT_URL, - PRODUCT_ID, - CHECKOUT_URL, - CHECKOUT_PAGE_ID, - CART_URL, - BLOCK_CHECKOUT_URL, - BLOCK_CHECKOUT_PAGE_ID, - BLOCK_CART_URL, - APM_ID, + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXPIRATION, + CREDIT_CARD_CVV, + PRODUCT_URL, + PRODUCT_ID, + CHECKOUT_URL, + CHECKOUT_PAGE_ID, + CART_URL, + BLOCK_CHECKOUT_URL, + BLOCK_CHECKOUT_PAGE_ID, + BLOCK_CART_URL, + APM_ID, } = process.env; -async function completeBlockContinuation(page) { - await expect(page.locator('#radio-control-wc-payment-method-options-ppcp-gateway')).toBeChecked(); +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 expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); - await Promise.all( - page.waitForNavigation(), - page.locator('.wc-block-components-checkout-place-order-button').click(), - ); + await Promise.all( + page.waitForNavigation(), + page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click() + ); } -async function expectContinuation(page) { - await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked(); +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); - await expect(page.locator('.component-frame')).toHaveCount(0); + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); } -async function completeContinuation(page) { - await expectContinuation(page); +async function completeContinuation( page ) { + await expectContinuation( page ); - await Promise.all([ - page.waitForNavigation(), - page.locator('#place_order').click(), - ]); + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); } -test.describe('Classic checkout', () => { - test.beforeAll(async ({ browser }) => { - await serverExec('wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID); - }); +test.describe( 'Classic checkout', () => { + test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID + ); + } ); - test('PayPal button place order from Product page', async ({page}) => { - 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 completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('Advanced Credit and Debit Card place order from Checkout page', async ({page}) => { - await page.goto(PRODUCT_URL); - await page.locator('.single_add_to_cart_button').click(); + test( 'Advanced Credit and Debit Card 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.goto( CHECKOUT_URL ); + await fillCheckoutForm( page ); - await page.click("text=Credit Cards"); + 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 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 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); + 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 Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal APM button place order', async ({page}) => { - await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID); + test( 'PayPal APM button place order', async ( { page } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - await page.goto(CHECKOUT_URL); + await page.goto( CHECKOUT_URL ); - await fillCheckoutForm(page); + await fillCheckoutForm( page ); - const popup = await openPaypalPopup(page, {fundingSource: APM_ID}); + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - await popup.getByText('Continue', { exact: true }).click(); - await completePaypalPayment(popup, {selector: '[name="Successful"]'}); + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal APM button place order when redirect fails', async ({page}) => { - await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID); + test( 'PayPal APM button place order when redirect fails', async ( { + page, + } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - await page.goto(CHECKOUT_URL); + await page.goto( CHECKOUT_URL ); - await fillCheckoutForm(page); + await fillCheckoutForm( page ); - await page.evaluate('PayPalCommerceGateway.ajax.approve_order = null'); + await page.evaluate( + 'PayPalCommerceGateway.ajax.approve_order = null' + ); - const popup = await openPaypalPopup(page, {fundingSource: APM_ID}); + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - await popup.getByText('Continue', { exact: true }).click(); - await completePaypalPayment(popup, {selector: '[name="Successful"]'}); + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); - await expect(page.locator('.woocommerce-error')).toBeVisible(); + await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); - await page.reload(); - await expectContinuation(page); + await page.reload(); + await expectContinuation( page ); - await acceptTerms(page); + await acceptTerms( page ); - await completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); - }); -}); + await expectOrderReceivedPage( page ); + } ); +} ); -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.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}) => { - await page.goto('?add-to-cart=' + PRODUCT_ID); + test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); - await page.goto(BLOCK_CHECKOUT_URL) + await page.goto( BLOCK_CHECKOUT_URL ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await completeBlockContinuation(page); + await completeBlockContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal express block cart', async ({page}) => { - await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) + test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await completeBlockContinuation(page); + await completeBlockContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test.describe('Without review', () => { - test.beforeAll(async ({browser}) => { - await serverExec('wp pcp settings update blocks_final_review_enabled false'); - }); + 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); + test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); - await page.goto(BLOCK_CHECKOUT_URL) + await page.goto( BLOCK_CHECKOUT_URL ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await waitForPaypalShippingList(popup); + await waitForPaypalShippingList( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal express block cart', async ({page}) => { - await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) + test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await waitForPaypalShippingList(popup); + await waitForPaypalShippingList( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await expectOrderReceivedPage(page); - }); - }); -}); + await expectOrderReceivedPage( page ); + } ); + } ); +} ); diff --git a/tests/Playwright/tests/save-payment-methods.spec.js b/tests/Playwright/tests/save-payment-methods.spec.js index f6fb46315..092bd8ee4 100644 --- a/tests/Playwright/tests/save-payment-methods.spec.js +++ b/tests/Playwright/tests/save-payment-methods.spec.js @@ -1,89 +1,103 @@ -const {test, expect} = require('@playwright/test'); -const {loginAsCustomer} = require("./utils/user"); -const {openPaypalPopup, loginIntoPaypal, completePaypalPayment} = require("./utils/paypal-popup"); -const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout"); - +const { test, expect } = require( '@playwright/test' ); +const { loginAsCustomer } = require( './utils/user' ); const { - PRODUCT_URL, -} = process.env; + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, +} = require( './utils/paypal-popup' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, +} = require( './utils/checkout' ); -async function expectContinuation(page) { - await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked(); +const { PRODUCT_URL } = process.env; - await expect(page.locator('.component-frame')).toHaveCount(0); +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); } -async function completeContinuation(page) { - await expectContinuation(page); +async function completeContinuation( page ) { + await expectContinuation( page ); - await Promise.all([ - page.waitForNavigation(), - page.locator('#place_order').click(), - ]); + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); } -test('Save during purchase', async ({page}) => { - await loginAsCustomer(page) +// preconditions: shipping callback disabled and no saved payments +test( 'Save during purchase', async ( { page } ) => { + await loginAsCustomer( page ); - await page.goto(PRODUCT_URL); - const popup = await openPaypalPopup(page); + await page.goto( PRODUCT_URL ); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); - await completePaypalPayment(popup); - await fillCheckoutForm(page); + await loginIntoPaypal( popup ); + await completePaypalPayment( popup ); + await fillCheckoutForm( page ); - await completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); -}); + await expectOrderReceivedPage( page ); +} ); -test('PayPal add payment method', async ({page}) => { - await loginAsCustomer(page); - await page.goto('/my-account/add-payment-method'); +test( 'PayPal add payment method', async ( { page } ) => { + await loginAsCustomer( page ); + await page.goto( '/my-account/add-payment-method' ); - const popup = await openPaypalPopup(page); - await loginIntoPaypal(popup); - popup.locator('#consentButton').click(); + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); - await page.waitForURL('/my-account/payment-methods'); -}); + await page.waitForURL( '/my-account/payment-methods' ); +} ); -test('ACDC add payment method', async ({page}) => { - await loginAsCustomer(page); - await page.goto('/my-account/add-payment-method'); +test( 'ACDC add payment method', async ( { page } ) => { + await loginAsCustomer( page ); + await page.goto( '/my-account/add-payment-method' ); - await page.click("text=Debit & Credit Cards"); + await page.click( 'text=Debit & Credit Cards' ); - const creditCardNumber = await page.frameLocator('[title="paypal_card_number_field"]').locator('.card-field-number'); - await creditCardNumber.fill('4005519200000004'); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( '4005519200000004' ); - const expirationDate = await page.frameLocator('[title="paypal_card_expiry_field"]').locator('.card-field-expiry'); - await expirationDate.fill('01/25'); + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '12/25' ); - const cvv = await page.frameLocator('[title="paypal_card_cvv_field"]').locator('.card-field-cvv'); - await cvv.fill('123'); + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( '123' ); - await page.waitForURL('/my-account/payment-methods'); -}); + await page.getByRole( 'button', { name: 'Add payment method' } ).click(); -test('PayPal logged-in user free trial subscription without payment token', async ({page}) => { - await loginAsCustomer(page); - - await page.goto('/shop'); - await page.click("text=Sign up now"); - await page.goto('/classic-checkout'); - - const popup = await openPaypalPopup(page); - await loginIntoPaypal(popup); - popup.locator('#consentButton').click(); - - await page.click("text=Proceed to PayPal"); - - const title = await page.locator('.entry-title'); - await expect(title).toHaveText('Order received'); -}) + await page.waitForURL( '/my-account/payment-methods' ); +} ); +test( 'PayPal logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); + await page.click( 'text=Proceed to PayPal' ); + const title = await page.locator( '.entry-title' ); + await expect( title ).toHaveText( 'Order received' ); +} ); From 96f8c5ef973f74d6fb8ffc3cab74c917590762ce Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 12:02:37 +0400 Subject: [PATCH 017/105] Automatically delete PayPal Package --- modules/ppcp-compat/resources/js/tracking-compat.js | 11 +++++++++++ .../src/Integration/DhlShipmentIntegration.php | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js index a907ddc8f..3849249ba 100644 --- a/modules/ppcp-compat/resources/js/tracking-compat.js +++ b/modules/ppcp-compat/resources/js/tracking-compat.js @@ -92,6 +92,17 @@ document.addEventListener( 'DOMContentLoaded', () => { } ); } + jQuery( document ).on( + 'mouseover mouseout', + '#dhl_delete_label', + function ( event ) { + jQuery( '#ppcp-shipment-status' ) + .val( 'CANCELLED' ) + .trigger( 'change' ); + document.querySelector( '.update_shipment' ).click(); + } + ); + if ( wcShippingTaxSyncEnabled && typeof wcShippingTaxSyncEnabled !== 'undefined' diff --git a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php index 1e8961fcb..f51d880ef 100644 --- a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php +++ b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php @@ -76,8 +76,6 @@ class DhlShipmentIntegration implements Integration { return; } - $foo = $tracking_details; - $paypal_order = ppcp_get_paypal_order( $wc_order ); $capture_id = $this->get_paypal_order_transaction_id( $paypal_order ); $tracking_number = $tracking_details['tracking_number']; From 548c0082eb6c0851f5cc0d32f80fbc7d246a5e73 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:11:54 +0400 Subject: [PATCH 018/105] Add China to GPay support, simplify country-currency matrix --- modules/ppcp-googlepay/services.php | 834 ++---------------- .../ppcp-googlepay/src/Helper/ApmApplies.php | 42 +- 2 files changed, 105 insertions(+), 771 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 11cbd740b..c1ef967a1 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -29,12 +29,13 @@ return array( $apm_applies = $container->get( 'googlepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency(); + return $apm_applies->for_country() && $apm_applies->for_currency(); }, 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( - $container->get( 'googlepay.supported-country-currency-matrix' ), + $container->get( 'googlepay.supported-countries' ), + $container->get( 'googlepay.supported-currencies' ), $container->get( 'api.shop.currency' ), $container->get( 'api.shop.country' ) ); @@ -81,767 +82,84 @@ return array( ), /** - * The matrix which countries and currency combinations can be used for GooglePay. + * The list of which countries can be used for GooglePay. */ - 'googlepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'googlepay.supported-countries' => static function ( ContainerInterface $container) : array { /** - * Returns which countries and currency combinations can be used for GooglePay. + * Returns which countries can be used for GooglePay. */ return apply_filters( - 'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix', + 'woocommerce_paypal_payments_googlepay_supported_countries', array( - 'AU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'AT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CA' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'HU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LV' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GB' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'US' => array( - 'AUD', - 'CAD', - 'EUR', - 'GBP', - 'JPY', - 'USD', - ), + 'AU', // Australia + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CA', // Canada + 'CN', // China + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DK', // Denmark + 'EE', // Estonia + 'FI', // Finland + 'FR', // France + 'DE', // Germany + 'GR', // Greece + 'HU', // Hungary + 'IE', // Ireland + 'IT', // Italy + 'LV', // Latvia + 'LI', // Liechtenstein + 'LT', // Lithuania + 'LU', // Luxembourg + 'MT', // Malta + 'NL', // Netherlands + 'NO', // Norway + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SK', // Slovakia + 'SI', // Slovenia + 'ES', // Spain + 'SE', // Sweden + 'US', // United States + 'GB' // United Kingdom + ) + ); + }, + + /** + * The list of which currencies can be used for GooglePay. + */ + 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + /** + * Returns which currencies can be used for GooglePay. + */ + return apply_filters( + 'woocommerce_paypal_payments_googlepay_supported_currencies', + array( + 'AUD', // Australian Dollar + 'BRL', // Brazilian Real + 'CAD', // Canadian Dollar + 'CHF', // Swiss Franc + 'CZK', // Czech Koruna + 'DKK', // Danish Krone + 'EUR', // Euro + 'GBP', // British Pound Sterling + 'HKD', // Hong Kong Dollar + 'HUF', // Hungarian Forint + 'ILS', // Israeli New Shekel + 'JPY', // Japanese Yen + 'MXN', // Mexican Peso + 'NOK', // Norwegian Krone + 'NZD', // New Zealand Dollar + 'PHP', // Philippine Peso + 'PLN', // Polish Zloty + 'SEK', // Swedish Krona + 'SGD', // Singapore Dollar + 'THB', // Thai Baht + 'TWD', // New Taiwan Dollar + 'USD' // United States Dollar ) ); }, diff --git a/modules/ppcp-googlepay/src/Helper/ApmApplies.php b/modules/ppcp-googlepay/src/Helper/ApmApplies.php index 84ad2e057..96a16b210 100644 --- a/modules/ppcp-googlepay/src/Helper/ApmApplies.php +++ b/modules/ppcp-googlepay/src/Helper/ApmApplies.php @@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Helper; class ApmApplies { /** - * The matrix which countries and currency combinations can be used for GooglePay. + * The list of which countries can be used for GooglePay. * * @var array */ - private $allowed_country_currency_matrix; + private $allowed_countries; + + /** + * The list of which currencies can be used for GooglePay. + * + * @var array + */ + private $allowed_currencies; /** * 3-letter currency code of the shop. @@ -39,30 +46,39 @@ class ApmApplies { /** * DccApplies constructor. * - * @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for GooglePay. + * @param array $allowed_countries The list of which countries can be used for GooglePay. + * @param array $allowed_currencies The list of which currencies can be used for GooglePay. * @param string $currency 3-letter currency code of the shop. * @param string $country 2-letter country code of the shop. */ public function __construct( - array $allowed_country_currency_matrix, + array $allowed_countries, + array $allowed_currencies, string $currency, string $country ) { - $this->allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_countries = $allowed_countries; + $this->allowed_currencies = $allowed_currencies; + $this->currency = $currency; + $this->country = $country; } /** - * Returns whether GooglePay can be used in the current country and the current currency used. + * Returns whether GooglePay can be used in the current country used. * * @return bool */ - public function for_country_currency(): bool { - if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { - return false; - } - return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); + public function for_country(): bool { + return in_array( $this->country, $this->allowed_countries, true ); + } + + /** + * Returns whether GooglePay can be used in the current currency used. + * + * @return bool + */ + public function for_currency(): bool { + return in_array( $this->currency, $this->allowed_currencies, true ); } } From a06c01ad1f631497e12e92f7c2deffdbe33b3033 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:13:56 +0400 Subject: [PATCH 019/105] phpcs fix --- modules/ppcp-googlepay/services.php | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index c1ef967a1..e496ae629 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -25,14 +25,14 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( // If GooglePay can be configured. - 'googlepay.eligible' => static function ( ContainerInterface $container ): bool { + 'googlepay.eligible' => static function ( ContainerInterface $container ): bool { $apm_applies = $container->get( 'googlepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); return $apm_applies->for_country() && $apm_applies->for_currency(); }, - 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { + 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( $container->get( 'googlepay.supported-countries' ), $container->get( 'googlepay.supported-currencies' ), @@ -42,7 +42,7 @@ return array( }, // If GooglePay is configured and onboarded. - 'googlepay.available' => static function ( ContainerInterface $container ): bool { + 'googlepay.available' => static function ( ContainerInterface $container ): bool { if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) { $status = $container->get( 'googlepay.helpers.apm-product-status' ); assert( $status instanceof ApmProductStatus ); @@ -55,14 +55,14 @@ return array( }, // We assume it's a referral if we can check product status without API request failures. - 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { + 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { $status = $container->get( 'googlepay.helpers.apm-product-status' ); assert( $status instanceof ApmProductStatus ); return ! $status->has_request_failure(); }, - 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { + 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { return new AvailabilityNotice( $container->get( 'googlepay.helpers.apm-product-status' ), $container->get( 'wcgateway.is-wc-gateways-list-page' ), @@ -70,7 +70,7 @@ return array( ); }, - 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( + 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( static function( ContainerInterface $container ): ApmProductStatus { return new ApmProductStatus( $container->get( 'wcgateway.settings' ), @@ -84,7 +84,7 @@ return array( /** * The list of which countries can be used for GooglePay. */ - 'googlepay.supported-countries' => static function ( ContainerInterface $container) : array { + 'googlepay.supported-countries' => static function ( ContainerInterface $container ) : array { /** * Returns which countries can be used for GooglePay. */ @@ -123,7 +123,7 @@ return array( 'ES', // Spain 'SE', // Sweden 'US', // United States - 'GB' // United Kingdom + 'GB', // United Kingdom ) ); }, @@ -131,7 +131,7 @@ return array( /** * The list of which currencies can be used for GooglePay. */ - 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { /** * Returns which currencies can be used for GooglePay. */ @@ -159,12 +159,12 @@ return array( 'SGD', // Singapore Dollar 'THB', // Thai Baht 'TWD', // New Taiwan Dollar - 'USD' // United States Dollar + 'USD', // United States Dollar ) ); }, - 'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface { + 'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface { return new Button( $container->get( 'googlepay.url' ), $container->get( 'googlepay.sdk_url' ), @@ -178,7 +178,7 @@ return array( ); }, - 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { + 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( 'ppcp-googlepay', $container->get( 'googlepay.url' ), @@ -188,7 +188,7 @@ return array( ); }, - 'googlepay.url' => static function ( ContainerInterface $container ): string { + 'googlepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); if ( false === $path ) { return ''; @@ -199,26 +199,26 @@ return array( ); }, - 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { + 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { return 'https://pay.google.com/gp/p/js/pay.js'; }, - 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { + 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { return new UpdatePaymentDataEndpoint( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; }, - 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { + 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; }, - 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); if ( $state->current_state() < State::STATE_ONBOARDED ) { return ''; @@ -256,7 +256,7 @@ return array( esc_html( $button_text ) ); }, - 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { + 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { return new GooglePayGateway( $container->get( 'wcgateway.order-processor' ), $container->get( 'api.factory.paypal-checkout-url' ), From 883a5b15bcea94c4b2ccd8cfd9966f720530b61e Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:28:33 +0400 Subject: [PATCH 020/105] Add China to ApplePay support, simplify country-currency matrix --- modules/ppcp-applepay/services.php | 877 ++---------------- .../ppcp-applepay/src/Helper/ApmApplies.php | 44 +- 2 files changed, 130 insertions(+), 791 deletions(-) diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 6079fcb40..e4a3297dc 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -24,32 +24,33 @@ use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'applepay.eligible' => static function ( ContainerInterface $container ): bool { + 'applepay.eligible' => static function ( ContainerInterface $container ): bool { $apm_applies = $container->get( 'applepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency(); + return $apm_applies->for_country() && $apm_applies->for_currency(); }, - 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { + 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( - $container->get( 'applepay.supported-country-currency-matrix' ), + $container->get( 'applepay.supported-countries' ), + $container->get( 'applepay.supported-currencies' ), $container->get( 'api.shop.currency' ), $container->get( 'api.shop.country' ) ); }, - 'applepay.status-cache' => static function( ContainerInterface $container ): Cache { + 'applepay.status-cache' => static function( ContainerInterface $container ): Cache { return new Cache( 'ppcp-paypal-apple-status-cache' ); }, // We assume it's a referral if we can check product status without API request failures. - 'applepay.is_referral' => static function ( ContainerInterface $container ): bool { + 'applepay.is_referral' => static function ( ContainerInterface $container ): bool { $status = $container->get( 'applepay.apple-product-status' ); assert( $status instanceof AppleProductStatus ); return ! $status->has_request_failure(); }, - 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { + 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { $settings = $container->get( 'wcgateway.settings' ); return new AvailabilityNotice( @@ -63,17 +64,17 @@ return array( ); }, - 'applepay.has_validated' => static function ( ContainerInterface $container ): bool { + 'applepay.has_validated' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'applepay_validated' ); }, - 'applepay.is_validated' => static function ( ContainerInterface $container ): bool { + 'applepay.is_validated' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false; }, - 'applepay.apple-product-status' => SingletonDecorator::make( + 'applepay.apple-product-status' => SingletonDecorator::make( static function( ContainerInterface $container ): AppleProductStatus { return new AppleProductStatus( $container->get( 'wcgateway.settings' ), @@ -83,7 +84,7 @@ return array( ); } ), - 'applepay.available' => static function ( ContainerInterface $container ): bool { + 'applepay.available' => static function ( ContainerInterface $container ): bool { if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', true ) ) { $status = $container->get( 'applepay.apple-product-status' ); assert( $status instanceof AppleProductStatus ); @@ -94,10 +95,10 @@ return array( } return true; }, - 'applepay.server_supported' => static function ( ContainerInterface $container ): bool { + 'applepay.server_supported' => static function ( ContainerInterface $container ): bool { return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off'; }, - 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { + 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ); if ( $user_agent ) { @@ -127,7 +128,7 @@ return array( } return false; }, - 'applepay.url' => static function ( ContainerInterface $container ): string { + 'applepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); if ( false === $path ) { return ''; @@ -137,13 +138,13 @@ return array( dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { + 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; }, - 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { + 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) ); }, - 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { + 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { return new ApplePayButton( $container->get( 'wcgateway.settings' ), $container->get( 'woocommerce.logger.woocommerce' ), @@ -155,7 +156,7 @@ return array( $container->get( 'button.helper.cart-products' ) ); }, - 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { + 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( 'ppcp-applepay', $container->get( 'applepay.url' ), @@ -164,781 +165,103 @@ return array( $container->get( 'blocks.method' ) ); }, + /** - * The matrix which countries and currency combinations can be used for ApplePay. + * The list of which countries can be used for ApplePay. */ - 'applepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'applepay.supported-countries' => static function ( ContainerInterface $container ) : array { /** - * Returns which countries and currency combinations can be used for ApplePay. + * Returns which countries can be used for ApplePay. */ return apply_filters( - 'woocommerce_paypal_payments_applepay_supported_country_currency_matrix', + 'woocommerce_paypal_payments_applepay_supported_countries', + // phpcs:disable Squiz.Commenting.InlineComment array( - 'AU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'AT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CA' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'HU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LV' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GB' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'US' => array( - 'AUD', - 'CAD', - 'EUR', - 'GBP', - 'JPY', - 'USD', - ), + 'AU', // Australia + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CA', // Canada + 'CN', // China + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DK', // Denmark + 'EE', // Estonia + 'FI', // Finland + 'FR', // France + 'DE', // Germany + 'GR', // Greece + 'HU', // Hungary + 'IE', // Ireland + 'IT', // Italy + 'LV', // Latvia + 'LI', // Liechtenstein + 'LT', // Lithuania + 'LU', // Luxembourg + 'MT', // Malta + 'NL', // Netherlands + 'NO', // Norway + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SK', // Slovakia + 'SI', // Slovenia + 'ES', // Spain + 'SE', // Sweden + 'US', // United States + 'GB', // United Kingdom ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, - 'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + /** + * The list of which currencies can be used for ApplePay. + */ + 'applepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + /** + * Returns which currencies can be used for ApplePay. + */ + return apply_filters( + 'woocommerce_paypal_payments_applepay_supported_currencies', + // phpcs:disable Squiz.Commenting.InlineComment + array( + 'AUD', // Australian Dollar + 'BRL', // Brazilian Real + 'CAD', // Canadian Dollar + 'CHF', // Swiss Franc + 'CZK', // Czech Koruna + 'DKK', // Danish Krone + 'EUR', // Euro + 'GBP', // British Pound Sterling + 'HKD', // Hong Kong Dollar + 'HUF', // Hungarian Forint + 'ILS', // Israeli New Shekel + 'JPY', // Japanese Yen + 'MXN', // Mexican Peso + 'NOK', // Norwegian Krone + 'NZD', // New Zealand Dollar + 'PHP', // Philippine Peso + 'PLN', // Polish Zloty + 'SEK', // Swedish Krona + 'SGD', // Singapore Dollar + 'THB', // Thai Baht + 'TWD', // New Taiwan Dollar + 'USD', // United States Dollar + ) + // phpcs:enable Squiz.Commenting.InlineComment + ); + }, + + 'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; }, - 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { + 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; }, - 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); if ( $state->current_state() < State::STATE_ONBOARDED ) { return ''; diff --git a/modules/ppcp-applepay/src/Helper/ApmApplies.php b/modules/ppcp-applepay/src/Helper/ApmApplies.php index 0fa12420b..4deeade14 100644 --- a/modules/ppcp-applepay/src/Helper/ApmApplies.php +++ b/modules/ppcp-applepay/src/Helper/ApmApplies.php @@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Applepay\Helper; class ApmApplies { /** - * The matrix which countries and currency combinations can be used for ApplePay. + * The list of which countries can be used for ApplePay. * * @var array */ - private $allowed_country_currency_matrix; + private $allowed_countries; + + /** + * The list of which currencies can be used for ApplePay. + * + * @var array + */ + private $allowed_currencies; /** * 3-letter currency code of the shop. @@ -37,32 +44,41 @@ class ApmApplies { private $country; /** - * ApmApplies constructor. + * DccApplies constructor. * - * @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for ApplePay. + * @param array $allowed_countries The list of which countries can be used for ApplePay. + * @param array $allowed_currencies The list of which currencies can be used for ApplePay. * @param string $currency 3-letter currency code of the shop. * @param string $country 2-letter country code of the shop. */ public function __construct( - array $allowed_country_currency_matrix, + array $allowed_countries, + array $allowed_currencies, string $currency, string $country ) { - $this->allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_countries = $allowed_countries; + $this->allowed_currencies = $allowed_currencies; + $this->currency = $currency; + $this->country = $country; } /** - * Returns whether ApplePay can be used in the current country and the current currency used. + * Returns whether ApplePay can be used in the current country used. * * @return bool */ - public function for_country_currency(): bool { - if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { - return false; - } - return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); + public function for_country(): bool { + return in_array( $this->country, $this->allowed_countries, true ); + } + + /** + * Returns whether ApplePay can be used in the current currency used. + * + * @return bool + */ + public function for_currency(): bool { + return in_array( $this->currency, $this->allowed_currencies, true ); } } From 5252888721d679c997394087996ab71156cdadb1 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:29:48 +0400 Subject: [PATCH 021/105] phpcs fix --- modules/ppcp-googlepay/services.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index e496ae629..110b56937 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -90,6 +90,7 @@ return array( */ return apply_filters( 'woocommerce_paypal_payments_googlepay_supported_countries', + // phpcs:disable Squiz.Commenting.InlineComment array( 'AU', // Australia 'AT', // Austria @@ -125,6 +126,7 @@ return array( 'US', // United States 'GB', // United Kingdom ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, @@ -137,6 +139,7 @@ return array( */ return apply_filters( 'woocommerce_paypal_payments_googlepay_supported_currencies', + // phpcs:disable Squiz.Commenting.InlineComment array( 'AUD', // Australian Dollar 'BRL', // Brazilian Real @@ -161,6 +164,7 @@ return array( 'TWD', // New Taiwan Dollar 'USD', // United States Dollar ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, From 06378c34c880deeae455af728409437a01d969c2 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 14:15:35 +0400 Subject: [PATCH 022/105] Add Amex support for ACDC China --- modules/ppcp-api-client/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index dbdf2efe3..241240ce2 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1443,6 +1443,7 @@ return array( 'CN' => array( 'mastercard' => array(), 'visa' => array(), + 'amex' => array(), ), 'CY' => array( 'mastercard' => array(), From 6f7270c35bc6c9ab3c53e02373e54b8bac2b971d Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 12:18:41 +0200 Subject: [PATCH 023/105] Fix card fields rendered multiple times --- .../ppcp-card-fields/resources/js/Render.js | 7 ++++++ tests/Playwright/playwright.config.js | 2 +- .../tests/free-trial-subscriptions.js | 22 +++++++++++++++++++ tests/Playwright/tests/place-order.spec.js | 21 +++++++++--------- .../tests/save-payment-methods.spec.js | 19 ---------------- 5 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 tests/Playwright/tests/free-trial-subscriptions.js diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js index 8b46f17d7..a77874159 100644 --- a/modules/ppcp-card-fields/resources/js/Render.js +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -1,6 +1,13 @@ import { cardFieldStyles } from './CardFieldsHelper'; +let fieldsRendered = false; + export function renderFields( cardFields ) { + if ( fieldsRendered === true ) { + return; + } + fieldsRendered = true; + const nameField = document.getElementById( 'ppcp-credit-card-gateway-card-name' ); diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js index 7273db2a0..402a04bb4 100644 --- a/tests/Playwright/playwright.config.js +++ b/tests/Playwright/playwright.config.js @@ -7,7 +7,7 @@ require( 'dotenv' ).config( { path: '.env' } ); * @see https://playwright.dev/docs/test-configuration */ module.exports = defineConfig( { - timeout: 60000, + timeout: 30000, testDir: './tests', /* Run tests in files in parallel */ fullyParallel: false, diff --git a/tests/Playwright/tests/free-trial-subscriptions.js b/tests/Playwright/tests/free-trial-subscriptions.js new file mode 100644 index 000000000..c4cc68baf --- /dev/null +++ b/tests/Playwright/tests/free-trial-subscriptions.js @@ -0,0 +1,22 @@ +const { test, expect } = require( '@playwright/test' ); +const { loginAsCustomer } = require( './utils/user' ); +const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); + +test( 'PayPal logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); + + await page.click( 'text=Proceed to PayPal' ); + + const title = await page.locator( '.entry-title' ); + await expect( title ).toHaveText( 'Order received' ); +} ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js index 086d8e9c7..d6d9a40cf 100644 --- a/tests/Playwright/tests/place-order.spec.js +++ b/tests/Playwright/tests/place-order.spec.js @@ -93,19 +93,20 @@ test.describe( 'Classic checkout', () => { await page.click( 'text=Credit Cards' ); - const creditCardNumber = page - .frameLocator( '#braintree-hosted-field-number' ) - .locator( '#credit-card-number' ); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - const expirationDate = page - .frameLocator( '#braintree-hosted-field-expirationDate' ) - .locator( '#expiration' ); - await expirationDate.fill( CREDIT_CARD_EXPIRATION ); + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( CREDIT_CARD_EXPIRATION ); - const cvv = page - .frameLocator( '#braintree-hosted-field-cvv' ) - .locator( '#cvv' ); + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); await cvv.fill( CREDIT_CARD_CVV ); await Promise.all( [ diff --git a/tests/Playwright/tests/save-payment-methods.spec.js b/tests/Playwright/tests/save-payment-methods.spec.js index 092bd8ee4..077207141 100644 --- a/tests/Playwright/tests/save-payment-methods.spec.js +++ b/tests/Playwright/tests/save-payment-methods.spec.js @@ -82,22 +82,3 @@ test( 'ACDC add payment method', async ( { page } ) => { await page.waitForURL( '/my-account/payment-methods' ); } ); - -test( 'PayPal logged-in user free trial subscription without payment token', async ( { - page, -} ) => { - await loginAsCustomer( page ); - - await page.goto( '/product/free-trial' ); - await page.click( 'text=Sign up now' ); - await page.goto( '/classic-checkout' ); - - const popup = await openPaypalPopup( page ); - await loginIntoPaypal( popup ); - popup.locator( '#consentButton' ).click(); - - await page.click( 'text=Proceed to PayPal' ); - - const title = await page.locator( '.entry-title' ); - await expect( title ).toHaveText( 'Order received' ); -} ); From a7c43ab0d125931220cf2ea1ab4220ba64e9fc29 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 29 Jul 2024 15:55:29 +0400 Subject: [PATCH 024/105] Do not add tex info into order meta during order creation --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 5ff2b02e6..888a567c5 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -303,13 +303,6 @@ class WooCommerceOrderCreator { $item->set_tax_class( $product->get_tax_class() ); $item->set_total_tax( (float) array_sum( $taxes ) ); - - foreach ( $taxes as $tax_rate_id => $tax_amount ) { - if ( $tax_amount > 0 ) { - $item->add_meta_data( 'tax_rate_id', $tax_rate_id, true ); - $item->add_meta_data( 'tax_amount', $tax_amount, true ); - } - } } /** From 8bda629df5d8d8c21004980adbbdf1e525db9672 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 14:20:27 +0200 Subject: [PATCH 025/105] Move pw tests into multiple files --- .../Playwright/tests/apms-place-order.spec.js | 76 ++++++ .../tests/blocks-place-order.spec.js | 109 ++++++++ .../tests/classic-place-order.spec.js | 98 +++++++ ...ns.js => free-trial-subscriptions.spec.js} | 9 +- tests/Playwright/tests/place-order.spec.js | 243 ------------------ 5 files changed, 289 insertions(+), 246 deletions(-) create mode 100644 tests/Playwright/tests/apms-place-order.spec.js create mode 100644 tests/Playwright/tests/blocks-place-order.spec.js create mode 100644 tests/Playwright/tests/classic-place-order.spec.js rename tests/Playwright/tests/{free-trial-subscriptions.js => free-trial-subscriptions.spec.js} (77%) delete mode 100644 tests/Playwright/tests/place-order.spec.js diff --git a/tests/Playwright/tests/apms-place-order.spec.js b/tests/Playwright/tests/apms-place-order.spec.js new file mode 100644 index 000000000..f12337840 --- /dev/null +++ b/tests/Playwright/tests/apms-place-order.spec.js @@ -0,0 +1,76 @@ +const { test, expect } = require( '@playwright/test' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, + acceptTerms, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + completePaypalPayment, +} = require( './utils/paypal-popup' ); + +const { PRODUCT_ID, CHECKOUT_URL, CART_URL, APM_ID } = process.env; + +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); +} + +async function completeContinuation( page ) { + await expectContinuation( page ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); +} + +test( 'PayPal APM button place order', async ( { page } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( CHECKOUT_URL ); + + await fillCheckoutForm( page ); + + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); + + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'PayPal APM button place order when redirect fails', async ( { + page, +} ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( CHECKOUT_URL ); + + await fillCheckoutForm( page ); + + await page.evaluate( 'PayPalCommerceGateway.ajax.approve_order = null' ); + + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); + + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); + + await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); + + await page.reload(); + await expectContinuation( page ); + + await acceptTerms( page ); + + await completeContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); diff --git a/tests/Playwright/tests/blocks-place-order.spec.js b/tests/Playwright/tests/blocks-place-order.spec.js new file mode 100644 index 000000000..efb0cf38b --- /dev/null +++ b/tests/Playwright/tests/blocks-place-order.spec.js @@ -0,0 +1,109 @@ +const { expect, test } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, + waitForPaypalShippingList, +} = require( './utils/paypal-popup' ); +const { expectOrderReceivedPage } = require( './utils/checkout' ); + +const { + PRODUCT_ID, + BLOCK_CHECKOUT_URL, + BLOCK_CHECKOUT_PAGE_ID, + BLOCK_CART_URL, +} = process.env; + +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 Promise.all( + page.waitForNavigation(), + page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click() + ); +} + +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 } ) => { + 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 ); + + 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 completePaypalPayment( popup ); + + await completeBlockContinuation( page ); + + await expectOrderReceivedPage( 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 ); + } ); +} ); diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js new file mode 100644 index 000000000..9a9bcae37 --- /dev/null +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -0,0 +1,98 @@ +const { test, expect } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, +} = require( './utils/paypal-popup' ); + +const { + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXPIRATION, + CREDIT_CARD_CVV, + PRODUCT_URL, + CHECKOUT_URL, + CHECKOUT_PAGE_ID, +} = process.env; + +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); +} + +async function completeContinuation( page ) { + await expectContinuation( page ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); +} + +test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID + ); +} ); + +test( 'PayPal button place order from Product page', async ( { page } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled true' + ); + + await page.goto( PRODUCT_URL ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await completePaypalPayment( popup ); + + await fillCheckoutForm( page ); + + await completeContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'Advanced Credit and Debit Card 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 = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( CREDIT_CARD_EXPIRATION ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); + + await expectOrderReceivedPage( page ); +} ); diff --git a/tests/Playwright/tests/free-trial-subscriptions.js b/tests/Playwright/tests/free-trial-subscriptions.spec.js similarity index 77% rename from tests/Playwright/tests/free-trial-subscriptions.js rename to tests/Playwright/tests/free-trial-subscriptions.spec.js index c4cc68baf..8254d3c76 100644 --- a/tests/Playwright/tests/free-trial-subscriptions.js +++ b/tests/Playwright/tests/free-trial-subscriptions.spec.js @@ -1,10 +1,15 @@ const { test, expect } = require( '@playwright/test' ); const { loginAsCustomer } = require( './utils/user' ); const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); +const { serverExec } = require( './utils/server' ); -test( 'PayPal logged-in user free trial subscription without payment token', async ( { +test( 'PayPal logged-in user free trial subscription without payment token with shipping callback enabled', async ( { page, } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled false' + ); + await loginAsCustomer( page ); await page.goto( '/product/free-trial' ); @@ -15,8 +20,6 @@ test( 'PayPal logged-in user free trial subscription without payment token', asy await loginIntoPaypal( popup ); popup.locator( '#consentButton' ).click(); - await page.click( 'text=Proceed to PayPal' ); - const title = await page.locator( '.entry-title' ); await expect( title ).toHaveText( 'Order received' ); } ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js deleted file mode 100644 index d6d9a40cf..000000000 --- a/tests/Playwright/tests/place-order.spec.js +++ /dev/null @@ -1,243 +0,0 @@ -const { test, expect } = require( '@playwright/test' ); -const { serverExec } = require( './utils/server' ); -const { - fillCheckoutForm, - expectOrderReceivedPage, - acceptTerms, -} = require( './utils/checkout' ); -const { - openPaypalPopup, - loginIntoPaypal, - waitForPaypalShippingList, - completePaypalPayment, -} = require( './utils/paypal-popup' ); - -const { - CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, - CREDIT_CARD_CVV, - PRODUCT_URL, - PRODUCT_ID, - CHECKOUT_URL, - CHECKOUT_PAGE_ID, - CART_URL, - BLOCK_CHECKOUT_URL, - BLOCK_CHECKOUT_PAGE_ID, - BLOCK_CART_URL, - APM_ID, -} = process.env; - -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 Promise.all( - page.waitForNavigation(), - page - .locator( '.wc-block-components-checkout-place-order-button' ) - .click() - ); -} - -async function expectContinuation( page ) { - await expect( - page.locator( '#payment_method_ppcp-gateway' ) - ).toBeChecked(); - - await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); -} - -async function completeContinuation( page ) { - await expectContinuation( page ); - - await Promise.all( [ - page.waitForNavigation(), - page.locator( '#place_order' ).click(), - ] ); -} - -test.describe( 'Classic checkout', () => { - test.beforeAll( async ( { browser } ) => { - await serverExec( - 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID - ); - } ); - - test( 'PayPal button place order from Product page', async ( { page } ) => { - await page.goto( PRODUCT_URL ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await completePaypalPayment( popup ); - - await fillCheckoutForm( page ); - - await completeContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'Advanced Credit and Debit Card 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 = await page - .frameLocator( '[title="paypal_card_number_field"]' ) - .locator( '.card-field-number' ); - await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - - const expirationDate = await page - .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) - .locator( 'input.card-field-expiry' ); - await expirationDate.click(); - await page.keyboard.type( CREDIT_CARD_EXPIRATION ); - - const cvv = await page - .frameLocator( '[title="paypal_card_cvv_field"]' ) - .locator( '.card-field-cvv' ); - await cvv.fill( CREDIT_CARD_CVV ); - - await Promise.all( [ - page.waitForNavigation(), - page.locator( '.ppcp-dcc-order-button' ).click(), - ] ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal APM button place order', async ( { page } ) => { - await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( CHECKOUT_URL ); - - await fillCheckoutForm( page ); - - const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - - await popup.getByText( 'Continue', { exact: true } ).click(); - await completePaypalPayment( popup, { - selector: '[name="Successful"]', - } ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal APM button place order when redirect fails', async ( { - page, - } ) => { - await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( CHECKOUT_URL ); - - await fillCheckoutForm( page ); - - await page.evaluate( - 'PayPalCommerceGateway.ajax.approve_order = null' - ); - - const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - - await popup.getByText( 'Continue', { exact: true } ).click(); - await completePaypalPayment( popup, { - selector: '[name="Successful"]', - } ); - - await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); - - await page.reload(); - await expectContinuation( page ); - - await acceptTerms( page ); - - await completeContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); -} ); - -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 } ) => { - 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 ); - - 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 completePaypalPayment( popup ); - - await completeBlockContinuation( page ); - - await expectOrderReceivedPage( 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 5e5c99bc7d09ce034159b26b15a9e5532bd17a1f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 15:56:45 +0200 Subject: [PATCH 026/105] Ensure field container is empty before render card field --- modules/ppcp-card-fields/resources/js/Render.js | 15 ++++----------- .../Playwright/tests/classic-place-order.spec.js | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js index a77874159..9a35ff449 100644 --- a/modules/ppcp-card-fields/resources/js/Render.js +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -1,17 +1,10 @@ import { cardFieldStyles } from './CardFieldsHelper'; -let fieldsRendered = false; - export function renderFields( cardFields ) { - if ( fieldsRendered === true ) { - return; - } - fieldsRendered = true; - const nameField = document.getElementById( 'ppcp-credit-card-gateway-card-name' ); - if ( nameField ) { + if ( nameField && nameField.hidden !== true ) { const styles = cardFieldStyles( nameField ); cardFields .NameField( { style: { input: styles } } ) @@ -22,7 +15,7 @@ export function renderFields( cardFields ) { const numberField = document.getElementById( 'ppcp-credit-card-gateway-card-number' ); - if ( numberField ) { + if ( numberField && numberField.hidden !== true ) { const styles = cardFieldStyles( numberField ); cardFields .NumberField( { style: { input: styles } } ) @@ -33,7 +26,7 @@ export function renderFields( cardFields ) { const expiryField = document.getElementById( 'ppcp-credit-card-gateway-card-expiry' ); - if ( expiryField ) { + if ( expiryField && expiryField.hidden !== true ) { const styles = cardFieldStyles( expiryField ); cardFields .ExpiryField( { style: { input: styles } } ) @@ -44,7 +37,7 @@ export function renderFields( cardFields ) { const cvvField = document.getElementById( 'ppcp-credit-card-gateway-card-cvc' ); - if ( cvvField ) { + if ( cvvField && cvvField.hidden !== true ) { const styles = cardFieldStyles( cvvField ); cardFields .CVVField( { style: { input: styles } } ) diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js index 9a9bcae37..f73a8a476 100644 --- a/tests/Playwright/tests/classic-place-order.spec.js +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -82,7 +82,7 @@ test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) .locator( 'input.card-field-expiry' ); await expirationDate.click(); - await page.keyboard.type( CREDIT_CARD_EXPIRATION ); + await page.keyboard.type( '01/42' ); const cvv = await page .frameLocator( '[title="paypal_card_cvv_field"]' ) From 8afa7e34dc9342c99817afa5e08fe68329053521 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 16:20:47 +0200 Subject: [PATCH 027/105] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20duplicate=20pa?= =?UTF-8?q?yment=20button=20instances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 87bc642f0..723f9dd8c 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -5,6 +5,19 @@ import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; class GooglepayButton { + /** + * Reference to the payment button created by this instance. + * + * @type {HTMLElement} + */ + #button; + + /** + * Client reference, provided by the Google Pay JS SDK. + * @see https://developers.google.com/pay/api/web/reference/client + */ + paymentsClient = null; + constructor( context, externalHandler, @@ -22,8 +35,6 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.paymentsClient = null; - this.log = function () { if ( this.buttonConfig.is_debug ) { //console.log('[GooglePayButton]', ...arguments); @@ -235,13 +246,19 @@ class GooglepayButton { const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); this.waitForWrapper( wrapper, () => { + // Prevent duplicate payment buttons. + this.removeButton(); + jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); } - const button = this.paymentsClient.createButton( { + /** + * @see https://developers.google.com/pay/api/web/reference/client#createButton + */ + this.#button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), allowedPaymentMethods: [ baseCardPaymentMethod ], buttonColor: buttonStyle.color || 'black', @@ -250,10 +267,22 @@ class GooglepayButton { buttonSizeMode: 'fill', } ); - jQuery( wrapper ).append( button ); + jQuery( wrapper ).append( this.#button ); } ); } + /** + * Removes the payment button that was injected via addButton() + */ + removeButton() { + if ( ! this.#button ) { + return; + } + + this.#button.remove(); + this.#button = null; + } + addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { const button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), From d0b8f4ccba261b0201bc62db4020bda655bfa9bc Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 16:28:22 +0200 Subject: [PATCH 028/105] Add test for card block free trial without saved payments --- .../tests/classic-place-order.spec.js | 11 ++- .../tests/free-trial-subscriptions.spec.js | 78 ++++++++++++++++++- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js index f73a8a476..22cc5055d 100644 --- a/tests/Playwright/tests/classic-place-order.spec.js +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -12,7 +12,6 @@ const { const { CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, CREDIT_CARD_CVV, PRODUCT_URL, CHECKOUT_URL, @@ -73,17 +72,17 @@ test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { await page.click( 'text=Credit Cards' ); - const creditCardNumber = await page - .frameLocator( '[title="paypal_card_number_field"]' ) - .locator( '.card-field-number' ); - await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - const expirationDate = await page .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) .locator( 'input.card-field-expiry' ); await expirationDate.click(); await page.keyboard.type( '01/42' ); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + const cvv = await page .frameLocator( '[title="paypal_card_cvv_field"]' ) .locator( '.card-field-cvv' ); diff --git a/tests/Playwright/tests/free-trial-subscriptions.spec.js b/tests/Playwright/tests/free-trial-subscriptions.spec.js index 8254d3c76..8451884fd 100644 --- a/tests/Playwright/tests/free-trial-subscriptions.spec.js +++ b/tests/Playwright/tests/free-trial-subscriptions.spec.js @@ -2,6 +2,9 @@ const { test, expect } = require( '@playwright/test' ); const { loginAsCustomer } = require( './utils/user' ); const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); const { serverExec } = require( './utils/server' ); +const { expectOrderReceivedPage } = require( './utils/checkout' ); + +const { CREDIT_CARD_NUMBER, CREDIT_CARD_CVV } = process.env; test( 'PayPal logged-in user free trial subscription without payment token with shipping callback enabled', async ( { page, @@ -11,7 +14,6 @@ test( 'PayPal logged-in user free trial subscription without payment token with ); await loginAsCustomer( page ); - await page.goto( '/product/free-trial' ); await page.click( 'text=Sign up now' ); await page.goto( '/classic-checkout' ); @@ -20,6 +22,76 @@ test( 'PayPal logged-in user free trial subscription without payment token with await loginIntoPaypal( popup ); popup.locator( '#consentButton' ).click(); - const title = await page.locator( '.entry-title' ); - await expect( title ).toHaveText( 'Order received' ); + await page.waitForURL( '**/order-received/**' ); +} ); + +test( 'ACDC logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + + await page.click( 'text=Credit Cards' ); + + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '01/42' ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'ACDC purchase free trial in Block checkout page as logged-in without saved card payments', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/checkout' ); + + await page + .locator( + '#radio-control-wc-payment-method-options-ppcp-credit-card-gateway' + ) + .click(); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '01/42' ); + + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click(); + + await page.waitForURL( '**/order-received/**' ); } ); From 1e5b6d5a210252b6f0f49d4d7c52e6ba0ff29337 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 18:05:08 +0200 Subject: [PATCH 029/105] =?UTF-8?q?=E2=9C=A8=20Improve=20debug-logging=20f?= =?UTF-8?q?or=20GooglePayButton=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement same logic as we use for the ApplePayButton --- .../resources/js/GooglepayButton.js | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 723f9dd8c..aa2c27921 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -25,6 +25,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { + this._initDebug( !! buttonConfig?.is_debug, context ); + apmButtonsInit( ppcpConfig ); this.isInitialized = false; @@ -34,12 +36,35 @@ class GooglepayButton { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + } - this.log = function () { - if ( this.buttonConfig.is_debug ) { - //console.log('[GooglePayButton]', ...arguments); - } + /** + * NOOP log function to avoid errors when debugging is disabled. + */ + log() {} + + /** + * Enables debugging tools, when the button's is_debug flag is set. + * + * @param {boolean} enableDebugging If debugging features should be enabled for this instance. + * @param {string} context Used to make the instance accessible via the global debug object. + * @private + */ + _initDebug( enableDebugging, context ) { + if ( ! enableDebugging ) { + return; + } + + document.ppcpGooglepayButtons = document.ppcpGooglepayButtons || {}; + document.ppcpGooglepayButtons[ context ] = this; + + this.log = ( ...args ) => { + console.log( `[GooglePayButton | ${ context }]`, ...args ); }; + + document.addEventListener( 'ppcp-googlepay-debug', () => { + this.log( this ); + } ); } init( config, transactionInfo ) { From 8814b1f6368d997a5191269ccaad171e819c094e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 18:30:03 +0200 Subject: [PATCH 030/105] =?UTF-8?q?=F0=9F=92=A1=20Improve/adjust=20debug?= =?UTF-8?q?=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index aa2c27921..9ee34fa0f 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -36,6 +36,8 @@ class GooglepayButton { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + + this.log( 'Create instance' ); } /** @@ -47,7 +49,8 @@ class GooglepayButton { * Enables debugging tools, when the button's is_debug flag is set. * * @param {boolean} enableDebugging If debugging features should be enabled for this instance. - * @param {string} context Used to make the instance accessible via the global debug object. + * @param {string} context Used to make the instance accessible via the global debug + * object. * @private */ _initDebug( enableDebugging, context ) { @@ -81,6 +84,8 @@ class GooglepayButton { return; } + this.log( 'Init' ); + this.googlePayConfig = config; this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; @@ -218,9 +223,13 @@ class GooglepayButton { this.onPaymentDataChanged.bind( this ); } + /** + * Consider providing merchant info here: + * + * @see https://developers.google.com/pay/api/web/reference/request-objects#PaymentOptions + */ this.paymentsClient = new google.payments.api.PaymentsClient( { environment: this.buttonConfig.environment, - // add merchant info maybe paymentDataCallbacks: callbacks, } ); } @@ -266,7 +275,7 @@ class GooglepayButton { * @param baseCardPaymentMethod */ addButton( baseCardPaymentMethod ) { - this.log( 'addButton', this.context ); + this.log( 'addButton' ); const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); @@ -344,17 +353,14 @@ class GooglepayButton { * Show Google Pay payment sheet when Google Pay payment button is clicked */ onButtonClick() { - this.log( 'onButtonClick', this.context ); + this.log( 'onButtonClick' ); const paymentDataRequest = this.paymentDataRequest(); - this.log( - 'onButtonClick: paymentDataRequest', - paymentDataRequest, - this.context - ); + this.log( 'onButtonClick: paymentDataRequest', paymentDataRequest ); - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. + // Do this on another place like on create order endpoint handler. + window.ppcpFundingSource = 'googlepay'; this.paymentsClient.loadPaymentData( paymentDataRequest ); } @@ -404,8 +410,7 @@ class GooglepayButton { } onPaymentDataChanged( paymentData ) { - this.log( 'onPaymentDataChanged', this.context ); - this.log( 'paymentData', paymentData ); + this.log( 'onPaymentDataChanged', paymentData ); return new Promise( async ( resolve, reject ) => { try { @@ -478,18 +483,18 @@ class GooglepayButton { //------------------------ onPaymentAuthorized( paymentData ) { - this.log( 'onPaymentAuthorized', this.context ); + this.log( 'onPaymentAuthorized' ); return this.processPayment( paymentData ); } async processPayment( paymentData ) { - this.log( 'processPayment', this.context ); + this.log( 'processPayment' ); return new Promise( async ( resolve, reject ) => { try { const id = await this.contextHandler.createOrder(); - this.log( 'processPayment: createOrder', id, this.context ); + this.log( 'processPayment: createOrder', id ); const confirmOrderResponse = await widgetBuilder.paypal .Googlepay() @@ -500,8 +505,7 @@ class GooglepayButton { this.log( 'processPayment: confirmOrder', - confirmOrderResponse, - this.context + confirmOrderResponse ); /** Capture the Order on the Server */ @@ -571,7 +575,7 @@ class GooglepayButton { }; } - this.log( 'processPaymentResponse', response, this.context ); + this.log( 'processPaymentResponse', response ); return response; } From 8286085f594372e4aba5b02aa4e566ddd01ce734 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 21:16:53 +0200 Subject: [PATCH 031/105] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20latest=20J?= =?UTF-8?q?S=20structure=20from=20ApplePay=20Gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Partially addresses the known display bug - Simplifies maintainance between both gateways - Reduces component-internal redundancies --- .../resources/js/GooglepayButton.js | 405 ++++++++++++------ modules/ppcp-googlepay/src/Assets/Button.php | 17 + 2 files changed, 299 insertions(+), 123 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9ee34fa0f..9571e9735 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,16 +1,61 @@ +/* global google */ +/* global jQuery */ + import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +/** + * Plugin-specific styling. + * + * Note that most properties of this object do not apply to the Google Pay button. + * + * @typedef {Object} PPCPStyle + * @property {string} shape - Outline shape. + * @property {?number} height - Button height in pixel. + */ + +/** + * Style options that are defined by the Google Pay SDK and are required to render the button. + * + * @typedef {Object} GooglePayStyle + * @property {string} type - Defines the button label. + * @property {string} color - Button color + * @property {string} language - The locale; an empty string will apply the user-agent's language. + */ + +/** + * List of valid context values that the button can have. + * + * @type {Object} + */ +const CONTEXT = { + Product: 'product', + Cart: 'cart', + Checkout: 'checkout', + PayNow: 'pay-now', + MiniCart: 'mini-cart', + BlockCart: 'cart-block', + BlockCheckout: 'checkout-block', + Preview: 'preview', + // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], +}; + class GooglepayButton { + #wrapperId = ''; + #ppcpButtonWrapperId = ''; + /** - * Reference to the payment button created by this instance. + * Whether the payment button is initialized. * - * @type {HTMLElement} + * @type {boolean} */ - #button; + #isInitialized = false; /** * Client reference, provided by the Google Pay JS SDK. @@ -29,8 +74,6 @@ class GooglepayButton { apmButtonsInit( ppcpConfig ); - this.isInitialized = false; - this.context = context; this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; @@ -54,7 +97,7 @@ class GooglepayButton { * @private */ _initDebug( enableDebugging, context ) { - if ( ! enableDebugging ) { + if ( ! enableDebugging || this.#isInitialized ) { return; } @@ -70,11 +113,164 @@ class GooglepayButton { } ); } + /** + * Determines if the current payment button should be rendered as a stand-alone gateway. + * The return value `false` usually means, that the payment button is bundled with all available + * payment buttons. + * + * The decision depends on the button context (placement) and the plugin settings. + * + * @return {boolean} True, if the current button represents a stand-alone gateway. + */ + get isSeparateGateway() { + return ( + this.buttonConfig.is_wc_gateway_enabled && + CONTEXT.Gateways.includes( this.context ) + ); + } + + /** + * Returns the wrapper ID for the current button context. + * The ID varies for the MiniCart context. + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get wrapperId() { + if ( ! this.#wrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.buttonConfig.button.mini_cart_wrapper; + } else if ( this.isSeparateGateway ) { + id = 'ppc-button-ppcp-googlepay'; + } else { + id = this.buttonConfig.button.wrapper; + } + + this.#wrapperId = id.replace( /^#/, '' ); + } + + return this.#wrapperId; + } + + /** + * Returns the wrapper ID for the ppcpButton + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get ppcpButtonWrapperId() { + if ( ! this.#ppcpButtonWrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.ppcpConfig.button.mini_cart_wrapper; + } else if ( CONTEXT.Blocks.includes( this.context ) ) { + id = 'express-payment-method-ppcp-gateway-paypal'; + } else { + id = this.ppcpConfig.button.wrapper; + } + + this.#ppcpButtonWrapperId = id.replace( /^#/, '' ); + } + + return this.#ppcpButtonWrapperId; + } + + /** + * Returns the context-relevant PPCP style object. + * The style for the MiniCart context can be different. + * + * The PPCP style are custom style options, that are provided by this plugin. + * + * @return {PPCPStyle} The style object. + */ + get ppcpStyle() { + if ( CONTEXT.MiniCart === this.context ) { + return this.ppcpConfig.button.mini_cart_style; + } + + return this.ppcpConfig.button.style; + } + + /** + * Returns default style options that are propagated to and rendered by the Google Pay button. + * + * These styles are the official style options provided by the Google Pay SDK. + * + * @return {GooglePayStyle} The style object. + */ + get buttonStyle() { + let style; + + if ( CONTEXT.MiniCart === this.context ) { + style = this.buttonConfig.button.mini_cart_style; + + // Handle incompatible types. + if ( style.type === 'buy' ) { + style.type = 'pay'; + } + } else { + style = this.buttonConfig.button.style; + } + + return { + type: style.type, + language: style.language, + color: style.color, + }; + } + + /** + * Returns the HTML element that wraps the current button + * + * @return {HTMLElement|null} The wrapper element, or null. + */ + get wrapperElement() { + return document.getElementById( this.wrapperId ); + } + + /** + * Returns an array of HTMLElements that belong to the payment button. + * + * @return {HTMLElement[]} List of payment button wrapper elements. + */ + get allElements() { + const selectors = []; + + // Payment button (Pay now, smart button block) + selectors.push( `#${ this.wrapperId }` ); + + // Block Checkout: Express checkout button. + if ( CONTEXT.Blocks.includes( this.context ) ) { + selectors.push( '#express-payment-method-ppcp-googlepay' ); + } + + // Classic Checkout: Google Pay gateway. + if ( CONTEXT.Gateways === this.context ) { + selectors.push( + '.wc_payment_method.payment_method_ppcp-googlepay' + ); + } + + this.log( 'Wrapper Elements:', selectors ); + return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => + Array.from( document.querySelectorAll( selector ) ) + ); + } + + /** + * Checks whether the main button-wrapper is present in the current DOM. + * + * @return {boolean} True, if the button context (wrapper element) is found. + */ + get isPresent() { + return this.wrapperElement instanceof HTMLElement; + } + init( config, transactionInfo ) { - if ( this.isInitialized ) { + if ( this.#isInitialized ) { return; } - this.isInitialized = true; if ( ! this.validateConfig() ) { return; @@ -85,6 +281,7 @@ class GooglepayButton { } this.log( 'Init' ); + this.#isInitialized = true; this.googlePayConfig = config; this.transactionInfo = transactionInfo; @@ -103,40 +300,7 @@ class GooglepayButton { ) .then( ( response ) => { if ( response.result ) { - if ( - ( this.context === 'checkout' || - this.context === 'pay-now' ) && - this.buttonConfig.is_wc_gateway_enabled === '1' - ) { - const wrapper = document.getElementById( - 'ppc-button-ppcp-googlepay' - ); - - if ( wrapper ) { - const { ppcpStyle, buttonStyle } = - this.contextConfig(); - - wrapper.classList.add( - `ppcp-button-${ ppcpStyle.shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); - - if ( ppcpStyle.height ) { - wrapper.style.height = `${ ppcpStyle.height }px`; - } - - this.addButtonCheckout( - this.baseCardPaymentMethod, - wrapper, - buttonStyle - ); - - return; - } - } - - this.addButton( this.baseCardPaymentMethod ); + this.addButton(); } } ) .catch( function ( err ) { @@ -149,7 +313,7 @@ class GooglepayButton { return; } - this.isInitialized = false; + this.#isInitialized = false; this.init( this.googlePayConfig, this.transactionInfo ); } @@ -177,39 +341,6 @@ class GooglepayButton { return true; } - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - const config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper, - }; - - if ( this.context === 'mini-cart' ) { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - - // Handle incompatible types. - if ( config.buttonStyle.type === 'buy' ) { - config.buttonStyle.type = 'pay'; - } - } - - if ( - [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 - ) { - config.ppcpButtonWrapper = - '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - initClient() { const callbacks = { onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), @@ -235,7 +366,8 @@ class GooglepayButton { } initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; if ( wrapper === ppcpButtonWrapper ) { throw new Error( @@ -272,77 +404,104 @@ class GooglepayButton { /** * Add a Google Pay purchase button - * @param baseCardPaymentMethod */ - addButton( baseCardPaymentMethod ) { + addButton() { this.log( 'addButton' ); - const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); + const insertButton = () => { + const wrapper = this.wrapperElement; + const baseCardPaymentMethod = this.baseCardPaymentMethod; + const { color, type, language } = this.buttonStyle; + const { shape, height } = this.ppcpStyle; - this.waitForWrapper( wrapper, () => { - // Prevent duplicate payment buttons. - this.removeButton(); + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); - jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); - - if ( ppcpStyle.height ) { - jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); + if ( height ) { + wrapper.style.height = `${ height }px`; } /** * @see https://developers.google.com/pay/api/web/reference/client#createButton */ - this.#button = this.paymentsClient.createButton( { + const button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', + buttonColor: color || 'black', + buttonType: type || 'pay', + buttonLocale: language || 'en', buttonSizeMode: 'fill', } ); - jQuery( wrapper ).append( this.#button ); + this.log( 'Insert Button', { wrapper, button } ); + + wrapper.replaceChildren( button ); + this.show(); + }; + + this.waitForWrapper( insertButton ); + } + + waitForWrapper( callback, delay = 100, timeout = 2000 ) { + let interval = 0; + const startTime = Date.now(); + + const stop = () => { + if ( interval ) { + clearInterval( interval ); + } + interval = 0; + }; + + const checkElement = () => { + if ( this.isPresent ) { + stop(); + callback(); + return; + } + + const timeElapsed = Date.now() - startTime; + + if ( timeElapsed > timeout ) { + stop(); + this.log( '!! Wrapper not found:', this.wrapperId ); + } + }; + + interval = setInterval( checkElement, delay ); + } + + /** + * Hides all wrappers that belong to this GooglePayButton instance. + */ + hide() { + this.log( 'Hide' ); + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; } ); } /** - * Removes the payment button that was injected via addButton() + * Ensures all wrapper elements of this GooglePayButton instance are visible. */ - removeButton() { - if ( ! this.#button ) { + show() { + if ( ! this.isPresent ) { + this.log( 'Cannot show button, wrapper is not present' ); return; } + this.log( 'Show' ); - this.#button.remove(); - this.#button = null; - } + // Classic Checkout: Make the Google Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-google-pay' ) + .forEach( ( el ) => el.remove() ); - addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { - const button = this.paymentsClient.createButton( { - onClick: this.onButtonClick.bind( this ), - allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', - buttonSizeMode: 'fill', + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; } ); - - wrapper.appendChild( button ); - } - - waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { - const startTime = Date.now(); - const interval = setInterval( () => { - const el = document.querySelector( selector ); - const timeElapsed = Date.now() - startTime; - - if ( el ) { - clearInterval( interval ); - callback( el ); - } else if ( timeElapsed > timeout ) { - clearInterval( interval ); - } - }, delay ); } //------------------------ diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index bd6faea79..98bff2c97 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -290,6 +290,7 @@ class Button implements ButtonInterface { $render_placeholder, function () { $this->googlepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -303,6 +304,7 @@ class Button implements ButtonInterface { $render_placeholder, function () { $this->googlepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -335,6 +337,21 @@ class Button implements ButtonInterface { + + Date: Tue, 30 Jul 2024 10:24:26 +0200 Subject: [PATCH 032/105] Google Pay: Prevent field validation from being triggered on checkout page load --- .../resources/js/Context/CheckoutHandler.js | 6 ++--- .../resources/js/GooglepayButton.js | 25 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index 251b4f3c4..82d04f578 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -4,7 +4,7 @@ import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; class CheckoutHandler extends BaseHandler { - transactionInfo() { + formValidator() { return new Promise( async ( resolve, reject ) => { try { const spinner = new Spinner(); @@ -23,7 +23,7 @@ class CheckoutHandler extends BaseHandler { : null; if ( ! formValidator ) { - resolve( super.transactionInfo() ); + resolve(); return; } @@ -42,7 +42,7 @@ class CheckoutHandler extends BaseHandler { reject(); } else { - resolve( super.transactionInfo() ); + resolve(); } } ); } catch ( error ) { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 87bc642f0..2c15571b3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -292,17 +292,24 @@ class GooglepayButton { onButtonClick() { this.log( 'onButtonClick', this.context ); - const paymentDataRequest = this.paymentDataRequest(); + this.contextHandler.formValidator().then( + () => { + window.ppcpFundingSource = 'googlepay'; - this.log( - 'onButtonClick: paymentDataRequest', - paymentDataRequest, - this.context + const paymentDataRequest = this.paymentDataRequest(); + + this.log( + 'onButtonClick: paymentDataRequest', + paymentDataRequest, + this.context + ); + + this.paymentsClient.loadPaymentData( paymentDataRequest ); + }, + () => { + console.error( '[GooglePayButton] Form validation failed.' ); + } ); - - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. - - this.paymentsClient.loadPaymentData( paymentDataRequest ); } paymentDataRequest() { From a301d901dd4a530032769e37f55f407d9e44ad37 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 30 Jul 2024 10:31:44 +0200 Subject: [PATCH 033/105] Google Pay: Update the formValidator name --- modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js | 2 +- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index 82d04f578..b4a07709e 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -4,7 +4,7 @@ import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; class CheckoutHandler extends BaseHandler { - formValidator() { + validateForm() { return new Promise( async ( resolve, reject ) => { try { const spinner = new Spinner(); diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 2c15571b3..4363c67be 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -292,7 +292,7 @@ class GooglepayButton { onButtonClick() { this.log( 'onButtonClick', this.context ); - this.contextHandler.formValidator().then( + this.contextHandler.validateForm().then( () => { window.ppcpFundingSource = 'googlepay'; From 19fd6c806d7ae148a2f1e730c5c6402d2bdcbdab Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 30 Jul 2024 11:37:03 +0200 Subject: [PATCH 034/105] Add free trial block checkout support for card payment --- modules/ppcp-blocks/package.json | 2 +- .../resources/js/Components/card-fields.js | 24 ++++++++-- .../resources/js/card-fields-config.js | 48 +++++++++++++++++++ modules/ppcp-blocks/yarn.lock | 18 +++---- 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 993b115e3..3398edd5e 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,7 +10,7 @@ "Edge >= 14" ], "dependencies": { - "@paypal/react-paypal-js": "^8.3.0", + "@paypal/react-paypal-js": "^8.5.0", "core-js": "^3.25.0", "react": "^17.0.0", "react-dom": "^17.0.0" diff --git a/modules/ppcp-blocks/resources/js/Components/card-fields.js b/modules/ppcp-blocks/resources/js/Components/card-fields.js index 4a1f25785..2726bc12e 100644 --- a/modules/ppcp-blocks/resources/js/Components/card-fields.js +++ b/modules/ppcp-blocks/resources/js/Components/card-fields.js @@ -7,7 +7,12 @@ import { } from '@paypal/react-paypal-js'; import { CheckoutHandler } from './checkout-handler'; -import { createOrder, onApprove } from '../card-fields-config'; +import { + createOrder, + onApprove, + createVaultSetupToken, + onApproveSavePayment, +} from '../card-fields-config'; import { cartHasSubscriptionProducts } from '../Helper/Subscription'; export function CardFields( { @@ -70,8 +75,21 @@ export function CardFields( { } } > { console.error( err ); } } diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js index 5383729dc..c4accf471 100644 --- a/modules/ppcp-blocks/resources/js/card-fields-config.js +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -44,3 +44,51 @@ export async function onApprove( data ) { console.error( err ); } ); } + +export async function createVaultSetupToken() { + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); + + return fetch( config.scriptData.ajax.create_setup_token.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: config.scriptData.ajax.create_setup_token.nonce, + payment_method: 'ppcp-credit-card-gateway', + } ), + } ) + .then( ( response ) => response.json() ) + .then( ( result ) => { + console.log( result ); + return result.data.id; + } ) + .catch( ( err ) => { + console.error( err ); + } ); +} + +export async function onApproveSavePayment( { vaultSetupToken } ) { + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); + + const response = await fetch( + config.scriptData.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: config.scriptData.ajax.create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + is_free_trial_cart: config.scriptData.is_free_trial_cart, + } ), + } + ); + + const result = await response.json(); + if ( result.success !== true ) { + console.error( result ); + } +} diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index 1e812a4ae..1debd6e24 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1005,19 +1005,19 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@paypal/paypal-js@^8.0.5": - version "8.0.5" - resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.5.tgz#77bc461b4d1e5a2c6f081269e3ef0b2e3331a68c" - integrity sha512-yQNV7rOILeaVCNU4aVDRPqEnbIlzfxgQfFsxzsBuZW1ouqRD/4kYBWJDzczCiscSr2xOeA/Pkm7e3a9fRfnuMQ== +"@paypal/paypal-js@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.1.0.tgz#4e7d10e0a0b4164985029cfdac748e5694d117e9" + integrity sha512-f64bom5xYwmxyeKPJUFS/XpM0tXojQEgjRIADPqe1R9WmK+PFqL4SEkT85cGU0ZXLVx4EGbjwREHhqEOR+OstA== dependencies: promise-polyfill "^8.3.0" -"@paypal/react-paypal-js@^8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.3.0.tgz#a103080b752766b8ff59b8620887abf802e1a01b" - integrity sha512-SX17d2h1CMNFGI+wtjb329AEDaBR8Ziy2LCV076eDcY1Q0MFKRkfQ/v0HOAvZtk3sJoydRmYez2pq47BRblwqQ== +"@paypal/react-paypal-js@^8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.5.0.tgz#cf17483202c8fa7a33dae86798d50a102705f182" + integrity sha512-YIAyLw4OiUoHHoUgXvibrBDdluzqnqVMGsJXyBcoOzlWHQIe5zhh8dgYezNNRXjXwy6t22YmPljjw7lr+eD9cw== dependencies: - "@paypal/paypal-js" "^8.0.5" + "@paypal/paypal-js" "^8.1.0" "@paypal/sdk-constants" "^1.0.122" "@paypal/sdk-constants@^1.0.122": From f3b4a317c831fb4e085bf581b4f29cec155830e6 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 30 Jul 2024 12:40:37 +0200 Subject: [PATCH 035/105] Add free trial block checkout card support for guest users --- .../resources/js/card-fields-config.js | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js index c4accf471..8932ef916 100644 --- a/modules/ppcp-blocks/resources/js/card-fields-config.js +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -71,21 +71,31 @@ export async function createVaultSetupToken() { export async function onApproveSavePayment( { vaultSetupToken } ) { const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); - const response = await fetch( - config.scriptData.ajax.create_payment_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: config.scriptData.ajax.create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - is_free_trial_cart: config.scriptData.is_free_trial_cart, - } ), - } - ); + let endpoint = + config.scriptData.ajax.create_payment_token_for_guest.endpoint; + let bodyContent = { + nonce: config.scriptData.ajax.create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + }; + + if ( config.scriptData.user.is_logged_in ) { + endpoint = config.scriptData.ajax.create_payment_token.endpoint; + + bodyContent = { + nonce: config.scriptData.ajax.create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + is_free_trial_cart: config.scriptData.is_free_trial_cart, + }; + } + + const response = await fetch( endpoint, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( bodyContent ), + } ); const result = await response.json(); if ( result.success !== true ) { From ef6632c85faac999754be89daeca2d74a2a7de21 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 12:50:41 +0200 Subject: [PATCH 036/105] =?UTF-8?q?=E2=9C=A8=20Add=20new=20isEligible=20fl?= =?UTF-8?q?ag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flag is set once Google’s PaymentClient responds to the isReadyToPay() request and controls the rendering of the button --- .../resources/js/GooglepayButton.js | 133 +++++++++++++----- 1 file changed, 95 insertions(+), 38 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9571e9735..364a6f4e3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -39,10 +39,8 @@ const CONTEXT = { MiniCart: 'mini-cart', BlockCart: 'cart-block', BlockCheckout: 'checkout-block', - Preview: 'preview', - // Block editor contexts. - Blocks: [ 'cart-block', 'checkout-block' ], - // Custom gateway contexts. + Preview: 'preview', // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], // Custom gateway contexts. Gateways: [ 'checkout', 'pay-now' ], }; @@ -57,6 +55,14 @@ class GooglepayButton { */ #isInitialized = false; + /** + * Whether the current client support the payment button. + * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` + * + * @type {boolean} + */ + #isEligible = false; + /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -267,6 +273,29 @@ class GooglepayButton { return this.wrapperElement instanceof HTMLElement; } + /** + * Whether the browser can accept Google Pay payments. + * + * @return {boolean} True, if payments are technically possible. + */ + get isEligible() { + return this.#isEligible; + } + + /** + * Changes the eligibility state of this button component. + * + * @param {boolean} newState Whether the browser can accept payments. + */ + set isEligible( newState ) { + if ( newState === this.#isEligible ) { + return; + } + + this.#isEligible = newState; + this.refresh(); + } + init( config, transactionInfo ) { if ( this.#isInitialized ) { return; @@ -299,12 +328,22 @@ class GooglepayButton { ) ) .then( ( response ) => { - if ( response.result ) { - this.addButton(); - } + this.log( 'PaymentsClient.isReadyToPay response:', response ); + + /** + * In case the button wrapper element is not present in the DOM yet, wait for it + * to appear. Only proceed, if a button wrapper is found on this page. + * + * Not sure if this is needed, or if we can directly test for `this.isPresent` + * without any delay. + */ + this.waitForWrapper( () => { + this.isEligible = !! response.result; + } ); } ) - .catch( function ( err ) { + .catch( ( err ) => { console.error( err ); + this.isEligible = false; } ); } @@ -397,6 +436,8 @@ class GooglepayButton { } buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { + this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods ); + return Object.assign( {}, baseRequest, { allowedPaymentMethods, } ); @@ -408,43 +449,47 @@ class GooglepayButton { addButton() { this.log( 'addButton' ); - const insertButton = () => { - const wrapper = this.wrapperElement; - const baseCardPaymentMethod = this.baseCardPaymentMethod; - const { color, type, language } = this.buttonStyle; - const { shape, height } = this.ppcpStyle; + const wrapper = this.wrapperElement; + const baseCardPaymentMethod = this.baseCardPaymentMethod; + const { color, type, language } = this.buttonStyle; + const { shape, height } = this.ppcpStyle; - wrapper.classList.add( - `ppcp-button-${ shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); - if ( height ) { - wrapper.style.height = `${ height }px`; - } + if ( height ) { + wrapper.style.height = `${ height }px`; + } - /** - * @see https://developers.google.com/pay/api/web/reference/client#createButton - */ - const button = this.paymentsClient.createButton( { - onClick: this.onButtonClick.bind( this ), - allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: color || 'black', - buttonType: type || 'pay', - buttonLocale: language || 'en', - buttonSizeMode: 'fill', - } ); + /** + * @see https://developers.google.com/pay/api/web/reference/client#createButton + */ + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: color || 'black', + buttonType: type || 'pay', + buttonLocale: language || 'en', + buttonSizeMode: 'fill', + } ); - this.log( 'Insert Button', { wrapper, button } ); + this.log( 'Insert Button', { wrapper, button } ); - wrapper.replaceChildren( button ); - this.show(); - }; - - this.waitForWrapper( insertButton ); + wrapper.replaceChildren( button ); } + /** + * Waits for the current button's wrapper element to become available in the DOM. + * + * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. + * + * @param {Function} callback Function to call when the wrapper element was detected. Only called on success. + * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec + * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec + */ waitForWrapper( callback, delay = 100, timeout = 2000 ) { let interval = 0; const startTime = Date.now(); @@ -474,6 +519,18 @@ class GooglepayButton { interval = setInterval( checkElement, delay ); } + /** + * Refreshes the payment button on the page. + */ + refresh() { + if ( this.isEligible && this.isPresent ) { + this.show(); + this.addButton(); + } else { + this.hide(); + } + } + /** * Hides all wrappers that belong to this GooglePayButton instance. */ From 490cd1958b7b64f75765862c4c7aef67b5239fa0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 13:51:16 +0200 Subject: [PATCH 037/105] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20config=20ob?= =?UTF-8?q?ject=20to=20appropriate=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/modules/Helper/CheckoutMethodState.js | 24 +++++++++++++++++++ .../resources/js/GooglepayButton.js | 19 +-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 3e284c8ef..aecf434f4 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -6,6 +6,30 @@ export const PaymentMethods = { GOOGLEPAY: 'ppcp-googlepay', }; +/** + * List of valid context values that the button can have. + * + * The "context" describes the placement or page where a payment button might be displayed. + * + * @type {Object} + */ +export const PaymentContext = { + Product: 'product', + Cart: 'cart', + Checkout: 'checkout', + PayNow: 'pay-now', + MiniCart: 'mini-cart', + BlockCart: 'cart-block', + BlockCheckout: 'checkout-block', + Preview: 'preview', + + // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], + + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], +}; + export const ORDER_BUTTON_SELECTOR = '#place_order'; export const getCurrentPaymentMethod = () => { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 364a6f4e3..b682ce7d3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -6,6 +6,7 @@ import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/But import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +import { PaymentContext as CONTEXT } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -26,24 +27,6 @@ import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper * @property {string} language - The locale; an empty string will apply the user-agent's language. */ -/** - * List of valid context values that the button can have. - * - * @type {Object} - */ -const CONTEXT = { - Product: 'product', - Cart: 'cart', - Checkout: 'checkout', - PayNow: 'pay-now', - MiniCart: 'mini-cart', - BlockCart: 'cart-block', - BlockCheckout: 'checkout-block', - Preview: 'preview', // Block editor contexts. - Blocks: [ 'cart-block', 'checkout-block' ], // Custom gateway contexts. - Gateways: [ 'checkout', 'pay-now' ], -}; - class GooglepayButton { #wrapperId = ''; #ppcpButtonWrapperId = ''; From 0888c696ff5fbaae6bb75ee66d16c6b820ca8510 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 13:55:24 +0200 Subject: [PATCH 038/105] =?UTF-8?q?=E2=9C=A8=20Sync=20gateway=20visibility?= =?UTF-8?q?=20via=20custom=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContextBootstrap/CheckoutBootstap.js | 21 +++++- .../resources/js/GooglepayButton.js | 69 ++++++++++++------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 33d1ecfd3..7a016fd9e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -68,6 +68,7 @@ class CheckoutBootstap { jQuery( document.body ).on( 'updated_checkout payment_method_selected', () => { + this.invalidatePaymentMethods(); this.updateUi(); } ); @@ -174,6 +175,14 @@ class CheckoutBootstap { ); } + invalidatePaymentMethods() { + /** + * Custom JS event to notify other modules that the payment button on the checkout page + * has become irrelevant or invalid. + */ + document.body.dispatchEvent( new Event( 'ppcp_invalidate_methods' ) ); + } + updateUi() { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; @@ -232,9 +241,17 @@ class CheckoutBootstap { } } - setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod ); + /** + * Custom JS event that is observed by the relevant payment gateway. + * + * Dynamic part of the event name is the payment method ID, for example + * "ppcp-credit-card-gateway" or "ppcp-googlepay" + */ + document.body.dispatchEvent( + new Event( `ppcp_render_method-${ currentPaymentMethod }` ) + ); - jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); + document.body.dispatchEvent( new Event( 'ppcp_checkout_rendered' ) ); } shouldShowMessages() { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index b682ce7d3..e6aa1851b 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -6,7 +6,10 @@ import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/But import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; -import { PaymentContext as CONTEXT } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { + PaymentMethods, + PaymentContext as CONTEXT, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -69,6 +72,10 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + this.hide = this.hide.bind( this ); + this.show = this.show.bind( this ); + this.refresh = this.refresh.bind( this ); + this.log( 'Create instance' ); } @@ -388,34 +395,50 @@ class GooglepayButton { } initEventHandlers() { - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + if ( CONTEXT.Gateways.includes( this.context ) ) { + document.body.addEventListener( + 'ppcp_invalidate_methods', + this.hide ); - } - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + document.body.addEventListener( + `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, + this.refresh ); - }; + } else { + /** + * Review: The following logic appears to be unnecessary. Is it still required? + */ - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; + + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); } - ); - syncButtonVisibility(); + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + + syncButtonVisibility(); + } } buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { From 592920b20bef8a6721213dfe4430dc6796e82d57 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 31 Jul 2024 11:24:12 +0400 Subject: [PATCH 039/105] Update ACDC signup URLs --- modules/ppcp-wc-gateway/services.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..48958ae37 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1410,10 +1410,10 @@ return array( return $label; }, 'wcgateway.enable-dcc-url-sandbox' => static function ( ContainerInterface $container ): string { - return 'https://www.sandbox.paypal.com/bizsignup/entry/product/ppcp'; + return 'https://www.sandbox.paypal.com/bizsignup/entry?product=ppcp'; }, 'wcgateway.enable-dcc-url-live' => static function ( ContainerInterface $container ): string { - return 'https://www.paypal.com/bizsignup/entry/product/ppcp'; + return 'https://www.paypal.com/bizsignup/entry?product=ppcp'; }, 'wcgateway.enable-pui-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/entry?country.x=DE&product=payment_methods&capabilities=PAY_UPON_INVOICE'; From 000cc8ed9eb6e1baee7a14731a231ff2fe3292ad Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 31 Jul 2024 11:53:13 +0400 Subject: [PATCH 040/105] Change Apple Pay and Google Pay default button labels to plain --- modules/ppcp-applepay/extensions.php | 2 +- modules/ppcp-googlepay/extensions.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index daf5cf2ea..8f9d84b5a 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -269,7 +269,7 @@ return array( 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), - 'default' => 'pay', + 'default' => 'plain', 'options' => PropertiesDictionary::button_types(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'dcc', diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index e0a01901f..df012fb8a 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -166,7 +166,7 @@ return array( 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), - 'default' => 'pay', + 'default' => 'plain', 'options' => PropertiesDictionary::button_types(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'dcc', From bfef725f6e5d8e492b0dd55dccb8607adf8f8b1d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 11:49:10 +0200 Subject: [PATCH 041/105] =?UTF-8?q?=E2=9C=A8=20Site-wide=20HTML=20tag=20wi?= =?UTF-8?q?th=20AXO=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..e92e01c61 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -194,9 +194,18 @@ class AxoModule implements ModuleInterface { add_action( 'wp_head', - function () { + function () use ( $c ) { // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript echo ''; + + // Add meta tag to allow feature-detection of the site's AXO payment state. + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + printf( + '', + $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ? 'enabled' : 'disabled' + ); } ); From be59960777da38e5cd5cead53177a418ed081477 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 31 Jul 2024 12:27:33 +0200 Subject: [PATCH 042/105] AXO: Temporarily hide the Fastlane payment method in the admin until the ACDC/Fastlane-sharing refactor is complete --- modules/ppcp-axo/src/AxoModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..0ab85a106 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -66,7 +66,7 @@ class AxoModule implements ModuleInterface { // Add the gateway in admin area. if ( is_admin() ) { - $methods[] = $gateway; + // $methods[] = $gateway; - Temporarily remove Fastlane from the payment gateway list in admin area. return $methods; } From 0e24db159ff63b803edcb64917f127e09796ea67 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 12:47:07 +0200 Subject: [PATCH 043/105] =?UTF-8?q?=E2=9C=A8=20Add=20feature-detection=20t?= =?UTF-8?q?ag=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index e92e01c61..359199041 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -202,9 +202,8 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - printf( - '', - $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ? 'enabled' : 'disabled' + $this->add_feature_detection_tag( + $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ); } ); @@ -405,4 +404,23 @@ class AxoModule implements ModuleInterface { // Exclude the Order Pay endpoint. return is_wc_endpoint_url( 'order-pay' ); } + + /** + * Outputs a meta tag to allow feature detection on certain pages. + * + * @param bool $axo_enabled Whether the gateway is enabled. + * @return void + */ + private function add_feature_detection_tag( bool $axo_enabled ) { + $show_tag = is_checkout() || is_cart() || is_shop(); + + if ( ! $show_tag ) { + return; + } + + printf( + '', + $axo_enabled ? 'enabled' : 'disabled' + ); + } } From dc2dbaed92eae93bdf2cb2209f19d8e7241c78aa Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 14:04:48 +0200 Subject: [PATCH 044/105] =?UTF-8?q?=E2=9C=A8=20Show=20Fastlane=20warning?= =?UTF-8?q?=20when=20DCC=20is=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/extensions.php | 1 + modules/ppcp-axo/services.php | 11 ++++- .../src/Helper/SettingsNoticeGenerator.php | 49 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 3bee1d391..5b2ac3214 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -119,6 +119,7 @@ return array( 'html' => implode( '', array( + $container->get( 'axo.settings-conflict-notice' ), $container->get( 'axo.shipping-config-notice' ), $container->get( 'axo.checkout-config-notice' ), $container->get( 'axo.incompatible-plugins-notice' ), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 31e448bfd..5b468835f 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -16,7 +16,6 @@ use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; -use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( @@ -164,6 +163,16 @@ return array( ); }, + 'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + return $settings_notice_generator->generate_settings_conflict_notice( $settings ); + }, + 'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php index f375e2943..503e135cb 100644 --- a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -11,11 +11,32 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo\Helper; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** * Class SettingsNoticeGenerator */ class SettingsNoticeGenerator { + /** + * Generates the full HTML of the notification. + * + * @param string $message HTML of the inner message contents. + * @param bool $is_error Whether the provided message is an error. Affects the notice color. + * + * @return string The full HTML code of the notification, or an empty string. + */ + private function render_notice( string $message, bool $is_error = false ) : string { + if ( ! $message ) { + return ''; + } + + return sprintf( + '

    %2$s

    ', + $is_error ? 'ppcp-notice-error' : '', + $message + ); + } /** * Generates the checkout notice. @@ -127,4 +148,32 @@ class SettingsNoticeGenerator { return '

    ' . $notice_content . '

    '; } + + /** + * Generates a warning notice with instructions on conflicting plugin-internal settings. + * + * @param Settings $settings The plugin settings container, which is checked for conflicting + * values. + * @return string + */ + public function generate_settings_conflict_notice( Settings $settings ) : string { + $notice_content = ''; + $is_dcc_enabled = false; + + try { + $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ); + // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } catch ( NotFoundException $ignored ) { + // Never happens. + } + + if ( ! $is_dcc_enabled ) { + $notice_content = __( + 'Warning: To enable Fastlane and accelerate payments, the Advanced Card Processing payment method must also be enabled.', + 'woocommerce-paypal-payments' + ); + } + + return $this->render_notice( $notice_content, true ); + } } From 7526189e407bb30062027af7b481220a77276850 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 11:48:52 +0200 Subject: [PATCH 045/105] AXO: Refactor the debug script in order to remove logger calls when logging is disabled --- modules/ppcp-axo/resources/js/Helper/Debug.js | 24 ++++++++++--------- modules/ppcp-axo/src/Assets/AxoManager.php | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-axo/resources/js/Helper/Debug.js b/modules/ppcp-axo/resources/js/Helper/Debug.js index 84cda012c..6345ee935 100644 --- a/modules/ppcp-axo/resources/js/Helper/Debug.js +++ b/modules/ppcp-axo/resources/js/Helper/Debug.js @@ -1,7 +1,19 @@ export function log( message, level = 'info' ) { const wpDebug = window.wc_ppcp_axo?.wp_debug; const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint; - if ( ! endpoint ) { + const loggingEnabled = window.wc_ppcp_axo?.logging_enabled; + + if ( wpDebug ) { + switch ( level ) { + case 'error': + console.error( `[AXO] ${ message }` ); + break; + default: + console.log( `[AXO] ${ message }` ); + } + } + + if ( ! endpoint || ! loggingEnabled ) { return; } @@ -15,15 +27,5 @@ export function log( message, level = 'info' ) { level, }, } ), - } ).then( () => { - if ( wpDebug ) { - switch ( level ) { - case 'error': - console.error( `[AXO] ${ message }` ); - break; - default: - console.log( `[AXO] ${ message }` ); - } - } } ); } diff --git a/modules/ppcp-axo/src/Assets/AxoManager.php b/modules/ppcp-axo/src/Assets/AxoManager.php index 363f0d092..2e7e60804 100644 --- a/modules/ppcp-axo/src/Assets/AxoManager.php +++ b/modules/ppcp-axo/src/Assets/AxoManager.php @@ -216,6 +216,7 @@ class AxoManager { 'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ), ), ), + 'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '', 'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, 'billing_email_button_text' => __( 'Continue', 'woocommerce-paypal-payments' ), ); From eab4f64a467bffb626899f462075161d9173a74f Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 18:44:31 +0200 Subject: [PATCH 046/105] AXO: Fix the conditions for rendering Fastlane when shipping config is incompatible --- modules/ppcp-axo/src/AxoModule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 6350b5adc..7491f1777 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -87,6 +87,10 @@ class AxoModule implements ModuleInterface { return $methods; } + if ( ! $this->is_compatible_shipping_config() ) { + return $methods; + } + $methods[] = $gateway; return $methods; }, @@ -150,7 +154,8 @@ class AxoModule implements ModuleInterface { // Check if the module is applicable, correct country, currency, ... etc. if ( ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) - || $subscription_helper->cart_contains_subscription() ) { + || $subscription_helper->cart_contains_subscription() + || ! $this->is_compatible_shipping_config() ) { return; } From 3488e6f6f38c0d136d3ce7db6c9f5fb72b4918f2 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 18:47:45 +0200 Subject: [PATCH 047/105] AXO: Simplify the is_compatible_shipping_config() condition --- modules/ppcp-axo/src/AxoModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 7491f1777..a6b9cb652 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -409,6 +409,6 @@ class AxoModule implements ModuleInterface { * @return bool */ private function is_compatible_shipping_config(): bool { - return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() ); + return ! wc_shipping_enabled() || ! wc_ship_to_billing_address_only(); } } From 6c4c31fbf01f6a9f4d3897187025d501f781eb18 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 22:25:46 +0200 Subject: [PATCH 048/105] AXO: Fix the main notice key in the display manager --- modules/ppcp-axo/extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5b2ac3214..636aee38e 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -83,7 +83,7 @@ return array( ->rule() ->condition_element( 'axo_enabled', '1' ) ->action_visible( 'axo_gateway_title' ) - ->action_visible( 'axo_checkout_config_notice' ) + ->action_visible( 'axo_main_notice' ) ->action_visible( 'axo_privacy' ) ->action_visible( 'axo_name_on_card' ) ->action_visible( 'axo_style_heading' ) From fe8101699f79320033ffd4a977781d65f9eea79d Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 2 Aug 2024 12:30:39 +0200 Subject: [PATCH 049/105] Fix the PayPal enabled settings key --- .megaignore | 1 + modules/ppcp-axo/src/AxoModule.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .megaignore diff --git a/.megaignore b/.megaignore new file mode 100644 index 000000000..6ad02d892 --- /dev/null +++ b/.megaignore @@ -0,0 +1 @@ +-s:* \ No newline at end of file diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 8612729a1..0c0576e55 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -77,7 +77,7 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; if ( ! $is_paypal_enabled || ! $is_dcc_enabled ) { @@ -152,7 +152,7 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false; $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); From 2815fcbb919dbccc9e2220034968700d538d39ef Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 2 Aug 2024 15:24:26 +0200 Subject: [PATCH 050/105] Fix phpcs --- modules/ppcp-button/src/Assets/SmartButton.php | 10 +++++----- .../src/Endpoint/CreatePaymentToken.php | 2 +- .../ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 43acaa81a..b80022a5e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1293,8 +1293,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'early_checkout_validation_enabled' => $this->early_validation_enabled, 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, 'user' => array( - 'is_logged' => is_user_logged_in(), - 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens(get_current_user_id()), + 'is_logged' => is_user_logged_in(), + 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens( get_current_user_id() ), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), @@ -2138,12 +2138,12 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages /** * Whether the given user has WC card payment tokens. * - * @param int $user_id + * @param int $user_id The user ID. * @return bool */ - private function user_has_wc_card_payment_tokens(int $user_id): bool { + private function user_has_wc_card_payment_tokens( int $user_id ): bool { $tokens = WC_Payment_Tokens::get_customer_tokens( $user_id, CreditCardGateway::ID ); - if($tokens) { + if ( $tokens ) { return true; } diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index acd4f988f..ae3d7dfa0 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -117,7 +117,7 @@ class CreatePaymentToken implements EndpointInterface { $wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result ); $is_free_trial_cart = $data['is_free_trial_cart'] ?? ''; - if($is_free_trial_cart === '1') { + if ( $is_free_trial_cart === '1' ) { WC()->session->set( 'ppcp_card_payment_token_for_free_trial', $wc_token_id ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 393328660..e60b9e1bd 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -436,7 +436,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $guest_card_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null; WC()->session->get( 'ppcp_guest_payment_for_free_trial', null ); - if($guest_card_payment_for_free_trial) { + if ( $guest_card_payment_for_free_trial ) { $customer_id = $guest_card_payment_for_free_trial->customer->id ?? ''; if ( $customer_id ) { update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id ); @@ -450,9 +450,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { } } - $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial' ) ?? null; WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); - if($card_payment_token_for_free_trial) { + if ( $card_payment_token_for_free_trial ) { $tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() ); foreach ( $tokens as $token ) { if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) { From 9da37a2cc68d4ab0ed1326d19af80cb21d1cb453 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 10:01:42 +0200 Subject: [PATCH 051/105] =?UTF-8?q?=E2=9C=A8=20Introduce=20new=20=E2=80=9C?= =?UTF-8?q?isVisible=E2=80=9D=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 171 ++++++++++-------- 1 file changed, 97 insertions(+), 74 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index e6aa1851b..0ec6d494f 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,8 +1,5 @@ /* global google */ -/* global jQuery */ -import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; @@ -49,6 +46,13 @@ class GooglepayButton { */ #isEligible = false; + /** + * Whether this button is visible. Modified by `show()` and `hide()` + * + * @type {boolean} + */ + #isVisible = false; + /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -72,8 +76,6 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.hide = this.hide.bind( this ); - this.show = this.show.bind( this ); this.refresh = this.refresh.bind( this ); this.log( 'Create instance' ); @@ -263,6 +265,34 @@ class GooglepayButton { return this.wrapperElement instanceof HTMLElement; } + /** + * The visibility state of the button. + * This flag does not reflect actual visibility on the page, but rather, if the button + * is intended/allowed to be displayed, in case all other checks pass. + * + * @return {boolean} True indicates, that the button can be displayed + */ + get isVisible() { + return this.#isVisible; + } + + /** + * Change the visibility of the button. + * + * A visible button does not always force the button to render on the page. It only means, that + * the button is allowed or not allowed to render, if certain other conditions are met. + * + * @param {boolean} newState Whether rendering the button is allowed. + */ + set isVisible( newState ) { + if ( this.#isVisible === newState ) { + return; + } + + this.#isVisible = newState; + this.refresh(); + } + /** * Whether the browser can accept Google Pay payments. * @@ -396,48 +426,46 @@ class GooglepayButton { initEventHandlers() { if ( CONTEXT.Gateways.includes( this.context ) ) { - document.body.addEventListener( - 'ppcp_invalidate_methods', - this.hide - ); + document.body.addEventListener( 'ppcp_invalidate_methods', () => { + this.isVisible = false; + } ); document.body.addEventListener( `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, - this.refresh + () => { + this.isVisible = true; + } ); } else { /** * Review: The following logic appears to be unnecessary. Is it still required? + * / + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); + } + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + syncButtonVisibility(); + // */ - - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` - ); - } - - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) - ); - }; - - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } - } - ); - - syncButtonVisibility(); } } @@ -482,7 +510,10 @@ class GooglepayButton { buttonSizeMode: 'fill', } ); - this.log( 'Insert Button', { wrapper, button } ); + this.log( 'Insert Button', { + wrapper, + button, + } ); wrapper.replaceChildren( button ); } @@ -492,7 +523,8 @@ class GooglepayButton { * * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. * - * @param {Function} callback Function to call when the wrapper element was detected. Only called on success. + * @param {Function} callback Function to call when the wrapper element was detected. Only + * called on success. * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec */ @@ -529,44 +561,35 @@ class GooglepayButton { * Refreshes the payment button on the page. */ refresh() { - if ( this.isEligible && this.isPresent ) { - this.show(); + const showButtonWrapper = () => { + this.log( 'Show' ); + + // Classic Checkout: Make the Google Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-google-pay' ) + .forEach( ( el ) => el.remove() ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; + } ); + }; + + const hideButtonWrapper = () => { + this.log( 'Hide' ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + }; + + if ( this.isVisible && this.isEligible && this.isPresent ) { + showButtonWrapper(); this.addButton(); } else { - this.hide(); + hideButtonWrapper(); } } - /** - * Hides all wrappers that belong to this GooglePayButton instance. - */ - hide() { - this.log( 'Hide' ); - this.allElements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - } - - /** - * Ensures all wrapper elements of this GooglePayButton instance are visible. - */ - show() { - if ( ! this.isPresent ) { - this.log( 'Cannot show button, wrapper is not present' ); - return; - } - this.log( 'Show' ); - - // Classic Checkout: Make the Google Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-google-pay' ) - .forEach( ( el ) => el.remove() ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'block'; - } ); - } - //------------------------ // Button click //------------------------ From f1f243505ce3fe2bcf2f21ebb7bf42523f987e9c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 2 Aug 2024 16:32:27 +0200 Subject: [PATCH 052/105] =?UTF-8?q?=E2=9C=A8=20Introduce=20a=20new=20Conso?= =?UTF-8?q?leLogger=20JS=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract debug logic to separate component --- .../js/modules/Helper/ConsoleLogger.js | 42 ++++++++++++++++++ .../resources/js/GooglepayButton.js | 43 ++++++------------- 2 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js b/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js new file mode 100644 index 000000000..689f07c0a --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js @@ -0,0 +1,42 @@ +/** + * Logs debug details to the console. + * + * A utility class that is used by payment buttons on the front-end, like the GooglePayButton. + */ +export default class ConsoleLogger { + /** + * The prefix to display before every log output. + * + * @type {string} + */ + #prefix = ''; + + /** + * Whether logging is enabled, disabled by default. + * + * @type {boolean} + */ + #enabled = false; + + constructor( ...prefixes ) { + if ( prefixes.length ) { + this.#prefix = `[${ prefixes.join( ' | ' ) }]`; + } + } + + set enabled( state ) { + this.#enabled = state; + } + + log( ...args ) { + if ( this.#enabled ) { + console.log( this.#prefix, ...args ); + } + } + + error( ...args ) { + if ( this.#enabled ) { + console.error( this.#prefix, ...args ); + } + } +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 0ec6d494f..a21f803a4 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,5 +1,6 @@ /* global google */ +import ConsoleLogger from '../../../ppcp-button/resources/js/modules/Helper/ConsoleLogger'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; @@ -28,6 +29,11 @@ import { */ class GooglepayButton { + /** + * @type {ConsoleLogger} + */ + #logger; + #wrapperId = ''; #ppcpButtonWrapperId = ''; @@ -66,7 +72,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { - this._initDebug( !! buttonConfig?.is_debug, context ); + this.#logger = new ConsoleLogger( 'GooglePayButton', context ); + this.#logger.enabled = !! buttonConfig?.is_debug; apmButtonsInit( ppcpConfig ); @@ -81,34 +88,12 @@ class GooglepayButton { this.log( 'Create instance' ); } - /** - * NOOP log function to avoid errors when debugging is disabled. - */ - log() {} + log( ...args ) { + this.#logger.log( ...args ); + } - /** - * Enables debugging tools, when the button's is_debug flag is set. - * - * @param {boolean} enableDebugging If debugging features should be enabled for this instance. - * @param {string} context Used to make the instance accessible via the global debug - * object. - * @private - */ - _initDebug( enableDebugging, context ) { - if ( ! enableDebugging || this.#isInitialized ) { - return; - } - - document.ppcpGooglepayButtons = document.ppcpGooglepayButtons || {}; - document.ppcpGooglepayButtons[ context ] = this; - - this.log = ( ...args ) => { - console.log( `[GooglePayButton | ${ context }]`, ...args ); - }; - - document.addEventListener( 'ppcp-googlepay-debug', () => { - this.log( this ); - } ); + error( ...args ) { + this.#logger.error( ...args ); } /** @@ -550,7 +535,7 @@ class GooglepayButton { if ( timeElapsed > timeout ) { stop(); - this.log( '!! Wrapper not found:', this.wrapperId ); + this.error( 'Wrapper not found:', this.wrapperId ); } }; From f69209b91ce6f4bd50bc5a17019e3bb21b84d1c6 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 2 Aug 2024 17:12:07 +0200 Subject: [PATCH 053/105] =?UTF-8?q?=E2=9C=A8=20New=20PaymentButton=20base?= =?UTF-8?q?=20class=20for=20APM=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This class is used to render buttons in the front end, and encapsulates logic that is shared between ApplePay and GooglePay buttons --- .../js/modules/Renderer/PaymentButton.js | 101 ++++++++++++++++++ .../resources/js/GooglepayButton.js | 59 ++++------ 2 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js new file mode 100644 index 000000000..8843ca662 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -0,0 +1,101 @@ +import ConsoleLogger from '../Helper/ConsoleLogger'; +import { apmButtonsInit } from '../Helper/ApmButtons'; + +/** + * Base class for APM payment buttons, like GooglePay and ApplePay. + * + * This class is not intended for the PayPal button. + */ +export default class PaymentButton { + /** + * @type {ConsoleLogger} + */ + #logger; + + /** + * Whether the payment button is initialized. + * + * @type {boolean} + */ + #isInitialized = false; + + /** + * The button's context. + */ + #context; + + #buttonConfig; + + #ppcpConfig; + + constructor( gatewayName, context, buttonConfig, ppcpConfig ) { + this.#logger = new ConsoleLogger( gatewayName, context ); + this.#logger.enabled = !! buttonConfig?.is_debug; + + this.#context = context; + this.#buttonConfig = buttonConfig; + this.#ppcpConfig = ppcpConfig; + + apmButtonsInit( ppcpConfig ); + } + + /** + * Whether the payment button was fully initialized. Read-only. + * + * @return {boolean} True indicates, that the button was fully initialized. + */ + get isInitialized() { + return this.#isInitialized; + } + + /** + * The button's context. Read-only. + * + * TODO: Convert the string to a context-object (primitive obsession smell) + * + * @return {string} The button context. + */ + get context() { + return this.#context; + } + + /** + * Log a debug detail to the browser console. + * + * @param {any} args + */ + log( ...args ) { + this.#logger.log( ...args ); + } + + /** + * Log an error message to the browser console. + * + * @param {any} args + */ + error( ...args ) { + this.#logger.error( ...args ); + } + + /** + * Must be named `init()` to simulate "protected" visibility: + * Since the derived class also implements a method with the same name, this method can only + * be called by the derived class, but not from any other code. + * + * @protected + */ + init() { + this.#isInitialized = true; + } + + /** + * Must be named `reinit()` to simulate "protected" visibility: + * Since the derived class also implements a method with the same name, this method can only + * be called by the derived class, but not from any other code. + * + * @protected + */ + reinit() { + this.#isInitialized = false; + } +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index a21f803a4..f778ff8e2 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,9 +1,8 @@ /* global google */ -import ConsoleLogger from '../../../ppcp-button/resources/js/modules/Helper/ConsoleLogger'; +import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; -import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; import { PaymentMethods, PaymentContext as CONTEXT, @@ -28,22 +27,10 @@ import { * @property {string} language - The locale; an empty string will apply the user-agent's language. */ -class GooglepayButton { - /** - * @type {ConsoleLogger} - */ - #logger; - +class GooglepayButton extends PaymentButton { #wrapperId = ''; #ppcpButtonWrapperId = ''; - /** - * Whether the payment button is initialized. - * - * @type {boolean} - */ - #isInitialized = false; - /** * Whether the current client support the payment button. * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` @@ -72,12 +59,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { - this.#logger = new ConsoleLogger( 'GooglePayButton', context ); - this.#logger.enabled = !! buttonConfig?.is_debug; + super( 'GooglePayButton', context, buttonConfig, ppcpConfig ); - apmButtonsInit( ppcpConfig ); - - this.context = context; this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -88,14 +71,6 @@ class GooglepayButton { this.log( 'Create instance' ); } - log( ...args ) { - this.#logger.log( ...args ); - } - - error( ...args ) { - this.#logger.error( ...args ); - } - /** * Determines if the current payment button should be rendered as a stand-alone gateway. * The return value `false` usually means, that the payment button is bundled with all available @@ -301,8 +276,21 @@ class GooglepayButton { this.refresh(); } - init( config, transactionInfo ) { - if ( this.#isInitialized ) { + init( config = null, transactionInfo = null ) { + if ( this.isInitialized ) { + return; + } + if ( config ) { + this.googlePayConfig = config; + } + if ( transactionInfo ) { + this.transactionInfo = transactionInfo; + } + + if ( ! this.googlePayConfig || ! this.transactionInfo ) { + this.error( + 'Init called without providing config or transactionInfo' + ); return; } @@ -314,11 +302,8 @@ class GooglepayButton { return; } - this.log( 'Init' ); - this.#isInitialized = true; + super.init(); - this.googlePayConfig = config; - this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; @@ -353,12 +338,12 @@ class GooglepayButton { } reinit() { - if ( ! this.googlePayConfig ) { + if ( ! this.isInitialized ) { return; } - this.#isInitialized = false; - this.init( this.googlePayConfig, this.transactionInfo ); + super.reinit(); + this.init(); } validateConfig() { From 3c200e408a8d957983046afa9870698f3f32f1fd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 5 Aug 2024 12:54:24 +0200 Subject: [PATCH 054/105] =?UTF-8?q?=F0=9F=9A=9A=20Move=20ConsoleLogger=20t?= =?UTF-8?q?o=20wc-gateway=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/modules/Renderer/PaymentButton.js | 2 +- .../resources/js/helper}/ConsoleLogger.js | 3 ++- .../resources/js/helper/preview-button.js | 10 ++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) rename modules/{ppcp-button/resources/js/modules/Helper => ppcp-wc-gateway/resources/js/helper}/ConsoleLogger.js (88%) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 8843ca662..20459f52e 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -1,4 +1,4 @@ -import ConsoleLogger from '../Helper/ConsoleLogger'; +import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger'; import { apmButtonsInit } from '../Helper/ApmButtons'; /** diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js similarity index 88% rename from modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js rename to modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js index 689f07c0a..4b8891247 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js @@ -1,5 +1,5 @@ /** - * Logs debug details to the console. + * Helper component to log debug details to the browser console. * * A utility class that is used by payment buttons on the front-end, like the GooglePayButton. */ @@ -30,6 +30,7 @@ export default class ConsoleLogger { log( ...args ) { if ( this.#enabled ) { + // eslint-disable-next-line console.log( this.#prefix, ...args ); } } diff --git a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js index 30b71f511..d9c4f8264 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js @@ -1,9 +1,11 @@ +/* global jQuery */ + /** * Returns a Map with all input fields that are relevant to render the preview of the * given payment button. * * @param {string} apmName - Value of the custom attribute `data-ppcp-apm-name`. - * @return {Map} + * @return {Map} List of input elements found on the current admin page. */ export function getButtonFormFields( apmName ) { const inputFields = document.querySelectorAll( @@ -28,9 +30,9 @@ export function getButtonFormFields( apmName ) { /** * Returns a function that triggers an update of the specified preview button, when invoked. - + * * @param {string} apmName - * @return {((object) => void)} + * @return {((object) => void)} Trigger-function; updates preview buttons when invoked. */ export function buttonRefreshTriggerFactory( apmName ) { const eventName = `ppcp_paypal_render_preview_${ apmName }`; @@ -44,7 +46,7 @@ export function buttonRefreshTriggerFactory( apmName ) { * Returns a function that gets the current form values of the specified preview button. * * @param {string} apmName - * @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} + * @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} Getter-function; returns preview config details when invoked. */ export function buttonSettingsGetterFactory( apmName ) { const fields = getButtonFormFields( apmName ); From bea17db5881dbc80c058fe7eca812f0d82ff4cdc Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 09:25:28 +0200 Subject: [PATCH 055/105] Use client credentials --- modules/ppcp-api-client/services.php | 14 +++++-- .../src/Authentication/ClientCredentials.php | 41 +++++++++++++++++++ .../src/Authentication/SdkClientToken.php | 26 ++++++------ .../src/Authentication/UserIdToken.php | 26 ++++++------ 4 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 modules/ppcp-api-client/src/Authentication/ClientCredentials.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 241240ce2..7cb890c18 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\Authentication\ClientCredentials; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; @@ -1656,18 +1657,23 @@ return array( return new PurchaseUnitSanitizer( $behavior, $line_name ); } ), + 'api.client-credentials' => static function(ContainerInterface $container): ClientCredentials { + return new ClientCredentials( + $container->get( 'wcgateway.settings' ) + ); + }, 'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken { return new UserIdToken( $container->get( 'api.host' ), - $container->get( 'api.bearer' ), - $container->get( 'woocommerce.logger.woocommerce' ) + $container->get( 'woocommerce.logger.woocommerce' ), + $container->get( 'api.client-credentials' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { return new SdkClientToken( $container->get( 'api.host' ), - $container->get( 'api.bearer' ), - $container->get( 'woocommerce.logger.woocommerce' ) + $container->get( 'woocommerce.logger.woocommerce' ), + $container->get( 'api.client-credentials' ) ); }, ); diff --git a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php new file mode 100644 index 000000000..7ec4628a5 --- /dev/null +++ b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php @@ -0,0 +1,41 @@ +settings = $settings; + } + + public function credentials(): string { + $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; + $client_secret = $this->settings->has( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : ''; + + return 'Basic ' . base64_encode($client_id . ':' . $client_secret); + } +} diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index ba0f3a373..c55847d30 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -27,13 +27,6 @@ class SdkClientToken { */ private $host; - /** - * The bearer. - * - * @var Bearer - */ - private $bearer; - /** * The logger. * @@ -41,21 +34,28 @@ class SdkClientToken { */ private $logger; + /** + * The client credentials. + * + * @var ClientCredentials + */ + private $client_credentials; + /** * SdkClientToken constructor. * * @param string $host The host. - * @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger. + * @param ClientCredentials $client_credentials The client credentials. */ public function __construct( string $host, - Bearer $bearer, - LoggerInterface $logger + LoggerInterface $logger, + ClientCredentials $client_credentials ) { $this->host = $host; - $this->bearer = $bearer; $this->logger = $logger; + $this->client_credentials = $client_credentials; } /** @@ -69,8 +69,6 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token( string $target_customer_id = '' ): string { - $bearer = $this->bearer->bearer(); - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); @@ -89,7 +87,7 @@ class SdkClientToken { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), + 'Authorization' => $this->client_credentials->credentials(), 'Content-Type' => 'application/x-www-form-urlencoded', ), ); diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index cae8cb58a..05dc62282 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -27,13 +27,6 @@ class UserIdToken { */ private $host; - /** - * The bearer. - * - * @var Bearer - */ - private $bearer; - /** * The logger. * @@ -41,21 +34,28 @@ class UserIdToken { */ private $logger; + /** + * The client credentials. + * + * @var ClientCredentials + */ + private $client_credentials; + /** * UserIdToken constructor. * * @param string $host The host. - * @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger. + * @param ClientCredentials $client_credentials The client credentials. */ public function __construct( string $host, - Bearer $bearer, - LoggerInterface $logger + LoggerInterface $logger, + ClientCredentials $client_credentials ) { $this->host = $host; - $this->bearer = $bearer; $this->logger = $logger; + $this->client_credentials = $client_credentials; } /** @@ -69,8 +69,6 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { - $bearer = $this->bearer->bearer(); - $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; if ( $target_customer_id ) { $url = add_query_arg( @@ -84,7 +82,7 @@ class UserIdToken { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), + 'Authorization' => $this->client_credentials->credentials(), 'Content-Type' => 'application/x-www-form-urlencoded', ), ); From 54657ed649352f3d5b8172c245bc4e9499bd9104 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 10:44:54 +0200 Subject: [PATCH 056/105] Add tokens cache --- modules/ppcp-api-client/services.php | 8 +++-- .../src/Authentication/SdkClientToken.php | 32 +++++++++++++---- .../src/Authentication/UserIdToken.php | 32 +++++++++++++---- modules/ppcp-onboarding/services.php | 6 ++-- .../src/Endpoint/LoginSellerEndpoint.php | 34 ++++++++++++++----- modules/ppcp-wc-gateway/services.php | 3 +- .../src/Settings/SettingsListener.php | 20 ++++++++++- 7 files changed, 107 insertions(+), 28 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 7cb890c18..dd2c786a1 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1657,7 +1657,7 @@ return array( return new PurchaseUnitSanitizer( $behavior, $line_name ); } ), - 'api.client-credentials' => static function(ContainerInterface $container): ClientCredentials { + 'api.client-credentials' => static function( ContainerInterface $container ): ClientCredentials { return new ClientCredentials( $container->get( 'wcgateway.settings' ) ); @@ -1666,14 +1666,16 @@ return array( return new UserIdToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.client-credentials' ) + $container->get( 'api.client-credentials' ), + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { return new SdkClientToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.client-credentials' ) + $container->get( 'api.client-credentials' ), + new Cache( 'ppcp-client-credentials-cache' ) ); }, ); diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index c55847d30..2e1f3a7ca 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WP_Error; /** @@ -20,6 +21,8 @@ class SdkClientToken { use RequestTrait; + const CACHE_KEY = 'sdk-client-token-key'; + /** * The host. * @@ -41,21 +44,31 @@ class SdkClientToken { */ private $client_credentials; + /** + * The cache. + * + * @var Cache + */ + private $cache; + /** * SdkClientToken constructor. * - * @param string $host The host. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials. + * @param Cache $cache The cache. */ public function __construct( string $host, LoggerInterface $logger, - ClientCredentials $client_credentials + ClientCredentials $client_credentials, + Cache $cache ) { - $this->host = $host; - $this->logger = $logger; + $this->host = $host; + $this->logger = $logger; $this->client_credentials = $client_credentials; + $this->cache = $cache; } /** @@ -69,6 +82,10 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token( string $target_customer_id = '' ): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); @@ -103,6 +120,9 @@ class SdkClientToken { throw new PayPalApiException( $json, $status_code ); } - return $json->access_token; + $access_token = $json->access_token; + $this->cache->set( self::CACHE_KEY, $access_token ); + + return $access_token; } } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 05dc62282..2d18d0507 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WP_Error; /** @@ -20,6 +21,8 @@ class UserIdToken { use RequestTrait; + const CACHE_KEY = 'user-id-token-key'; + /** * The host. * @@ -41,21 +44,31 @@ class UserIdToken { */ private $client_credentials; + /** + * The cache. + * + * @var Cache + */ + private $cache; + /** * UserIdToken constructor. * - * @param string $host The host. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials. + * @param Cache $cache The cache. */ public function __construct( string $host, LoggerInterface $logger, - ClientCredentials $client_credentials + ClientCredentials $client_credentials, + Cache $cache ) { - $this->host = $host; - $this->logger = $logger; + $this->host = $host; + $this->logger = $logger; $this->client_credentials = $client_credentials; + $this->cache = $cache; } /** @@ -69,6 +82,10 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; if ( $target_customer_id ) { $url = add_query_arg( @@ -98,6 +115,9 @@ class UserIdToken { throw new PayPalApiException( $json, $status_code ); } - return $json->id_token; + $id_token = $json->id_token; + $this->cache->set( self::CACHE_KEY, $id_token ); + + return $id_token; } } diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index b76519f62..e7f493999 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -189,9 +189,8 @@ return array( $login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' ); $partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' ); $settings = $container->get( 'wcgateway.settings' ); - $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $cache = new Cache( 'ppcp-paypal-bearer' ); + $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new LoginSellerEndpoint( $request_data, $login_seller_production, @@ -199,7 +198,8 @@ return array( $partner_referrals_data, $settings, $cache, - $logger + $logger, + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { diff --git a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php index 20ab7b95a..2a30ad6b1 100644 --- a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\Onboarding\Endpoint; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData; @@ -76,6 +78,13 @@ class LoginSellerEndpoint implements EndpointInterface { */ protected $logger; + /** + * The client credentials cache. + * + * @var Cache + */ + private $client_credentials_cache; + /** * LoginSellerEndpoint constructor. * @@ -86,6 +95,7 @@ class LoginSellerEndpoint implements EndpointInterface { * @param Settings $settings The Settings. * @param Cache $cache The Cache. * @param LoggerInterface $logger The logger. + * @param Cache $client_credentials_cache The client credentials cache. */ public function __construct( RequestData $request_data, @@ -94,16 +104,18 @@ class LoginSellerEndpoint implements EndpointInterface { PartnerReferralsData $partner_referrals_data, Settings $settings, Cache $cache, - LoggerInterface $logger + LoggerInterface $logger, + Cache $client_credentials_cache ) { - $this->request_data = $request_data; - $this->login_seller_production = $login_seller_production; - $this->login_seller_sandbox = $login_seller_sandbox; - $this->partner_referrals_data = $partner_referrals_data; - $this->settings = $settings; - $this->cache = $cache; - $this->logger = $logger; + $this->request_data = $request_data; + $this->login_seller_production = $login_seller_production; + $this->login_seller_sandbox = $login_seller_sandbox; + $this->partner_referrals_data = $partner_referrals_data; + $this->settings = $settings; + $this->cache = $cache; + $this->logger = $logger; + $this->client_credentials_cache = $client_credentials_cache; } /** @@ -175,6 +187,12 @@ class LoginSellerEndpoint implements EndpointInterface { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } + if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } wp_schedule_single_event( time() + 5, diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..52a41576e 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -366,7 +366,8 @@ return array( $container->get( 'api.partner_merchant_id-production' ), $container->get( 'api.partner_merchant_id-sandbox' ), $container->get( 'api.endpoint.billing-agreements' ), - $logger + $logger, + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 9986d5edc..e7e39a9e6 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; @@ -164,6 +166,13 @@ class SettingsListener { */ private $logger; + /** + * The client credentials cache. + * + * @var Cache + */ + private $client_credentials_cache; + /** * SettingsListener constructor. * @@ -183,6 +192,7 @@ class SettingsListener { * @param string $partner_merchant_id_sandbox Partner merchant ID sandbox. * @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint. * @param ?LoggerInterface $logger The logger. + * @param Cache $client_credentials_cache The client credentials cache. */ public function __construct( Settings $settings, @@ -200,7 +210,8 @@ class SettingsListener { string $partner_merchant_id_production, string $partner_merchant_id_sandbox, BillingAgreementsEndpoint $billing_agreements_endpoint, - LoggerInterface $logger = null + LoggerInterface $logger = null, + Cache $client_credentials_cache ) { $this->settings = $settings; @@ -219,6 +230,7 @@ class SettingsListener { $this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox; $this->billing_agreements_endpoint = $billing_agreements_endpoint; $this->logger = $logger ?: new NullLogger(); + $this->client_credentials_cache = $client_credentials_cache; } /** @@ -490,6 +502,12 @@ class SettingsListener { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } + if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } if ( $this->pui_status_cache->has( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ) ) { $this->pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ); From bc58fce8a0853178fa04bf9398f3d89320e4af1b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 6 Aug 2024 10:56:37 +0200 Subject: [PATCH 057/105] Apple Pay: Fix the shipping callback --- .../ppcp-applepay/resources/js/Context/BaseHandler.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index f763ac5d9..00d7bd463 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,6 +1,5 @@ import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; -import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription'; class BaseHandler { constructor( buttonConfig, ppcpConfig ) { @@ -24,7 +23,7 @@ class BaseHandler { } shippingAllowed() { - return this.buttonConfig.product.needsShipping; + return this.buttonConfig.product.needShipping; } transactionInfo() { @@ -76,13 +75,6 @@ class BaseHandler { document.querySelector( '.woocommerce-notices-wrapper' ) ); } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) - ); - } } export default BaseHandler; From 9c380917c55ac75c4d394264da9c9dd2f705c8b0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:14:46 +0200 Subject: [PATCH 058/105] Fix phpunit --- tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php index c34146f3e..c747b19b8 100644 --- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php +++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php @@ -43,6 +43,7 @@ class SettingsListenerTest extends ModularTestCase $billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class); $logger = Mockery::mock(LoggerInterface::class); + $client_credentials_cache = Mockery::mock(Cache::class); $testee = new SettingsListener( $settings, @@ -60,7 +61,8 @@ class SettingsListenerTest extends ModularTestCase '', '', $billing_agreement_endpoint, - $logger + $logger, + $client_credentials_cache ); $_GET['section'] = PayPalGateway::ID; From 86860d4fd6c285d2497cc0b6f5458a89516b24c3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:23:50 +0200 Subject: [PATCH 059/105] Fix phpunit --- tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php index c747b19b8..efa1b7de6 100644 --- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php +++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php @@ -96,6 +96,9 @@ class SettingsListenerTest extends ModularTestCase ->andReturn(false); $dcc_status_cache->shouldReceive('has') ->andReturn(false); + $client_credentials_cache->shouldReceive('has')->andReturn(true); + $client_credentials_cache->shouldReceive('delete'); + $testee->listen(); } From c68db5c1a11f961ad7890fb9695e58e33edc594c Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:35:28 +0200 Subject: [PATCH 060/105] Fix phpcs --- .../src/Authentication/ClientCredentials.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php index 7ec4628a5..08d37bead 100644 --- a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php +++ b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Authentication; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** * Class ClientCredentials @@ -28,14 +29,21 @@ class ClientCredentials { * * @param Settings $settings The settings. */ - public function __construct(Settings $settings) { + public function __construct( Settings $settings ) { $this->settings = $settings; } + /** + * Returns encoded client credentials. + * + * @return string + * @throws NotFoundException If setting does not found. + */ public function credentials(): string { - $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; + $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; $client_secret = $this->settings->has( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : ''; - return 'Basic ' . base64_encode($client_id . ':' . $client_secret); + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + return 'Basic ' . base64_encode( $client_id . ':' . $client_secret ); } } From 22c2b5afd4703ae02f602dcf99d18b72cacf61fb Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 12:04:58 +0200 Subject: [PATCH 061/105] Delete transients stored while user is logged in --- modules/ppcp-api-client/services.php | 7 +++++-- modules/ppcp-api-client/src/ApiModule.php | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index dd2c786a1..7f62da5f6 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1662,12 +1662,15 @@ return array( $container->get( 'wcgateway.settings' ) ); }, + 'api.client-credentials-cache' => static function( ContainerInterface $container ): Cache { + return new Cache( 'ppcp-client-credentials-cache' ); + }, 'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken { return new UserIdToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.client-credentials' ), - new Cache( 'ppcp-client-credentials-cache' ) + $container->get( 'api.client-credentials-cache' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { @@ -1675,7 +1678,7 @@ return array( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.client-credentials' ), - new Cache( 'ppcp-client-credentials-cache' ) + $container->get( 'api.client-credentials-cache' ) ); }, ); diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 42bc1f117..5c3ec56bd 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -10,6 +10,9 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; @@ -94,6 +97,21 @@ class ApiModule implements ModuleInterface { 10, 2 ); + + add_action( + 'wp_logout', + function() use ( $c ) { + $client_credentials_cache = $c->get( 'api.client-credentials-cache' ); + assert( $client_credentials_cache instanceof Cache ); + + if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } + } + ); } /** From a1413782e98dd78ccd2cecb66d056d7f3917a311 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 12:32:40 +0200 Subject: [PATCH 062/105] Invalidate cache when switching user --- .../src/Authentication/SdkClientToken.php | 15 +++++++++++++-- .../src/Authentication/UserIdToken.php | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 2e1f3a7ca..85a447176 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -83,7 +83,12 @@ class SdkClientToken { */ public function sdk_client_token( string $target_customer_id = '' ): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); + $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; + $access_token = $this->cache->get( self::CACHE_KEY )['access_token'] ?? ''; + + if ( $user_id === get_current_user_id() && $access_token ) { + return $access_token; + } } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized @@ -121,7 +126,13 @@ class SdkClientToken { } $access_token = $json->access_token; - $this->cache->set( self::CACHE_KEY, $access_token ); + + $data = array( + 'access_token' => $access_token, + 'user_id' => get_current_user_id(), + ); + + $this->cache->set( self::CACHE_KEY, $data ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 2d18d0507..c60801dd5 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -83,7 +83,12 @@ class UserIdToken { */ public function id_token( string $target_customer_id = '' ): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); + $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; + $id_token = $this->cache->get( self::CACHE_KEY )['id_token'] ?? ''; + + if ( $user_id === get_current_user_id() && $id_token ) { + return $id_token; + } } $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; @@ -116,7 +121,13 @@ class UserIdToken { } $id_token = $json->id_token; - $this->cache->set( self::CACHE_KEY, $id_token ); + + $data = array( + 'id_token' => $id_token, + 'user_id' => get_current_user_id(), + ); + + $this->cache->set( self::CACHE_KEY, $data ); return $id_token; } From b42e9436ae88ed159b645b65f07cfdd4cccc8320 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 15:30:50 +0200 Subject: [PATCH 063/105] Add id token cache key for each user --- modules/ppcp-api-client/src/ApiModule.php | 10 ++----- .../src/Authentication/SdkClientToken.php | 30 +++---------------- .../src/Authentication/UserIdToken.php | 16 ++-------- modules/ppcp-axo/src/AxoModule.php | 10 +------ .../js/modules/Helper/ScriptLoading.js | 5 +++- .../src/Endpoint/LoginSellerEndpoint.php | 3 -- .../src/Settings/SettingsListener.php | 3 -- 7 files changed, 15 insertions(+), 62 deletions(-) diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 5c3ec56bd..2d63bd80f 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -10,7 +10,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; -use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; @@ -100,15 +99,12 @@ class ApiModule implements ModuleInterface { add_action( 'wp_logout', - function() use ( $c ) { + function( int $user_id ) use ( $c ) { $client_credentials_cache = $c->get( 'api.client-credentials-cache' ); assert( $client_credentials_cache instanceof Cache ); - if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } - if ( $client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { - $client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY . '-' . (string) $user_id ) ) { + $client_credentials_cache->delete( UserIdToken::CACHE_KEY . '-' . (string) $user_id ); } } ); diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 85a447176..6236869e2 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -72,23 +72,16 @@ class SdkClientToken { } /** - * Returns `sdk_client_token` which uniquely identifies the payer. - * - * @param string $target_customer_id Vaulted customer id. + * Returns the client token for SDK `data-sdk-client-token`. * * @return string * * @throws PayPalApiException If the request fails. * @throws RuntimeException If something unexpected happens. */ - public function sdk_client_token( string $target_customer_id = '' ): string { + public function sdk_client_token(): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; - $access_token = $this->cache->get( self::CACHE_KEY )['access_token'] ?? ''; - - if ( $user_id === get_current_user_id() && $access_token ) { - return $access_token; - } + $this->cache->get( self::CACHE_KEY ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized @@ -97,15 +90,6 @@ class SdkClientToken { $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=client_token&intent=sdk_init&domains[]=' . $domain; - if ( $target_customer_id ) { - $url = add_query_arg( - array( - 'target_customer_id' => $target_customer_id, - ), - $url - ); - } - $args = array( 'method' => 'POST', 'headers' => array( @@ -126,13 +110,7 @@ class SdkClientToken { } $access_token = $json->access_token; - - $data = array( - 'access_token' => $access_token, - 'user_id' => get_current_user_id(), - ); - - $this->cache->set( self::CACHE_KEY, $data ); + $this->cache->set( self::CACHE_KEY, $access_token ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index c60801dd5..3c215a11f 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -82,13 +82,8 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { - if ( $this->cache->has( self::CACHE_KEY ) ) { - $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; - $id_token = $this->cache->get( self::CACHE_KEY )['id_token'] ?? ''; - - if ( $user_id === get_current_user_id() && $id_token ) { - return $id_token; - } + if ( $this->cache->has( self::CACHE_KEY . '-' . (string) get_current_user_id() ) ) { + return $this->cache->get( self::CACHE_KEY . '-' . (string) get_current_user_id() ); } $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; @@ -122,12 +117,7 @@ class UserIdToken { $id_token = $json->id_token; - $data = array( - 'id_token' => $id_token, - 'user_id' => get_current_user_id(), - ); - - $this->cache->set( self::CACHE_KEY, $data ); + $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token ); return $id_token; } diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..c0fc34b24 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -280,15 +280,7 @@ class AxoModule implements ModuleInterface { array $localized_script_data ): array { try { - $target_customer_id = ''; - if ( is_user_logged_in() ) { - $target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true ); - if ( ! $target_customer_id ) { - $target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true ); - } - } - - $sdk_client_token = $api->sdk_client_token( $target_customer_id ); + $sdk_client_token = $api->sdk_client_token(); $localized_script_data['axo'] = array( 'sdk_client_token' => $sdk_client_token, ); diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 0aa70f793..588e14cd6 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -71,7 +71,10 @@ export const loadPaypalScript = ( config, onLoaded, onError = null ) => { } // Load PayPal script for special case with data-client-token - if ( config.data_client_id?.set_attribute ) { + if ( + config.data_client_id?.set_attribute && + config.vault_v3_enabled !== '1' + ) { dataClientIdAttributeHandler( scriptOptions, config.data_client_id, diff --git a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php index 2a30ad6b1..e1d23234f 100644 --- a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php @@ -187,9 +187,6 @@ class LoginSellerEndpoint implements EndpointInterface { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } - if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); } diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index e7e39a9e6..6c01830d6 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -502,9 +502,6 @@ class SettingsListener { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } - if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); } From b85a16abda79b2db715f8ac0b78e43e6a261f9b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 15:59:54 +0200 Subject: [PATCH 064/105] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20button=20ev?= =?UTF-8?q?ent=20dispatcher=20to=20helper=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContextBootstrap/CheckoutBootstap.js | 13 +++-- .../js/modules/Helper/PaymentButtonHelpers.js | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 7a016fd9e..d679a7f21 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -7,6 +7,10 @@ import { PaymentMethods, } from '../Helper/CheckoutMethodState'; import BootstrapHelper from '../Helper/BootstrapHelper'; +import { + ButtonEvents, + dispatchButtonEvent, +} from '../Helper/PaymentButtonHelpers'; class CheckoutBootstap { constructor( gateway, renderer, spinner, errorHandler ) { @@ -180,7 +184,7 @@ class CheckoutBootstap { * Custom JS event to notify other modules that the payment button on the checkout page * has become irrelevant or invalid. */ - document.body.dispatchEvent( new Event( 'ppcp_invalidate_methods' ) ); + dispatchButtonEvent( { event: ButtonEvents.INVALIDATE } ); } updateUi() { @@ -247,9 +251,10 @@ class CheckoutBootstap { * Dynamic part of the event name is the payment method ID, for example * "ppcp-credit-card-gateway" or "ppcp-googlepay" */ - document.body.dispatchEvent( - new Event( `ppcp_render_method-${ currentPaymentMethod }` ) - ); + dispatchButtonEvent( { + event: ButtonEvents.RENDER, + paymentMethod: currentPaymentMethod, + } ); document.body.dispatchEvent( new Event( 'ppcp_checkout_rendered' ) ); } diff --git a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js new file mode 100644 index 000000000..19ecfc001 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js @@ -0,0 +1,48 @@ +/** + * Helper function used by PaymentButton instances. + * + * @file + */ + +/** + * Collection of recognized event names for payment button events. + * + * @type {Object} + */ +export const ButtonEvents = Object.freeze( { + INVALIDATE: 'ppcp_invalidate_methods', + RENDER: 'ppcp_render_method', + REDRAW: 'ppcp_redraw_method', +} ); + +/** + * Verifies if the given event name is a valid Payment Button event. + * + * @param {string} event - The event name to verify. + * @return {boolean} True, if the event name is valid. + */ +export function isValidButtonEvent( event ) { + const buttonEventValues = Object.values( ButtonEvents ); + + return buttonEventValues.includes( event ); +} + +/** + * Dispatches a payment button event. + * + * @param {Object} options - The options for dispatching the event. + * @param {string} options.event - Event to dispatch. + * @param {string} [options.paymentMethod] - Optional. Name of payment method, to target a specific button only. + * @throws {Error} Throws an error if the event is invalid. + */ +export function dispatchButtonEvent( { event, paymentMethod = '' } ) { + if ( ! isValidButtonEvent( event ) ) { + throw new Error( `Invalid event: ${ event }` ); + } + + const fullEventName = paymentMethod + ? `${ event }-${ paymentMethod }` + : event; + + document.body.dispatchEvent( new Event( fullEventName ) ); +} From fc805a4369c5e4bb35a133af765a7730e74af8d2 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 17:45:53 +0200 Subject: [PATCH 065/105] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20most=20of?= =?UTF-8?q?=20the=20display=20logic=20to=20base=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PaymentButton base class now handles display logic that is shared between different APMs --- .../js/modules/Helper/PaymentButtonHelpers.js | 69 +++ .../js/modules/Renderer/PaymentButton.js | 418 +++++++++++++++- .../resources/js/GooglepayButton.js | 459 ++---------------- modules/ppcp-googlepay/src/Assets/Button.php | 7 +- 4 files changed, 539 insertions(+), 414 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js index 19ecfc001..f9a066a23 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js @@ -15,6 +15,54 @@ export const ButtonEvents = Object.freeze( { REDRAW: 'ppcp_redraw_method', } ); +/** + * + * @param {string} defaultId - Default wrapper ID. + * @param {string} miniCartId - Wrapper inside the mini-cart. + * @param {string} smartButtonId - ID of the smart button wrapper. + * @param {string} blockId - Block wrapper ID (express checkout, block cart). + * @param {string} gatewayId - Gateway wrapper ID (classic checkout). + * @return {{MiniCart, Gateway, Block, SmartButton, Default}} List of all wrapper IDs, by context. + */ +export function combineWrapperIds( + defaultId = '', + miniCartId = '', + smartButtonId = '', + blockId = '', + gatewayId = '' +) { + const sanitize = ( id ) => id.replace( /^#/, '' ); + + return { + Default: sanitize( defaultId ), + SmartButton: sanitize( smartButtonId ), + Block: sanitize( blockId ), + Gateway: sanitize( gatewayId ), + MiniCart: sanitize( miniCartId ), + }; +} + +/** + * Returns full payment button styles by combining the global ppcpConfig with + * payment-method-specific styling provided via buttonConfig. + * + * @param {Object} ppcpConfig - Global plugin configuration. + * @param {Object} buttonConfig - Payment method specific configuration. + * @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context. + */ +export function combineStyles( ppcpConfig, buttonConfig ) { + return { + Default: { + ...ppcpConfig.style, + ...buttonConfig.style, + }, + MiniCart: { + ...ppcpConfig.mini_cart_style, + ...buttonConfig.mini_cart_style, + }, + }; +} + /** * Verifies if the given event name is a valid Payment Button event. * @@ -46,3 +94,24 @@ export function dispatchButtonEvent( { event, paymentMethod = '' } ) { document.body.dispatchEvent( new Event( fullEventName ) ); } + +/** + * Adds an event listener for the provided button event. + * + * @param {Object} options - The options for the event listener. + * @param {string} options.event - Event to observe. + * @param {string} [options.paymentMethod] - The payment method name (optional). + * @param {Function} options.callback - The callback function to execute when the event is triggered. + * @throws {Error} Throws an error if the event is invalid. + */ +export function observeButtonEvent( { event, paymentMethod = '', callback } ) { + if ( ! isValidButtonEvent( event ) ) { + throw new Error( `Invalid event: ${ event }` ); + } + + const fullEventName = paymentMethod + ? `${ event }-${ paymentMethod }` + : event; + + document.body.addEventListener( fullEventName, callback ); +} diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 20459f52e..8275d1ee4 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -1,5 +1,30 @@ import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger'; import { apmButtonsInit } from '../Helper/ApmButtons'; +import { PaymentContext } from '../Helper/CheckoutMethodState'; +import { + ButtonEvents, + dispatchButtonEvent, + observeButtonEvent, +} from '../Helper/PaymentButtonHelpers'; + +/** + * Collection of all available styling options for this button. + * + * @typedef {Object} StylesCollection + * @property {string} Default - Default button styling. + * @property {string} MiniCart - Styles for mini-cart button. + */ + +/** + * Collection of all available wrapper IDs that are possible for the button. + * + * @typedef {Object} WrapperCollection + * @property {string} Default - Default button wrapper. + * @property {string} Gateway - Wrapper for separate gateway. + * @property {string} Block - Wrapper for block checkout button. + * @property {string} MiniCart - Wrapper for mini-cart button. + * @property {string} SmartButton - Wrapper for smart button container. + */ /** * Base class for APM payment buttons, like GooglePay and ApplePay. @@ -12,6 +37,11 @@ export default class PaymentButton { */ #logger; + /** + * @type {string} + */ + #methodId; + /** * Whether the payment button is initialized. * @@ -21,27 +51,105 @@ export default class PaymentButton { /** * The button's context. + * + * @type {string} */ #context; + /** + * Object containing the IDs of all possible wrapper elements that might contain this + * button; only one wrapper is relevant, depending on the value of the context. + * + * @type {Object} + */ + #wrappers; + + /** + * @type {StylesCollection} + */ + #styles; + + /** + * APM relevant configuration; e.g., configuration of the GooglePay button + */ #buttonConfig; + /** + * Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc. + */ #ppcpConfig; - constructor( gatewayName, context, buttonConfig, ppcpConfig ) { - this.#logger = new ConsoleLogger( gatewayName, context ); + /** + * Whether the current browser/website support the payment method. + * + * @type {boolean} + */ + #isEligible = false; + + /** + * Whether this button is visible. Modified by `show()` and `hide()` + * + * @type {boolean} + */ + #isVisible = true; + + /** + * The currently visible payment button. + * + * @see {PaymentButton.insertButton} + * @type {HTMLElement|null} + */ + #button = null; + + /** + * Initialize the payment button instance. + * + * @param {string} methodId - Payment method ID (slug, e.g., "ppcp-googlepay"). + * @param {string} context - Button context name. + * @param {WrapperCollection} wrappers - Button wrapper IDs, by context. + * @param {StylesCollection} styles - Button styles, by context. + * @param {Object} buttonConfig - Payment button specific configuration. + * @param {Object} ppcpConfig - Plugin wide configuration object. + */ + constructor( + methodId, + context, + wrappers, + styles, + buttonConfig, + ppcpConfig + ) { + const methodName = methodId.replace( /^ppcp?-/, '' ); + + this.#methodId = methodId; + + this.#logger = new ConsoleLogger( methodName, context ); this.#logger.enabled = !! buttonConfig?.is_debug; this.#context = context; + this.#wrappers = wrappers; + this.#styles = styles; this.#buttonConfig = buttonConfig; this.#ppcpConfig = ppcpConfig; apmButtonsInit( ppcpConfig ); + this.initEventListeners(); } /** - * Whether the payment button was fully initialized. Read-only. + * Internal ID of the payment gateway. * + * @readonly + * @return {string} The internal gateway ID. + */ + get methodId() { + return this.#methodId; + } + + /** + * Whether the payment button was fully initialized. + * + * @readonly * @return {boolean} True indicates, that the button was fully initialized. */ get isInitialized() { @@ -49,16 +157,188 @@ export default class PaymentButton { } /** - * The button's context. Read-only. + * The button's context. * * TODO: Convert the string to a context-object (primitive obsession smell) * + * @readonly * @return {string} The button context. */ get context() { return this.#context; } + /** + * Button wrapper details. + * + * @readonly + * @return {WrapperCollection} Wrapper IDs. + */ + get wrappers() { + return this.#wrappers; + } + + /** + * Returns the context-relevant button style object. + * + * @readonly + * @return {string} Styling options. + */ + get style() { + if ( PaymentContext.MiniCart === this.context ) { + return this.#styles.MiniCart; + } + + return this.#styles.Default; + } + + /** + * Returns the context-relevant wrapper ID. + * + * @readonly + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get wrapperId() { + if ( PaymentContext.MiniCart === this.context ) { + return this.wrappers.MiniCart; + } else if ( this.isSeparateGateway ) { + return this.wrappers.Gateway; + } else if ( PaymentContext.Blocks.includes( this.context ) ) { + return this.wrappers.Block; + } + + return this.wrappers.Default; + } + + /** + * Determines if the current payment button should be rendered as a stand-alone gateway. + * The return value `false` usually means, that the payment button is bundled with all available + * payment buttons. + * + * The decision depends on the button context (placement) and the plugin settings. + * + * @return {boolean} True, if the current button represents a stand-alone gateway. + */ + get isSeparateGateway() { + return ( + this.#buttonConfig.is_wc_gateway_enabled && + PaymentContext.Gateways.includes( this.context ) + ); + } + + /** + * Determines if the current button instance has valid and complete configuration details. + * Used during initialization to decide if the button can be initialized or should be skipped. + * + * Can be implemented by the derived class. + * + * @return {boolean} True indicates the config is valid and initialization can continue. + */ + get isConfigValid() { + return true; + } + + /** + * Whether the browser can accept this payment method. + * + * @return {boolean} True, if payments are technically possible. + */ + get isEligible() { + return this.#isEligible; + } + + /** + * Changes the eligibility state of this button component. + * + * @param {boolean} newState Whether the browser can accept payments. + */ + set isEligible( newState ) { + if ( newState === this.#isEligible ) { + return; + } + + this.#isEligible = newState; + this.triggerRedraw(); + } + + /** + * The visibility state of the button. + * This flag does not reflect actual visibility on the page, but rather, if the button + * is intended/allowed to be displayed, in case all other checks pass. + * + * @return {boolean} True indicates, that the button can be displayed. + */ + get isVisible() { + return this.#isVisible; + } + + /** + * Change the visibility of the button. + * + * A visible button does not always force the button to render on the page. It only means, that + * the button is allowed or not allowed to render, if certain other conditions are met. + * + * @param {boolean} newState Whether rendering the button is allowed. + */ + set isVisible( newState ) { + if ( this.#isVisible === newState ) { + return; + } + + this.#isVisible = newState; + this.triggerRedraw(); + } + + /** + * Returns the HTML element that wraps the current button + * + * @readonly + * @return {HTMLElement|null} The wrapper element, or null. + */ + get wrapperElement() { + return document.getElementById( this.wrapperId ); + } + + /** + * Checks whether the main button-wrapper is present in the current DOM. + * + * @readonly + * @return {boolean} True, if the button context (wrapper element) is found. + */ + get isPresent() { + return this.wrapperElement instanceof HTMLElement; + } + + /** + * Returns an array of HTMLElements that belong to the payment button. + * + * @readonly + * @return {HTMLElement[]} List of payment button wrapper elements. + */ + get allElements() { + const selectors = []; + + // Payment button (Pay now, smart button block) + selectors.push( `#${ this.wrapperId }` ); + + // Block Checkout: Express checkout button. + if ( PaymentContext.Blocks.includes( this.context ) ) { + selectors.push( `#${ this.wrappers.Block }` ); + } + + // Classic Checkout: Separate gateway. + if ( this.isSeparateGateway ) { + selectors.push( + `.wc_payment_method.payment_method_${ this.methodId }` + ); + } + + this.log( 'Wrapper Elements:', selectors ); + return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => + Array.from( document.querySelectorAll( selector ) ) + ); + } + /** * Log a debug detail to the browser console. * @@ -98,4 +378,134 @@ export default class PaymentButton { reinit() { this.#isInitialized = false; } + + triggerRedraw() { + dispatchButtonEvent( { + event: ButtonEvents.REDRAW, + paymentMethod: this.methodId, + } ); + } + + /** + * Attaches event listeners to show or hide the payment button when needed. + */ + initEventListeners() { + // Refresh the button - this might show, hide or re-create the payment button. + observeButtonEvent( { + event: ButtonEvents.REDRAW, + paymentMethod: this.methodId, + callback: () => this.refresh(), + } ); + + // Events relevant for buttons inside a payment gateway. + if ( PaymentContext.Gateways.includes( this.context ) ) { + // Hide the button right after the user selected _any_ gateway. + observeButtonEvent( { + event: ButtonEvents.INVALIDATE, + callback: () => ( this.isVisible = false ), + } ); + + // Show the button (again) when the user selected the current gateway. + observeButtonEvent( { + event: ButtonEvents.RENDER, + paymentMethod: this.methodId, + callback: () => ( this.isVisible = true ), + } ); + } + } + + /** + * Refreshes the payment button on the page. + */ + refresh() { + const showButtonWrapper = () => { + this.log( 'Show' ); + + const styleSelectors = `style[data-hide-gateway="${ this.methodId }"]`; + + document + .querySelectorAll( styleSelectors ) + .forEach( ( el ) => el.remove() ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; + } ); + }; + + const hideButtonWrapper = () => { + this.log( 'Hide' ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + }; + + // Refresh or hide the actual payment button. + if ( this.isVisible ) { + this.addButton(); + } else { + this.removeButton(); + } + + // Show the wrapper or gateway entry, i.e. add space for the button. + if ( this.isEligible && this.isPresent ) { + showButtonWrapper(); + } else { + hideButtonWrapper(); + } + } + + /** + * Prepares the button wrapper element and inserts the provided payment button into the DOM. + * + * @param {HTMLElement} button - The button element to inject. + */ + insertButton( button ) { + if ( ! this.isPresent ) { + return; + } + + if ( this.#button ) { + this.#button.remove(); + } + + this.#button = button; + this.log( 'addButton', button ); + + const wrapper = this.wrapperElement; + const { shape, height } = this.style; + const methodSlug = this.methodId.replace( /^ppcp?-/, '' ); + + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + `ppcp-button-${ methodSlug }` + ); + + if ( height ) { + wrapper.style.height = `${ height }px`; + } + + wrapper.appendChild( button ); + } + + /** + * Removes the payment button from the DOM. + */ + removeButton() { + if ( ! this.isPresent ) { + return; + } + + this.log( 'removeButton' ); + + if ( this.#button ) { + this.#button.remove(); + } + this.#button = null; + + const wrapper = this.wrapperElement; + + wrapper.innerHTML = ''; + } } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index f778ff8e2..347eb819b 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,12 +1,13 @@ /* global google */ +import { + combineStyles, + combineWrapperIds, +} from '../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers'; import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; -import { - PaymentMethods, - PaymentContext as CONTEXT, -} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -28,24 +29,6 @@ import { */ class GooglepayButton extends PaymentButton { - #wrapperId = ''; - #ppcpButtonWrapperId = ''; - - /** - * Whether the current client support the payment button. - * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` - * - * @type {boolean} - */ - #isEligible = false; - - /** - * Whether this button is visible. Modified by `show()` and `hide()` - * - * @type {boolean} - */ - #isVisible = false; - /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -59,221 +42,54 @@ class GooglepayButton extends PaymentButton { ppcpConfig, contextHandler ) { - super( 'GooglePayButton', context, buttonConfig, ppcpConfig ); + const wrappers = combineWrapperIds( + buttonConfig.button.wrapper, + buttonConfig.button.mini_cart_wrapper, + ppcpConfig.button.wrapper, + 'express-payment-method-ppcp-googlepay', + 'ppc-button-ppcp-googlepay' + ); + + console.log( ppcpConfig.button, buttonConfig.button ); + + const styles = combineStyles( ppcpConfig.button, buttonConfig.button ); + + if ( 'buy' === styles.MiniCart.type ) { + styles.MiniCart.type = 'pay'; + } + + super( + PaymentMethods.GOOGLEPAY, + context, + wrappers, + styles, + buttonConfig, + ppcpConfig + ); - this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.refresh = this.refresh.bind( this ); - this.log( 'Create instance' ); } /** - * Determines if the current payment button should be rendered as a stand-alone gateway. - * The return value `false` usually means, that the payment button is bundled with all available - * payment buttons. - * - * The decision depends on the button context (placement) and the plugin settings. - * - * @return {boolean} True, if the current button represents a stand-alone gateway. + * @inheritDoc */ - get isSeparateGateway() { - return ( - this.buttonConfig.is_wc_gateway_enabled && - CONTEXT.Gateways.includes( this.context ) - ); - } + get isConfigValid() { + const validEnvs = [ 'PRODUCTION', 'TEST' ]; - /** - * Returns the wrapper ID for the current button context. - * The ID varies for the MiniCart context. - * - * @return {string} The wrapper-element's ID (without the `#` prefix). - */ - get wrapperId() { - if ( ! this.#wrapperId ) { - let id; - - if ( CONTEXT.MiniCart === this.context ) { - id = this.buttonConfig.button.mini_cart_wrapper; - } else if ( this.isSeparateGateway ) { - id = 'ppc-button-ppcp-googlepay'; - } else { - id = this.buttonConfig.button.wrapper; - } - - this.#wrapperId = id.replace( /^#/, '' ); + if ( ! validEnvs.includes( this.buttonConfig.environment ) ) { + this.error( 'Invalid environment.', this.buttonConfig.environment ); + return false; } - return this.#wrapperId; - } - - /** - * Returns the wrapper ID for the ppcpButton - * - * @return {string} The wrapper-element's ID (without the `#` prefix). - */ - get ppcpButtonWrapperId() { - if ( ! this.#ppcpButtonWrapperId ) { - let id; - - if ( CONTEXT.MiniCart === this.context ) { - id = this.ppcpConfig.button.mini_cart_wrapper; - } else if ( CONTEXT.Blocks.includes( this.context ) ) { - id = 'express-payment-method-ppcp-gateway-paypal'; - } else { - id = this.ppcpConfig.button.wrapper; - } - - this.#ppcpButtonWrapperId = id.replace( /^#/, '' ); + if ( ! typeof this.contextHandler?.validateContext() ) { + this.error( 'Invalid context handler.', this.contextHandler ); + return false; } - return this.#ppcpButtonWrapperId; - } - - /** - * Returns the context-relevant PPCP style object. - * The style for the MiniCart context can be different. - * - * The PPCP style are custom style options, that are provided by this plugin. - * - * @return {PPCPStyle} The style object. - */ - get ppcpStyle() { - if ( CONTEXT.MiniCart === this.context ) { - return this.ppcpConfig.button.mini_cart_style; - } - - return this.ppcpConfig.button.style; - } - - /** - * Returns default style options that are propagated to and rendered by the Google Pay button. - * - * These styles are the official style options provided by the Google Pay SDK. - * - * @return {GooglePayStyle} The style object. - */ - get buttonStyle() { - let style; - - if ( CONTEXT.MiniCart === this.context ) { - style = this.buttonConfig.button.mini_cart_style; - - // Handle incompatible types. - if ( style.type === 'buy' ) { - style.type = 'pay'; - } - } else { - style = this.buttonConfig.button.style; - } - - return { - type: style.type, - language: style.language, - color: style.color, - }; - } - - /** - * Returns the HTML element that wraps the current button - * - * @return {HTMLElement|null} The wrapper element, or null. - */ - get wrapperElement() { - return document.getElementById( this.wrapperId ); - } - - /** - * Returns an array of HTMLElements that belong to the payment button. - * - * @return {HTMLElement[]} List of payment button wrapper elements. - */ - get allElements() { - const selectors = []; - - // Payment button (Pay now, smart button block) - selectors.push( `#${ this.wrapperId }` ); - - // Block Checkout: Express checkout button. - if ( CONTEXT.Blocks.includes( this.context ) ) { - selectors.push( '#express-payment-method-ppcp-googlepay' ); - } - - // Classic Checkout: Google Pay gateway. - if ( CONTEXT.Gateways === this.context ) { - selectors.push( - '.wc_payment_method.payment_method_ppcp-googlepay' - ); - } - - this.log( 'Wrapper Elements:', selectors ); - return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => - Array.from( document.querySelectorAll( selector ) ) - ); - } - - /** - * Checks whether the main button-wrapper is present in the current DOM. - * - * @return {boolean} True, if the button context (wrapper element) is found. - */ - get isPresent() { - return this.wrapperElement instanceof HTMLElement; - } - - /** - * The visibility state of the button. - * This flag does not reflect actual visibility on the page, but rather, if the button - * is intended/allowed to be displayed, in case all other checks pass. - * - * @return {boolean} True indicates, that the button can be displayed - */ - get isVisible() { - return this.#isVisible; - } - - /** - * Change the visibility of the button. - * - * A visible button does not always force the button to render on the page. It only means, that - * the button is allowed or not allowed to render, if certain other conditions are met. - * - * @param {boolean} newState Whether rendering the button is allowed. - */ - set isVisible( newState ) { - if ( this.#isVisible === newState ) { - return; - } - - this.#isVisible = newState; - this.refresh(); - } - - /** - * Whether the browser can accept Google Pay payments. - * - * @return {boolean} True, if payments are technically possible. - */ - get isEligible() { - return this.#isEligible; - } - - /** - * Changes the eligibility state of this button component. - * - * @param {boolean} newState Whether the browser can accept payments. - */ - set isEligible( newState ) { - if ( newState === this.#isEligible ) { - return; - } - - this.#isEligible = newState; - this.refresh(); + return true; } init( config = null, transactionInfo = null ) { @@ -288,27 +104,24 @@ class GooglepayButton extends PaymentButton { } if ( ! this.googlePayConfig || ! this.transactionInfo ) { - this.error( - 'Init called without providing config or transactionInfo' - ); + this.error( 'Missing config or transactionInfo during init.' ); return; } - if ( ! this.validateConfig() ) { + if ( ! this.isConfigValid ) { return; } - if ( ! this.contextHandler.validateContext() ) { - return; - } - - super.init(); - this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; + super.init(); this.initClient(); - this.initEventHandlers(); + + if ( ! this.isPresent ) { + this.log( 'Payment wrapper not found', this.wrapperId ); + return; + } this.paymentsClient .isReadyToPay( @@ -319,17 +132,7 @@ class GooglepayButton extends PaymentButton { ) .then( ( response ) => { this.log( 'PaymentsClient.isReadyToPay response:', response ); - - /** - * In case the button wrapper element is not present in the DOM yet, wait for it - * to appear. Only proceed, if a button wrapper is found on this page. - * - * Not sure if this is needed, or if we can directly test for `this.isPresent` - * without any delay. - */ - this.waitForWrapper( () => { - this.isEligible = !! response.result; - } ); + this.isEligible = !! response.result; } ) .catch( ( err ) => { console.error( err ); @@ -346,30 +149,6 @@ class GooglepayButton extends PaymentButton { this.init(); } - validateConfig() { - if ( - [ 'PRODUCTION', 'TEST' ].indexOf( - this.buttonConfig.environment - ) === -1 - ) { - console.error( - '[GooglePayButton] Invalid environment.', - this.buttonConfig.environment - ); - return false; - } - - if ( ! this.contextHandler ) { - console.error( - '[GooglePayButton] Invalid context handler.', - this.contextHandler - ); - return false; - } - - return true; - } - initClient() { const callbacks = { onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), @@ -394,51 +173,6 @@ class GooglepayButton extends PaymentButton { } ); } - initEventHandlers() { - if ( CONTEXT.Gateways.includes( this.context ) ) { - document.body.addEventListener( 'ppcp_invalidate_methods', () => { - this.isVisible = false; - } ); - - document.body.addEventListener( - `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, - () => { - this.isVisible = true; - } - ); - } else { - /** - * Review: The following logic appears to be unnecessary. Is it still required? - * / - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` - ); - } - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) - ); - }; - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } - } - ); - syncButtonVisibility(); - // - */ - } - } - buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods ); @@ -448,25 +182,11 @@ class GooglepayButton extends PaymentButton { } /** - * Add a Google Pay purchase button + * Add a Google Pay purchase button. */ addButton() { - this.log( 'addButton' ); - - const wrapper = this.wrapperElement; const baseCardPaymentMethod = this.baseCardPaymentMethod; - const { color, type, language } = this.buttonStyle; - const { shape, height } = this.ppcpStyle; - - wrapper.classList.add( - `ppcp-button-${ shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); - - if ( height ) { - wrapper.style.height = `${ height }px`; - } + const { color, type, language } = this.style; /** * @see https://developers.google.com/pay/api/web/reference/client#createButton @@ -480,84 +200,7 @@ class GooglepayButton extends PaymentButton { buttonSizeMode: 'fill', } ); - this.log( 'Insert Button', { - wrapper, - button, - } ); - - wrapper.replaceChildren( button ); - } - - /** - * Waits for the current button's wrapper element to become available in the DOM. - * - * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. - * - * @param {Function} callback Function to call when the wrapper element was detected. Only - * called on success. - * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec - * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec - */ - waitForWrapper( callback, delay = 100, timeout = 2000 ) { - let interval = 0; - const startTime = Date.now(); - - const stop = () => { - if ( interval ) { - clearInterval( interval ); - } - interval = 0; - }; - - const checkElement = () => { - if ( this.isPresent ) { - stop(); - callback(); - return; - } - - const timeElapsed = Date.now() - startTime; - - if ( timeElapsed > timeout ) { - stop(); - this.error( 'Wrapper not found:', this.wrapperId ); - } - }; - - interval = setInterval( checkElement, delay ); - } - - /** - * Refreshes the payment button on the page. - */ - refresh() { - const showButtonWrapper = () => { - this.log( 'Show' ); - - // Classic Checkout: Make the Google Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-google-pay' ) - .forEach( ( el ) => el.remove() ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'block'; - } ); - }; - - const hideButtonWrapper = () => { - this.log( 'Hide' ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - }; - - if ( this.isVisible && this.isEligible && this.isPresent ) { - showButtonWrapper(); - this.addButton(); - } else { - hideButtonWrapper(); - } + this.insertButton( button ); } //------------------------ diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 98bff2c97..2f87a487d 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -345,9 +345,12 @@ class Button implements ButtonInterface { * @return void */ protected function hide_gateway_until_eligible() : void { + ?> - Date: Wed, 7 Aug 2024 09:51:12 +0200 Subject: [PATCH 066/105] Fix not returning the cached token --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 6236869e2..10b1eb0eb 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -81,7 +81,7 @@ class SdkClientToken { */ public function sdk_client_token(): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - $this->cache->get( self::CACHE_KEY ); + return $this->cache->get( self::CACHE_KEY ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized From 4f47b382c571ead74c5f5cf768e37ecae79389ea Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 10:36:05 +0200 Subject: [PATCH 067/105] Add token expiration --- .../ppcp-api-client/src/Authentication/SdkClientToken.php | 4 +++- modules/ppcp-api-client/src/Authentication/UserIdToken.php | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 10b1eb0eb..5031f1291 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -110,7 +110,9 @@ class SdkClientToken { } $access_token = $json->access_token; - $this->cache->set( self::CACHE_KEY, $access_token ); + $expires_in = (int) $json->expires_in; + + $this->cache->set( self::CACHE_KEY, $access_token, $expires_in ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 3c215a11f..9487a7238 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -115,9 +115,10 @@ class UserIdToken { throw new PayPalApiException( $json, $status_code ); } - $id_token = $json->id_token; + $id_token = $json->id_token; + $expires_in = (int) $json->expires_in; - $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token ); + $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token, $expires_in ); return $id_token; } From 7778cfafed613b6286d08e38ed9eb9b2ec1f48c0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 12:45:44 +0200 Subject: [PATCH 068/105] Revert #2283 --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 5031f1291..b85490acf 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -80,10 +80,6 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token(): string { - if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); - } - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); From 939991c42f794ff26813c6d811e412fe355793b4 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 12:47:11 +0200 Subject: [PATCH 069/105] Revert #2283 --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 4 ++++ .../src/SavePaymentMethodsModule.php | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index b85490acf..5031f1291 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -80,6 +80,10 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token(): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index db7335cf5..b667fb10b 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -84,10 +84,6 @@ class SavePaymentMethodsModule implements ModuleInterface { add_filter( 'woocommerce_paypal_payments_localized_script_data', function( array $localized_script_data ) use ( $c ) { - if ( ! is_user_logged_in() ) { - return $localized_script_data; - } - $api = $c->get( 'api.user-id-token' ); assert( $api instanceof UserIdToken ); From c0bd7a5c46be17e589f90a2cc73a272c9feb091e Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 14:24:44 +0200 Subject: [PATCH 070/105] Release 2.8.3-rc1 --- changelog.txt | 17 +++++++++++++++++ package.json | 2 +- readme.txt | 19 ++++++++++++++++++- woocommerce-paypal-payments.php | 4 ++-- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index aaf167948..e28d7a258 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ *** Changelog *** += 2.8.3 - xxxx-xx-xx = +* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474 +* Fix - Do not add tax info into order meta during order creation #2471 +* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425 +* Fix - PayPal js files loaded on non PayPal pages #2411 +* Fix - Google Pay: Fix the incorrect popup triggering #2414 +* Fix - Add tax configurator when programmatically creating WC orders #2431 +* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402 +* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334 +* Fix - Prevent displaying smart button multiple times on variable product page #2420 +* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404 +* Fix - Use client credentials for user tokens #2491 +* Fix - Apple Pay: Fix the shipping callback #2492 +* Enhancement - Separate Google Pay button for Classic Checkout #2430 +* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468 +* Enhancement - Add AMEX support for Advanced Card Processing in China #2469 + = 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 diff --git a/package.json b/package.json index 61c625ca9..72aad99db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "2.8.2", + "version": "2.8.3", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index e4404042e..fc1976e15 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple Requires at least: 5.3 Tested up to: 6.6 Requires PHP: 7.2 -Stable tag: 2.8.2 +Stable tag: 2.8.3 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -179,6 +179,23 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == += 2.8.3 - xxxx-xx-xx = +* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474 +* Fix - Do not add tax info into order meta during order creation #2471 +* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425 +* Fix - PayPal js files loaded on non PayPal pages #2411 +* Fix - Google Pay: Fix the incorrect popup triggering #2414 +* Fix - Add tax configurator when programmatically creating WC orders #2431 +* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402 +* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334 +* Fix - Prevent displaying smart button multiple times on variable product page #2420 +* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404 +* Fix - Use client credentials for user tokens #2491 +* Fix - Apple Pay: Fix the shipping callback #2492 +* Enhancement - Separate Google Pay button for Classic Checkout #2430 +* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468 +* Enhancement - Add AMEX support for Advanced Card Processing in China #2469 + = 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 2cdf4f8ed..fcf66ae12 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.2 + * Version: 2.8.3 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 @@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2024-07-17' ); +define( 'PAYPAL_INTEGRATION_DATE', '2024-08-07' ); define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); From 4b30449ddb4ce7145a4e69ace7f93d6bb38c25b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 7 Aug 2024 14:36:51 +0200 Subject: [PATCH 071/105] =?UTF-8?q?=F0=9F=A9=B9=20ConsoleLogger=20will=20a?= =?UTF-8?q?lways=20output=20error=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 4 ++-- .../resources/js/helper/ConsoleLogger.js | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 832768401..ec0f4bd7a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -227,8 +227,8 @@ class GooglepayButton extends PaymentButton { this.paymentsClient.loadPaymentData( paymentDataRequest ); }, - () => { - console.error( '[GooglePayButton] Form validation failed.' ); + ( reason ) => { + this.error( 'Form validation failed.', reason ); } ); } diff --git a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js index 4b8891247..c76aa8960 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js @@ -24,10 +24,20 @@ export default class ConsoleLogger { } } + /** + * Enable or disable logging. Only impacts `log()` output. + * + * @param {boolean} state True to enable log output. + */ set enabled( state ) { this.#enabled = state; } + /** + * Output log-level details to the browser console, if logging is enabled. + * + * @param {...any} args - All provided values are output to the browser console. + */ log( ...args ) { if ( this.#enabled ) { // eslint-disable-next-line @@ -35,9 +45,14 @@ export default class ConsoleLogger { } } + /** + * Generate an error message in the browser's console. + * + * Error messages are always output, even when logging is disabled. + * + * @param {...any} args - All provided values are output to the browser console. + */ error( ...args ) { - if ( this.#enabled ) { - console.error( this.#prefix, ...args ); - } + console.error( this.#prefix, ...args ); } } From 429568fbd9a1024399fe22d7369f0bb5726a4a83 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 7 Aug 2024 14:45:08 +0200 Subject: [PATCH 072/105] =?UTF-8?q?=F0=9F=94=A5=20Minor=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/modules/Renderer/PaymentButton.js | 4 ---- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 -- modules/ppcp-googlepay/src/Assets/Button.php | 3 +-- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 8275d1ee4..9e1def508 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -503,9 +503,5 @@ export default class PaymentButton { this.#button.remove(); } this.#button = null; - - const wrapper = this.wrapperElement; - - wrapper.innerHTML = ''; } } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index ec0f4bd7a..af4d977d5 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -50,8 +50,6 @@ class GooglepayButton extends PaymentButton { 'ppc-button-ppcp-googlepay' ); - console.log( ppcpConfig.button, buttonConfig.button ); - const styles = combineStyles( ppcpConfig.button, buttonConfig.button ); if ( 'buy' === styles.MiniCart.type ) { diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 2f87a487d..575def21f 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -339,13 +339,12 @@ class Button implements ButtonInterface { /** * Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout). - * The style is removed by `GooglepayButton.js` once the eligibility of the payment method + * The style is removed by `PaymentButton.js` once the eligibility of the payment method * is confirmed. * * @return void */ protected function hide_gateway_until_eligible() : void { - ?>