From af286c8f823c2aaf7cee27f0ecf2b0c0c64fd446 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 16:43:51 +0400 Subject: [PATCH 001/133] 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 002/133] 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 003/133] 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 004/133] 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 694caac891206339ef61d6b4f501865e266ee93f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 16:11:52 +0400 Subject: [PATCH 005/133] 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 006/133] 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 007/133] 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 008/133] 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 009/133] 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 010/133] 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 011/133] =?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 012/133] 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 013/133] 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 014/133] 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 015/133] 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 016/133] 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 017/133] 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 018/133] 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 019/133] 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 020/133] 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 021/133] 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 022/133] 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 023/133] =?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 024/133] =?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 025/133] =?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 026/133] =?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 027/133] =?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 028/133] =?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 029/133] 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 030/133] 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 031/133] =?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 032/133] =?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 033/133] =?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 034/133] =?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 035/133] =?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 036/133] =?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 037/133] =?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 038/133] =?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 039/133] =?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 40c81683d926630290b4c595f54c111446ef24e2 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Thu, 25 Jul 2024 14:08:45 +0400 Subject: [PATCH 040/133] 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 041/133] 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 042/133] =?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 043/133] =?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 044/133] 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 045/133] 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 046/133] 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 047/133] =?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 048/133] =?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 049/133] =?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 050/133] =?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 051/133] 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 052/133] 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 053/133] 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 054/133] 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 055/133] 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 056/133] =?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 057/133] 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 058/133] =?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 059/133] =?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 060/133] =?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 061/133] 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 062/133] 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 063/133] =?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 064/133] =?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 065/133] =?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 066/133] 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 067/133] 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 2815fcbb919dbccc9e2220034968700d538d39ef Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 2 Aug 2024 15:24:26 +0200 Subject: [PATCH 068/133] 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 069/133] =?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 070/133] =?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 071/133] =?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 072/133] =?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 2edeac55442a91353a6b4a8ee6d759635f060ecb Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 5 Aug 2024 17:36:56 +0200 Subject: [PATCH 073/133] Add new create order endpoint (WIP) --- modules/ppcp-api-client/services.php | 7 ++ .../ppcp-api-client/src/Endpoint/Orders.php | 76 +++++++++++++++++++ .../ppcp-wc-gateway/src/WCGatewayModule.php | 1 + .../PHPUnit/ApiClient/Endpoint/OrdersTest.php | 26 +++++++ tests/e2e/PHPUnit/OrdersTest.php | 49 ++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 modules/ppcp-api-client/src/Endpoint/Orders.php create mode 100644 tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php create mode 100644 tests/e2e/PHPUnit/OrdersTest.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 241240ce2..5b60c7720 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult; @@ -239,6 +240,12 @@ return array( $bn_code ); }, + 'api.endpoint.orders' => static function (ContainerInterface $container): Orders { + return new Orders( + $container->get( 'api.host' ), + $container->get( 'api.bearer' ) + ); + }, 'api.endpoint.billing-agreements' => static function ( ContainerInterface $container ): BillingAgreementsEndpoint { return new BillingAgreementsEndpoint( $container->get( 'api.host' ), diff --git a/modules/ppcp-api-client/src/Endpoint/Orders.php b/modules/ppcp-api-client/src/Endpoint/Orders.php new file mode 100644 index 000000000..51d01a24d --- /dev/null +++ b/modules/ppcp-api-client/src/Endpoint/Orders.php @@ -0,0 +1,76 @@ +host = $host; + $this->bearer = $bearer; + } + + public function create(array $request_body, array $headers = array()): array { + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders'; + + $default_headers = array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + ); + $headers = array_merge( + $default_headers, + $headers + ); + + $args = array( + 'method' => 'POST', + 'headers' => $headers, + 'body' => wp_json_encode( $request_body ), + ); + + $response = wp_remote_get( $url, $args ); + if ( $response instanceof WP_Error ) { + throw new RuntimeException( $response->get_error_message() ); + } + + return $response; + } +} diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 0b1315376..f01084da9 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway; use Psr\Log\LoggerInterface; use Throwable; use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php new file mode 100644 index 000000000..1033a015b --- /dev/null +++ b/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php @@ -0,0 +1,26 @@ +shouldReceive('token')->andReturn(''); + $bearer->shouldReceive('bearer')->andReturn($token); + + $sut = new Orders('', $bearer); + + expect('wp_remote_get')->andReturn([]); + + $this->assertEquals([], $sut->create([])); + } +} diff --git a/tests/e2e/PHPUnit/OrdersTest.php b/tests/e2e/PHPUnit/OrdersTest.php new file mode 100644 index 000000000..da9dc990e --- /dev/null +++ b/tests/e2e/PHPUnit/OrdersTest.php @@ -0,0 +1,49 @@ +getContainer(); + + $orders = new Orders($host, $container->get('api.bearer')); + + $requestBody = [ + "intent" => "CAPTURE", + "payment_source" => [ + "bancontact" => [ + "country_code" => "BE", + "name" => "John Doe" + ] + ], + "processing_instruction" => "ORDER_COMPLETE_ON_PAYMENT_APPROVAL", + "purchase_units" => [ + [ + "reference_id" => "d9f80740-38f0-11e8-b467-0ed5f89f718b", + "amount" => [ + "currency_code" => "EUR", + "value" => "1.00" + ], + ] + ], + "application_context" => [ + "locale" => "en-BE", + "return_url" => "https://example.com/returnUrl", + "cancel_url" => "https://example.com/cancelUrl" + ] + ]; + + $headers = array( + 'PayPal-Request-Id' => uniqid( 'ppcp-', true ), + ); + + $result = $orders->create($requestBody, $headers); + + $this->assertEquals(200, $result['response']['code']); + } +} From b85a16abda79b2db715f8ac0b78e43e6a261f9b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 15:59:54 +0200 Subject: [PATCH 074/133] =?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 075/133] =?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 076/133] =?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 077/133] =?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 { - ?>