From ce5214970c268b989e8fbcf4f92e75b9cea808ee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 16 Jul 2024 14:33:03 +0200 Subject: [PATCH 001/119] 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 af286c8f823c2aaf7cee27f0ecf2b0c0c64fd446 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 16:43:51 +0400 Subject: [PATCH 002/119] Add needs shipping to classic cart --- modules/ppcp-button/resources/js/modules/Renderer/Renderer.js | 2 +- modules/ppcp-button/src/Assets/SmartButton.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 448432e3c..7d6139d4c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -139,7 +139,7 @@ class Renderer { }; // Check the condition and add the handler if needed - if ( this.defaultSettings.should_handle_shipping_in_paypal ) { + if ( this.defaultSettings.should_handle_shipping_in_paypal && this.defaultSettings.needShipping ) { options.onShippingOptionsChange = ( data, actions ) => { let shippingOptionsChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 4b309818e..02e01397e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1094,6 +1094,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $url_params = $this->url_params(); + $cart = WC()->cart; + $this->request_data->enqueue_nonce_fix(); $localize = array( 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ), @@ -1295,6 +1297,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), + 'needShipping' => $cart && $cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From c199ce1f8bc51dca7fae0f50487e0730a92fd7e3 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 16:44:35 +0400 Subject: [PATCH 003/119] Add needs shipping to block pages --- .../resources/js/checkout-block.js | 26 +++++++++++-------- .../ppcp-blocks/src/PayPalPaymentMethod.php | 2 ++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index bf36dcb65..d1b6ad990 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -227,7 +227,7 @@ const PayPalComponent = ( { throw new Error( config.scriptData.labels.error.generic ); } - if ( ! shouldHandleShippingInPayPal() ) { + if ( ! shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } else { setGotoContinuationOnError( true ); @@ -318,7 +318,7 @@ const PayPalComponent = ( { throw new Error( config.scriptData.labels.error.generic ); } - if ( ! shouldHandleShippingInPayPal() ) { + if ( ! shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } else { setGotoContinuationOnError( true ); @@ -364,16 +364,20 @@ const PayPalComponent = ( { }; const shouldHandleShippingInPayPal = () => { - if ( config.finalReviewEnabled ) { - return false; - } - - return ( - window.ppcpFundingSource !== 'venmo' || - ! config.scriptData.vaultingEnabled - ); + return shouldskipFinalConfirmation() && config.needShipping }; + const shouldskipFinalConfirmation = () => { + if ( config.finalReviewEnabled ) { + return false; + } + + return ( + window.ppcpFundingSource !== 'venmo' || + ! config.scriptData.vaultingEnabled + ); + }; + let handleShippingOptionsChange = null; let handleShippingAddressChange = null; let handleSubscriptionShippingOptionsChange = null; @@ -544,7 +548,7 @@ const PayPalComponent = ( { if ( config.scriptData.continuation ) { return true; } - if ( shouldHandleShippingInPayPal() ) { + if ( shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } return true; diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index d94eec53d..8da3ecefd 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -210,6 +210,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ public function get_payment_method_data() { $script_data = $this->smart_button()->script_data(); + $cart = WC()->cart; if ( isset( $script_data['continuation'] ) ) { $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() ); @@ -254,6 +255,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { ), ), 'scriptData' => $script_data, + 'needShipping' => $cart && $cart->needs_shipping(), ); } From 0e3c550082b707216b2f210cd8a0ec37c89d1428 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 17:29:13 +0400 Subject: [PATCH 004/119] Fix the coding styles --- modules/ppcp-blocks/src/PayPalPaymentMethod.php | 3 +-- modules/ppcp-button/src/Assets/SmartButton.php | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 8da3ecefd..1d0654bac 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -210,7 +210,6 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ public function get_payment_method_data() { $script_data = $this->smart_button()->script_data(); - $cart = WC()->cart; if ( isset( $script_data['continuation'] ) ) { $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() ); @@ -255,7 +254,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { ), ), 'scriptData' => $script_data, - 'needShipping' => $cart && $cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), ); } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 02e01397e..d4a775b8a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1094,8 +1094,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $url_params = $this->url_params(); - $cart = WC()->cart; - $this->request_data->enqueue_nonce_fix(); $localize = array( 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ), @@ -1297,7 +1295,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), - 'needShipping' => $cart && $cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From e7ece3d394a8481e6517dd8deac5ebfab4d977b8 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 17:37:38 +0400 Subject: [PATCH 005/119] Fix the coding styles --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index d4a775b8a..e54b3c97f 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1295,7 +1295,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), - 'needShipping' => WC()->cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From 0d7b557fa8e9df05b5e72d974558914ab8ae4e09 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 17 Jul 2024 19:06:40 +0200 Subject: [PATCH 006/119] 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 007/119] 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 008/119] 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 694caac891206339ef61d6b4f501865e266ee93f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 16:11:52 +0400 Subject: [PATCH 009/119] Patch the order with no shipping methods, instead of throwing an error --- .../ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php index be196fa03..c8e0417ab 100644 --- a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php +++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php @@ -97,15 +97,6 @@ class UpdateShippingEndpoint implements EndpointInterface { $pu = $this->purchase_unit_factory->from_wc_cart( null, true ); $pu_data = $pu->to_array(); - if ( ! isset( $pu_data['shipping']['options'] ) ) { - wp_send_json_error( - array( - 'message' => 'No shipping methods.', - ) - ); - return false; - } - // TODO: maybe should patch only if methods changed. // But it seems a bit difficult to detect, // e.g. ->order($id) may not have Shipping because we drop it when address or name are missing. From c3d46d89af1c1cb54cf511fc465cb0dedf153464 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:15:02 +0400 Subject: [PATCH 010/119] 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 011/119] 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 012/119] 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 d685de73b71519a40a929e844a97c3b3e83856fc Mon Sep 17 00:00:00 2001 From: George Burduli Date: Fri, 19 Jul 2024 18:25:28 +0400 Subject: [PATCH 013/119] Removed currency matrix from card fields module --- modules/ppcp-card-fields/services.php | 811 +----------------- .../src/Helper/CardFieldsApplies.php | 33 +- 2 files changed, 44 insertions(+), 800 deletions(-) diff --git a/modules/ppcp-card-fields/services.php b/modules/ppcp-card-fields/services.php index 85f8004b6..d9d6b4dd8 100644 --- a/modules/ppcp-card-fields/services.php +++ b/modules/ppcp-card-fields/services.php @@ -17,7 +17,7 @@ return array( $save_payment_methods_applies = $container->get( 'card-fields.helpers.save-payment-methods-applies' ); assert( $save_payment_methods_applies instanceof CardFieldsApplies ); - return $save_payment_methods_applies->for_country_currency(); + return $save_payment_methods_applies->for_country(); }, 'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies { return new CardFieldsApplies( @@ -30,782 +30,39 @@ return array( return apply_filters( 'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix', 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', - ), - 'CN' => 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', - ), - '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', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), + 'AU', + 'AT', + 'BE', + 'BG', + 'CA', + 'CN', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HU', + 'IE', + 'IT', + 'LV', + 'LI', + 'LT', + 'LU', + 'MT', + 'NL', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'GB', + 'US', + 'NO', ) ); }, diff --git a/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php b/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php index c5111b9bb..43e885e73 100644 --- a/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php +++ b/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php @@ -1,6 +1,6 @@ allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_country_matrix = $allowed_country_matrix; + $this->country = $country; } /** - * Returns whether Card Fields can be used in the current country and the current currency. + * Returns whether Card Fields can be used in the current country. * * @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_country_matrix, true ); } } From caee378ba4f8ba0a95aa97c5b78c3dc12e2305b7 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 22 Jul 2024 14:02:52 +0400 Subject: [PATCH 014/119] Fix CardFieldsApplies method, service and filter names --- modules/ppcp-card-fields/services.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-card-fields/services.php b/modules/ppcp-card-fields/services.php index d9d6b4dd8..1e32cc4f6 100644 --- a/modules/ppcp-card-fields/services.php +++ b/modules/ppcp-card-fields/services.php @@ -21,14 +21,13 @@ return array( }, 'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies { return new CardFieldsApplies( - $container->get( 'card-fields.supported-country-currency-matrix' ), - $container->get( 'api.shop.currency' ), + $container->get( 'card-fields.supported-country-matrix' ), $container->get( 'api.shop.country' ) ); }, - 'card-fields.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'card-fields.supported-country-matrix' => static function ( ContainerInterface $container ) : array { return apply_filters( - 'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix', + 'woocommerce_paypal_payments_card_fields_supported_country_matrix', array( 'AU', 'AT', From 10439ff02b209e7a16b7135924594c1a1a0ef52d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 22 Jul 2024 13:30:20 +0200 Subject: [PATCH 015/119] =?UTF-8?q?=E2=9C=A8=20Add=20empty=20ApplePay=20ga?= =?UTF-8?q?teway=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/ApplePayGateway.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 modules/ppcp-applepay/ApplePayGateway.php diff --git a/modules/ppcp-applepay/ApplePayGateway.php b/modules/ppcp-applepay/ApplePayGateway.php new file mode 100644 index 000000000..6073ba859 --- /dev/null +++ b/modules/ppcp-applepay/ApplePayGateway.php @@ -0,0 +1,20 @@ + Date: Mon, 22 Jul 2024 18:16:42 +0400 Subject: [PATCH 016/119] Create stubs for WC Bookings --- .psalm/wc-bookings.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .psalm/wc-bookings.php diff --git a/.psalm/wc-bookings.php b/.psalm/wc-bookings.php new file mode 100644 index 000000000..914b83bed --- /dev/null +++ b/.psalm/wc-bookings.php @@ -0,0 +1,14 @@ + Date: Mon, 22 Jul 2024 18:16:59 +0400 Subject: [PATCH 017/119] include stubs for WC Bookings --- psalm.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index 092361998..33986e444 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -35,6 +35,7 @@ + From 2c6d84c32c4274541c31b5836c7707a33ebda3e0 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:18:11 +0400 Subject: [PATCH 018/119] Add action to hook when WC order is created programmatically --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..41d233531 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -95,6 +95,8 @@ class WooCommerceOrderCreator { $wc_order->calculate_totals(); $wc_order->save(); + do_action( 'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created', $wc_order, $wc_cart ); + return $wc_order; } From 7ba58ab98a6b522ec5b2b23cb55f6599f243fb6e Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:18:52 +0400 Subject: [PATCH 019/119] Add service to check if WC Bookings is active --- modules/ppcp-compat/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index 70ae6dac2..b812d3c1f 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -83,6 +83,9 @@ return array( 'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool { return class_exists( 'WC_Connect_Loader' ); }, + 'compat.wc_bookings.is_supported_plugin_version_active' => function (): bool { + return class_exists( 'WC_Bookings' ); + }, 'compat.module.url' => static function ( ContainerInterface $container ): string { /** From 2a31189c0409f5115adb6d6456202336d7eb2c98 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:19:33 +0400 Subject: [PATCH 020/119] Add compat layer for WC Bookings --- modules/ppcp-compat/src/CompatModule.php | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 09c35c24f..6032a0427 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -9,6 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; +use Exception; +use Psr\Log\LoggerInterface; +use WC_Cart; +use WC_Order; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -56,6 +60,13 @@ class CompatModule implements ModuleInterface { $this->fix_page_builders(); $this->exclude_cache_plugins_js_minification( $c ); $this->set_elementor_checkout_context(); + + $logger = $c->get( 'woocommerce.logger.woocommerce' ); + + $is_wc_bookings_active = $c->get( 'compat.wc_bookings.is_supported_plugin_version_active' ); + if ( $is_wc_bookings_active ) { + $this->initialize_wc_bookings_compat_layer( $logger ); + } } /** @@ -387,4 +398,51 @@ class CompatModule implements ModuleInterface { 3 ); } + + /** + * Sets up the compatibility layer for WooCommerce Bookings plugin. + * + * @param LoggerInterface $logger The logger. + * @return void + */ + protected function initialize_wc_bookings_compat_layer( LoggerInterface $logger ): void { + add_action( + 'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created', + static function ( WC_Order $wc_order, WC_Cart $wc_cart ) use ( $logger ): void { + try { + $cart_contents = $wc_cart->get_cart(); + foreach ( $cart_contents as $cart_item ) { + if ( empty( $cart_item['booking'] ) ) { + continue; + } + + foreach ( $wc_order->get_items() as $wc_order_item ) { + $booking_data = array( + 'cost' => $cart_item['booking']['_cost'] ?? 0, + 'start_date' => $cart_item['booking']['_start_date'] ?? 0, + 'end_date' => $cart_item['booking']['_end_date'] ?? 0, + 'all_day' => $cart_item['booking']['_all_day'] ?? 0, + 'local_timezone' => $cart_item['booking']['_local_timezone'] ?? 0, + 'order_item_id' => $wc_order_item->get_id(), + ); + + if ( isset( $cart_item['booking']['_resource_id'] ) ) { + $booking_data['resource_id'] = $cart_item['booking']['_resource_id']; + } + + if ( isset( $cart_item['booking']['_persons'] ) ) { + $booking_data['persons'] = $cart_item['booking']['_persons']; + } + + create_wc_booking( $cart_item['product_id'], $booking_data, $wc_order->get_status() ); + } + } + } catch ( Exception $exception ) { + $logger->warning( 'Failed to create booking for WooCommerce Bookings plugin: ' . $exception->getMessage() ); + } + }, + 10, + 2 + ); + } } From 25f229c25286225ba366ac5ee24bcd4a27f6891a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:23:02 +0400 Subject: [PATCH 021/119] Set booking status to 'unpaid' --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 6032a0427..27b4008b1 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -434,7 +434,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, $wc_order->get_status() ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); } } } catch ( Exception $exception ) { From 6a71c302b0372d67b3feba26ed817220b683df13 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:45:57 +0400 Subject: [PATCH 022/119] Check if line item is bookable --- modules/ppcp-compat/src/CompatModule.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 27b4008b1..79406f5f2 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -417,6 +417,12 @@ class CompatModule implements ModuleInterface { } foreach ( $wc_order->get_items() as $wc_order_item ) { + $product = wc_get_product( $wc_order_item->get_product_id() ); + + if ( ! is_wc_booking_product( $product ) ) { + continue; + } + $booking_data = array( 'cost' => $cart_item['booking']['_cost'] ?? 0, 'start_date' => $cart_item['booking']['_start_date'] ?? 0, @@ -434,7 +440,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'pending-confirmation' ); } } } catch ( Exception $exception ) { From 6df22c2444a7823ab993e1fb415521b4f2f0329a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:46:22 +0400 Subject: [PATCH 023/119] Improve stubs for WC Bookings --- .psalm/wc-bookings.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.psalm/wc-bookings.php b/.psalm/wc-bookings.php index 914b83bed..2ef838f51 100644 --- a/.psalm/wc-bookings.php +++ b/.psalm/wc-bookings.php @@ -12,3 +12,9 @@ * @return mixed WC_Booking object on success or false on fail */ function create_wc_booking( $product_id, $new_booking_data = array(), $status = 'confirmed', $exact = false ) {} + +/** + * Returns true if the product is a booking product, false if not + * @return bool + */ +function is_wc_booking_product( $product ) {} From 4c030e813490cf435eb4b083ab184732220d4147 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:55:23 +0400 Subject: [PATCH 024/119] Check if item is product --- modules/ppcp-compat/src/CompatModule.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 79406f5f2..a5fe3fc48 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Cart; use WC_Order; +use WC_Order_Item_Product; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -417,7 +418,12 @@ class CompatModule implements ModuleInterface { } foreach ( $wc_order->get_items() as $wc_order_item ) { - $product = wc_get_product( $wc_order_item->get_product_id() ); + if ( ! is_a( $wc_order_item, WC_Order_Item_Product::class ) ) { + continue; + } + + $product_id = $wc_order_item->get_variation_id() ?: $wc_order_item->get_product_id(); + $product = wc_get_product( $product_id ); if ( ! is_wc_booking_product( $product ) ) { continue; From e300ccaa0beacdb01cf389f02acb71f8f163a6e9 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 23 Jul 2024 14:23:38 +0400 Subject: [PATCH 025/119] Set the default status to unpaid --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index a5fe3fc48..b3c8a5288 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -446,7 +446,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, 'pending-confirmation' ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); } } } catch ( Exception $exception ) { From 112b98875bbd0e87f8e20dab474d867c6bca1363 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 23 Jul 2024 15:38:07 +0200 Subject: [PATCH 026/119] 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 eb216425fb3729be9b649076b716186b207aae45 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 22 Jul 2024 18:29:39 +0200 Subject: [PATCH 027/119] =?UTF-8?q?=F0=9F=9A=A7=20New=20Gateway=20for=20Ap?= =?UTF-8?q?ple=20Pay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/ApplePayGateway.php | 20 -- .../ppcp-applepay/resources/css/styles.scss | 5 + .../resources/js/ApplepayButton.js | 200 ++++++++------ .../resources/js/Helper/utils.js | 2 +- modules/ppcp-applepay/services.php | 10 + modules/ppcp-applepay/src/ApplePayGateway.php | 234 +++++++++++++++++ modules/ppcp-applepay/src/ApplepayModule.php | 45 +++- .../src/Assets/ApplePayButton.php | 7 +- .../src/Assets/DataToAppleButtonScripts.php | 244 +++++++----------- .../ContextBootstrap/CheckoutBootstap.js | 11 +- .../js/modules/Helper/CheckoutMethodState.js | 1 + .../ppcp-wc-gateway/src/Settings/Settings.php | 5 +- 12 files changed, 516 insertions(+), 268 deletions(-) delete mode 100644 modules/ppcp-applepay/ApplePayGateway.php create mode 100644 modules/ppcp-applepay/src/ApplePayGateway.php diff --git a/modules/ppcp-applepay/ApplePayGateway.php b/modules/ppcp-applepay/ApplePayGateway.php deleted file mode 100644 index 6073ba859..000000000 --- a/modules/ppcp-applepay/ApplePayGateway.php +++ /dev/null @@ -1,20 +0,0 @@ - { - this.addButton(); - const id_minicart = - '#apple-' + this.buttonConfig.button.mini_cart_wrapper; - const id = '#apple-' + this.buttonConfig.button.wrapper; + const idMinicart = this.buttonConfig.button.mini_cart_wrapper; + const idButton = this.buttonConfig.button.wrapper; - if ( this.context === 'mini-cart' ) { - document - .querySelector( id_minicart ) - ?.addEventListener( 'click', ( evt ) => { - evt.preventDefault(); - this.onButtonClick(); - } ); - } else { - document - .querySelector( id ) - ?.addEventListener( 'click', ( evt ) => { - evt.preventDefault(); - this.onButtonClick(); - } ); - } - } ); - } else { - jQuery( '#' + this.buttonConfig.button.wrapper ).hide(); - jQuery( '#' + this.buttonConfig.button.mini_cart_wrapper ).hide(); + if ( ! this.isEligible ) { + jQuery( '#' + idButton ).hide(); + jQuery( '#' + idMinicart ).hide(); jQuery( '#express-payment-method-ppcp-applepay' ).hide(); + + return; } + + // Add click-handler to the button. + const setupButtonEvents = ( id ) => { + document + .getElementById( id ) + ?.addEventListener( 'click', ( evt ) => { + evt.preventDefault(); + this.onButtonClick(); + } ); + }; + + this.fetchTransactionInfo().then( () => { + this.addButton(); + + if ( this.context === 'mini-cart' ) { + setupButtonEvents( idMinicart ); + } else { + setupButtonEvents( idButton ); + } + } ); } reinit() { @@ -144,11 +169,11 @@ class ApplepayButton { initEventHandlers() { const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - const wrapper_id = '#' + wrapper; + const wrapperId = '#' + wrapper; - if ( wrapper_id === ppcpButtonWrapper ) { + if ( wrapperId === ppcpButtonWrapper ) { throw new Error( - `[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper_id }"` + `[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapperId }"` ); } @@ -158,9 +183,9 @@ class ApplepayButton { } const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper_id, $ppcpButtonWrapper.is( ':visible' ) ); + setVisible( wrapperId, $ppcpButtonWrapper.is( ':visible' ) ); setEnabled( - wrapper_id, + wrapperId, ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) ); }; @@ -192,6 +217,7 @@ class ApplepayButton { session.onshippingcontactselected = this.onShippingContactSelected( session ); } + session.onvalidatemerchant = this.onValidateMerchant( session ); session.onpaymentauthorized = this.onPaymentAuthorized( session ); return session; @@ -211,19 +237,19 @@ class ApplepayButton { const color = this.buttonConfig.button.color; const id = 'apple-' + wrapper; - if ( appleContainer ) { - appleContainer.innerHTML = ``; + if ( ! appleContainer ) { + return; } - const $wrapper = jQuery( '#' + wrapper ); - $wrapper.addClass( 'ppcp-button-' + ppcpStyle.shape ); + appleContainer.innerHTML = ``; + appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { - $wrapper.css( + appleContainer.style.setProperty( '--apple-pay-button-height', `${ ppcpStyle.height }px` ); - $wrapper.css( 'height', `${ ppcpStyle.height }px` ); + appleContainer.style.height = `${ ppcpStyle.height }px`; } } @@ -239,7 +265,8 @@ class ApplepayButton { const paymentRequest = this.paymentRequest(); - window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler. + // Do this on another place like on create order endpoint handler. + window.ppcpFundingSource = 'apple_pay'; // Trigger woocommerce validation if we are in the checkout page. if ( this.context === 'checkout' ) { @@ -323,8 +350,8 @@ class ApplepayButton { } /** - * Indicates how payment completion should be handled if with the context handler default actions. - * Or with ApplePay module specific completion. + * Indicates how payment completion should be handled if with the context handler default + * actions. Or with Apple Pay module specific completion. * * @return {boolean} */ @@ -333,18 +360,17 @@ class ApplepayButton { if ( ! this.contextHandler.shippingAllowed() ) { return true; } + // Use WC form data mode in Checkout. - if ( + return ( this.context === 'checkout' && ! this.shouldUpdateButtonWithFormData() - ) { - return true; - } - return false; + ); } /** - * Updates ApplePay paymentRequest with form data. + * Updates Apple Pay paymentRequest with form data. + * * @param paymentRequest */ updateRequestDataWithForm( paymentRequest ) { @@ -358,8 +384,9 @@ class ApplepayButton { ); // Add custom data. - // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). - // paymentRequest.applicationData = this.fillApplicationData(this.formData); + // "applicationData" is originating a "PayPalApplePayError: An internal server error has + // occurred" on paypal.Applepay().confirmOrder(). paymentRequest.applicationData = + // this.fillApplicationData(this.formData); if ( ! this.shouldRequireShippingInButton() ) { return; @@ -425,7 +452,8 @@ class ApplepayButton { 'email', 'phone', ], - requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing email and phone fields. + requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing + // email and phone fields. }; if ( ! this.shouldRequireShippingInButton() ) { @@ -453,14 +481,11 @@ class ApplepayButton { } refreshContextData() { - switch ( this.context ) { - case 'product': + if ( 'product' === this.context ) { // Refresh product data that makes the price change. - this.productQuantity = - document.querySelector( 'input.qty' )?.value; + this.productQuantity = document.querySelector( 'input.qty' )?.value; this.products = this.contextHandler.products(); this.log( 'Products updated', this.products ); - break; } } @@ -468,8 +493,35 @@ class ApplepayButton { // Payment process //------------------------ + /** + * Make ajax call to change the verification-status of the current domain. + * + * @param {boolean} isValid + */ + adminValidation( isValid ) { + // eslint-disable-next-line no-unused-vars + const ignored = fetch( this.buttonConfig.ajax_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams( { + action: 'ppcp_validate', + 'woocommerce-process-checkout-nonce': this.nonce, + validation: isValid, + } ).toString(), + } ); + } + + /** + * Returns an event handler that Apple Pay calls when displaying the payment sheet. + * + * @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant + * + * @param session + * @return {(function(*): void)|*} + */ onValidateMerchant( session ) { - this.log( 'onvalidatemerchant', this.buttonConfig.ajax_url ); return ( applePayValidateMerchantEvent ) => { this.log( 'onvalidatemerchant call' ); @@ -479,34 +531,15 @@ class ApplepayButton { validationUrl: applePayValidateMerchantEvent.validationURL, } ) .then( ( validateResult ) => { - this.log( 'onvalidatemerchant ok' ); session.completeMerchantValidation( validateResult.merchantSession ); - //call backend to update validation to true - jQuery.ajax( { - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: true, - 'woocommerce-process-checkout-nonce': this.nonce, - }, - } ); + + this.adminValidation( true ); } ) .catch( ( validateError ) => { - this.log( 'onvalidatemerchant error', validateError ); console.error( validateError ); - //call backend to update validation to false - jQuery.ajax( { - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: false, - 'woocommerce-process-checkout-nonce': this.nonce, - }, - } ); + this.adminValidation( false ); this.log( 'onvalidatemerchant session abort' ); session.abort(); } ); @@ -537,7 +570,8 @@ class ApplepayButton { } this.selectedShippingMethod = event.shippingMethod; - // Sort the response shipping methods, so that the selected shipping method is the first one. + // Sort the response shipping methods, so that the selected shipping method is + // the first one. response.newShippingMethods = response.newShippingMethods.sort( ( a, b ) => { if ( @@ -680,6 +714,7 @@ class ApplepayButton { function form() { return document.querySelector( 'form.cart' ); } + const processInWooAndCapture = async ( data ) => { return new Promise( ( resolve, reject ) => { try { @@ -781,7 +816,8 @@ class ApplepayButton { if ( this.shouldCompletePaymentWithContextHandler() ) { - // No shipping, expect immediate capture, ex: PayNow, Checkout with form data. + // No shipping, expect immediate capture, ex: PayNow, Checkout with + // form data. let approveFailed = false; await this.contextHandler.approveOrder( @@ -950,4 +986,4 @@ class ApplepayButton { } } -export default ApplepayButton; +export default ApplePayButton; diff --git a/modules/ppcp-applepay/resources/js/Helper/utils.js b/modules/ppcp-applepay/resources/js/Helper/utils.js index 3b8edc2c8..793fe7f42 100644 --- a/modules/ppcp-applepay/resources/js/Helper/utils.js +++ b/modules/ppcp-applepay/resources/js/Helper/utils.js @@ -1,4 +1,4 @@ -export const buttonID = 'applepay-container'; +export const buttonID = 'ppc-button-applepay-container'; export const endpoints = { validation: '_apple_pay_validation', createOrderCart: '_apple_pay_create_order_cart', diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 6079fcb40..491e5f2aa 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -976,5 +976,15 @@ return array( esc_html( $button_text ) ); }, + 'applepay.wc-gateway' => static function ( ContainerInterface $container ): ApplePayGateway { + return new ApplePayGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'api.factory.paypal-checkout-url' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'session.handler' ), + $container->get( 'applepay.url' ) + ); + }, ); diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php new file mode 100644 index 000000000..327ccae53 --- /dev/null +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -0,0 +1,234 @@ +id = self::ID; + + $this->method_title = __( 'Apple Pay (via PayPal) ', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the Apple Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + + $this->title = $this->get_option( 'title', __( 'Apple Pay', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->module_url = $module_url; + $this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.png'; + + $this->init_form_fields(); + $this->init_settings(); + $this->order_processor = $order_processor; + $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; + $this->refund_processor = $refund_processor; + $this->transaction_url_provider = $transaction_url_provider; + $this->session_handler = $session_handler; + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * Initialize the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable Apple Pay payment gateway.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } + + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) : array { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order ); + + try { + try { + $this->order_processor->process( $wc_order ); + + do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order ); + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalOrderMissingException $exc ) { + $order = $this->order_processor->create_order( $wc_order ); + + return array( + 'result' => 'success', + 'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ), + ); + } + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( Exception $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + + /** + * Process refund. + * + * If the gateway declares 'refunds' support, this will allow it to refund. + * a passed in amount. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) : bool { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, WC_Order::class ) ) { + return false; + } + + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + + /** + * Return transaction url for this gateway and given order. + * + * @param WC_Order $order WC order to get transaction url by. + * + * @return string + */ + public function get_transaction_url( $order ) : string { + $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); + + return parent::get_transaction_url( $order ); + } +} diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 409a9c81e..878d4b322 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; +use WC_Payment_Gateway; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; @@ -117,6 +118,48 @@ class ApplepayModule implements ModuleInterface { 100, 2 ); + + add_filter( + 'woocommerce_payment_gateways', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $methods ) use ( $c ): array { + if ( ! is_array( $methods ) ) { + return $methods; + } + + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + if ( $settings->has( 'applepay_button_enabled' ) && $settings->get( 'applepay_button_enabled' ) ) { + $applepay_gateway = $c->get( 'applepay.wc-gateway' ); + assert( $applepay_gateway instanceof WC_Payment_Gateway ); + + $methods[] = $applepay_gateway; + } + + return $methods; + } + ); + + add_action( + 'woocommerce_review_order_after_submit', + function () { + // Wrapper ID: #ppc-button-ppcp-applepay. + echo '
'; + } + ); + + add_action( + 'woocommerce_pay_order_after_submit', + function () { + // Wrapper ID: #ppc-button-ppcp-applepay. + echo '
'; + } + ); } /** @@ -306,7 +349,7 @@ class ApplepayModule implements ModuleInterface { * @param bool $is_sandbox The environment for this merchant. * @return string */ - public function validation_string( bool $is_sandbox ) { + public function validation_string( bool $is_sandbox ) : string { $sandbox_string = $this->sandbox_validation_string(); $live_string = $this->live_validation_string(); return $is_sandbox ? $sandbox_string : $live_string; diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 926fc5ce2..b710e75ca 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -20,12 +20,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait; +use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; /** * Class ApplePayButton */ class ApplePayButton implements ButtonInterface { - use RequestHandlerTrait; + use RequestHandlerTrait, ContextTrait; /** * The settings. @@ -973,7 +974,7 @@ class ApplePayButton implements ButtonInterface { add_action( $render_placeholder, function () { - echo ''; + echo ''; }, 21 ); @@ -986,7 +987,7 @@ class ApplePayButton implements ButtonInterface { */ protected function applepay_button(): void { ?> -
+
data_for_product_page( - $shop_country_code, - $currency_code, - $total_label - ); + return $this->data_for_product_page(); } - return $this->data_for_cart_page( - $shop_country_code, - $currency_code, - $total_label - ); + return $this->data_for_cart_page(); } /** * Returns the appropriate admin data to send to ApplePay script * * @return array - * @throws NotFoundException When the setting is not found. */ public function apple_pay_script_data_for_admin() : array { + return $this->data_for_admin_page(); + } + + /** + * Returns the full config array for the Apple Pay integration with default values. + * + * @param array $product - Optional. Product details for the payment button. + * + * @return array + */ + private function get_apple_pay_data( array $product = [] ) : array { + // true: Use Apple Pay as distinct gateway. + // false: integrate it with the smart buttons. + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $is_wc_gateway_enabled = isset( $available_gateways[ ApplePayGateway::ID ] ); + + // use_wc: Use WC checkout data + // use_applepay: Use data provided by Apple Pay. + $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) + ? $this->settings->get( 'applepay_checkout_data_mode' ) + : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; + + // Store country, currency and name. $base_location = wc_get_base_location(); $shop_country_code = $base_location['country']; $currency_code = get_woocommerce_currency(); $total_label = get_bloginfo( 'name' ); - return $this->data_for_admin_page( - $shop_country_code, - $currency_code, - $total_label + // Button layout (label, color, language). + $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; + $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; + $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; + $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); + $is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' ); + + return array( + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, + 'is_admin' => false, + 'is_enabled' => $is_enabled, + 'is_wc_gateway_enabled' => $is_wc_gateway_enabled, + 'preferences' => array( + 'checkout_data_mode' => $checkout_data_mode, + ), + 'button' => array( + 'wrapper' => 'ppc-button-applepay-container', + 'mini_cart_wrapper' => 'ppc-button-applepay-container-minicart', + 'type' => $type, + 'color' => $color, + 'lang' => $lang, + ), + 'product' => $product, + 'shop' => array( + 'countryCode' => $shop_country_code, + 'currencyCode' => $currency_code, + 'totalLabel' => $total_label, + ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } /** * Check if the product needs shipping * - * @param \WC_Product $product The product. - * * @return bool */ - protected function check_if_need_shipping( $product ) { + protected function check_if_need_shipping( WC_Product $product ) : bool { if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( @@ -104,30 +139,20 @@ class DataToAppleButtonScripts { ) { return false; } - $needs_shipping = false; if ( $product->needs_shipping() ) { - $needs_shipping = true; + return true; } - return $needs_shipping; + return false; } /** * Prepares the data for the product page. * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. - * * @return array - * @throws NotFoundException When the setting is not found. */ - protected function data_for_product_page( - $shop_country_code, - $currency_code, - $total_label - ) { + protected function data_for_product_page() : array { $product = wc_get_product( get_the_id() ); if ( ! $product ) { return array(); @@ -136,146 +161,53 @@ class DataToAppleButtonScripts { if ( $product->get_type() === 'variable' || $product->get_type() === 'variable-subscription' ) { $is_variation = true; } + $product_need_shipping = $this->check_if_need_shipping( $product ); $product_id = get_the_id(); $product_price = $product->get_price(); $product_stock = $product->get_stock_status(); - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, - 'is_admin' => false, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => $product_need_shipping, - 'id' => $product_id, - 'price' => $product_price, - 'isVariation' => $is_variation, - 'stock' => $product_stock, - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), - ); + return $this->get_apple_pay_data( array( + 'needShipping' => $product_need_shipping, + 'id' => $product_id, + 'price' => $product_price, + 'isVariation' => $is_variation, + 'stock' => $product_stock, + ) ); } /** * Prepares the data for the cart page. * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. - * * @return array */ - protected function data_for_cart_page( - $shop_country_code, - $currency_code, - $total_label - ) { + protected function data_for_cart_page() : array { $cart = WC()->cart; if ( ! $cart ) { return array(); } - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, - 'is_admin' => false, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => $cart->needs_shipping(), - 'subtotal' => $cart->get_subtotal(), - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), - ); + return $this->get_apple_pay_data( array( + 'needShipping' => $cart->needs_shipping(), + 'subtotal' => $cart->get_subtotal(), + ) ); } /** * Prepares the data for the cart page. - * Consider refactoring this method along with data_for_cart_page() and data_for_product_page() methods. - * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. + * Consider refactoring this method along with data_for_cart_page() and data_for_product_page() + * methods. * * @return array */ - protected function data_for_admin_page( - $shop_country_code, - $currency_code, - $total_label - ) { - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - $is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' ); + protected function data_for_admin_page() : array { + $data = $this->get_apple_pay_data( array( + 'needShipping' => false, + 'subtotal' => 0, + ) ); - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, - 'is_admin' => true, - 'is_enabled' => $is_enabled, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => false, - 'subtotal' => 0, - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - ); + $data['is_admin'] = true; + + return $data; } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 848285815..0ec918667 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,3 +1,5 @@ +/* global PayPalCommerceGateway */ + import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import { setVisible, setVisibleByClass } from '../Helper/Hiding'; import { @@ -181,9 +183,14 @@ class CheckoutBootstap { const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes( currentPaymentMethod ); + const isApplePayMethod = + currentPaymentMethod === PaymentMethods.APPLEPAY; const isSavedCard = isCard && isSavedCardSelected(); const isNotOurGateway = - ! isPaypal && ! isCard && ! isSeparateButtonGateway; + ! isPaypal && + ! isCard && + ! isSeparateButtonGateway && + ! isApplePayMethod; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; @@ -227,6 +234,8 @@ class CheckoutBootstap { } } + setVisible( '#ppc-button-ppcp-applepay', isApplePayMethod ); + jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); } diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 0ea05f255..6fa3d6c73 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -3,6 +3,7 @@ export const PaymentMethods = { CARDS: 'ppcp-credit-card-gateway', OXXO: 'ppcp-oxxo-gateway', CARD_BUTTON: 'ppcp-card-button-gateway', + APPLEPAY: 'ppcp-applepay', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; diff --git a/modules/ppcp-wc-gateway/src/Settings/Settings.php b/modules/ppcp-wc-gateway/src/Settings/Settings.php index 25fcb808d..e91256653 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Settings.php +++ b/modules/ppcp-wc-gateway/src/Settings/Settings.php @@ -117,19 +117,16 @@ class Settings implements ContainerInterface { /** * Stores the settings to the database. */ - public function persist() { - + public function persist() : bool { return update_option( self::KEY, $this->settings ); } - /** * Loads the settings. * * @return bool */ private function load(): bool { - if ( $this->settings ) { return false; } From a473459ddbf377f3f9d01f596365f89a8e4287fd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 23 Jul 2024 17:36:10 +0200 Subject: [PATCH 028/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Minor=20code=20cle?= =?UTF-8?q?anup=20and=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 192 +++++++++++------- .../resources/js/ApplepayManager.js | 15 +- .../js/ApplepayManagerBlockEditor.js | 10 +- .../resources/js/Context/BaseHandler.js | 8 - .../resources/js/Context/PreviewHandler.js | 6 +- .../ppcp-applepay/resources/js/boot-admin.js | 4 +- .../ppcp-applepay/resources/js/boot-block.js | 8 +- modules/ppcp-applepay/resources/js/boot.js | 4 +- 8 files changed, 142 insertions(+), 105 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index b703a9565..3a1a13a00 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -10,6 +10,23 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +/** + * 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', + Blocks: [ 'cart-block', 'checkout-block' ], +}; + /** * A payment button for Apple Pay. * @@ -32,6 +49,20 @@ class ApplePayButton { */ context = ''; + externalHandler = null; + buttonConfig = null; + ppcpConfig = null; + paymentsClient = null; + formData = null; + contextHandler = null; + updatedContactInfo = []; + selectedShippingMethod = []; + + /** + * Stores initialization data sent to the button. + */ + initialPaymentRequest = null; + constructor( context, externalHandler, buttonConfig, ppcpConfig ) { apmButtonsInit( ppcpConfig ); @@ -39,8 +70,6 @@ class ApplePayButton { this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; - this.paymentsClient = null; - this.formData = null; this.contextHandler = ContextHandlerFactory.create( this.context, @@ -48,18 +77,6 @@ class ApplePayButton { this.ppcpConfig ); - this.updatedContactInfo = []; - this.selectedShippingMethod = []; - this.nonce = - document.getElementById( 'woocommerce-process-checkout-nonce' ) - ?.value || buttonConfig.nonce; - - // Stores initialization data sent to the button. - this.initialPaymentRequest = null; - - // Default eligibility status. - this.isEligible = true; - this.log = function () { if ( this.buttonConfig.is_debug ) { //console.log('[ApplePayButton]', ...arguments); @@ -76,6 +93,39 @@ class ApplePayButton { document.ppcpApplepayButtons[ this.context ] = this; } + /** + * The nonce for ajax requests. + * + * @return {string} The nonce value + */ + get nonce() { + const input = document.getElementById( + 'woocommerce-process-checkout-nonce' + ); + + return input?.value || this.buttonConfig.nonce; + } + + /** + * Whether the current page qualifies to use the Apple Pay button. + * + * In admin, the button is always eligible, to display an accurate preview. + * On front-end, PayPal's response decides if customers can use Apple Pay. + * + * @return {boolean} True, if the button can be displayed. + */ + get isEligible() { + if ( ! this.isInitialized ) { + return true; + } + + if ( this.buttonConfig.is_admin ) { + return true; + } + + return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); + } + init( config ) { if ( this.isInitialized ) { return; @@ -90,9 +140,6 @@ class ApplePayButton { this.isInitialized = true; this.applePayConfig = config; - this.isEligible = - ( this.applePayConfig.isEligible && window.ApplePaySession ) || - this.buttonConfig.is_admin; const idMinicart = this.buttonConfig.button.mini_cart_wrapper; const idButton = this.buttonConfig.button.wrapper; @@ -118,7 +165,7 @@ class ApplePayButton { this.fetchTransactionInfo().then( () => { this.addButton(); - if ( this.context === 'mini-cart' ) { + if ( CONTEXT.MiniCart === this.context ) { setupButtonEvents( idMinicart ); } else { setupButtonEvents( idButton ); @@ -143,23 +190,22 @@ class ApplePayButton { * 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, - }; + const config = {}; - if ( this.context === 'mini-cart' ) { + if ( CONTEXT.MiniCart === this.context ) { 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; + } else { + config.wrapper = this.buttonConfig.button.wrapper; + config.ppcpStyle = this.ppcpConfig.button.style; + config.buttonStyle = this.buttonConfig.button.style; + config.ppcpButtonWrapper = this.ppcpConfig.button.wrapper; } - if ( - [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 - ) { + // Block editor configuration. + if ( CONTEXT.Blocks.includes( this.context ) ) { config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal'; } @@ -203,8 +249,9 @@ class ApplePayButton { } /** - * Starts an ApplePay session. - * @param paymentRequest + * Starts an Apple Pay session. + * + * @param {Object} paymentRequest The payment request object. */ applePaySession( paymentRequest ) { this.log( 'applePaySession', paymentRequest ); @@ -269,7 +316,7 @@ class ApplePayButton { window.ppcpFundingSource = 'apple_pay'; // Trigger woocommerce validation if we are in the checkout page. - if ( this.context === 'checkout' ) { + if ( CONTEXT.Checkout === this.context ) { const checkoutFormSelector = 'form.woocommerce-checkout'; const errorHandler = new ErrorHandler( PayPalCommerceGateway.labels.error.generic, @@ -323,13 +370,13 @@ class ApplePayButton { /** * If the button should show the shipping fields. * - * @return {false|*} + * @return {boolean} True, if shipping fields should be captured by ApplePay. */ shouldRequireShippingInButton() { return ( this.contextHandler.shippingAllowed() && this.buttonConfig.product.needShipping && - ( this.context !== 'checkout' || + ( CONTEXT.Checkout !== this.context || this.shouldUpdateButtonWithFormData() ) ); } @@ -337,10 +384,10 @@ class ApplePayButton { /** * If the button should be updated with the form addresses. * - * @return {boolean} + * @return {boolean} True, when Apple Pay data should be submitted to WooCommerce. */ shouldUpdateButtonWithFormData() { - if ( this.context !== 'checkout' ) { + if ( CONTEXT.Checkout !== this.context ) { return false; } return ( @@ -353,7 +400,7 @@ class ApplePayButton { * Indicates how payment completion should be handled if with the context handler default * actions. Or with Apple Pay module specific completion. * - * @return {boolean} + * @return {boolean} True, when the Apple Pay data should be submitted to WooCommerce. */ shouldCompletePaymentWithContextHandler() { // Data already handled, ex: PayNow @@ -363,7 +410,7 @@ class ApplePayButton { // Use WC form data mode in Checkout. return ( - this.context === 'checkout' && + CONTEXT.Checkout === this.context && ! this.shouldUpdateButtonWithFormData() ); } @@ -371,7 +418,7 @@ class ApplePayButton { /** * Updates Apple Pay paymentRequest with form data. * - * @param paymentRequest + * @param {Object} paymentRequest Object to extend with form data. */ updateRequestDataWithForm( paymentRequest ) { if ( ! this.shouldUpdateButtonWithFormData() ) { @@ -481,11 +528,11 @@ class ApplePayButton { } refreshContextData() { - if ( 'product' === this.context ) { - // Refresh product data that makes the price change. + if ( CONTEXT.Product === this.context ) { + // Refresh product data that makes the price change. this.productQuantity = document.querySelector( 'input.qty' )?.value; - this.products = this.contextHandler.products(); - this.log( 'Products updated', this.products ); + this.products = this.contextHandler.products(); + this.log( 'Products updated', this.products ); } } @@ -518,8 +565,9 @@ class ApplePayButton { * * @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant * - * @param session - * @return {(function(*): void)|*} + * @param {Object} session The ApplePaySession object. + * + * @return {(function(*): void)|*} Callback that runs after the merchant validation */ onValidateMerchant( session ) { return ( applePayValidateMerchantEvent ) => { @@ -548,14 +596,14 @@ class ApplePayButton { onShippingMethodSelected( session ) { this.log( 'onshippingmethodselected', this.buttonConfig.ajax_url ); - const ajax_url = this.buttonConfig.ajax_url; + const ajaxUrl = this.buttonConfig.ajax_url; return ( event ) => { this.log( 'onshippingmethodselected call' ); const data = this.getShippingMethodData( event ); jQuery.ajax( { - url: ajax_url, + url: ajaxUrl, method: 'POST', data, success: ( @@ -599,7 +647,7 @@ class ApplePayButton { onShippingContactSelected( session ) { this.log( 'onshippingcontactselected', this.buttonConfig.ajax_url ); - const ajax_url = this.buttonConfig.ajax_url; + const ajaxUrl = this.buttonConfig.ajax_url; return ( event ) => { this.log( 'onshippingcontactselected call' ); @@ -607,7 +655,7 @@ class ApplePayButton { const data = this.getShippingContactData( event ); jQuery.ajax( { - url: ajax_url, + url: ajaxUrl, method: 'POST', data, success: ( @@ -637,15 +685,15 @@ class ApplePayButton { } getShippingContactData( event ) { - const product_id = this.buttonConfig.product.id; + const productId = this.buttonConfig.product.id; this.refreshContextData(); switch ( this.context ) { - case 'product': + case CONTEXT.Product: return { action: 'ppcp_update_shipping_contact', - product_id, + product_id: productId, products: JSON.stringify( this.products ), caller_page: 'productDetail', product_quantity: this.productQuantity, @@ -653,11 +701,12 @@ class ApplePayButton { need_shipping: this.shouldRequireShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; - case 'cart': - case 'checkout': - case 'cart-block': - case 'checkout-block': - case 'mini-cart': + + case CONTEXT.Cart: + case CONTEXT.Checkout: + case CONTEXT.BlockCart: + case CONTEXT.BlockCheckout: + case CONTEXT.MiniCart: return { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, @@ -669,12 +718,12 @@ class ApplePayButton { } getShippingMethodData( event ) { - const product_id = this.buttonConfig.product.id; + const productId = this.buttonConfig.product.id; this.refreshContextData(); switch ( this.context ) { - case 'product': + case CONTEXT.Product: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, @@ -682,17 +731,18 @@ class ApplePayButton { this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact, - product_id, + product_id: productId, products: JSON.stringify( this.products ), caller_page: 'productDetail', product_quantity: this.productQuantity, 'woocommerce-process-checkout-nonce': this.nonce, }; - case 'cart': - case 'checkout': - case 'cart-block': - case 'checkout-block': - case 'mini-cart': + + case CONTEXT.Cart: + case CONTEXT.Checkout: + case CONTEXT.BlockCart: + case CONTEXT.BlockCheckout: + case CONTEXT.MiniCart: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, @@ -711,10 +761,6 @@ class ApplePayButton { return async ( event ) => { this.log( 'onpaymentauthorized call' ); - function form() { - return document.querySelector( 'form.cart' ); - } - const processInWooAndCapture = async ( data ) => { return new Promise( ( resolve, reject ) => { try { @@ -729,7 +775,7 @@ class ApplePayButton { ( this.initialPaymentRequest.shippingMethods || [] )[ 0 ]; - const request_data = { + const requestData = { action: 'ppcp_create_order', caller_page: this.context, product_id: this.buttonConfig.product.id ?? null, @@ -754,7 +800,7 @@ class ApplePayButton { jQuery.ajax( { url: this.buttonConfig.ajax_url, method: 'POST', - data: request_data, + data: requestData, complete: ( jqXHR, textStatus ) => { this.log( 'onpaymentauthorized complete' ); }, @@ -829,15 +875,15 @@ class ApplePayButton { restart: () => new Promise( ( resolve, reject ) => { - approveFailed = true; - resolve(); + approveFailed = true; + resolve(); } ), order: { get: () => new Promise( ( resolve, reject ) => { - resolve( null ); + resolve( null ); } ), }, diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 0992d9e7d..264610f28 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -1,7 +1,9 @@ -import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; -import ApplepayButton from './ApplepayButton'; +/* global paypal */ -class ApplepayManager { +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import ApplePayButton from './ApplepayButton'; + +class ApplePayManager { constructor( buttonConfig, ppcpConfig ) { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -9,7 +11,7 @@ class ApplepayManager { this.buttons = []; buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { - const button = new ApplepayButton( + const button = new ApplePayButton( bootstrap.context, bootstrap.handler, buttonConfig, @@ -40,8 +42,7 @@ class ApplepayManager { } /** - * Gets ApplePay configuration of the PayPal merchant. - * @return {Promise} + * Gets Apple Pay configuration of the PayPal merchant. */ async config() { this.ApplePayConfig = await paypal.Applepay().config(); @@ -49,4 +50,4 @@ class ApplepayManager { } } -export default ApplepayManager; +export default ApplePayManager; diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index 2f4db9d41..1c10bb997 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -1,6 +1,8 @@ -import ApplepayButton from './ApplepayButton'; +/* global paypal */ -class ApplepayManagerBlockEditor { +import ApplePayButton from './ApplepayButton'; + +class ApplePayManagerBlockEditor { constructor( buttonConfig, ppcpConfig ) { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -17,7 +19,7 @@ class ApplepayManagerBlockEditor { try { this.applePayConfig = await paypal.Applepay().config(); - const button = new ApplepayButton( + const button = new ApplePayButton( this.ppcpConfig.context, null, this.buttonConfig, @@ -31,4 +33,4 @@ class ApplepayManagerBlockEditor { } } -export default ApplepayManagerBlockEditor; +export default ApplePayManagerBlockEditor; diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index f763ac5d9..cc3a3aeb2 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 ) { @@ -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; diff --git a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js index 8740705e9..5febfe0c3 100644 --- a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js @@ -1,10 +1,6 @@ import BaseHandler from './BaseHandler'; class PreviewHandler extends BaseHandler { - constructor( buttonConfig, ppcpConfig, externalHandler ) { - super( buttonConfig, ppcpConfig, externalHandler ); - } - transactionInfo() { // We need to return something as ApplePay button initialization expects valid data. return { @@ -19,7 +15,7 @@ class PreviewHandler extends BaseHandler { throw new Error( 'Create order fail. This is just a preview.' ); } - approveOrder( data, actions ) { + approveOrder() { throw new Error( 'Approve order fail. This is just a preview.' ); } diff --git a/modules/ppcp-applepay/resources/js/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js index 080d7c8aa..81d8c0b7b 100644 --- a/modules/ppcp-applepay/resources/js/boot-admin.js +++ b/modules/ppcp-applepay/resources/js/boot-admin.js @@ -1,4 +1,4 @@ -import ApplepayButton from './ApplepayButton'; +import ApplePayButton from './ApplepayButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; @@ -86,7 +86,7 @@ class ApplePayPreviewButton extends PreviewButton { } createButton( buttonConfig ) { - const button = new ApplepayButton( + const button = new ApplePayButton( 'preview', null, buttonConfig, diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index 8445466eb..c447cb064 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -4,8 +4,8 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription'; import { loadCustomScript } from '@paypal/paypal-js'; import CheckoutHandler from './Context/CheckoutHandler'; -import ApplepayManager from './ApplepayManager'; -import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor'; +import ApplePayManager from './ApplepayManager'; +import ApplePayManagerBlockEditor from './ApplepayManagerBlockEditor'; const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' ); const ppcpConfig = ppcpData.scriptData; @@ -24,8 +24,8 @@ const ApplePayComponent = ( props ) => { const bootstrap = function () { const ManagerClass = props.isEditing - ? ApplepayManagerBlockEditor - : ApplepayManager; + ? ApplePayManagerBlockEditor + : ApplePayManager; const manager = new ManagerClass( buttonConfig, ppcpConfig ); manager.init(); }; diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index aee735231..8eddafbcb 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -1,13 +1,13 @@ import { loadCustomScript } from '@paypal/paypal-js'; import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; -import ApplepayManager from './ApplepayManager'; +import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; ( function ( { buttonConfig, ppcpConfig, jQuery } ) { let manager; const bootstrap = function () { - manager = new ApplepayManager( buttonConfig, ppcpConfig ); + manager = new ApplePayManager( buttonConfig, ppcpConfig ); manager.init(); }; From f4abf7028e1bfbac98de2ec7e3d6199f563a3829 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 14:20:40 +0200 Subject: [PATCH 029/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Make=20is?= =?UTF-8?q?Initialized=20flag=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reason: The button instance is added to the global document scope, this helps to protect internal component state --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 3a1a13a00..d1a56d616 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -40,7 +40,7 @@ class ApplePayButton { * * @type {boolean} */ - isInitialized = false; + #isInitialized = false; /** * Context describes the button's location on the website and what details it submits. @@ -115,7 +115,7 @@ class ApplePayButton { * @return {boolean} True, if the button can be displayed. */ get isEligible() { - if ( ! this.isInitialized ) { + if ( ! this.#isInitialized ) { return true; } @@ -127,7 +127,7 @@ class ApplePayButton { } init( config ) { - if ( this.isInitialized ) { + if ( this.#isInitialized ) { return; } @@ -138,7 +138,7 @@ class ApplePayButton { this.log( 'Init', this.context ); this.initEventHandlers(); - this.isInitialized = true; + this.#isInitialized = true; this.applePayConfig = config; const idMinicart = this.buttonConfig.button.mini_cart_wrapper; @@ -178,7 +178,7 @@ class ApplePayButton { return; } - this.isInitialized = false; + this.#isInitialized = false; this.init( this.applePayConfig ); } From f75c3610d813522c6a2e35608e244f855ee7438f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 14:21:26 +0200 Subject: [PATCH 030/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Slightly?= =?UTF-8?q?=20improve=20the=20debug-log=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d1a56d616..63368e305 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -77,20 +77,24 @@ class ApplePayButton { this.ppcpConfig ); - this.log = function () { - if ( this.buttonConfig.is_debug ) { - //console.log('[ApplePayButton]', ...arguments); - } - }; - this.refreshContextData(); // Debug helpers - jQuery( document ).on( 'ppcp-applepay-debug', () => { - console.log( 'ApplePayButton', this.context, this ); - } ); document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; document.ppcpApplepayButtons[ this.context ] = this; + + this.log = function () { + if ( ! this.buttonConfig.is_debug ) { + return; + } + console.log( `[ApplePayButton | ${ this.context }]`, ...arguments ); + }; + + if ( this.buttonConfig.is_debug ) { + jQuery( document ).on( 'ppcp-applepay-debug', () => { + this.log( this ); + } ); + } } /** @@ -135,7 +139,7 @@ class ApplePayButton { return; } - this.log( 'Init', this.context ); + this.log( 'Init' ); this.initEventHandlers(); this.#isInitialized = true; @@ -274,7 +278,7 @@ class ApplePayButton { * Adds an Apple Pay purchase button. */ addButton() { - this.log( 'addButton', this.context ); + this.log( 'addButton' ); const { wrapper, ppcpStyle } = this.contextConfig(); @@ -308,7 +312,7 @@ class ApplePayButton { * Show Apple Pay payment sheet when Apple Pay payment button is clicked */ async onButtonClick() { - this.log( 'onButtonClick', this.context ); + this.log( 'onButtonClick' ); const paymentRequest = this.paymentRequest(); From 43d7e0788fe4e5eeb58cd22546644ebb0e8d093c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 15:19:35 +0200 Subject: [PATCH 031/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Minor=20c?= =?UTF-8?q?ode=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 1 + modules/ppcp-applepay/src/ApplePayGateway.php | 13 +++--- .../src/Assets/ApplePayButton.php | 43 ++++++++----------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 63368e305..4f09d28d2 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,3 +1,4 @@ +/* global jQuery */ /* global ApplePaySession */ /* global PayPalCommerceGateway */ diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php index 327ccae53..8b46dda64 100644 --- a/modules/ppcp-applepay/src/ApplePayGateway.php +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -9,17 +9,17 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Applepay; +use Exception; +use WC_Order; use WC_Payment_Gateway; +use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; -use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WC_Order; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; -use Exception; use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException; -use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages; /** @@ -114,10 +114,7 @@ class ApplePayGateway extends WC_Payment_Gateway { add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, - array( - $this, - 'process_admin_options', - ) + array( $this, 'process_admin_options' ) ); } diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index b710e75ca..d9a9d5393 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -341,7 +341,7 @@ class ApplePayButton implements ButtonInterface { } $response = $this->response_templates->apple_formatted_response( $payment_details ); $this->response_templates->response_success( $response ); - } catch ( \Exception $e ) { + } catch ( Exception $e ) { $this->response_templates->response_with_data_errors( array( array( @@ -383,7 +383,7 @@ class ApplePayButton implements ButtonInterface { } $response = $this->response_templates->apple_formatted_response( $payment_details ); $this->response_templates->response_success( $response ); - } catch ( \Exception $e ) { + } catch ( Exception $e ) { $this->response_templates->response_with_data_errors( array( array( @@ -400,7 +400,7 @@ class ApplePayButton implements ButtonInterface { * On error returns an array of errors to be handled by the script * On success returns the new order data * - * @throws \Exception When validation fails. + * @throws Exception When validation fails. */ public function create_wc_order(): void { $applepay_request_data_object = $this->applepay_data_object_http(); @@ -421,15 +421,18 @@ class ApplePayButton implements ButtonInterface { $applepay_request_data_object->order_data( $context ); $this->update_posted_data( $applepay_request_data_object ); + if ( $context === 'product' ) { $cart_item_key = $this->prepare_cart( $applepay_request_data_object ); $cart = WC()->cart; $address = $applepay_request_data_object->shipping_address(); + $this->calculate_totals_single_product( $cart, $address, $applepay_request_data_object->shipping_method() ); + if ( ! $cart_item_key ) { $this->response_templates->response_with_data_errors( array( @@ -439,20 +442,17 @@ class ApplePayButton implements ButtonInterface { ), ) ); - return; - } + } else { add_filter( 'woocommerce_payment_successful_result', function ( array $result ) use ( $cart, $cart_item_key ) : array { - if ( ! is_string( $cart_item_key ) ) { - return $result; - } $this->clear_current_cart( $cart, $cart_item_key ); $this->reload_cart( $cart ); return $result; } ); } + } WC()->checkout()->process_checkout(); } @@ -461,17 +461,20 @@ class ApplePayButton implements ButtonInterface { /** * Checks if the nonce in the data object is valid * - * @return bool|int + * @return bool */ protected function is_nonce_valid(): bool { $nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS ); if ( ! $nonce ) { return false; } - return wp_verify_nonce( + + // Return value 1 indicates "valid nonce, generated in past 12 hours". + // Return value 2 also indicated valid nonce, but older than 12 hours. + return 1 === wp_verify_nonce( $nonce, 'woocommerce-process_checkout' - ) === 1; + ); } /** @@ -512,7 +515,7 @@ class ApplePayButton implements ButtonInterface { $address, $applepay_request_data_object->shipping_method() ); - if ( is_string( $cart_item_key ) ) { + if ( $cart_item_key ) { $this->clear_current_cart( $cart, $cart_item_key ); $this->reload_cart( $cart ); } @@ -820,9 +823,9 @@ class ApplePayButton implements ButtonInterface { /** * Removes the old cart, saves it, and creates a new one * + * @throws Exception If it cannot be added to cart. * @param ApplePayDataObjectHttp $applepay_request_data_object The request data object. - * @return bool | string The cart item key after adding to the new cart. - * @throws \Exception If it cannot be added to cart. + * @return string The cart item key after adding to the new cart. */ public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string { $this->save_old_cart(); @@ -839,7 +842,7 @@ class ApplePayButton implements ButtonInterface { ); $this->cart_products->add_products( array( $product ) ); - return $this->cart_products->cart_item_keys()[0]; + return $this->cart_products->cart_item_keys()[0] ?? ''; } /** @@ -982,6 +985,7 @@ class ApplePayButton implements ButtonInterface { return true; } + /** * ApplePay button markup */ @@ -993,15 +997,6 @@ class ApplePayButton implements ButtonInterface { Date: Wed, 24 Jul 2024 15:21:09 +0200 Subject: [PATCH 032/119] =?UTF-8?q?=E2=9C=A8=20Hide=20Apple=20Pay=20gatewa?= =?UTF-8?q?y=20on=20ineligible=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 24 ++++++++++++-- .../src/Assets/ApplePayButton.php | 32 +++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4f09d28d2..21b410427 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -150,13 +150,31 @@ class ApplePayButton { const idButton = this.buttonConfig.button.wrapper; if ( ! this.isEligible ) { - jQuery( '#' + idButton ).hide(); - jQuery( '#' + idMinicart ).hide(); - jQuery( '#express-payment-method-ppcp-applepay' ).hide(); + const hideContainers = [ + // Payment button (Pay now, smart button block) + `#${ idButton }`, + // Mini Cart button + `#${ idMinicart }`, + // Block Checkout: Express checkout button. + '#express-payment-method-ppcp-applepay', + ]; + + hideContainers.forEach( ( selector ) => { + const elements = document.querySelectorAll( selector ); + + elements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + } ); return; } + // Classic Checkout: Make the Apple Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); + // Add click-handler to the button. const setupButtonEvents = ( id ) => { document diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index d9a9d5393..22adce359 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -443,15 +443,15 @@ class ApplePayButton implements ButtonInterface { ) ); } else { - add_filter( - 'woocommerce_payment_successful_result', - function ( array $result ) use ( $cart, $cart_item_key ) : array { - $this->clear_current_cart( $cart, $cart_item_key ); - $this->reload_cart( $cart ); - return $result; - } - ); - } + add_filter( + 'woocommerce_payment_successful_result', + function ( array $result ) use ( $cart, $cart_item_key ) : array { + $this->clear_current_cart( $cart, $cart_item_key ); + $this->reload_cart( $cart ); + return $result; + } + ); + } } WC()->checkout()->process_checkout(); @@ -953,6 +953,7 @@ class ApplePayButton implements ButtonInterface { $render_placeholder, function () { $this->applepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -997,6 +998,19 @@ class ApplePayButton implements ButtonInterface { + + Date: Wed, 24 Jul 2024 16:00:55 +0200 Subject: [PATCH 033/119] 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 034/119] 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 525b75e6a1ee50543b6b2c647d7307a11ac8c5cf Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 18:27:04 +0200 Subject: [PATCH 035/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Entangle=20code=20?= =?UTF-8?q?into=20getters=20and=20atomic=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 290 +++++++++++++----- 1 file changed, 206 insertions(+), 84 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 21b410427..28ab2e990 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,4 +1,6 @@ +/* eslint-env browser */ /* global jQuery */ + /* global ApplePaySession */ /* global PayPalCommerceGateway */ @@ -11,6 +13,25 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; 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 Apple Pay button. + * + * @typedef {Object} PPCPStyle + * @property {string} shape - Outline shape. + * @property {?number} height - Button height in pixel. + */ + +/** + * Style options that are defined by the Apple Pay SDK and are required to render the button. + * + * @typedef {Object} ApplePayStyle + * @property {string} type - Defines the button label. + * @property {string} color - Button color + * @property {string} lang - The locale; an empty string will apply the user-agent's language. + */ + /** * List of valid context values that the button can have. * @@ -43,6 +64,9 @@ class ApplePayButton { */ #isInitialized = false; + #wrapperId = ''; + #ppcpButtonWrapperId = ''; + /** * Context describes the button's location on the website and what details it submits. * @@ -92,9 +116,9 @@ class ApplePayButton { }; if ( this.buttonConfig.is_debug ) { - jQuery( document ).on( 'ppcp-applepay-debug', () => { + jQuery( document ).on( 'ppcp-applepay-debug', () => { this.log( this ); - } ); + } ); } } @@ -131,6 +155,127 @@ class ApplePayButton { return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); } + /** + * 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 { + 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 Apple Pay button. + * + * These styles are the official style options provided by the Apple Pay SDK. + * + * @return {ApplePayStyle} The style object. + */ + get buttonStyle() { + return { + type: this.buttonConfig.button.type, + lang: this.buttonConfig.button.lang, + color: this.buttonConfig.button.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-applepay' ); + } + + // Classic Checkout: Apple Pay gateway. + if ( CONTEXT.Checkout === this.context ) { + selectors.push( '.wc_payment_method.payment_method_ppcp-applepay' ); + } + + 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 ) { if ( this.#isInitialized ) { return; @@ -146,54 +291,24 @@ class ApplePayButton { this.#isInitialized = true; this.applePayConfig = config; - const idMinicart = this.buttonConfig.button.mini_cart_wrapper; - const idButton = this.buttonConfig.button.wrapper; - if ( ! this.isEligible ) { - const hideContainers = [ - // Payment button (Pay now, smart button block) - `#${ idButton }`, - // Mini Cart button - `#${ idMinicart }`, - // Block Checkout: Express checkout button. - '#express-payment-method-ppcp-applepay', - ]; + this.hide(); + } else { + this.show(); - hideContainers.forEach( ( selector ) => { - const elements = document.querySelectorAll( selector ); + this.fetchTransactionInfo().then( () => { + const button = this.addButton(); - elements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - } ); + if ( ! button ) { + return; + } - return; - } - - // Classic Checkout: Make the Apple Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-apple-pay' ) - .forEach( ( el ) => el.remove() ); - - // Add click-handler to the button. - const setupButtonEvents = ( id ) => { - document - .getElementById( id ) - ?.addEventListener( 'click', ( evt ) => { + button.addEventListener( 'click', ( evt ) => { evt.preventDefault(); this.onButtonClick(); } ); - }; - - this.fetchTransactionInfo().then( () => { - this.addButton(); - - if ( CONTEXT.MiniCart === this.context ) { - setupButtonEvents( idMinicart ); - } else { - setupButtonEvents( idButton ); - } - } ); + } ); + } } reinit() { @@ -205,40 +320,43 @@ class ApplePayButton { this.init( this.applePayConfig ); } + /** + * Hides all wrappers that belong to this ApplePayButton instance. + */ + hide() { + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + } + + /** + * Ensures all wrapper elements of this ApplePayButton instance are visible. + */ + show() { + if ( ! this.isPresent ) { + this.log( 'Cannot show button, wrapper is not present' ); + return; + } + + // Classic Checkout: Make the Apple Pay gateway visible after page load. + if ( CONTEXT.Checkout === this.context ) { + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); + } + + this.allElements.forEach( ( element ) => { + element.style.display = ''; + } ); + } + async fetchTransactionInfo() { this.transactionInfo = await this.contextHandler.transactionInfo(); } - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - const config = {}; - - if ( CONTEXT.MiniCart === this.context ) { - 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; - } else { - config.wrapper = this.buttonConfig.button.wrapper; - config.ppcpStyle = this.ppcpConfig.button.style; - config.buttonStyle = this.buttonConfig.button.style; - config.ppcpButtonWrapper = this.ppcpConfig.button.wrapper; - } - - // Block editor configuration. - if ( CONTEXT.Blocks.includes( this.context ) ) { - config.ppcpButtonWrapper = - '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - const wrapperId = '#' + wrapper; + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapperId = `#${ this.wrapperId }`; if ( wrapperId === ppcpButtonWrapper ) { throw new Error( @@ -295,23 +413,23 @@ class ApplePayButton { /** * Adds an Apple Pay purchase button. + * + * @return {HTMLElement|null} The newly created `` element. Null on failure. */ addButton() { this.log( 'addButton' ); - const { wrapper, ppcpStyle } = this.contextConfig(); - - const appleContainer = document.getElementById( wrapper ); - const type = this.buttonConfig.button.type; - const language = this.buttonConfig.button.lang; - const color = this.buttonConfig.button.color; - const id = 'apple-' + wrapper; + const appleContainer = document.getElementById( this.wrapperId ); + const style = this.buttonStyle; + const id = 'apple-' + this.wrapperId; if ( ! appleContainer ) { - return; + return null; } - appleContainer.innerHTML = ``; + const ppcpStyle = this.ppcpStyle; + + appleContainer.innerHTML = ``; appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { @@ -321,6 +439,8 @@ class ApplePayButton { ); appleContainer.style.height = `${ ppcpStyle.height }px`; } + + return appleContainer.querySelector( 'apple-pay-button' ); } //------------------------ @@ -345,6 +465,7 @@ class ApplePayButton { PayPalCommerceGateway.labels.error.generic, document.querySelector( '.woocommerce-notices-wrapper' ) ); + try { const formData = new FormData( document.querySelector( checkoutFormSelector ) @@ -366,6 +487,7 @@ class ApplePayButton { PayPalCommerceGateway.ajax.validate_checkout.nonce ) : null; + if ( formValidator ) { try { const errors = await formValidator.validate( @@ -898,15 +1020,15 @@ class ApplePayButton { restart: () => new Promise( ( resolve, reject ) => { - approveFailed = true; - resolve(); + approveFailed = true; + resolve(); } ), order: { get: () => new Promise( ( resolve, reject ) => { - resolve( null ); + resolve( null ); } ), }, From 4a0e410f52487afb31d76816e1a96af2565a153d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 18:55:22 +0200 Subject: [PATCH 036/119] =?UTF-8?q?=F0=9F=94=A7=20Add=20missing=20config?= =?UTF-8?q?=20to=20eslintrc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env.browser: Linter recognizes browser elements, like `HTMLElement` - globals.jQuery: Library is present on all pages --- .eslintrc | 6 +++++- modules/ppcp-applepay/resources/js/ApplepayButton.js | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.eslintrc b/.eslintrc index 7a97d815d..947e3acbe 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,11 @@ { "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], + "env": { + "browser": true + }, "globals": { - "wc": true + "wc": true, + "jQuery": "readonly" }, "rules": { "no-console": ["error", { "allow": ["warn", "error"] }] diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 28ab2e990..3c9f0acf7 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,6 +1,3 @@ -/* eslint-env browser */ -/* global jQuery */ - /* global ApplePaySession */ /* global PayPalCommerceGateway */ From 5df93b2dd48d5f90c6e7d61bb778eda4182ec359 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 19:42:37 +0200 Subject: [PATCH 037/119] =?UTF-8?q?=F0=9F=92=AC=20Improve=20the=20gateway?= =?UTF-8?q?=20description=20for=20admins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/src/ApplePayGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php index 8b46dda64..1cccda0f2 100644 --- a/modules/ppcp-applepay/src/ApplePayGateway.php +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -96,7 +96,7 @@ class ApplePayGateway extends WC_Payment_Gateway { $this->id = self::ID; $this->method_title = __( 'Apple Pay (via PayPal) ', 'woocommerce-paypal-payments' ); - $this->method_description = __( 'The separate payment gateway with the Apple Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'Display Apple Pay as a standalone payment option instead of bundling it with PayPal.', 'woocommerce-paypal-payments' ); $this->title = $this->get_option( 'title', __( 'Apple Pay', 'woocommerce-paypal-payments' ) ); $this->description = $this->get_option( 'description', '' ); From f1c5b70877f79880c17356ecaa6f3a81a293a599 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:02:27 +0200 Subject: [PATCH 038/119] =?UTF-8?q?=E2=9C=A8=20Extract=20payment=20button?= =?UTF-8?q?=20to=20own=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 3c9f0acf7..024782a1d 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -43,7 +43,10 @@ const CONTEXT = { BlockCart: 'cart-block', BlockCheckout: 'checkout-block', Preview: 'preview', + // Block editor contexts. Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], }; /** @@ -152,6 +155,22 @@ class ApplePayButton { return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); } + /** + * 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. @@ -164,6 +183,8 @@ class ApplePayButton { if ( CONTEXT.MiniCart === this.context ) { id = this.buttonConfig.button.mini_cart_wrapper; + } else if ( this.isSeparateGateway ) { + id = 'ppc-button-ppcp-applepay'; } else { id = this.buttonConfig.button.wrapper; } From cffaa7a92f7a14462753536cba8889cd58c145bd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:03:23 +0200 Subject: [PATCH 039/119] =?UTF-8?q?=F0=9F=92=84=20Fix=20button=20size=20in?= =?UTF-8?q?=20stand-alone=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 024782a1d..4d135bce5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -437,28 +437,32 @@ class ApplePayButton { addButton() { this.log( 'addButton' ); - const appleContainer = document.getElementById( this.wrapperId ); + const wrapper = this.wrapperElement; const style = this.buttonStyle; const id = 'apple-' + this.wrapperId; - if ( ! appleContainer ) { + if ( ! wrapper ) { return null; } const ppcpStyle = this.ppcpStyle; - appleContainer.innerHTML = ``; - appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); + wrapper.innerHTML = ``; + wrapper.classList.add( + `ppcp-button-${ ppcpStyle.shape }`, + 'ppcp-button-apm', + 'ppcp-button-applepay' + ); if ( ppcpStyle.height ) { - appleContainer.style.setProperty( + wrapper.style.setProperty( '--apple-pay-button-height', `${ ppcpStyle.height }px` ); - appleContainer.style.height = `${ ppcpStyle.height }px`; + wrapper.style.height = `${ ppcpStyle.height }px`; } - return appleContainer.querySelector( 'apple-pay-button' ); + return wrapper.querySelector( 'apple-pay-button' ); } //------------------------ From 354a9ff175ce72105034612af2bee7aa917db618 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:13:14 +0200 Subject: [PATCH 040/119] =?UTF-8?q?=F0=9F=92=A1=20Better=20debug=20logs=20?= =?UTF-8?q?for=20show/hide=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4d135bce5..68edf2e6a 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -312,6 +312,12 @@ class ApplePayButton { if ( ! this.isEligible ) { this.hide(); } else { + // Bail if the button wrapper is not present; handles mini-cart logic on checkout page. + if ( ! this.isPresent ) { + this.log( 'Abort init (no wrapper found)' ); + return; + } + this.show(); this.fetchTransactionInfo().then( () => { @@ -342,6 +348,7 @@ class ApplePayButton { * Hides all wrappers that belong to this ApplePayButton instance. */ hide() { + this.log( 'Hide button' ); this.allElements.forEach( ( element ) => { element.style.display = 'none'; } ); @@ -351,8 +358,9 @@ class ApplePayButton { * Ensures all wrapper elements of this ApplePayButton instance are visible. */ show() { + this.log( 'Show button' ); if ( ! this.isPresent ) { - this.log( 'Cannot show button, wrapper is not present' ); + this.log( '!! Cannot show button, wrapper is not present' ); return; } From a4d3af848c378ae296e2da950e42433227a7f535 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:13:30 +0200 Subject: [PATCH 041/119] =?UTF-8?q?=F0=9F=A9=B9=20Hide=20gateway=20on=20Pa?= =?UTF-8?q?yNow=20page=20when=20ineligible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 12 +++++------- modules/ppcp-applepay/src/Assets/ApplePayButton.php | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 68edf2e6a..227f1533b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -275,7 +275,7 @@ class ApplePayButton { } // Classic Checkout: Apple Pay gateway. - if ( CONTEXT.Checkout === this.context ) { + if ( CONTEXT.Gateways.includes( this.context ) ) { selectors.push( '.wc_payment_method.payment_method_ppcp-applepay' ); } @@ -364,12 +364,10 @@ class ApplePayButton { return; } - // Classic Checkout: Make the Apple Pay gateway visible after page load. - if ( CONTEXT.Checkout === this.context ) { - document - .querySelectorAll( 'style#ppcp-hide-apple-pay' ) - .forEach( ( el ) => el.remove() ); - } + // Classic Checkout/PayNow: Make the Apple Pay gateway visible after page load. + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); this.allElements.forEach( ( element ) => { element.style.display = ''; diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 22adce359..bfcc88133 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -966,6 +966,7 @@ class ApplePayButton implements ButtonInterface { $render_placeholder, function () { $this->applepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); From b9b13d7b05718e9cc55ba76618e6b4a8d2f64a8e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:42:57 +0200 Subject: [PATCH 042/119] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20unnecessa?= =?UTF-8?q?ry=20styles=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/css/styles.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index 26c43b63e..3818b8db5 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -52,8 +52,3 @@ } } } - -// Initially hide the APM button until it's explicitly activated. -#ppc-button-ppcp-applepay { - display: none; -} From 75f4a6f94a154fdc66fbc5ba586c237c92e235ac Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:47:59 +0200 Subject: [PATCH 043/119] =?UTF-8?q?=F0=9F=8E=A8=20Fully=20disable=20debug?= =?UTF-8?q?=20logic=20via=20WP=5FDEBUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 227f1533b..ce998f04c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -104,18 +104,20 @@ class ApplePayButton { this.refreshContextData(); + this.log = () => {}; + // Debug helpers - document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; - document.ppcpApplepayButtons[ this.context ] = this; - - this.log = function () { - if ( ! this.buttonConfig.is_debug ) { - return; - } - console.log( `[ApplePayButton | ${ this.context }]`, ...arguments ); - }; - if ( this.buttonConfig.is_debug ) { + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[ this.context ] = this; + + this.log = function () { + console.log( + `[ApplePayButton | ${ this.context }]`, + ...arguments + ); + }; + jQuery( document ).on( 'ppcp-applepay-debug', () => { this.log( this ); } ); From 8535601810a7605b9f1721d4ac03110903510cee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 25 Jul 2024 00:18:15 +0200 Subject: [PATCH 044/119] 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 045/119] 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 046/119] 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 64fae60da44ee071c27c14733acfdbb5f5b262a3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 25 Jul 2024 15:48:35 +0200 Subject: [PATCH 047/119] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Undo=20a=20type-hi?= =?UTF-8?q?nt=20that=20causes=20CI=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our Stub object has a void return value; CI fails with this type-hint, as the Stub is not compatible with the actual object. --- modules/ppcp-wc-gateway/src/Settings/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/Settings.php b/modules/ppcp-wc-gateway/src/Settings/Settings.php index e91256653..e083a91d1 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Settings.php +++ b/modules/ppcp-wc-gateway/src/Settings/Settings.php @@ -117,7 +117,7 @@ class Settings implements ContainerInterface { /** * Stores the settings to the database. */ - public function persist() : bool { + public function persist() { return update_option( self::KEY, $this->settings ); } From a14276bf528f89fe8ddc7bbf2530b7b365986ee9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 25 Jul 2024 16:50:00 +0200 Subject: [PATCH 048/119] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fix=20all=20report?= =?UTF-8?q?ed=20phpcs=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Assets/DataToAppleButtonScripts.php | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 004500355..52663a4f0 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -73,7 +73,7 @@ class DataToAppleButtonScripts { * * @return array */ - private function get_apple_pay_data( array $product = [] ) : array { + private function get_apple_pay_data( array $product = array() ) : array { // true: Use Apple Pay as distinct gateway. // false: integrate it with the smart buttons. $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); @@ -128,6 +128,8 @@ class DataToAppleButtonScripts { /** * Check if the product needs shipping * + * @param WC_Product $product Product to check. + * * @return bool */ protected function check_if_need_shipping( WC_Product $product ) : bool { @@ -167,13 +169,15 @@ class DataToAppleButtonScripts { $product_price = $product->get_price(); $product_stock = $product->get_stock_status(); - return $this->get_apple_pay_data( array( - 'needShipping' => $product_need_shipping, - 'id' => $product_id, - 'price' => $product_price, - 'isVariation' => $is_variation, - 'stock' => $product_stock, - ) ); + return $this->get_apple_pay_data( + array( + 'needShipping' => $product_need_shipping, + 'id' => $product_id, + 'price' => $product_price, + 'isVariation' => $is_variation, + 'stock' => $product_stock, + ) + ); } /** @@ -187,10 +191,12 @@ class DataToAppleButtonScripts { return array(); } - return $this->get_apple_pay_data( array( - 'needShipping' => $cart->needs_shipping(), - 'subtotal' => $cart->get_subtotal(), - ) ); + return $this->get_apple_pay_data( + array( + 'needShipping' => $cart->needs_shipping(), + 'subtotal' => $cart->get_subtotal(), + ) + ); } /** @@ -201,10 +207,12 @@ class DataToAppleButtonScripts { * @return array */ protected function data_for_admin_page() : array { - $data = $this->get_apple_pay_data( array( - 'needShipping' => false, - 'subtotal' => 0, - ) ); + $data = $this->get_apple_pay_data( + array( + 'needShipping' => false, + 'subtotal' => 0, + ) + ); $data['is_admin'] = true; From 2da1fa0323062b0f9f22a3b2e4be2d704a9e7c41 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Fri, 26 Jul 2024 16:20:08 +0400 Subject: [PATCH 049/119] 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 050/119] 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 b96c87597a89b739f5295de15740887bc3370993 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 26 Jul 2024 18:06:09 +0400 Subject: [PATCH 051/119] Do not consider needShipping from single page --- .../ppcp-button/resources/js/modules/Renderer/Renderer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 7d6139d4c..63bd5e2ed 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -139,7 +139,7 @@ class Renderer { }; // Check the condition and add the handler if needed - if ( this.defaultSettings.should_handle_shipping_in_paypal && this.defaultSettings.needShipping ) { + if ( this.shouldEnableShippingCallback() ) { options.onShippingOptionsChange = ( data, actions ) => { let shippingOptionsChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( @@ -227,6 +227,12 @@ class Renderer { return venmoButtonClicked && this.defaultSettings.vaultingEnabled; }; + shouldEnableShippingCallback = () => { + console.log(this.defaultSettings.context, this.defaultSettings) + let needShipping = this.defaultSettings.needShipping || this.defaultSettings.context === 'product' + return this.defaultSettings.should_handle_shipping_in_paypal && needShipping + }; + isAlreadyRendered( wrapper, fundingSource ) { return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) ); } From cbf9bff80836946b28b8f31027668e82f9ec3399 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:06:42 +0200 Subject: [PATCH 052/119] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20with=20undef?= =?UTF-8?q?ined=20log()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index ce998f04c..d84870fd9 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -43,8 +43,10 @@ const CONTEXT = { BlockCart: 'cart-block', BlockCheckout: 'checkout-block', Preview: 'preview', + // Block editor contexts. Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. Gateways: [ 'checkout', 'pay-now' ], }; @@ -89,6 +91,8 @@ class ApplePayButton { initialPaymentRequest = null; constructor( context, externalHandler, buttonConfig, ppcpConfig ) { + this._initDebug( !! buttonConfig?.is_debug ); + apmButtonsInit( ppcpConfig ); this.context = context; @@ -103,25 +107,34 @@ class ApplePayButton { ); this.refreshContextData(); + } - this.log = () => {}; + /** + * NOOP log function to avoid errors when debugging is disabled. + */ + log() {} - // Debug helpers - if ( this.buttonConfig.is_debug ) { - document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; - document.ppcpApplepayButtons[ this.context ] = this; - - this.log = function () { - console.log( - `[ApplePayButton | ${ this.context }]`, - ...arguments - ); - }; - - jQuery( document ).on( 'ppcp-applepay-debug', () => { - this.log( this ); - } ); + /** + * Enables debugging tools, when the button's is_debug flag is set. + * + * @param {boolean} enableDebugging If debugging features should be enabled for this instance. + * @private + */ + _initDebug( enableDebugging ) { + if ( ! enableDebugging || this.#isInitialized ) { + return; } + + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[ this.context ] = this; + + this.log = ( ...args ) => { + console.log( `[ApplePayButton | ${ this.context }]`, ...args ); + }; + + jQuery( document ).on( 'ppcp-applepay-debug', () => { + this.log( this ); + } ); } /** From 09308f15b65183f145b06461db309be3aed2d773 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:20:48 +0200 Subject: [PATCH 053/119] =?UTF-8?q?=F0=9F=90=9B=20Hide=20Apple=20Pay=20but?= =?UTF-8?q?ton=20until=20gateway=20is=20selected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/css/styles.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index 3818b8db5..3402b1a20 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -52,3 +52,7 @@ } } } + +#ppc-button-ppcp-applepay { + display: none; +} From c15715a02f5e2fb30502479b90894ab291998130 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:28:30 +0200 Subject: [PATCH 054/119] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20reliable=20?= =?UTF-8?q?eligibility-check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eligibility better aligns with the ApplePay SDK logic and will not insert a hidden button anymore. --- .../resources/js/ApplepayButton.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d84870fd9..fb853182c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -163,11 +163,26 @@ class ApplePayButton { return true; } - if ( this.buttonConfig.is_admin ) { + if ( CONTEXT.Preview === this.context ) { return true; } - return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); + /** + * Ensure the ApplePaySession is available and accepts payments + * This check is required when using Apple Pay SDK v1; canMakePayments() returns false + * if the current device is not liked to iCloud or the Apple Wallet is not available + * for a different reason. + */ + try { + if ( ! window.ApplePaySession?.canMakePayments() ) { + return false; + } + } catch ( error ) { + console.warn( error ); + return false; + } + + return !! this.applePayConfig.isEligible; } /** From 1ac9b460b6aae0ee35dad718411628194c42f905 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 18:32:33 +0200 Subject: [PATCH 055/119] =?UTF-8?q?=F0=9F=90=9B=20Display=20the=20PayPal?= =?UTF-8?q?=20payment=20tabs=20in=20Gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-wc-gateway/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..92acc6b0c 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -71,6 +71,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; +use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway; return array( 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { @@ -198,6 +199,7 @@ return array( Settings::PAY_LATER_TAB_ID, AxoGateway::ID, GooglePayGateway::ID, + ApplePayGateway::ID, ), true ); @@ -220,6 +222,7 @@ return array( Settings::PAY_LATER_TAB_ID, Settings::CONNECTION_TAB_ID, GooglePayGateway::ID, + ApplePayGateway::ID, ), true ); From 6fbdb9417b922138218414a304c2785e656a3be3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 26 Jul 2024 18:50:35 +0200 Subject: [PATCH 056/119] 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 057/119] 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 6f7270c35bc6c9ab3c53e02373e54b8bac2b971d Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 12:18:41 +0200 Subject: [PATCH 058/119] 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 8bda629df5d8d8c21004980adbbdf1e525db9672 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 14:20:27 +0200 Subject: [PATCH 059/119] 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 060/119] 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 061/119] =?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 062/119] 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 063/119] =?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 064/119] =?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 065/119] =?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 11:37:03 +0200 Subject: [PATCH 066/119] 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 067/119] 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 068/119] =?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 069/119] =?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 070/119] =?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 071/119] 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 072/119] 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 073/119] =?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 074/119] 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 075/119] =?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 076/119] =?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 077/119] 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 078/119] 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 079/119] 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 080/119] 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 081/119] 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 082/119] 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 083/119] =?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 084/119] =?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 085/119] =?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 086/119] =?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 b85a16abda79b2db715f8ac0b78e43e6a261f9b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 15:59:54 +0200 Subject: [PATCH 087/119] =?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 088/119] =?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 14:36:51 +0200 Subject: [PATCH 089/119] =?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 090/119] =?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 { - ?>