From 7782e79aae652225bd7b5b6a1b358819a7ac91ed Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 18 Jun 2024 12:31:50 +0200 Subject: [PATCH 001/271] Add `buttonAttributes` for getting button styles --- modules/ppcp-blocks/resources/js/checkout-block.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index bf36dcb65..fdd5e04bf 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -40,6 +40,7 @@ const PayPalComponent = ( { shippingData, isEditing, fundingSource, + buttonAttributes, } ) => { const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } = eventRegistration; @@ -582,6 +583,12 @@ const PayPalComponent = ( { fundingSource ); + if ( typeof buttonAttributes !== 'undefined' ) { + style.height = buttonAttributes?.height + ? Number( buttonAttributes.height ) + : style.height; + } + if ( ! paypalScriptLoaded ) { return null; } @@ -606,11 +613,11 @@ const PayPalComponent = ( { } return ( data, actions ) => { - let shippingAddressChange = shouldHandleShippingInPayPal() + const shippingAddressChange = shouldHandleShippingInPayPal() ? handleShippingAddressChange( data, actions ) : null; - return shippingAddressChange; + return shippingAddressChange; }; }; From 60ffa996b0dab1d876dd47906e0f619be937ecd8 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 22 Jul 2024 13:44:48 +0200 Subject: [PATCH 002/271] Add the notice informing merchants that the Cart & Express Checkout Smart Button Stylings may be controlled by the Checkout block configuration --- modules/ppcp-googlepay/src/Assets/Button.php | 2 +- .../resources/css/gateway-settings.scss | 2 +- modules/ppcp-wc-gateway/services.php | 19 +++++++++++++++++++ .../Fields/paypal-smart-button-fields.php | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 6fab601b5..a5eed6934 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -409,7 +409,7 @@ class Button implements ButtonInterface { */ public function script_data(): array { $shipping = array( - 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) + 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) ? boolval( $this->settings->get( 'googlepay_button_shipping_enabled' ) ) : false, 'configured' => wc_shipping_enabled() && wc_get_shipping_method_count( false, true ) > 0, diff --git a/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss b/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss index 23e642c8f..768f95c75 100644 --- a/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss +++ b/modules/ppcp-wc-gateway/resources/css/gateway-settings.scss @@ -32,7 +32,7 @@ @media (min-width: 1200px) { float: right; - margin-top: -300px; + margin-top: -250px; max-width: calc(100vw - 850px); } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index f0d848216..4eb55a920 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -26,6 +26,7 @@ use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer; @@ -1552,6 +1553,24 @@ return array( unset( $button_locations['mini-cart'] ); return array_keys( $button_locations ); }, + 'wcgateway.button.recommended-styling-notice' => static function ( ContainerInterface $container ) : string { + if ( CartCheckoutDetector::has_block_checkout() ) { + $block_checkout_page_string_html = '' . __( 'Checkout block', 'woocommerce-paypal-payments' ) . ''; + } else { + $block_checkout_page_string_html = __( 'Checkout block', 'woocommerce-paypal-payments' ); + } + + $notice_content = sprintf( + /* translators: %1$s: URL to the Checkout edit page. */ + __( + 'Important: The Cart & Express Checkout Smart Button Stylings may be controlled by the %1$s configuration.', + 'woocommerce-paypal-payments' + ), + $block_checkout_page_string_html + ); + + return '

' . $notice_content . '

'; + }, 'wcgateway.settings.pay-later.messaging-locations' => static function( ContainerInterface $container ): array { $button_locations = $container->get( 'wcgateway.button.locations' ); unset( $button_locations['mini-cart'] ); diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php index 3a2513e98..f1efb1497 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php @@ -88,6 +88,7 @@ return function ( ContainerInterface $container, array $fields ): array { 'type' => 'checkbox', 'label' => __( 'Customize smart button style per location', 'woocommerce-paypal-payments' ), 'default' => false, + 'description' => $container->has( 'wcgateway.button.recommended-styling-notice' ) ? $container->get( 'wcgateway.button.recommended-styling-notice' ) : '', 'screens' => array( State::STATE_START, State::STATE_ONBOARDED ), 'requirements' => array(), 'gateway' => 'paypal', From 422b609b2c3d44d620468a432d9542720b400a7a Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 19 Aug 2024 01:16:20 -0500 Subject: [PATCH 003/271] Add support for buttonAttributes API in the editor --- .../resources/css/gateway-editor.scss | 3 + .../resources/js/checkout-block.js | 61 +++++++++++++++---- modules/ppcp-blocks/src/BlocksModule.php | 17 ++++++ modules/ppcp-blocks/webpack.config.js | 3 +- 4 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 modules/ppcp-blocks/resources/css/gateway-editor.scss diff --git a/modules/ppcp-blocks/resources/css/gateway-editor.scss b/modules/ppcp-blocks/resources/css/gateway-editor.scss new file mode 100644 index 000000000..68c419cb8 --- /dev/null +++ b/modules/ppcp-blocks/resources/css/gateway-editor.scss @@ -0,0 +1,3 @@ +li[id^="express-payment-method-ppcp-"] div[class^="ppc-button-container-"] { + display: flex; +} diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index fdd5e04bf..5852b805a 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useState, useMemo } from '@wordpress/element'; import { registerExpressPaymentMethod, registerPaymentMethod, @@ -587,6 +587,10 @@ const PayPalComponent = ( { style.height = buttonAttributes?.height ? Number( buttonAttributes.height ) : style.height; + style.borderRadius = buttonAttributes?.borderRadius + ? Number( buttonAttributes.borderRadius ) + : style.borderRadius; + style.color = buttonAttributes?.darkMode ? 'white' : ''; } if ( ! paypalScriptLoaded ) { @@ -660,19 +664,47 @@ const PayPalComponent = ( { ); }; -const BlockEditorPayPalComponent = () => { - const urlParams = { - clientId: 'test', - ...config.scriptData.url_params, - dataNamespace: 'ppcp-blocks-editor-paypal-buttons', - components: 'buttons', - }; +const BlockEditorPayPalComponent = ( { fundingSource, buttonAttributes } ) => { + const urlParams = useMemo( + () => ( { + clientId: 'test', + ...config.scriptData.url_params, + dataNamespace: 'ppcp-blocks-editor-paypal-buttons', + components: 'buttons', + } ), + [] + ); + + const style = useMemo( () => { + const configStyle = normalizeStyleForFundingSource( + config.scriptData.button.style, + fundingSource + ); + + if ( buttonAttributes ) { + return { + ...configStyle, + height: buttonAttributes.height + ? Number( buttonAttributes.height ) + : configStyle.height, + borderRadius: buttonAttributes.borderRadius + ? Number( buttonAttributes.borderRadius ) + : configStyle.borderRadius, + color: buttonAttributes.darkMode ? 'white' : configStyle.color, + }; + } + + return configStyle; + }, [ fundingSource, buttonAttributes ] ); + return ( { - return false; - } } + className={ `ppc-button-container-${ fundingSource }` } + fundingSource={ fundingSource } + style={ style } + forceReRender={ [ buttonAttributes || {} ] } + onClick={ () => false } /> ); @@ -775,6 +807,7 @@ if ( block_enabled && config.enabled ) { 'paypal', ...config.enabledFundingSources, ] ) { + console.log( 'earlier fundingSource', fundingSource ); registerExpressPaymentMethod( { name: `${ config.id }-${ fundingSource }`, paymentMethodId: config.id, @@ -787,7 +820,11 @@ if ( block_enabled && config.enabled ) { fundingSource={ fundingSource } /> ), - edit: , + edit: ( + + ), ariaLabel: config.title, canMakePayment: async () => { if ( ! paypalScriptPromise ) { diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 1dd1560dd..413cc8b4d 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -111,6 +111,23 @@ class BlocksModule implements ModuleInterface { } ); + // Enqueue editor styles. + add_action( + 'enqueue_block_editor_assets', + static function () use ( $c ) { + $module_url = $c->get( 'blocks.url' ); + $asset_version = $c->get( 'ppcp.asset-version' ); + + wp_register_style( + 'wc-ppcp-blocks-editor', + untrailingslashit( $module_url ) . '/assets/css/gateway-editor.css', + array(), + $asset_version + ); + wp_enqueue_style( 'wc-ppcp-blocks-editor' ); + } + ); + add_filter( 'woocommerce_paypal_payments_sdk_components_hook', function( array $components ) { diff --git a/modules/ppcp-blocks/webpack.config.js b/modules/ppcp-blocks/webpack.config.js index 44ab3e68f..72bbc93ea 100644 --- a/modules/ppcp-blocks/webpack.config.js +++ b/modules/ppcp-blocks/webpack.config.js @@ -11,7 +11,8 @@ module.exports = { entry: { 'checkout-block': path.resolve('./resources/js/checkout-block.js'), 'advanced-card-checkout-block': path.resolve('./resources/js/advanced-card-checkout-block.js'), - "gateway": path.resolve('./resources/css/gateway.scss') + "gateway": path.resolve('./resources/css/gateway.scss'), + "gateway-editor": path.resolve('./resources/css/gateway-editor.scss') }, output: { path: path.resolve(__dirname, 'assets/'), From 5604454650caeeab167aed2bb50d3a40b12c486c Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 19 Aug 2024 01:19:00 -0500 Subject: [PATCH 004/271] Remove the console log --- modules/ppcp-blocks/resources/js/checkout-block.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 5852b805a..13bd2c6cd 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -807,7 +807,6 @@ if ( block_enabled && config.enabled ) { 'paypal', ...config.enabledFundingSources, ] ) { - console.log( 'earlier fundingSource', fundingSource ); registerExpressPaymentMethod( { name: `${ config.id }-${ fundingSource }`, paymentMethodId: config.id, From f32fd228425988c4449dbf3ddf2c3413fa351ad6 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 20 Aug 2024 07:47:25 -0700 Subject: [PATCH 005/271] Remove redundant darkMode code --- modules/ppcp-blocks/resources/js/checkout-block.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 13bd2c6cd..fa39ede49 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -590,7 +590,6 @@ const PayPalComponent = ( { style.borderRadius = buttonAttributes?.borderRadius ? Number( buttonAttributes.borderRadius ) : style.borderRadius; - style.color = buttonAttributes?.darkMode ? 'white' : ''; } if ( ! paypalScriptLoaded ) { @@ -690,7 +689,6 @@ const BlockEditorPayPalComponent = ( { fundingSource, buttonAttributes } ) => { borderRadius: buttonAttributes.borderRadius ? Number( buttonAttributes.borderRadius ) : configStyle.borderRadius, - color: buttonAttributes.darkMode ? 'white' : configStyle.color, }; } From bffcf74c1cbe8af270a79cdfe79bf3a010b00e68 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Thu, 5 Sep 2024 10:45:54 +0300 Subject: [PATCH 006/271] Do not add pay later button in editor --- modules/ppcp-blocks/resources/js/checkout-block.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index d1b6ad990..ab914f27a 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -657,7 +657,7 @@ const PayPalComponent = ( { ); }; -const BlockEditorPayPalComponent = () => { +const BlockEditorPayPalComponent = ( fundingSource ) => { const urlParams = { clientId: 'test', ...config.scriptData.url_params, @@ -670,6 +670,7 @@ const BlockEditorPayPalComponent = () => { onClick={ ( data, actions ) => { return false; } } + fundingSource={ fundingSource } /> ); @@ -758,7 +759,7 @@ if ( block_enabled && config.enabled ) { name: config.id, label:
, content: , - edit: , + edit: , ariaLabel: config.title, canMakePayment: () => { return true; @@ -784,7 +785,9 @@ if ( block_enabled && config.enabled ) { fundingSource={ fundingSource } /> ), - edit: , + edit: , ariaLabel: config.title, canMakePayment: async () => { if ( ! paypalScriptPromise ) { From 9bc9c0305398721467bf4a3ac48fc172e04d4f48 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 13 Sep 2024 15:35:13 +0200 Subject: [PATCH 007/271] Extract renewal handler logic to new class --- .../ppcp-paypal-subscriptions/services.php | 3 + .../src/RenewalHandler.php | 70 +++++++++++++++++++ modules/ppcp-webhooks/services.php | 2 +- .../src/Handler/PaymentSaleCompleted.php | 46 ++++++------ 4 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 modules/ppcp-paypal-subscriptions/src/RenewalHandler.php diff --git a/modules/ppcp-paypal-subscriptions/services.php b/modules/ppcp-paypal-subscriptions/services.php index 4e64e678b..32f32b193 100644 --- a/modules/ppcp-paypal-subscriptions/services.php +++ b/modules/ppcp-paypal-subscriptions/services.php @@ -40,4 +40,7 @@ return array( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, + 'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler { + return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) ); + }, ); diff --git a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php new file mode 100644 index 000000000..1261f418e --- /dev/null +++ b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php @@ -0,0 +1,70 @@ +logger = $logger; + } + + /** + * Process subscription renewal. + * + * @param WC_Subscription[] $subscriptions WC Subscriptions. + * @param string $transaction_id PayPal transaction ID. + * @return void + * @throws WC_Data_Exception If something goes wrong while setting payment method. + */ + public function process( array $subscriptions, string $transaction_id ): void { + foreach ( $subscriptions as $subscription ) { + $is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? ''; + if ( $is_renewal ) { + $renewal_order = wcs_create_renewal_order( $subscription ); + if ( is_a( $renewal_order, WC_Order::class ) ) { + $renewal_order->set_payment_method( $subscription->get_payment_method() ); + $renewal_order->payment_complete(); + $this->update_transaction_id( $transaction_id, $renewal_order, $this->logger ); + break; + } + } + + $parent_order = wc_get_order( $subscription->get_parent() ); + if ( is_a( $parent_order, WC_Order::class ) ) { + $subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' ); + $subscription->save_meta_data(); + $this->update_transaction_id( $transaction_id, $parent_order, $this->logger ); + } + } + } +} diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index 0f75a58a0..e179fd24a 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -99,7 +99,7 @@ return array( new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory, $payment_token_helper ), new VaultPaymentTokenDeleted( $logger ), new PaymentCapturePending( $logger ), - new PaymentSaleCompleted( $logger ), + new PaymentSaleCompleted( $logger, $container->get( 'paypal-subscriptions.renewal-handler' ) ), new PaymentSaleRefunded( $logger, $refund_fees_updater ), new BillingSubscriptionCancelled( $logger ), new BillingPlanPricingChangeActivated( $logger ), diff --git a/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php b/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php index 1679c46db..757cfd163 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php @@ -10,8 +10,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks\Handler; use Psr\Log\LoggerInterface; -use WC_Order; -use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; +use WC_Data_Exception; +use WooCommerce\PayPalCommerce\PayPalSubscriptions\RenewalHandler; use WP_REST_Request; use WP_REST_Response; @@ -20,7 +20,14 @@ use WP_REST_Response; */ class PaymentSaleCompleted implements RequestHandler { - use TransactionIdHandlingTrait, RequestHandlerTrait; + use RequestHandlerTrait; + + /** + * Renewal handler. + * + * @var RenewalHandler + */ + private $renewal_handler; /** * The logger. @@ -33,9 +40,11 @@ class PaymentSaleCompleted implements RequestHandler { * PaymentSaleCompleted constructor. * * @param LoggerInterface $logger The logger. + * @param RenewalHandler $renewal_handler Renewal handler. */ - public function __construct( LoggerInterface $logger ) { - $this->logger = $logger; + public function __construct( LoggerInterface $logger, RenewalHandler $renewal_handler ) { + $this->logger = $logger; + $this->renewal_handler = $renewal_handler; } /** @@ -68,7 +77,7 @@ class PaymentSaleCompleted implements RequestHandler { */ public function handle_request( WP_REST_Request $request ): WP_REST_Response { if ( is_null( $request['resource'] ) ) { - return $this->failure_response(); + return $this->failure_response( 'Could not retrieve resource.' ); } if ( ! function_exists( 'wcs_get_subscriptions' ) ) { @@ -85,7 +94,7 @@ class PaymentSaleCompleted implements RequestHandler { return $this->failure_response( 'Could not retrieve transaction id for subscription.' ); } - $args = array( + $args = array( // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_query' => array( array( @@ -95,24 +104,13 @@ class PaymentSaleCompleted implements RequestHandler { ), ), ); - $subscriptions = wcs_get_subscriptions( $args ); - foreach ( $subscriptions as $subscription ) { - $is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? ''; - if ( $is_renewal ) { - $renewal_order = wcs_create_renewal_order( $subscription ); - if ( is_a( $renewal_order, WC_Order::class ) ) { - $renewal_order->set_payment_method( $subscription->get_payment_method() ); - $renewal_order->payment_complete(); - $this->update_transaction_id( $transaction_id, $renewal_order, $this->logger ); - break; - } - } - $parent_order = wc_get_order( $subscription->get_parent() ); - if ( is_a( $parent_order, WC_Order::class ) ) { - $subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' ); - $subscription->save_meta_data(); - $this->update_transaction_id( $transaction_id, $parent_order, $this->logger ); + $subscriptions = wcs_get_subscriptions( $args ); + if ( $subscriptions ) { + try { + $this->renewal_handler->process( $subscriptions, $transaction_id ); + } catch ( WC_Data_Exception $exception ) { + return $this->failure_response( 'Could not update payment method.' ); } } From 17cff6bd09f182dd4f11e823ad54aab2c913e76f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 13 Sep 2024 17:05:39 +0200 Subject: [PATCH 008/271] Add test for renewal handler --- .../PayPalSubscriptionsRenewalTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php diff --git a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php new file mode 100644 index 000000000..f67b6be55 --- /dev/null +++ b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php @@ -0,0 +1,42 @@ +getContainer(); + $handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce')); + + $order = wc_create_order(); + $order->set_customer_id( 1 ); + $order->save(); + + $subscription = wcs_create_subscription( + array( + 'order_id' => $order->get_id(), + 'status' => 'active', + 'billing_period' => WC_Subscriptions_Product::get_period( $_ENV['PAYPAL_SUBSCRIPTIONS_PRODUCT_ID'] ), + 'billing_interval' => WC_Subscriptions_Product::get_interval( $_ENV['PAYPAL_SUBSCRIPTIONS_PRODUCT_ID'] ), + 'customer_id' => $order->get_customer_id(), + ) + ); + + $parent = $subscription->get_related_orders( 'ids', array( 'parent' ) ); + $this->assertEquals(count($parent), 1); + $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); + $this->assertEquals(count($renewal), 0); + + $handler->process([$subscription], 'TRANSACTION-ID'); + $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); + $this->assertEquals(count($renewal), 0); + + $handler->process([$subscription], 'TRANSACTION-ID'); + $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); + $this->assertEquals(count($renewal), 1); + } +} From 80e64575025e059392cbe606022dbab7fac9d295 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 16 Sep 2024 14:58:25 +0200 Subject: [PATCH 009/271] Extract is renewal logic to method --- .../src/RenewalHandler.php | 18 ++++++++- .../PayPalSubscriptionsRenewalTest.php | 39 +++++++++---------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php index 1261f418e..898a81c57 100644 --- a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php @@ -48,8 +48,7 @@ class RenewalHandler { */ public function process( array $subscriptions, string $transaction_id ): void { foreach ( $subscriptions as $subscription ) { - $is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? ''; - if ( $is_renewal ) { + if ( $this->is_renewal( $subscription ) ) { $renewal_order = wcs_create_renewal_order( $subscription ); if ( is_a( $renewal_order, WC_Order::class ) ) { $renewal_order->set_payment_method( $subscription->get_payment_method() ); @@ -67,4 +66,19 @@ class RenewalHandler { } } } + + /** + * Checks whether subscription order is for renewal or not. + * + * @param WC_Subscription $subscription WC Subscription. + * @return bool + */ + private function is_renewal( WC_Subscription $subscription ): bool { + $subscription_renewal_meta = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? ''; + if ( $subscription_renewal_meta === 'true' ) { + return true; + } + + return false; + } } diff --git a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php index f67b6be55..f951aadc2 100644 --- a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php +++ b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php @@ -2,34 +2,16 @@ namespace WooCommerce\PayPalCommerce\Tests\E2e; -use WC_Subscriptions_Product; use WooCommerce\PayPalCommerce\PayPalSubscriptions\RenewalHandler; class PayPalSubscriptionsRenewalTest extends TestCase { - public function test_process() + public function test_is_renewal_by_meta() { $c = $this->getContainer(); $handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce')); - $order = wc_create_order(); - $order->set_customer_id( 1 ); - $order->save(); - - $subscription = wcs_create_subscription( - array( - 'order_id' => $order->get_id(), - 'status' => 'active', - 'billing_period' => WC_Subscriptions_Product::get_period( $_ENV['PAYPAL_SUBSCRIPTIONS_PRODUCT_ID'] ), - 'billing_interval' => WC_Subscriptions_Product::get_interval( $_ENV['PAYPAL_SUBSCRIPTIONS_PRODUCT_ID'] ), - 'customer_id' => $order->get_customer_id(), - ) - ); - - $parent = $subscription->get_related_orders( 'ids', array( 'parent' ) ); - $this->assertEquals(count($parent), 1); - $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); - $this->assertEquals(count($renewal), 0); + $subscription = $this->createSubscription(); $handler->process([$subscription], 'TRANSACTION-ID'); $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); @@ -39,4 +21,21 @@ class PayPalSubscriptionsRenewalTest extends TestCase $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); $this->assertEquals(count($renewal), 1); } + + private function createSubscription() + { + $order = wc_create_order(); + $order->set_customer_id(1); + $order->save(); + + return wcs_create_subscription( + array( + 'order_id' => $order->get_id(), + 'status' => 'active', + 'billing_period' => 'day', + 'billing_interval' => '1', + 'customer_id' => $order->get_customer_id(), + ) + ); + } } From 3d018945f49d2227d834fa6dfd5b82de5793239f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 17 Sep 2024 12:05:35 +0200 Subject: [PATCH 010/271] Create subscription via rest api for testing --- .../PayPalSubscriptionsRenewalTest.php | 73 ++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php index f951aadc2..2e1229685 100644 --- a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php +++ b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php @@ -24,18 +24,73 @@ class PayPalSubscriptionsRenewalTest extends TestCase private function createSubscription() { - $order = wc_create_order(); - $order->set_customer_id(1); - $order->save(); + $args = [ + 'method' => 'POST', + 'headers' => [ + 'Authorization' => 'Basic ' . base64_encode( 'admin:admin' ), + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode([ + 'customer_id' => 1, + 'set_paid' => true, + 'payment_method' => 'ppcp-gateway', + 'billing' => [ + 'first_name' => 'John', + 'last_name' => 'Doe', + 'address_1' => '969 Market', + 'address_2' => '', + 'city' => 'San Francisco', + 'state' => 'CA', + 'postcode' => '94103', + 'country' => 'US', + 'email' => 'john.doe@example.com', + 'phone' => '(555) 555-5555' + ], + 'line_items' => [ + [ + 'product_id' => 156, + 'quantity' => 1 + ] + ], + ]), + ]; - return wcs_create_subscription( - array( - 'order_id' => $order->get_id(), + $response = wp_remote_request( + 'https://woocommerce-paypal-payments.ddev.site/wp-json/wc/v3/orders', + $args + ); + + $body = json_decode( $response['body'] ); + + $args = [ + 'method' => 'POST', + 'headers' => [ + 'Authorization' => 'Basic ' . base64_encode( 'admin:admin' ), + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode([ + 'parent_id' => $body->id, + 'customer_id' => 1, 'status' => 'active', 'billing_period' => 'day', - 'billing_interval' => '1', - 'customer_id' => $order->get_customer_id(), - ) + 'billing_interval' => 1, + 'payment_method' => 'ppcp-gateway', + 'line_items' => [ + [ + 'product_id' => $_ENV['PAYPAL_SUBSCRIPTIONS_PRODUCT_ID'], + 'quantity' => 1 + ] + ], + ]), + ]; + + $response = wp_remote_request( + 'https://woocommerce-paypal-payments.ddev.site/wp-json/wc/v3/subscriptions?per_page=1', + $args ); + + $body = json_decode( $response['body'] ); + + return wcs_get_subscription($body->id); } } From eb19f05226184e737489d087faf094b631e9b54d Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 18 Sep 2024 11:52:28 +0200 Subject: [PATCH 011/271] Do not return if `_ppcp_enable_subscription_product` not exist in the post request --- .../src/PayPalSubscriptionsModule.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 07dd5c8c6..1319feae9 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -66,9 +66,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu function( $product_id ) use ( $c ) { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - - $connect_subscription = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) ); - if ( ! $subscriptions_helper->plugin_is_active() || $connect_subscription !== 'yes' ) { + if ( ! $subscriptions_helper->plugin_is_active() ) { return; } From 750e5891c41480b1b380c55c11d9ed8c48f854a3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 18 Sep 2024 12:15:32 +0200 Subject: [PATCH 012/271] Revert latest change, will be included in its own PR --- .../src/PayPalSubscriptionsModule.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 1319feae9..07dd5c8c6 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -66,7 +66,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu function( $product_id ) use ( $c ) { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - if ( ! $subscriptions_helper->plugin_is_active() ) { + + $connect_subscription = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) ); + if ( ! $subscriptions_helper->plugin_is_active() || $connect_subscription !== 'yes' ) { return; } From 2f21df65b38c27af0e8d73bb7e85553ce7436211 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 18 Sep 2024 14:43:41 +0200 Subject: [PATCH 013/271] Add time based logic for checking if is order is parent or for renewal --- .../src/RenewalHandler.php | 29 +++++++++++++++++-- .../PayPalSubscriptionsRenewalTest.php | 17 +++++++++-- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php index 898a81c57..f88fea7d8 100644 --- a/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-paypal-subscriptions/src/RenewalHandler.php @@ -48,9 +48,17 @@ class RenewalHandler { */ public function process( array $subscriptions, string $transaction_id ): void { foreach ( $subscriptions as $subscription ) { - if ( $this->is_renewal( $subscription ) ) { + if ( $this->is_for_renewal_order( $subscription ) ) { $renewal_order = wcs_create_renewal_order( $subscription ); if ( is_a( $renewal_order, WC_Order::class ) ) { + $this->logger->info( + sprintf( + 'Processing renewal order #%s for subscription #%s', + $renewal_order->get_id(), + $subscription->get_id() + ) + ); + $renewal_order->set_payment_method( $subscription->get_payment_method() ); $renewal_order->payment_complete(); $this->update_transaction_id( $transaction_id, $renewal_order, $this->logger ); @@ -60,6 +68,14 @@ class RenewalHandler { $parent_order = wc_get_order( $subscription->get_parent() ); if ( is_a( $parent_order, WC_Order::class ) ) { + $this->logger->info( + sprintf( + 'Processing parent order #%s for subscription #%s', + $parent_order->get_id(), + $subscription->get_id() + ) + ); + $subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' ); $subscription->save_meta_data(); $this->update_transaction_id( $transaction_id, $parent_order, $this->logger ); @@ -73,12 +89,19 @@ class RenewalHandler { * @param WC_Subscription $subscription WC Subscription. * @return bool */ - private function is_renewal( WC_Subscription $subscription ): bool { + private function is_for_renewal_order( WC_Subscription $subscription ): bool { $subscription_renewal_meta = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? ''; if ( $subscription_renewal_meta === 'true' ) { return true; } - return false; + if ( + time() >= $subscription->get_time( 'start' ) + && ( time() - $subscription->get_time( 'start' ) ) <= ( 8 * HOUR_IN_SECONDS ) + ) { + return false; + } + + return true; } } diff --git a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php index 2e1229685..322b54731 100644 --- a/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php +++ b/tests/e2e/PHPUnit/PayPalSubscriptionsRenewalTest.php @@ -6,23 +6,33 @@ use WooCommerce\PayPalCommerce\PayPalSubscriptions\RenewalHandler; class PayPalSubscriptionsRenewalTest extends TestCase { - public function test_is_renewal_by_meta() + public function test_parent_order() { $c = $this->getContainer(); $handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce')); - $subscription = $this->createSubscription(); + // Simulates receiving webhook 1 minute after subscription start. + $subscription = $this->createSubscription('-1 minute'); $handler->process([$subscription], 'TRANSACTION-ID'); $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); $this->assertEquals(count($renewal), 0); + } + + public function test_renewal_order() + { + $c = $this->getContainer(); + $handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce')); + + // Simulates receiving webhook 9 hours after subscription start. + $subscription = $this->createSubscription('-9 hour'); $handler->process([$subscription], 'TRANSACTION-ID'); $renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) ); $this->assertEquals(count($renewal), 1); } - private function createSubscription() + private function createSubscription(string $startDate) { $args = [ 'method' => 'POST', @@ -69,6 +79,7 @@ class PayPalSubscriptionsRenewalTest extends TestCase 'Content-Type' => 'application/json', ], 'body' => wp_json_encode([ + 'start_date' => gmdate( 'Y-m-d H:i:s', strtotime($startDate) ), 'parent_id' => $body->id, 'customer_id' => 1, 'status' => 'active', From 2675132deddba04d30b9fdb958d8c4d3c556d9f7 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Thu, 19 Sep 2024 16:42:44 +0300 Subject: [PATCH 014/271] Fix funding source parameter --- modules/ppcp-blocks/resources/js/checkout-block.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index ab914f27a..7248d3a2b 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -657,7 +657,7 @@ const PayPalComponent = ( { ); }; -const BlockEditorPayPalComponent = ( fundingSource ) => { +const BlockEditorPayPalComponent = ({ fundingSource } ) => { const urlParams = { clientId: 'test', ...config.scriptData.url_params, From a2d02cd7f4399f15da4f684757fd22339425e92e Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 20 Sep 2024 12:15:11 +0200 Subject: [PATCH 015/271] Extrat update subscription status logic to its own class --- .../ppcp-paypal-subscriptions/services.php | 6 ++ .../src/PayPalSubscriptionsModule.php | 57 ++--------- .../src/SubscriptionStatus.php | 96 +++++++++++++++++++ 3 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php diff --git a/modules/ppcp-paypal-subscriptions/services.php b/modules/ppcp-paypal-subscriptions/services.php index 4e64e678b..a39a84c8a 100644 --- a/modules/ppcp-paypal-subscriptions/services.php +++ b/modules/ppcp-paypal-subscriptions/services.php @@ -40,4 +40,10 @@ return array( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, + 'paypal-subscriptions.status' => static function ( ContainerInterface $container ): SubscriptionStatus { + return new SubscriptionStatus( + $container->get( 'api.endpoint.billing-subscriptions' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, ); diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 07dd5c8c6..ba88097be 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -189,6 +189,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu 30 ); + /** + * Executed when updating WC Subscription. + */ add_action( 'woocommerce_process_shop_subscription_meta', /** @@ -196,65 +199,23 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu * * @psalm-suppress MissingClosureParamType */ - function( $id, $post ) use ( $c ) { + function( $id ) use ( $c ) { $subscription = wcs_get_subscription( $id ); if ( ! is_a( $subscription, WC_Subscription::class ) ) { return; } + $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; if ( ! $subscription_id ) { return; } - $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' ); - assert( $subscriptions_endpoint instanceof BillingSubscriptions ); - if ( $subscription->get_status() === 'cancelled' ) { - try { - $subscriptions_endpoint->cancel( $subscription_id ); - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } + $subscription_status = $c->get( 'paypal-subscriptions.status' ); + assert( $subscription_status instanceof SubscriptionStatus ); - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - $logger->error( 'Could not cancel subscription product on PayPal. ' . $error ); - } - } - - if ( $subscription->get_status() === 'pending-cancel' ) { - try { - $subscriptions_endpoint->suspend( $subscription_id ); - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } - - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - $logger->error( 'Could not suspend subscription product on PayPal. ' . $error ); - } - } - - if ( $subscription->get_status() === 'active' ) { - try { - $current_subscription = $subscriptions_endpoint->subscription( $subscription_id ); - if ( $current_subscription->status === 'SUSPENDED' ) { - $subscriptions_endpoint->activate( $subscription_id ); - } - } catch ( RuntimeException $exception ) { - $error = $exception->getMessage(); - if ( is_a( $exception, PayPalApiException::class ) ) { - $error = $exception->get_details( $error ); - } - - $logger = $c->get( 'woocommerce.logger.woocommerce' ); - $logger->error( 'Could not reactivate subscription product on PayPal. ' . $error ); - } - } + $subscription_status->update_status( $subscription->get_status(), $subscription_id ); }, - 20, - 2 + 20 ); add_filter( diff --git a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php new file mode 100644 index 000000000..f5a0217ac --- /dev/null +++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php @@ -0,0 +1,96 @@ +subscriptions_endpoint = $subscriptions_endpoint; + $this->logger = $logger; + } + + /** + * Updates PayPal subscription status from the given WC Subscription status. + * + * @param string $subscription_status The WC Subscription status. + * @param string $subscription_id The PayPal Subscription ID. + * @return void + */ + public function update_status(string $subscription_status, string $subscription_id): void { + if ($subscription_status === 'cancelled') { + try { + $this->subscriptions_endpoint->cancel($subscription_id); + } catch (RuntimeException $exception) { + + $this->logger->error('Could not cancel subscription product on PayPal. ' + . $this->get_error($exception)); + } + } + + if ($subscription_status === 'pending-cancel') { + try { + $this->subscriptions_endpoint->suspend($subscription_id); + } catch (RuntimeException $exception) { + $this->logger->error('Could not suspend subscription product on PayPal. ' + . $this->get_error($exception)); + } + } + + if ($subscription_status === 'active') { + try { + $current_subscription = $this->subscriptions_endpoint->subscription($subscription_id); + if ($current_subscription->status === 'SUSPENDED') { + $this->subscriptions_endpoint->activate($subscription_id); + } + } catch (RuntimeException $exception) { + $this->logger->error('Could not reactivate subscription product on PayPal. ' + . $this->get_error($exception)); + } + } + } + + /** + * Get error from exception. + * + * @param RuntimeException $exception The exception. + * @return string + */ + private function get_error(RuntimeException $exception): string { + $error = $exception->getMessage(); + if (is_a($exception, PayPalApiException::class)) { + $error = $exception->get_details($error); + } + + return $error; + } +} From 6518ae06552070658f4f5e0ec995dfd0c6e58998 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 20 Sep 2024 12:49:11 +0200 Subject: [PATCH 016/271] Updates subscription status from subscriptions list page --- .../src/PayPalSubscriptionsModule.php | 44 +++++++-- .../src/SubscriptionStatus.php | 91 ++++++++++++++----- 2 files changed, 105 insertions(+), 30 deletions(-) diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index ba88097be..681645444 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -200,13 +200,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu * @psalm-suppress MissingClosureParamType */ function( $id ) use ( $c ) { - $subscription = wcs_get_subscription( $id ); - if ( ! is_a( $subscription, WC_Subscription::class ) ) { - return; - } - + $subscription = wcs_get_subscription( $id ); $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( ! $subscription_id ) { + if ( ! is_a( $subscription, WC_Subscription::class ) || ! $subscription_id ) { return; } @@ -218,6 +214,42 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu 20 ); + /** + * Update status to pending-cancel from WC Subscriptions list page action link. + */ + add_action( + 'woocommerce_subscription_status_pending-cancel', + function( WC_Subscription $subscription ) use ( $c ) { + $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; + if ( ! $subscription_id ) { + return; + } + + $subscription_status = $c->get( 'paypal-subscriptions.status' ); + assert( $subscription_status instanceof SubscriptionStatus ); + + $subscription_status->update_status( $subscription->get_status(), $subscription_id ); + } + ); + + /** + * Update status to cancelled from WC Subscriptions list page action link. + */ + add_action( + 'woocommerce_subscription_status_cancelled', + function( WC_Subscription $subscription ) use ( $c ) { + $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; + if ( ! $subscription_id ) { + return; + } + + $subscription_status = $c->get( 'paypal-subscriptions.status' ); + assert( $subscription_status instanceof SubscriptionStatus ); + + $subscription_status->update_status( $subscription->get_status(), $subscription_id ); + } + ); + add_filter( 'woocommerce_order_actions', /** diff --git a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php index f5a0217ac..1b969ee49 100644 --- a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php +++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php @@ -13,8 +13,10 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +/** + * Class SubscriptionStatus + */ class SubscriptionStatus { /** @@ -22,7 +24,7 @@ class SubscriptionStatus { * * @var BillingSubscriptions */ - private $subscriptions_endpoint ; + private $subscriptions_endpoint; /** * The logger. @@ -31,12 +33,18 @@ class SubscriptionStatus { */ private $logger; + /** + * SubscriptionStatus constructor. + * + * @param BillingSubscriptions $subscriptions_endpoint Billing subscriptions endpoint. + * @param LoggerInterface $logger The logger. + */ public function __construct( BillingSubscriptions $subscriptions_endpoint, LoggerInterface $logger ) { $this->subscriptions_endpoint = $subscriptions_endpoint; - $this->logger = $logger; + $this->logger = $logger; } /** @@ -46,35 +54,70 @@ class SubscriptionStatus { * @param string $subscription_id The PayPal Subscription ID. * @return void */ - public function update_status(string $subscription_status, string $subscription_id): void { - if ($subscription_status === 'cancelled') { + public function update_status( string $subscription_status, string $subscription_id ): void { + if ( $subscription_status === 'cancelled' ) { try { - $this->subscriptions_endpoint->cancel($subscription_id); - } catch (RuntimeException $exception) { + $this->logger->info( + sprintf( + 'Canceling PayPal subscription #%s.', + $subscription_id + ) + ); - $this->logger->error('Could not cancel subscription product on PayPal. ' - . $this->get_error($exception)); + $this->subscriptions_endpoint->cancel( $subscription_id ); + } catch ( RuntimeException $exception ) { + $this->logger->error( + sprintf( + 'Could not cancel PayPal subscription #%s. %s', + $subscription_id, + $this->get_error( $exception ) + ) + ); } } - if ($subscription_status === 'pending-cancel') { + if ( $subscription_status === 'pending-cancel' || $subscription_status === 'on-hold' ) { try { - $this->subscriptions_endpoint->suspend($subscription_id); - } catch (RuntimeException $exception) { - $this->logger->error('Could not suspend subscription product on PayPal. ' - . $this->get_error($exception)); + $this->logger->info( + sprintf( + 'Suspending PayPal subscription #%s.', + $subscription_id + ) + ); + + $this->subscriptions_endpoint->suspend( $subscription_id ); + } catch ( RuntimeException $exception ) { + $this->logger->error( + sprintf( + 'Could not suspend PayPal subscription #%s. %s', + $subscription_id, + $this->get_error( $exception ) + ) + ); } } - if ($subscription_status === 'active') { + if ( $subscription_status === 'active' ) { try { - $current_subscription = $this->subscriptions_endpoint->subscription($subscription_id); - if ($current_subscription->status === 'SUSPENDED') { - $this->subscriptions_endpoint->activate($subscription_id); + $current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id ); + if ( $current_subscription->status === 'SUSPENDED' ) { + $this->logger->info( + sprintf( + 'Activating suspended PayPal subscription #%s.', + $subscription_id + ) + ); + + $this->subscriptions_endpoint->activate( $subscription_id ); } - } catch (RuntimeException $exception) { - $this->logger->error('Could not reactivate subscription product on PayPal. ' - . $this->get_error($exception)); + } catch ( RuntimeException $exception ) { + $this->logger->error( + sprintf( + 'Could not reactivate PayPal subscription #%s. %s', + $subscription_id, + $this->get_error( $exception ) + ) + ); } } } @@ -85,10 +128,10 @@ class SubscriptionStatus { * @param RuntimeException $exception The exception. * @return string */ - private function get_error(RuntimeException $exception): string { + private function get_error( RuntimeException $exception ): string { $error = $exception->getMessage(); - if (is_a($exception, PayPalApiException::class)) { - $error = $exception->get_details($error); + if ( is_a( $exception, PayPalApiException::class ) ) { + $error = $exception->get_details( $error ); } return $error; From c056b33b3c9fb561b825785d0e7c79d71c1cab6f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 20 Sep 2024 15:25:49 +0200 Subject: [PATCH 017/271] Use `woocommerce_subscription_status_updated` for all subscription status updates --- .../src/Endpoint/BillingSubscriptions.php | 2 +- .../src/PayPalSubscriptionsModule.php | 22 ++----------------- .../src/SubscriptionStatus.php | 9 ++++++-- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php index 8a46506b6..91380ac53 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php @@ -148,7 +148,7 @@ class BillingSubscriptions { */ public function cancel( string $id ): void { $data = array( - 'reason' => 'Cancelled by customer', + 'reason' => sprintf( 'Cancelled by %s.', is_admin() ? 'merchant' : 'customer' ), ); $bearer = $this->bearer->bearer(); diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 681645444..b55addb69 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -215,28 +215,10 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu ); /** - * Update status to pending-cancel from WC Subscriptions list page action link. + * Update subscription status from WC Subscriptions list page action link. */ add_action( - 'woocommerce_subscription_status_pending-cancel', - function( WC_Subscription $subscription ) use ( $c ) { - $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( ! $subscription_id ) { - return; - } - - $subscription_status = $c->get( 'paypal-subscriptions.status' ); - assert( $subscription_status instanceof SubscriptionStatus ); - - $subscription_status->update_status( $subscription->get_status(), $subscription_id ); - } - ); - - /** - * Update status to cancelled from WC Subscriptions list page action link. - */ - add_action( - 'woocommerce_subscription_status_cancelled', + 'woocommerce_subscription_status_updated', function( WC_Subscription $subscription ) use ( $c ) { $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; if ( ! $subscription_id ) { diff --git a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php index 1b969ee49..1fbec3926 100644 --- a/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php +++ b/modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php @@ -55,8 +55,13 @@ class SubscriptionStatus { * @return void */ public function update_status( string $subscription_status, string $subscription_id ): void { - if ( $subscription_status === 'cancelled' ) { + if ( $subscription_status === 'pending-cancel' || $subscription_status === 'cancelled' ) { try { + $current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id ); + if ( $current_subscription->status === 'CANCELLED' ) { + return; + } + $this->logger->info( sprintf( 'Canceling PayPal subscription #%s.', @@ -76,7 +81,7 @@ class SubscriptionStatus { } } - if ( $subscription_status === 'pending-cancel' || $subscription_status === 'on-hold' ) { + if ( $subscription_status === 'on-hold' ) { try { $this->logger->info( sprintf( From aafa11ad2be7630586bf47a40ec4f496bc1f77f4 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 23 Sep 2024 09:38:23 +0200 Subject: [PATCH 018/271] Fix psalm --- .../src/PayPalSubscriptionsModule.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index b55addb69..009c036c5 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -200,7 +200,11 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu * @psalm-suppress MissingClosureParamType */ function( $id ) use ( $c ) { - $subscription = wcs_get_subscription( $id ); + $subscription = wcs_get_subscription( $id ); + if ( $subscription === false ) { + return; + } + $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; if ( ! is_a( $subscription, WC_Subscription::class ) || ! $subscription_id ) { return; From 150843298108ccff0483a342c785c0f2a2dc651b Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 23 Sep 2024 09:40:20 +0200 Subject: [PATCH 019/271] Fix psalm --- .../ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index 009c036c5..69dd3d841 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -206,7 +206,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu } $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? ''; - if ( ! is_a( $subscription, WC_Subscription::class ) || ! $subscription_id ) { + if ( ! $subscription_id ) { return; } From 4c0a0c1c6d58a0efd849703600bb2b69d1782599 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 2 Oct 2024 11:40:32 +0200 Subject: [PATCH 020/271] Update reason message based on context --- modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php index 91380ac53..281afe0ec 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php @@ -67,7 +67,7 @@ class BillingSubscriptions { */ public function suspend( string $id ):void { $data = array( - 'reason' => 'Suspended by customer', + 'reason' => sprintf( 'Suspended by %s.', is_admin() ? 'merchant' : 'customer' ), ); $bearer = $this->bearer->bearer(); @@ -107,7 +107,7 @@ class BillingSubscriptions { */ public function activate( string $id ): void { $data = array( - 'reason' => 'Reactivated by customer', + 'reason' => sprintf( 'Reactivated by %s.', is_admin() ? 'merchant' : 'customer' ), ); $bearer = $this->bearer->bearer(); From 3430ccc01ad0e0156b1d0c814c86ca1c8df20170 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 4 Oct 2024 16:14:46 +0200 Subject: [PATCH 021/271] =?UTF-8?q?=F0=9F=90=9B=20Removed=20deprecated=20C?= =?UTF-8?q?SS=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The correct CSS that is expected and handled by the JS integration is generated by PHP in the class method `ApplePayButton::hide_gateway_until_eligible()` --- modules/ppcp-applepay/resources/css/styles.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index d2a0bf675..1cf632fd2 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -57,7 +57,3 @@ } } } - -#ppc-button-ppcp-applepay { - display: none; -} From 096aa4fc534df606da50df182502f3bfd85604b5 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 10:48:04 +0200 Subject: [PATCH 022/271] =?UTF-8?q?=F0=9F=9A=A7=20Extend=20class=20from=20?= =?UTF-8?q?PaymentButton=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-applepay/resources/js/ApplepayButton.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 0f217f606..b5a3d770b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -9,6 +9,8 @@ import FormValidator from '../../../ppcp-button/resources/js/modules/Helper/Form 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'; +import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; +import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -57,14 +59,13 @@ const CONTEXT = { * On a single page, multiple Apple Pay buttons can be displayed, which also means multiple * ApplePayButton instances exist. A typical case is on the product page, where one Apple Pay button * is located inside the minicart-popup, and another pay-now button is in the product context. - * - * TODO - extend from PaymentButton (same as we do in GooglepayButton.js) */ -class ApplePayButton { +class ApplePayButton extends PaymentButton { /** - * Whether the payment button is initialized. - * - * @type {boolean} + * @inheritDoc + */ + static methodId = PaymentMethods.APPLEPAY; + */ #isInitialized = false; From eef4c80a48319a57aca550efc01b7f910c638c03 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 10:58:53 +0200 Subject: [PATCH 023/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20unused=20method?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index b5a3d770b..f79d347c0 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -94,8 +94,6 @@ class ApplePayButton extends PaymentButton { initialPaymentRequest = null; constructor( context, externalHandler, buttonConfig, ppcpConfig ) { - this._initDebug( !! buttonConfig?.is_debug ); - apmButtonsInit( ppcpConfig ); this.context = context; @@ -112,34 +110,6 @@ class ApplePayButton extends PaymentButton { this.refreshContextData(); } - /** - * 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. - * @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 ); - } ); - } - /** * The nonce for ajax requests. * From d1bb87755353b04c6bceaa9e43038348dde0fcb0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 11:00:39 +0200 Subject: [PATCH 024/271] =?UTF-8?q?=F0=9F=9A=A7=20Update=20the=20construct?= =?UTF-8?q?or?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index f79d347c0..a7688700c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -93,21 +93,29 @@ class ApplePayButton extends PaymentButton { */ initialPaymentRequest = null; - constructor( context, externalHandler, buttonConfig, ppcpConfig ) { - apmButtonsInit( ppcpConfig ); + constructor( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ) { + // Disable debug output in the browser console: + // buttonConfig.is_debug = false; - this.context = context; - this.externalHandler = externalHandler; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; + super( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ); - this.contextHandler = ContextHandlerFactory.create( - this.context, - this.buttonConfig, - this.ppcpConfig - ); + this.init = this.init.bind( this ); + this.onPaymentAuthorized = this.onPaymentAuthorized.bind( this ); + this.onButtonClick = this.onButtonClick.bind( this ); - this.refreshContextData(); + this.log( 'Create instance' ); } /** From c6ed763f0fb2a30868b2b3d8657f202ea3a9703f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 11:11:42 +0200 Subject: [PATCH 025/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20obsolete=20func?= =?UTF-8?q?tions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 145 ------------------ 1 file changed, 145 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index a7688700c..a881ee8f5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -66,7 +66,6 @@ class ApplePayButton extends PaymentButton { */ static methodId = PaymentMethods.APPLEPAY; - */ #isInitialized = false; #wrapperId = ''; @@ -182,120 +181,6 @@ class ApplePayButton extends PaymentButton { ); } - /** - * 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-applepay'; - } 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.Gateways.includes( 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. * @@ -361,36 +246,6 @@ class ApplePayButton extends PaymentButton { this.init( this.applePayConfig ); } - /** - * Hides all wrappers that belong to this ApplePayButton instance. - */ - hide() { - this.log( 'Hide button' ); - this.allElements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - } - - /** - * 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' ); - return; - } - - // 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 = ''; - } ); - } - async fetchTransactionInfo() { this.transactionInfo = await this.contextHandler.transactionInfo(); } From 9322812caa5be30c16627c0335af5012f68ffaa0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 11:35:13 +0200 Subject: [PATCH 026/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20#isInitialized?= =?UTF-8?q?=20and=20obsolete=20getter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 49 +++---------------- 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index a881ee8f5..87d220c84 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -66,7 +66,6 @@ class ApplePayButton extends PaymentButton { */ static methodId = PaymentMethods.APPLEPAY; - #isInitialized = false; #wrapperId = ''; #ppcpButtonWrapperId = ''; @@ -130,41 +129,6 @@ class ApplePayButton extends PaymentButton { 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 ( CONTEXT.Preview === this.context ) { - return true; - } - - /** - * 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; - } - /** * 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 @@ -191,7 +155,8 @@ class ApplePayButton extends PaymentButton { } init( config ) { - if ( this.#isInitialized ) { + // Use `reinit()` to force a full refresh of an initialized button. + if ( this.isInitialized ) { return; } @@ -199,10 +164,10 @@ class ApplePayButton extends PaymentButton { return; } + super.init(); this.log( 'Init' ); this.initEventHandlers(); - this.#isInitialized = true; this.applePayConfig = config; if ( this.isSeparateGateway ) { @@ -238,12 +203,14 @@ class ApplePayButton extends PaymentButton { } reinit() { - if ( ! this.applePayConfig ) { + // Missing (invalid) configuration indicates, that the first `init()` call did not happen yet. + if ( ! this.validateConfiguration( true ) ) { return; } - this.#isInitialized = false; - this.init( this.applePayConfig ); + super.reinit(); + + this.init(); } async fetchTransactionInfo() { From 0ef461e4b24d952819c59d31933dafa5cc172992 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 11:35:39 +0200 Subject: [PATCH 027/271] =?UTF-8?q?=F0=9F=9A=A7=20Fix=20code=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 87d220c84..6038ac7e9 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -92,28 +92,28 @@ class ApplePayButton extends PaymentButton { initialPaymentRequest = null; constructor( - context, - externalHandler, - buttonConfig, - ppcpConfig, - contextHandler - ) { - // Disable debug output in the browser console: - // buttonConfig.is_debug = false; + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ) { + // Disable debug output in the browser console: + // buttonConfig.is_debug = false; - super( - context, - externalHandler, - buttonConfig, - ppcpConfig, - contextHandler - ); + super( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ); - this.init = this.init.bind( this ); - this.onPaymentAuthorized = this.onPaymentAuthorized.bind( this ); - this.onButtonClick = this.onButtonClick.bind( this ); + this.init = this.init.bind( this ); + this.onPaymentAuthorized = this.onPaymentAuthorized.bind( this ); + this.onButtonClick = this.onButtonClick.bind( this ); - this.log( 'Create instance' ); + this.log( 'Create instance' ); } /** From 39b9c784d48aa8d567a951732263c910d41bcba4 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 11:46:46 +0200 Subject: [PATCH 028/271] =?UTF-8?q?=F0=9F=9A=A7=20Implement=20getters=20fo?= =?UTF-8?q?r=20wrapper=20&=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 6038ac7e9..ccabd793b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -11,6 +11,10 @@ import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/Wi import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { + combineStyles, + combineWrapperIds, +} from '../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers'; /** * Plugin-specific styling. @@ -67,9 +71,6 @@ class ApplePayButton extends PaymentButton { static methodId = PaymentMethods.APPLEPAY; - #wrapperId = ''; - #ppcpButtonWrapperId = ''; - /** * Context describes the button's location on the website and what details it submits. * @@ -91,6 +92,29 @@ class ApplePayButton extends PaymentButton { */ initialPaymentRequest = null; + /** + * @inheritDoc + */ + static getWrappers( buttonConfig, ppcpConfig ) { + return combineWrapperIds( + buttonConfig?.button?.wrapper || '', + buttonConfig?.button?.mini_cart_wrapper || '', + ppcpConfig?.button?.wrapper || '', + 'ppc-button-applepay-container', + 'ppc-button-ppcp-applepay' + ); + } + + /** + * @inheritDoc + */ + static getStyles( buttonConfig, ppcpConfig ) { + return combineStyles( + ppcpConfig?.button || {}, + buttonConfig?.button || {} + ); + } + constructor( context, externalHandler, From 3c03c2ce3be5504c2c0214e9956484bfcf7d04e1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:01:26 +0200 Subject: [PATCH 029/271] =?UTF-8?q?=F0=9F=9A=A7=20Add=20css-class=20to=20s?= =?UTF-8?q?tyle=20the=20payment=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Used by the parent class --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index ccabd793b..dba57d913 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -70,6 +70,10 @@ class ApplePayButton extends PaymentButton { */ static methodId = PaymentMethods.APPLEPAY; + /** + * @inheritDoc + */ + static cssClass = 'ppcp-button-applepay'; /** * Context describes the button's location on the website and what details it submits. From 3180c2933ca2796f63562cadd436d7a73a54c960 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:02:21 +0200 Subject: [PATCH 030/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20obsolete=20attr?= =?UTF-8?q?ibutes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Those have moved to the parent class --- .../ppcp-applepay/resources/js/ApplepayButton.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index dba57d913..52903267e 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -75,19 +75,7 @@ class ApplePayButton extends PaymentButton { */ static cssClass = 'ppcp-button-applepay'; - /** - * Context describes the button's location on the website and what details it submits. - * - * @type {''|'product'|'cart'|'checkout'|'pay-now'|'mini-cart'|'cart-block'|'checkout-block'|'preview'} - */ - context = ''; - - externalHandler = null; - buttonConfig = null; - ppcpConfig = null; - paymentsClient = null; formData = null; - contextHandler = null; updatedContactInfo = []; selectedShippingMethod = []; @@ -193,7 +181,6 @@ class ApplePayButton extends PaymentButton { } super.init(); - this.log( 'Init' ); this.initEventHandlers(); this.applePayConfig = config; From 7b2397f86ba27335ae334d9537073cea1ad471a2 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:04:50 +0200 Subject: [PATCH 031/271] =?UTF-8?q?=F0=9F=9A=A7=20Make=20remaining=20attri?= =?UTF-8?q?butes=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 52903267e..d5ecaf073 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -75,14 +75,14 @@ class ApplePayButton extends PaymentButton { */ static cssClass = 'ppcp-button-applepay'; - formData = null; - updatedContactInfo = []; - selectedShippingMethod = []; + #formData = null; + #updatedContactInfo = []; + #selectedShippingMethod = []; /** - * Stores initialization data sent to the button. + * Initialization data sent to the button. */ - initialPaymentRequest = null; + #initialPaymentRequest = null; /** * @inheritDoc @@ -353,7 +353,7 @@ class ApplePayButton extends PaymentButton { const formData = new FormData( document.querySelector( checkoutFormSelector ) ); - this.formData = Object.fromEntries( formData.entries() ); + this.#formData = Object.fromEntries( formData.entries() ); this.updateRequestDataWithForm( paymentRequest ); } catch ( error ) { @@ -455,13 +455,13 @@ class ApplePayButton extends PaymentButton { // Add billing address. paymentRequest.billingContact = this.fillBillingContact( - this.formData + this.#formData ); // Add custom data. // "applicationData" is originating a "PayPalApplePayError: An internal server error has // occurred" on paypal.Applepay().confirmOrder(). paymentRequest.applicationData = - // this.fillApplicationData(this.formData); + // this.fillApplicationData(this.#formData); if ( ! this.shouldRequireShippingInButton() ) { return; @@ -469,7 +469,7 @@ class ApplePayButton extends PaymentButton { // Add shipping address. paymentRequest.shippingContact = this.fillShippingContact( - this.formData + this.#formData ); // Get shipping methods. @@ -487,7 +487,7 @@ class ApplePayButton extends PaymentButton { }; // Remember this shipping method as the selected one. - this.selectedShippingMethod = shippingMethod; + this.#selectedShippingMethod = shippingMethod; paymentRequest.shippingMethods.push( shippingMethod ); break; @@ -507,7 +507,7 @@ class ApplePayButton extends PaymentButton { } // Store for reuse in case this data is not provided by ApplePay on authorization. - this.initialPaymentRequest = paymentRequest; + this.#initialPaymentRequest = paymentRequest; this.log( '=== paymentRequest.shippingMethods', @@ -516,7 +516,7 @@ class ApplePayButton extends PaymentButton { } paymentRequest() { - const applepayConfig = this.applePayConfig; + const applepayConfig = this.#applePayConfig; const buttonConfig = this.buttonConfig; const baseRequest = { countryCode: applepayConfig.countryCode, @@ -644,14 +644,14 @@ class ApplePayButton extends PaymentButton { if ( applePayShippingMethodUpdate.success === false ) { response.errors = createAppleErrors( response.errors ); } - this.selectedShippingMethod = event.shippingMethod; + this.#selectedShippingMethod = event.shippingMethod; // Sort the response shipping methods, so that the selected shipping method is // the first one. response.newShippingMethods = response.newShippingMethods.sort( ( a, b ) => { if ( - a.label === this.selectedShippingMethod.label + a.label === this.#selectedShippingMethod.label ) { return -1; } @@ -693,12 +693,12 @@ class ApplePayButton extends PaymentButton { ) => { this.log( 'onshippingcontactselected ok' ); const response = applePayShippingContactUpdate.data; - this.updatedContactInfo = event.shippingContact; + this.#updatedContactInfo = event.shippingContact; if ( applePayShippingContactUpdate.success === false ) { response.errors = createAppleErrors( response.errors ); } if ( response.newShippingMethods ) { - this.selectedShippingMethod = + this.#selectedShippingMethod = response.newShippingMethods[ 0 ]; } session.completeShippingContactSelection( response ); @@ -756,11 +756,11 @@ class ApplePayButton extends PaymentButton { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, simplified_contact: this.hasValidContactInfo( - this.updatedContactInfo + this.#updatedContactInfo ) - ? this.updatedContactInfo - : this.initialPaymentRequest?.shippingContact ?? - this.initialPaymentRequest?.billingContact, + ? this.#updatedContactInfo + : this.#initialPaymentRequest?.shippingContact ?? + this.#initialPaymentRequest?.billingContact, product_id: productId, products: JSON.stringify( this.products ), caller_page: 'productDetail', @@ -777,11 +777,11 @@ class ApplePayButton extends PaymentButton { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, simplified_contact: this.hasValidContactInfo( - this.updatedContactInfo + this.#updatedContactInfo ) - ? this.updatedContactInfo - : this.initialPaymentRequest?.shippingContact ?? - this.initialPaymentRequest?.billingContact, + ? this.#updatedContactInfo + : this.#initialPaymentRequest?.shippingContact ?? + this.#initialPaymentRequest?.billingContact, caller_page: 'cart', 'woocommerce-process-checkout-nonce': this.nonce, }; @@ -798,13 +798,13 @@ class ApplePayButton extends PaymentButton { try { const billingContact = data.billing_contact || - this.initialPaymentRequest.billingContact; + this.#initialPaymentRequest.billingContact; const shippingContact = data.shipping_contact || - this.initialPaymentRequest.shippingContact; + this.#initialPaymentRequest.shippingContact; const shippingMethod = - this.selectedShippingMethod || - ( this.initialPaymentRequest.shippingMethods || + this.#selectedShippingMethod || + ( this.#initialPaymentRequest.shippingMethods || [] )[ 0 ]; const requestData = { From 2646cf4a4498510eb20d306b6370b26300aa7512 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:07:19 +0200 Subject: [PATCH 032/271] =?UTF-8?q?=F0=9F=9A=A7=20Add=20new=20validation?= =?UTF-8?q?=20&=20configuration=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d5ecaf073..e8cf2de14 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -84,6 +84,11 @@ class ApplePayButton extends PaymentButton { */ #initialPaymentRequest = null; + /** + * Apple Pay specific API configuration. + */ + #applePayConfig = null; + /** * @inheritDoc */ @@ -170,21 +175,61 @@ class ApplePayButton extends PaymentButton { return this.wrapperElement instanceof HTMLElement; } + /** + * @inheritDoc + */ + validateConfiguration( silent = false ) { + const validEnvs = [ 'PRODUCTION', 'TEST' ]; + + const isInvalid = ( ...args ) => { + if ( ! silent ) { + this.error( ...args ); + } + return false; + }; + + if ( ! validEnvs.includes( this.buttonConfig.environment ) ) { + return isInvalid( + 'Invalid environment:', + this.buttonConfig.environment + ); + } + + // Preview buttons only need a valid environment. + if ( this.isPreview ) { + return true; + } + + if ( ! typeof this.contextHandler?.validateContext() ) { + return isInvalid( 'Invalid context handler.', this.contextHandler ); + } + + return true; + } + + /** + * Configures the button instance. Must be called before the initial `init()`. + * + * @param {Object} apiConfig - API configuration. + */ + configure( apiConfig ) { + this.#applePayConfig = apiConfig; + } + init( config ) { // Use `reinit()` to force a full refresh of an initialized button. if ( this.isInitialized ) { return; } - if ( ! this.contextHandler.validateContext() ) { + // Stop, if configuration is invalid. + if ( ! this.validateConfiguration() ) { return; } super.init(); this.initEventHandlers(); - this.applePayConfig = config; - if ( this.isSeparateGateway ) { document .querySelectorAll( '#ppc-button-applepay-container' ) From 64b1bf3c4141137c8e11f3b6d44c90fb1f6984d8 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:12:21 +0200 Subject: [PATCH 033/271] =?UTF-8?q?=F0=9F=9A=A7=20Minor=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index e8cf2de14..c8b449571 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,14 +1,12 @@ /* global ApplePaySession */ /* global PayPalCommerceGateway */ -import ContextHandlerFactory from './Context/ContextHandlerFactory'; import { createAppleErrors } from './Helper/applePayError'; import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import FormValidator from '../../../ppcp-button/resources/js/modules/Helper/FormValidator'; 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'; import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { @@ -216,7 +214,7 @@ class ApplePayButton extends PaymentButton { this.#applePayConfig = apiConfig; } - init( config ) { + init() { // Use `reinit()` to force a full refresh of an initialized button. if ( this.isInitialized ) { return; From 4604e68b5a92623a76860c91fec0fe2e5c395a30 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:12:46 +0200 Subject: [PATCH 034/271] =?UTF-8?q?=F0=9F=92=A1=20Add=20missing=20annotati?= =?UTF-8?q?on=20in=20Google=20Pay=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-googlepay/resources/js/GooglepayManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index aaf85a6b0..a6bae6c58 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,3 +1,5 @@ +/* global paypal */ + import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; import GooglepayButton from './GooglepayButton'; import ContextHandlerFactory from './Context/ContextHandlerFactory'; From d5e406a563c5b58f01a38c9a600cf86ae439aee4 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:13:46 +0200 Subject: [PATCH 035/271] =?UTF-8?q?=F0=9F=9A=A7=20Migrate=20button=20initi?= =?UTF-8?q?alization=20to=20new=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ContextHandlerFactory previously was used inside the ApplePayButton constructor. --- .../ppcp-applepay/resources/js/ApplepayManager.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 264610f28..59e6b3e0f 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -2,6 +2,7 @@ import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; import ApplePayButton from './ApplepayButton'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; class ApplePayManager { constructor( buttonConfig, ppcpConfig ) { @@ -11,11 +12,19 @@ class ApplePayManager { this.buttons = []; buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { - const button = new ApplePayButton( + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); + + const button = ApplePayButton.createButton( bootstrap.context, bootstrap.handler, buttonConfig, - ppcpConfig + ppcpConfig, + this.contextHandler ); this.buttons.push( button ); From c2d5c8f3c58a008f88126ce766fd1ace90af1e9a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:27:20 +0200 Subject: [PATCH 036/271] =?UTF-8?q?=F0=9F=9A=A7=20Adjust=20button-manager?= =?UTF-8?q?=20to=20new=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayManager.js | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 59e6b3e0f..37297920b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -11,7 +11,7 @@ class ApplePayManager { this.ApplePayConfig = null; this.buttons = []; - buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { + buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { this.contextHandler = ContextHandlerFactory.create( bootstrap.context, buttonConfig, @@ -29,19 +29,26 @@ class ApplePayManager { this.buttons.push( button ); - if ( this.ApplePayConfig ) { - button.init( this.ApplePayConfig ); - } + // Ensure ApplePayConfig is loaded before proceeding. + await this.init(); + + button.configure( this.ApplePayConfig ); + button.init(); } ); } - init() { - ( async () => { - await this.config(); - for ( const button of this.buttons ) { - button.init( this.ApplePayConfig ); + async init() { + try { + if ( ! this.ApplePayConfig ) { + this.ApplePayConfig = await paypal.Applepay().config(); + + if ( ! this.ApplePayConfig ) { + console.error( 'No ApplePayConfig received during init' ); + } } - } )(); + } catch ( error ) { + console.error( 'Error during initialization:', error ); + } } reinit() { @@ -49,14 +56,6 @@ class ApplePayManager { button.reinit(); } } - - /** - * Gets Apple Pay configuration of the PayPal merchant. - */ - async config() { - this.ApplePayConfig = await paypal.Applepay().config(); - return this.ApplePayConfig; - } } export default ApplePayManager; From 060dedaae91bd4097dfc8e71e2b05e97289ce7a4 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:29:12 +0200 Subject: [PATCH 037/271] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Match=20button-man?= =?UTF-8?q?ager=20of=20GooglePay=20and=20ApplePay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayManager.js | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index a6bae6c58..eb10ee90a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -32,21 +32,11 @@ class GooglepayManager { this.buttons.push( button ); - const initButton = () => { - button.configure( this.googlePayConfig, this.transactionInfo ); - button.init(); - }; + // Ensure googlePayConfig and transactionInfo are loaded. + await this.init(); - // Initialize button only if googlePayConfig and transactionInfo are already fetched. - if ( this.googlePayConfig && this.transactionInfo ) { - initButton(); - } else { - await this.init(); - - if ( this.googlePayConfig && this.transactionInfo ) { - initButton(); - } - } + button.configure( this.googlePayConfig, this.transactionInfo ); + button.init(); } ); } @@ -55,23 +45,17 @@ class GooglepayManager { if ( ! this.googlePayConfig ) { // Gets GooglePay configuration of the PayPal merchant. this.googlePayConfig = await paypal.Googlepay().config(); + + if ( ! this.googlePayConfig ) { + console.error( 'No GooglePayConfig received during init' ); + } } if ( ! this.transactionInfo ) { this.transactionInfo = await this.fetchTransactionInfo(); - } - if ( ! this.googlePayConfig ) { - console.error( 'No GooglePayConfig received during init' ); - } else if ( ! this.transactionInfo ) { - console.error( 'No transactionInfo found during init' ); - } else { - for ( const button of this.buttons ) { - button.configure( - this.googlePayConfig, - this.transactionInfo - ); - button.init(); + if ( ! this.transactionInfo ) { + console.error( 'No transactionInfo found during init' ); } } } catch ( error ) { From 0a212edb89f8802eaeecae0fc5349c33d84a4cce Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:34:05 +0200 Subject: [PATCH 038/271] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Further=20streamli?= =?UTF-8?q?ne=20button-manager=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayManager.js | 43 +++++++++--------- .../resources/js/GooglepayManager.js | 44 ++++++++++--------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 37297920b..270ba3e7a 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -11,30 +11,33 @@ class ApplePayManager { this.ApplePayConfig = null; this.buttons = []; - buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); + this.onContextBootstrap = this.onContextBootstrap.bind( this ); + buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap ); + } - const button = ApplePayButton.createButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - this.contextHandler - ); + async onContextBootstrap( bootstrap ) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + this.buttonConfig, + this.ppcpConfig, + bootstrap.handler + ); - this.buttons.push( button ); + const button = ApplePayButton.createButton( + bootstrap.context, + bootstrap.handler, + this.buttonConfig, + this.ppcpConfig, + this.contextHandler + ); - // Ensure ApplePayConfig is loaded before proceeding. - await this.init(); + this.buttons.push( button ); - button.configure( this.ApplePayConfig ); - button.init(); - } ); + // Ensure ApplePayConfig is loaded before proceeding. + await this.init(); + + button.configure( this.ApplePayConfig ); + button.init(); } async init() { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index eb10ee90a..5e79a880e 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -11,33 +11,35 @@ class GooglepayManager { this.googlePayConfig = null; this.transactionInfo = null; this.contextHandler = null; - this.buttons = []; - buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); + this.onContextBootstrap = this.onContextBootstrap.bind( this ); + buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap ); + } - const button = GooglepayButton.createButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - this.contextHandler - ); + async onContextBootstrap( bootstrap ) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + this.buttonConfig, + this.ppcpConfig, + bootstrap.handler + ); - this.buttons.push( button ); + const button = GooglepayButton.createButton( + bootstrap.context, + bootstrap.handler, + this.buttonConfig, + this.ppcpConfig, + this.contextHandler + ); - // Ensure googlePayConfig and transactionInfo are loaded. - await this.init(); + this.buttons.push( button ); - button.configure( this.googlePayConfig, this.transactionInfo ); - button.init(); - } ); + // Ensure googlePayConfig and transactionInfo are loaded. + await this.init(); + + button.configure( this.googlePayConfig, this.transactionInfo ); + button.init(); } async init() { From 7c56d58fe4e9d66d4866ee84f9e8a29662df4dd5 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 12:37:05 +0200 Subject: [PATCH 039/271] =?UTF-8?q?=F0=9F=9A=A7=20Swap=20local=20object=20?= =?UTF-8?q?for=20official=20config=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 63 +++++++------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index c8b449571..356fc13ae 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -8,7 +8,10 @@ import FormValidator from '../../../ppcp-button/resources/js/modules/Helper/Form import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; -import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { + PaymentContext, + PaymentMethods, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { combineStyles, combineWrapperIds, @@ -33,28 +36,6 @@ import { * @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. - * - * @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' ], -}; - /** * A payment button for Apple Pay. * @@ -160,7 +141,7 @@ class ApplePayButton extends PaymentButton { get isSeparateGateway() { return ( this.buttonConfig.is_wc_gateway_enabled && - CONTEXT.Gateways.includes( this.context ) + PaymentContext.Gateways.includes( this.context ) ); } @@ -385,7 +366,7 @@ class ApplePayButton extends PaymentButton { window.ppcpFundingSource = 'apple_pay'; // Trigger woocommerce validation if we are in the checkout page. - if ( CONTEXT.Checkout === this.context ) { + if ( PaymentContext.Checkout === this.context ) { const checkoutFormSelector = 'form.woocommerce-checkout'; const errorHandler = new ErrorHandler( PayPalCommerceGateway.labels.error.generic, @@ -447,7 +428,7 @@ class ApplePayButton extends PaymentButton { return ( this.contextHandler.shippingAllowed() && this.buttonConfig.product.needShipping && - ( CONTEXT.Checkout !== this.context || + ( PaymentContext.Checkout !== this.context || this.shouldUpdateButtonWithFormData() ) ); } @@ -458,7 +439,7 @@ class ApplePayButton extends PaymentButton { * @return {boolean} True, when Apple Pay data should be submitted to WooCommerce. */ shouldUpdateButtonWithFormData() { - if ( CONTEXT.Checkout !== this.context ) { + if ( PaymentContext.Checkout !== this.context ) { return false; } return ( @@ -481,7 +462,7 @@ class ApplePayButton extends PaymentButton { // Use WC form data mode in Checkout. return ( - CONTEXT.Checkout === this.context && + PaymentContext.Checkout === this.context && ! this.shouldUpdateButtonWithFormData() ); } @@ -599,7 +580,7 @@ class ApplePayButton extends PaymentButton { } refreshContextData() { - if ( CONTEXT.Product === this.context ) { + if ( PaymentContext.Product === this.context ) { // Refresh product data that makes the price change. this.productQuantity = document.querySelector( 'input.qty' )?.value; this.products = this.contextHandler.products(); @@ -761,7 +742,7 @@ class ApplePayButton extends PaymentButton { this.refreshContextData(); switch ( this.context ) { - case CONTEXT.Product: + case PaymentContext.Product: return { action: 'ppcp_update_shipping_contact', product_id: productId, @@ -773,11 +754,11 @@ class ApplePayButton extends PaymentButton { 'woocommerce-process-checkout-nonce': this.nonce, }; - case CONTEXT.Cart: - case CONTEXT.Checkout: - case CONTEXT.BlockCart: - case CONTEXT.BlockCheckout: - case CONTEXT.MiniCart: + case PaymentContext.Cart: + case PaymentContext.Checkout: + case PaymentContext.BlockCart: + case PaymentContext.BlockCheckout: + case PaymentContext.MiniCart: return { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, @@ -794,7 +775,7 @@ class ApplePayButton extends PaymentButton { this.refreshContextData(); switch ( this.context ) { - case CONTEXT.Product: + case PaymentContext.Product: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, @@ -811,11 +792,11 @@ class ApplePayButton extends PaymentButton { 'woocommerce-process-checkout-nonce': this.nonce, }; - case CONTEXT.Cart: - case CONTEXT.Checkout: - case CONTEXT.BlockCart: - case CONTEXT.BlockCheckout: - case CONTEXT.MiniCart: + case PaymentContext.Cart: + case PaymentContext.Checkout: + case PaymentContext.BlockCart: + case PaymentContext.BlockCheckout: + case PaymentContext.MiniCart: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, From 3d271586a3a560f35b299721926f1d5fc6cc61f0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 14:56:09 +0200 Subject: [PATCH 040/271] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20the=20a?= =?UTF-8?q?ddress=20extraction=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 75 +++++++------------ 1 file changed, 26 insertions(+), 49 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 356fc13ae..47a53656f 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1017,62 +1017,39 @@ class ApplePayButton extends PaymentButton { }; } - fillBillingContact( data ) { + #extractContactInfo( data, primaryPrefix, fallbackPrefix ) { + if ( ! data || typeof data !== 'object' ) { + data = {}; + } + + const getValue = ( key ) => + data[ `${ primaryPrefix }_${ key }` ] || + data[ `${ fallbackPrefix }_${ key }` ] || + ''; + return { - givenName: data.billing_first_name ?? '', - familyName: data.billing_last_name ?? '', - emailAddress: data.billing_email ?? '', - phoneNumber: data.billing_phone ?? '', - addressLines: [ data.billing_address_1, data.billing_address_2 ], - locality: data.billing_city ?? '', - postalCode: data.billing_postcode ?? '', - countryCode: data.billing_country ?? '', - administrativeArea: data.billing_state ?? '', + givenName: getValue( 'first_name' ), + familyName: getValue( 'last_name' ), + emailAddress: getValue( 'email' ), + phoneNumber: getValue( 'phone' ), + addressLines: [ getValue( 'address_1' ), getValue( 'address_2' ) ], + locality: getValue( 'city' ), + postalCode: getValue( 'postcode' ), + countryCode: getValue( 'country' ), + administrativeArea: getValue( 'state' ), }; } + fillBillingContact( data ) { + return this.#extractContactInfo( data, 'billing', '' ); + } + fillShippingContact( data ) { - if ( data.shipping_first_name === '' ) { + if ( ! data?.shipping_first_name ) { return this.fillBillingContact( data ); } - return { - givenName: - data?.shipping_first_name && data.shipping_first_name !== '' - ? data.shipping_first_name - : data?.billing_first_name, - familyName: - data?.shipping_last_name && data.shipping_last_name !== '' - ? data.shipping_last_name - : data?.billing_last_name, - emailAddress: - data?.shipping_email && data.shipping_email !== '' - ? data.shipping_email - : data?.billing_email, - phoneNumber: - data?.shipping_phone && data.shipping_phone !== '' - ? data.shipping_phone - : data?.billing_phone, - addressLines: [ - data.shipping_address_1 ?? '', - data.shipping_address_2 ?? '', - ], - locality: - data?.shipping_city && data.shipping_city !== '' - ? data.shipping_city - : data?.billing_city, - postalCode: - data?.shipping_postcode && data.shipping_postcode !== '' - ? data.shipping_postcode - : data?.billing_postcode, - countryCode: - data?.shipping_country && data.shipping_country !== '' - ? data.shipping_country - : data?.billing_country, - administrativeArea: - data?.shipping_state && data.shipping_state !== '' - ? data.shipping_state - : data?.billing_state, - }; + + return this.#extractContactInfo( data, 'shipping', 'billing' ); } fillApplicationData( data ) { From b78a1a112a81961cfb6f4cccb04d2a1c509b837c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 15:16:01 +0200 Subject: [PATCH 041/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20jQuery=20from?= =?UTF-8?q?=20boot.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/boot.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index 8eddafbcb..0efb9cf4a 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -3,7 +3,7 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; -( function ( { buttonConfig, ppcpConfig, jQuery } ) { +( function ( { buttonConfig, ppcpConfig } ) { let manager; const bootstrap = function () { @@ -24,8 +24,11 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel ) { return; } + const isMiniCart = ppcpConfig.mini_cart_buttons_enabled; - const isButton = jQuery( '#' + buttonConfig.button.wrapper ).length > 0; + const isButton = + null !== document.getElementById( buttonConfig.button.wrapper ); + // If button wrapper is not present then there is no need to load the scripts. // minicart loads later? if ( ! isMiniCart && ! isButton ) { @@ -58,5 +61,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel } )( { buttonConfig: window.wc_ppcp_applepay, ppcpConfig: window.PayPalCommerceGateway, - jQuery: window.jQuery, } ); From c00c9c2e13cb95b9877dd6fd41c17fc6383bfbdc Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 15:17:28 +0200 Subject: [PATCH 042/271] =?UTF-8?q?=F0=9F=9A=A7=20Update=20boot.js=20to=20?= =?UTF-8?q?same=20logic=20as=20Google=20Pay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/boot.js | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index 0efb9cf4a..b1aa0b81d 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -4,24 +4,28 @@ import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; ( function ( { buttonConfig, ppcpConfig } ) { - let manager; - const bootstrap = function () { - manager = new ApplePayManager( buttonConfig, ppcpConfig ); - manager.init(); + if ( ! buttonConfig || ! ppcpConfig ) { + return; + } + + const manager = new ApplePayManager( buttonConfig, ppcpConfig ); + + setupButtonEvents( function () { + manager.reinit(); + } ); }; - setupButtonEvents( function () { - if ( manager ) { - manager.reinit(); - } - } ); document.addEventListener( 'DOMContentLoaded', () => { - if ( - typeof buttonConfig === 'undefined' || - typeof ppcpConfig === 'undefined' - ) { + if ( ! buttonConfig || ! ppcpConfig ) { + /* + * No PayPal buttons present on this page, but maybe a bootstrap module needs to be + * initialized. Skip loading the SDK or gateway configuration, and directly initialize + * the module. + */ + bootstrap(); + return; } From d80e92b5e8d0ea04cc5e55c5ba91b6be5898f337 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 15:18:51 +0200 Subject: [PATCH 043/271] =?UTF-8?q?=F0=9F=9A=A7=20Minor=20refactoring=20in?= =?UTF-8?q?=20boot.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/boot.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index b1aa0b81d..c76635378 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -4,7 +4,7 @@ import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; ( function ( { buttonConfig, ppcpConfig } ) { - const bootstrap = function () { + function bootstrapPayButton() { if ( ! buttonConfig || ! ppcpConfig ) { return; } @@ -14,8 +14,12 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel setupButtonEvents( function () { manager.reinit(); } ); - }; + } + function bootstrap() { + bootstrapPayButton(); + // Other Apple Pay bootstrapping could happen here. + } document.addEventListener( 'DOMContentLoaded', () => { if ( ! buttonConfig || ! ppcpConfig ) { @@ -29,13 +33,13 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel return; } - const isMiniCart = ppcpConfig.mini_cart_buttons_enabled; - const isButton = + const usedInMiniCart = ppcpConfig.mini_cart_buttons_enabled; + const pageHasButton = null !== document.getElementById( buttonConfig.button.wrapper ); // If button wrapper is not present then there is no need to load the scripts. // minicart loads later? - if ( ! isMiniCart && ! isButton ) { + if ( ! usedInMiniCart && ! pageHasButton ) { return; } From c9bae62d6c2863f51989ca6d2552350b812cc404 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 7 Oct 2024 15:32:48 +0200 Subject: [PATCH 044/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20unused=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 63 +++++-------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 47a53656f..8cdff46bb 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -482,11 +482,6 @@ class ApplePayButton extends PaymentButton { this.#formData ); - // Add custom data. - // "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; } @@ -658,11 +653,7 @@ class ApplePayButton extends PaymentButton { url: ajaxUrl, method: 'POST', data, - success: ( - applePayShippingMethodUpdate, - textStatus, - jqXHR - ) => { + success: ( applePayShippingMethodUpdate ) => { this.log( 'onshippingmethodselected ok' ); const response = applePayShippingMethodUpdate.data; if ( applePayShippingMethodUpdate.success === false ) { @@ -673,7 +664,7 @@ class ApplePayButton extends PaymentButton { // Sort the response shipping methods, so that the selected shipping method is // the first one. response.newShippingMethods = - response.newShippingMethods.sort( ( a, b ) => { + response.newShippingMethods.sort( ( a ) => { if ( a.label === this.#selectedShippingMethod.label ) { @@ -710,11 +701,7 @@ class ApplePayButton extends PaymentButton { url: ajaxUrl, method: 'POST', data, - success: ( - applePayShippingContactUpdate, - textStatus, - jqXHR - ) => { + success: ( applePayShippingContactUpdate ) => { this.log( 'onshippingcontactselected ok' ); const response = applePayShippingContactUpdate.data; this.#updatedContactInfo = event.shippingContact; @@ -857,14 +844,10 @@ class ApplePayButton extends PaymentButton { url: this.buttonConfig.ajax_url, method: 'POST', data: requestData, - complete: ( jqXHR, textStatus ) => { + complete: () => { this.log( 'onpaymentauthorized complete' ); }, - success: ( - authorizationResult, - textStatus, - jqXHR - ) => { + success: ( authorizationResult ) => { this.log( 'onpaymentauthorized ok' ); resolve( authorizationResult ); }, @@ -877,8 +860,7 @@ class ApplePayButton extends PaymentButton { }, } ); } catch ( error ) { - this.log( 'onpaymentauthorized catch', error ); - console.log( error ); // handle error + this.error( 'onpaymentauthorized catch', error ); } } ); }; @@ -929,19 +911,15 @@ class ApplePayButton extends PaymentButton { { // actions mock object. restart: () => - new Promise( - ( resolve, reject ) => { - approveFailed = true; - resolve(); - } - ), + new Promise( ( resolve ) => { + approveFailed = true; + resolve(); + } ), order: { get: () => - new Promise( - ( resolve, reject ) => { - resolve( null ); - } - ), + new Promise( ( resolve ) => { + resolve( null ); + } ), }, } ); @@ -954,14 +932,13 @@ class ApplePayButton extends PaymentButton { ApplePaySession.STATUS_SUCCESS ); } else { - this.log( + this.error( 'onpaymentauthorized approveOrder FAIL' ); session.completePayment( ApplePaySession.STATUS_FAILURE ); session.abort(); - console.error( error ); } } else { // Default payment. @@ -1052,18 +1029,6 @@ class ApplePayButton extends PaymentButton { return this.#extractContactInfo( data, 'shipping', 'billing' ); } - fillApplicationData( data ) { - const jsonString = JSON.stringify( data ); - const utf8Str = encodeURIComponent( jsonString ).replace( - /%([0-9A-F]{2})/g, - ( match, p1 ) => { - return String.fromCharCode( '0x' + p1 ); - } - ); - - return btoa( utf8Str ); - } - hasValidContactInfo( value ) { return Array.isArray( value ) ? value.length > 0 From 0d72641ded1c02ff10e2ed145fbbb77b65a92e79 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 8 Oct 2024 14:25:43 +0200 Subject: [PATCH 045/271] =?UTF-8?q?=E2=9C=A8=20Apply=20new=20namespaced=20?= =?UTF-8?q?script=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayManager.js | 15 +++--- .../js/ApplepayManagerBlockEditor.js | 41 +++++++++------ .../ppcp-applepay/resources/js/boot-block.js | 52 +++++++++++-------- modules/ppcp-applepay/resources/js/boot.js | 22 +++++--- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 270ba3e7a..5dcb0cf7b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -1,11 +1,10 @@ -/* global paypal */ - import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; import ApplePayButton from './ApplepayButton'; import ContextHandlerFactory from './Context/ContextHandlerFactory'; class ApplePayManager { - constructor( buttonConfig, ppcpConfig ) { + constructor( namespace, buttonConfig, ppcpConfig ) { + this.namespace = namespace; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.ApplePayConfig = null; @@ -36,16 +35,18 @@ class ApplePayManager { // Ensure ApplePayConfig is loaded before proceeding. await this.init(); - button.configure( this.ApplePayConfig ); + button.configure( this.applePayConfig ); button.init(); } async init() { try { - if ( ! this.ApplePayConfig ) { - this.ApplePayConfig = await paypal.Applepay().config(); + if ( ! this.applePayConfig ) { + this.applePayConfig = await window[ this.namespace ] + .Applepay() + .config(); - if ( ! this.ApplePayConfig ) { + if ( ! this.applePayConfig ) { console.error( 'No ApplePayConfig received during init' ); } } diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index 7276abbb7..a8fa9d7c9 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -1,32 +1,43 @@ -/* global paypal */ - import ApplePayButton from './ApplepayButton'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; class ApplePayManagerBlockEditor { - constructor( buttonConfig, ppcpConfig ) { + constructor( namespace, buttonConfig, ppcpConfig ) { + this.namespace = namespace; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; - this.applePayConfig = null; + + /* + * On the front-end, the init method is called when a new button context was detected + * via `buttonModuleWatcher`. In the block editor, we do not need to wait for the + * context, but can initialize the button in the next event loop. + */ + setTimeout( () => this.init() ); } - init() { - ( async () => { - await this.config(); - } )(); - } - - async config() { + async init() { try { - this.applePayConfig = await ppcpBlocksEditorPaypalApplepay.Applepay().config(); + this.applePayConfig = await window[ this.namespace ] + .Applepay() + .config(); - const button = new ApplePayButton( + this.contextHandler = ContextHandlerFactory.create( + this.ppcpConfig.context, + this.buttonConfig, + this.ppcpConfig, + null + ); + + const button = ApplePayButton.createButton( this.ppcpConfig.context, null, this.buttonConfig, - this.ppcpConfig + this.ppcpConfig, + this.contextHandler ); - button.init( this.applePayConfig ); + button.configure( this.applePayConfig ); + button.init(); } catch ( error ) { console.error( 'Failed to initialize Apple Pay:', error ); } diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index ea15477b0..72e3a84db 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,6 +1,7 @@ -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry'; -import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import { __ } from '@wordpress/i18n'; +import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading'; import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription'; import { loadCustomScript } from '@paypal/paypal-js'; import CheckoutHandler from './Context/CheckoutHandler'; @@ -12,24 +13,16 @@ const ppcpConfig = ppcpData.scriptData; const buttonData = wc.wcSettings.getSetting( 'ppcp-applepay_data' ); const buttonConfig = buttonData.scriptData; -const dataNamespace = 'ppcpBlocksEditorPaypalApplepay'; +const namespace = 'ppcpBlocksPaypalAppglepay'; if ( typeof window.PayPalCommerceGateway === 'undefined' ) { window.PayPalCommerceGateway = ppcpConfig; } const ApplePayComponent = ( props ) => { - const [ bootstrapped, setBootstrapped ] = useState( false ); const [ paypalLoaded, setPaypalLoaded ] = useState( false ); const [ applePayLoaded, setApplePayLoaded ] = useState( false ); - - const bootstrap = function () { - const ManagerClass = props.isEditing - ? ApplePayManagerBlockEditor - : ApplePayManager; - const manager = new ManagerClass( buttonConfig, ppcpConfig ); - manager.init(); - }; + const wrapperRef = useRef( null ); useEffect( () => { // Load ApplePay SDK @@ -39,25 +32,33 @@ const ApplePayComponent = ( props ) => { ppcpConfig.url_params.components += ',applepay'; - if ( props.isEditing ) { - ppcpConfig.data_namespace = dataNamespace; - } - // Load PayPal - loadPaypalScript( ppcpConfig, () => { - setPaypalLoaded( true ); - } ); + loadPayPalScript( namespace, ppcpConfig ) + .then( () => { + setPaypalLoaded( true ); + } ) + .catch( ( error ) => { + console.error( 'Failed to load PayPal script: ', error ); + } ); }, [] ); useEffect( () => { - if ( ! bootstrapped && paypalLoaded && applePayLoaded ) { - setBootstrapped( true ); - bootstrap(); + if ( ! paypalLoaded || ! applePayLoaded ) { + return; } - }, [ paypalLoaded, applePayLoaded ] ); + + const ManagerClass = props.isEditing + ? ApplePayManagerBlockEditor + : ApplePayManager; + + buttonConfig.reactWrapper = wrapperRef.current; + + new ManagerClass( namespace, buttonConfig, ppcpConfig ); + }, [ paypalLoaded, applePayLoaded, props.isEditing ] ); return (
@@ -75,6 +76,11 @@ if ( registerExpressPaymentMethod( { name: buttonData.id, + title: `PayPal - ${ buttonData.title }`, + description: __( + 'Eligible users will see the PayPal button.', + 'woocommerce-paypal-payments' + ), label:
, content: , edit: , diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index c76635378..95b4381e4 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -1,15 +1,21 @@ import { loadCustomScript } from '@paypal/paypal-js'; -import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; +import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading'; import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; ( function ( { buttonConfig, ppcpConfig } ) { + const namespace = 'ppcpPaypalApplepay'; + function bootstrapPayButton() { if ( ! buttonConfig || ! ppcpConfig ) { return; } - const manager = new ApplePayManager( buttonConfig, ppcpConfig ); + const manager = new ApplePayManager( + namespace, + buttonConfig, + ppcpConfig + ); setupButtonEvents( function () { manager.reinit(); @@ -61,10 +67,14 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel } ); // Load PayPal - loadPaypalScript( ppcpConfig, () => { - paypalLoaded = true; - tryToBoot(); - } ); + loadPayPalScript( namespace, ppcpConfig ) + .then( () => { + paypalLoaded = true; + tryToBoot(); + } ) + .catch( ( error ) => { + console.error( 'Failed to load PayPal script: ', error ); + } ); } ); } )( { buttonConfig: window.wc_ppcp_applepay, From 35473df661c52f5c2e759f859ed4a3a320395205 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 8 Oct 2024 14:26:46 +0200 Subject: [PATCH 046/271] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Make=20internal=20?= =?UTF-8?q?attributes=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayManager.js | 39 +++++++++++-------- .../js/ApplepayManagerBlockEditor.js | 32 ++++++++------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 5dcb0cf7b..6bff52577 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -3,50 +3,55 @@ import ApplePayButton from './ApplepayButton'; import ContextHandlerFactory from './Context/ContextHandlerFactory'; class ApplePayManager { + #namespace = ''; + #buttonConfig = null; + #ppcpConfig = null; + #applePayConfig = null; + #contextHandler = null; + #buttons = []; + constructor( namespace, buttonConfig, ppcpConfig ) { - this.namespace = namespace; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.ApplePayConfig = null; - this.buttons = []; + this.#namespace = namespace; + this.#buttonConfig = buttonConfig; + this.#ppcpConfig = ppcpConfig; this.onContextBootstrap = this.onContextBootstrap.bind( this ); buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap ); } async onContextBootstrap( bootstrap ) { - this.contextHandler = ContextHandlerFactory.create( + this.#contextHandler = ContextHandlerFactory.create( bootstrap.context, - this.buttonConfig, - this.ppcpConfig, + this.#buttonConfig, + this.#ppcpConfig, bootstrap.handler ); const button = ApplePayButton.createButton( bootstrap.context, bootstrap.handler, - this.buttonConfig, - this.ppcpConfig, - this.contextHandler + this.#buttonConfig, + this.#ppcpConfig, + this.#contextHandler ); - this.buttons.push( button ); + this.#buttons.push( button ); // Ensure ApplePayConfig is loaded before proceeding. await this.init(); - button.configure( this.applePayConfig ); + button.configure( this.#applePayConfig ); button.init(); } async init() { try { - if ( ! this.applePayConfig ) { - this.applePayConfig = await window[ this.namespace ] + if ( ! this.#applePayConfig ) { + this.#applePayConfig = await window[ this.#namespace ] .Applepay() .config(); - if ( ! this.applePayConfig ) { + if ( ! this.#applePayConfig ) { console.error( 'No ApplePayConfig received during init' ); } } @@ -56,7 +61,7 @@ class ApplePayManager { } reinit() { - for ( const button of this.buttons ) { + for ( const button of this.#buttons ) { button.reinit(); } } diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index a8fa9d7c9..ee992a9b7 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -2,10 +2,16 @@ import ApplePayButton from './ApplepayButton'; import ContextHandlerFactory from './Context/ContextHandlerFactory'; class ApplePayManagerBlockEditor { + #namespace = ''; + #buttonConfig = null; + #ppcpConfig = null; + #applePayConfig = null; + #contextHandler = null; + constructor( namespace, buttonConfig, ppcpConfig ) { - this.namespace = namespace; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; + this.#namespace = namespace; + this.#buttonConfig = buttonConfig; + this.#ppcpConfig = ppcpConfig; /* * On the front-end, the init method is called when a new button context was detected @@ -17,26 +23,26 @@ class ApplePayManagerBlockEditor { async init() { try { - this.applePayConfig = await window[ this.namespace ] + this.#applePayConfig = await window[ this.#namespace ] .Applepay() .config(); - this.contextHandler = ContextHandlerFactory.create( - this.ppcpConfig.context, - this.buttonConfig, - this.ppcpConfig, + this.#contextHandler = ContextHandlerFactory.create( + this.#ppcpConfig.context, + this.#buttonConfig, + this.#ppcpConfig, null ); const button = ApplePayButton.createButton( - this.ppcpConfig.context, + this.#ppcpConfig.context, null, - this.buttonConfig, - this.ppcpConfig, - this.contextHandler + this.#buttonConfig, + this.#ppcpConfig, + this.#contextHandler ); - button.configure( this.applePayConfig ); + button.configure( this.#applePayConfig ); button.init(); } catch ( error ) { console.error( 'Failed to initialize Apple Pay:', error ); From 6a48c0723614e11ecc9308d35574f2d2bbbe63b6 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 8 Oct 2024 14:54:00 +0200 Subject: [PATCH 047/271] =?UTF-8?q?=E2=9C=A8=20Add=20transaction=20info=20?= =?UTF-8?q?to=20ApplePay=20initialization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 48 ++++++++++++++++++- .../resources/js/ApplepayManager.js | 23 ++++++++- .../js/ApplepayManagerBlockEditor.js | 18 ++++++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 8cdff46bb..971d0ef27 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -36,6 +36,18 @@ import { * @property {string} lang - The locale; an empty string will apply the user-agent's language. */ +/** + * This object describes the transaction details. + * + * @typedef {Object} TransactionInfo + * @property {string} countryCode - The ISO country code + * @property {string} currencyCode - The ISO currency code + * @property {string} totalPriceStatus - Usually 'FINAL', can also be 'DRAFT' + * @property {string} totalPrice - Total monetary value of the transaction. + * @property {Array} chosenShippingMethods - Selected shipping method. + * @property {string} shippingPackages - A list of available shipping methods, defined by WooCommerce. + */ + /** * A payment button for Apple Pay. * @@ -63,6 +75,13 @@ class ApplePayButton extends PaymentButton { */ #initialPaymentRequest = null; + /** + * Details about the processed transaction, provided to the Apple SDK. + * + * @type {?TransactionInfo} + */ + #transactionInfo = null; + /** * Apple Pay specific API configuration. */ @@ -116,6 +135,29 @@ class ApplePayButton extends PaymentButton { this.log( 'Create instance' ); } + /** + * Details about the processed transaction. + * + * This object defines the price that is charged, and text that is displayed inside the + * payment sheet. + * + * @return {?TransactionInfo} The TransactionInfo object. + */ + get transactionInfo() { + return this.#transactionInfo; + } + + /** + * Assign the new transaction details to the payment button. + * + * @param {TransactionInfo} newTransactionInfo - Transaction details. + */ + set transactionInfo( newTransactionInfo ) { + this.#transactionInfo = newTransactionInfo; + + this.refresh(); + } + /** * The nonce for ajax requests. * @@ -189,10 +231,12 @@ class ApplePayButton extends PaymentButton { /** * Configures the button instance. Must be called before the initial `init()`. * - * @param {Object} apiConfig - API configuration. + * @param {Object} apiConfig - API configuration. + * @param {TransactionInfo} transactionInfo - Transaction details. */ - configure( apiConfig ) { + configure( apiConfig, transactionInfo ) { this.#applePayConfig = apiConfig; + this.#transactionInfo = transactionInfo; } init() { diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 6bff52577..673078121 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -8,6 +8,7 @@ class ApplePayManager { #ppcpConfig = null; #applePayConfig = null; #contextHandler = null; + #transactionInfo = null; #buttons = []; constructor( namespace, buttonConfig, ppcpConfig ) { @@ -40,7 +41,7 @@ class ApplePayManager { // Ensure ApplePayConfig is loaded before proceeding. await this.init(); - button.configure( this.#applePayConfig ); + button.configure( this.#applePayConfig, this.#transactionInfo ); button.init(); } @@ -55,11 +56,31 @@ class ApplePayManager { console.error( 'No ApplePayConfig received during init' ); } } + + if ( ! this.#transactionInfo ) { + this.#transactionInfo = await this.fetchTransactionInfo(); + + if ( ! this.#applePayConfig ) { + console.error( 'No transactionInfo found during init' ); + } + } } catch ( error ) { console.error( 'Error during initialization:', error ); } } + async fetchTransactionInfo() { + try { + if ( ! this.#contextHandler ) { + throw new Error( 'ContextHandler is not initialized' ); + } + return await this.#contextHandler.transactionInfo(); + } catch ( error ) { + console.error( 'Error fetching transaction info:', error ); + throw error; + } + } + reinit() { for ( const button of this.#buttons ) { button.reinit(); diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index ee992a9b7..e467f0709 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -7,6 +7,7 @@ class ApplePayManagerBlockEditor { #ppcpConfig = null; #applePayConfig = null; #contextHandler = null; + #transactionInfo = null; constructor( namespace, buttonConfig, ppcpConfig ) { this.#namespace = namespace; @@ -34,6 +35,9 @@ class ApplePayManagerBlockEditor { null ); + // Fetch transaction information. + this.#transactionInfo = await this.fetchTransactionInfo(); + const button = ApplePayButton.createButton( this.#ppcpConfig.context, null, @@ -42,12 +46,24 @@ class ApplePayManagerBlockEditor { this.#contextHandler ); - button.configure( this.#applePayConfig ); + button.configure( this.#applePayConfig, this.#transactionInfo ); button.init(); } catch ( error ) { console.error( 'Failed to initialize Apple Pay:', error ); } } + + async fetchTransactionInfo() { + try { + if ( ! this.#contextHandler ) { + throw new Error( 'ContextHandler is not initialized' ); + } + return await this.#contextHandler.transactionInfo(); + } catch ( error ) { + console.error( 'Error fetching transaction info:', error ); + throw error; + } + } } export default ApplePayManagerBlockEditor; From 560ec32e26b6a31d5707e4cc72ed9b0a94c08650 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 8 Oct 2024 14:54:42 +0200 Subject: [PATCH 048/271] =?UTF-8?q?=F0=9F=9A=A7=20Implement=20requireShipp?= =?UTF-8?q?ing=20getter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 971d0ef27..3a7c8cdd7 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -135,6 +135,24 @@ class ApplePayButton extends PaymentButton { this.log( 'Create instance' ); } + /** + * @inheritDoc + */ + get requiresShipping() { + if ( ! super.requiresShipping ) { + return false; + } + + if ( ! this.buttonConfig.product?.needShipping ) { + return false; + } + + return ( + PaymentContext.Checkout !== this.context || + this.shouldUpdateButtonWithFormData() + ); + } + /** * Details about the processed transaction. * @@ -345,7 +363,7 @@ class ApplePayButton extends PaymentButton { const session = new ApplePaySession( 4, paymentRequest ); session.begin(); - if ( this.shouldRequireShippingInButton() ) { + if ( this.requiresShipping ) { session.onshippingmethodselected = this.onShippingMethodSelected( session ); session.onshippingcontactselected = @@ -463,20 +481,6 @@ class ApplePayButton extends PaymentButton { this.applePaySession( paymentRequest ); } - /** - * If the button should show the shipping fields. - * - * @return {boolean} True, if shipping fields should be captured by ApplePay. - */ - shouldRequireShippingInButton() { - return ( - this.contextHandler.shippingAllowed() && - this.buttonConfig.product.needShipping && - ( PaymentContext.Checkout !== this.context || - this.shouldUpdateButtonWithFormData() ) - ); - } - /** * If the button should be updated with the form addresses. * @@ -526,7 +530,7 @@ class ApplePayButton extends PaymentButton { this.#formData ); - if ( ! this.shouldRequireShippingInButton() ) { + if ( ! this.requiresShipping ) { return; } @@ -594,7 +598,7 @@ class ApplePayButton extends PaymentButton { // email and phone fields. }; - if ( ! this.shouldRequireShippingInButton() ) { + if ( ! this.requiresShipping ) { if ( this.shouldCompletePaymentWithContextHandler() ) { // Data needs handled externally. baseRequest.requiredShippingContactFields = []; @@ -781,7 +785,7 @@ class ApplePayButton extends PaymentButton { caller_page: 'productDetail', product_quantity: this.productQuantity, simplified_contact: event.shippingContact, - need_shipping: this.shouldRequireShippingInButton(), + need_shipping: this.requiresShipping, 'woocommerce-process-checkout-nonce': this.nonce, }; @@ -794,7 +798,7 @@ class ApplePayButton extends PaymentButton { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, caller_page: 'cart', - need_shipping: this.shouldRequireShippingInButton(), + need_shipping: this.requiresShipping, 'woocommerce-process-checkout-nonce': this.nonce, }; } From f41fa4f951a58288dfee7d9e08ec3641bb4c1af1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 8 Oct 2024 18:47:32 +0200 Subject: [PATCH 049/271] =?UTF-8?q?=E2=9C=A8=20Streamline=20button=20confi?= =?UTF-8?q?guraton=20validation=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 44 ++++++------ .../js/modules/Renderer/PaymentButton.js | 72 ++++++++++++++++++- .../resources/js/GooglepayButton.js | 56 ++++++--------- 3 files changed, 113 insertions(+), 59 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 3a7c8cdd7..89634747a 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -217,33 +217,31 @@ class ApplePayButton extends PaymentButton { /** * @inheritDoc */ - validateConfiguration( silent = false ) { - const validEnvs = [ 'PRODUCTION', 'TEST' ]; + registerValidationRules( invalidIf, validIf ) { + invalidIf( + () => + [ 'TEST', 'PRODUCTION' ].includes( + this.buttonConfig.environment + ), + `Invalid environment: ${ this.buttonConfig.environment }` + ); - const isInvalid = ( ...args ) => { - if ( ! silent ) { - this.error( ...args ); - } - return false; - }; + validIf( () => this.isPreview ); - if ( ! validEnvs.includes( this.buttonConfig.environment ) ) { - return isInvalid( - 'Invalid environment:', - this.buttonConfig.environment - ); - } + invalidIf( + () => ! this.#applePayConfig, + 'No API configuration - missing configure() call?' + ); - // Preview buttons only need a valid environment. - if ( this.isPreview ) { - return true; - } + invalidIf( + () => ! this.#transactionInfo, + 'No transactionInfo - missing configure() call?' + ); - if ( ! typeof this.contextHandler?.validateContext() ) { - return isInvalid( 'Invalid context handler.', this.contextHandler ); - } - - return true; + invalidIf( + () => ! this.contextHandler?.validateContext(), + `Invalid context handler.` + ); } /** diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 3ce35c9b5..35d938b34 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -192,6 +192,13 @@ export default class PaymentButton { */ #button = null; + /** + * List of checks to perform to verify the PaymentButton has is configured correctly. + * + * @type {{check, errorMessage, shouldPass}[]} + */ + #validationChecks = []; + /** * Factory method to create a new PaymentButton while limiting a single instance per context. * @@ -305,6 +312,11 @@ export default class PaymentButton { ); this.applyButtonStyles( this.#buttonConfig ); + this.registerValidationRules( + this.#assertIsInvalid.bind( this ), + this.#assertIsValid.bind( this ) + ); + apmButtonsInit( this.#ppcpConfig ); this.initEventListeners(); } @@ -634,16 +646,74 @@ export default class PaymentButton { this.#logger.group( label ); } + /** + * Register a validation check that marks the configuration as invalid when passed. + * + * @param {Function} check - A function that returns a truthy value if the check passes. + * @param {string} errorMessage - The error message to display if the check fails. + */ + #assertIsInvalid( check, errorMessage ) { + this.#validationChecks.push( { + check, + errorMessage, + shouldPass: false, + } ); + } + + /** + * Register a validation check that instantly marks the configuration as valid when passed. + * + * @param {Function} check - A function that returns a truthy value if the check passes. + */ + #assertIsValid( check ) { + this.#validationChecks.push( { check, shouldPass: true } ); + } + + /** + * Defines a series of validation steps to ensure the payment button is configured correctly. + * + * Each validation step is executed in the order they are defined within this method. + * + * If a validation step using `invalidIf` returns true, the configuration is immediately considered + * invalid, and an error message is logged. Conversely, if a validation step using `validIf` + * returns true, the configuration is immediately considered valid. + * + * If no validation step returns true, the configuration is assumed to be valid by default. + * + * @param {(condition: () => boolean, errorMessage: string) => void} invalidIf - Registers a validation step that fails if the condition returns true. + * @param {(condition: () => boolean) => void} validIf - Registers a validation step that passes if the condition returns true. + */ + registerValidationRules( invalidIf, validIf ) {} + /** * 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. + * All required validation steps must be registered in the constructor of the derived class + * using `this.addValidationFailure()` or `this.addValidationSuccess()`. * * @param {boolean} [silent=false] - Set to true to suppress console errors. * @return {boolean} True indicates the config is valid and initialization can continue. */ validateConfiguration( silent = false ) { + for ( const step of this.#validationChecks ) { + const result = step.check(); + + if ( step.shouldPass && result ) { + // If a success check passes, mark as valid immediately. + return true; + } + + if ( ! step.shouldPass && result ) { + // If a failure check passes, mark as invalid. + if ( ! silent && step.errorMessage ) { + this.error( step.errorMessage ); + } + + return false; + } + } + return true; } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 97bfa6d2c..0cd2bab70 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -215,45 +215,31 @@ class GooglepayButton extends PaymentButton { /** * @inheritDoc */ - validateConfiguration( silent = false ) { - const validEnvs = [ 'PRODUCTION', 'TEST' ]; + registerValidationRules( invalidIf, validIf ) { + invalidIf( + () => + [ 'TEST', 'PRODUCTION' ].includes( + this.buttonConfig.environment + ), + `Invalid environment: ${ this.buttonConfig.environment }` + ); - const isInvalid = ( ...args ) => { - if ( ! silent ) { - this.error( ...args ); - } - return false; - }; + validIf( () => this.isPreview ); - if ( ! validEnvs.includes( this.buttonConfig.environment ) ) { - return isInvalid( - 'Invalid environment:', - this.buttonConfig.environment - ); - } + invalidIf( + () => ! this.googlePayConfig, + 'No API configuration - missing configure() call?' + ); - // Preview buttons only need a valid environment. - if ( this.isPreview ) { - return true; - } + invalidIf( + () => ! this.transactionInfo, + 'No transactionInfo - missing configure() call?' + ); - if ( ! this.googlePayConfig ) { - return isInvalid( - 'No API configuration - missing configure() call?' - ); - } - - if ( ! this.transactionInfo ) { - return isInvalid( - 'No transactionInfo - missing configure() call?' - ); - } - - if ( ! typeof this.contextHandler?.validateContext() ) { - return isInvalid( 'Invalid context handler.', this.contextHandler ); - } - - return true; + invalidIf( + () => ! this.contextHandler?.validateContext(), + `Invalid context handler.` + ); } /** From 22945c4df51bb0f5fbee6e37211451d22d4c1237 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 9 Oct 2024 10:30:36 +0300 Subject: [PATCH 050/271] Add void button --- .../resources/js/void-button.js | 53 +++++ modules/ppcp-wc-gateway/services.php | 41 +++- .../src/Assets/VoidButtonAssets.php | 171 +++++++++++++++ .../src/Endpoint/VoidOrderEndpoint.php | 198 ++++++++++++++++++ .../src/Processor/RefundProcessor.php | 40 ++-- .../ppcp-wc-gateway/src/WCGatewayModule.php | 32 +++ modules/ppcp-wc-gateway/webpack.config.js | 1 + 7 files changed, 513 insertions(+), 23 deletions(-) create mode 100644 modules/ppcp-wc-gateway/resources/js/void-button.js create mode 100644 modules/ppcp-wc-gateway/src/Assets/VoidButtonAssets.php create mode 100644 modules/ppcp-wc-gateway/src/Endpoint/VoidOrderEndpoint.php diff --git a/modules/ppcp-wc-gateway/resources/js/void-button.js b/modules/ppcp-wc-gateway/resources/js/void-button.js new file mode 100644 index 000000000..55e06ca51 --- /dev/null +++ b/modules/ppcp-wc-gateway/resources/js/void-button.js @@ -0,0 +1,53 @@ +import { + hide, + show, +} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; + +document.addEventListener( 'DOMContentLoaded', function () { + const refundButton = document.querySelector( 'button.refund-items' ); + if ( ! refundButton ) { + return; + } + + refundButton.insertAdjacentHTML( + 'afterend', + `` + ); + + hide( refundButton ); + + const voidButton = document.querySelector( '#pcpVoid' ); + + voidButton.addEventListener( 'click', async () => { + if ( ! window.confirm( PcpVoidButton.popup_text ) ) { + return; + } + + voidButton.setAttribute( 'disabled', 'disabled' ); + + const res = await fetch( PcpVoidButton.ajax.void.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify( { + nonce: PcpVoidButton.ajax.void.nonce, + wc_order_id: PcpVoidButton.wc_order_id, + } ), + } ); + + const data = await res.json(); + + if ( ! data.success ) { + hide( voidButton ); + show( refundButton ); + + alert( PcpVoidButton.error_text ); + + throw Error( data.data.message ); + } + + location.reload(); + } ); +} ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 9ed1a83a8..da397640d 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -25,8 +25,10 @@ use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction; +use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint; +use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater; use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactory; @@ -46,7 +48,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet; -use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSessionId; use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; @@ -514,12 +515,20 @@ return array( ); }, 'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor { - $order_endpoint = $container->get( 'api.endpoint.order' ); - $payments_endpoint = $container->get( 'api.endpoint.payments' ); - $refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' ); - $prefix = $container->get( 'api.prefix' ); - $logger = $container->get( 'woocommerce.logger.woocommerce' ); - return new RefundProcessor( $order_endpoint, $payments_endpoint, $refund_fees_updater, $prefix, $logger ); + return new RefundProcessor( + $container->get( 'api.endpoint.order' ), + $container->get( 'api.endpoint.payments' ), + $container->get( 'wcgateway.helper.refund-fees-updater' ), + $container->get( 'wcgateway.allowed_refund_payment_methods' ), + $container->get( 'api.prefix' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, + 'wcgateway.allowed_refund_payment_methods' => static function ( ContainerInterface $container ): array { + return apply_filters( + 'woocommerce_paypal_payments_allowed_refund_payment_methods', + array( PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID, PayUponInvoiceGateway::ID ) + ); }, 'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor { $order_endpoint = $container->get( 'api.endpoint.order' ); @@ -1914,4 +1923,22 @@ return array( return $simple_redirect_tasks; }, + + 'wcgateway.void-button.assets' => function( ContainerInterface $container ) : VoidButtonAssets { + return new VoidButtonAssets( + $container->get( 'wcgateway.url' ), + $container->get( 'ppcp.asset-version' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'wcgateway.allowed_refund_payment_methods' ) + ); + }, + 'wcgateway.void-button.endpoint' => function( ContainerInterface $container ) : VoidOrderEndpoint { + return new VoidOrderEndpoint( + $container->get( 'button.request-data' ), + $container->get( 'api.endpoint.order' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, ); diff --git a/modules/ppcp-wc-gateway/src/Assets/VoidButtonAssets.php b/modules/ppcp-wc-gateway/src/Assets/VoidButtonAssets.php new file mode 100644 index 000000000..9a2435449 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Assets/VoidButtonAssets.php @@ -0,0 +1,171 @@ +module_url = $module_url; + $this->version = $version; + $this->order_endpoint = $order_endpoint; + $this->refund_processor = $refund_processor; + $this->allowed_refund_payment_methods = $allowed_refund_payment_methods; + } + + /** + * Checks if should register assets on the current page. + */ + public function should_register(): bool { + if ( ! is_admin() || wp_doing_ajax() ) { + return false; + } + + global $theorder; + + if ( ! $theorder instanceof WC_Order ) { + return false; + } + + $current_screen = get_current_screen(); + if ( ! $current_screen instanceof WP_Screen ) { + return false; + } + if ( $current_screen->post_type !== 'shop_order' ) { + return false; + } + + if ( ! in_array( $theorder->get_payment_method(), $this->allowed_refund_payment_methods, true ) ) { + return false; + } + + // Skip if there are refunds already, it is probably not voidable anymore + void cannot be partial. + if ( $theorder->get_remaining_refund_amount() !== $theorder->get_total() ) { + return false; + } + + $order_id = $theorder->get_meta( PayPalGateway::ORDER_ID_META_KEY ); + if ( ! $order_id ) { + return false; + } + + try { + $order = $this->order_endpoint->order( $order_id ); + + if ( $this->refund_processor->determine_refund_mode( $order ) !== RefundProcessor::REFUND_MODE_VOID ) { + return false; + } + } catch ( Exception $exception ) { + return false; + } + + return true; + } + + /** + * Enqueues the assets. + */ + public function register(): void { + global $theorder; + assert( $theorder instanceof WC_Order ); + + wp_enqueue_script( + 'ppcp-void-button', + trailingslashit( $this->module_url ) . 'assets/js/void-button.js', + array(), + $this->version, + true + ); + + wp_localize_script( + 'ppcp-void-button', + 'PcpVoidButton', + array( + 'button_text' => __( 'Void authorization', 'woocommerce-paypal-payments' ), + 'popup_text' => __( + 'After voiding an authorized transaction, you cannot capture any funds associated with that transaction, and the funds are returned to the customer. Voiding an authorization cancels the entire open amount.', + 'woocommerce-paypal-payments' + ), + 'error_text' => __( + 'The operation failed. Use the Refund button if the funds were already captured.', + 'woocommerce-paypal-payments' + ), + 'wc_order_id' => $theorder->get_id(), + 'ajax' => array( + 'void' => array( + 'endpoint' => WC_AJAX::get_endpoint( VoidOrderEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( VoidOrderEndpoint::nonce() ), + ), + ), + ), + ); + } +} diff --git a/modules/ppcp-wc-gateway/src/Endpoint/VoidOrderEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/VoidOrderEndpoint.php new file mode 100644 index 000000000..c929044da --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Endpoint/VoidOrderEndpoint.php @@ -0,0 +1,198 @@ +request_data = $request_data; + $this->order_endpoint = $order_endpoint; + $this->refund_processor = $refund_processor; + $this->logger = $logger; + } + + /** + * Returns the nonce. + */ + public static function nonce(): string { + return self::ENDPOINT; + } + + /** + * Handles the incoming request. + */ + public function handle_request(): void { + $request = $this->request_data->read_request( self::nonce() ); + + if ( ! current_user_can( 'manage_woocommerce' ) ) { + wp_send_json_error( + array( + 'message' => 'Invalid request.', + ) + ); + return; + } + + $wc_order_id = (int) $request['wc_order_id']; + + $wc_order = wc_get_order( $wc_order_id ); + if ( ! $wc_order instanceof WC_Order ) { + wp_send_json_error( + array( + 'message' => 'WC order not found.', + ) + ); + return; + } + $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); + if ( ! $order_id ) { + wp_send_json_error( + array( + 'message' => 'PayPal order ID not found in meta.', + ) + ); + return; + } + + try { + $order = $this->order_endpoint->order( $order_id ); + + $this->refund_processor->void( $order ); + + $this->make_refunded( $wc_order ); + } catch ( Exception $exception ) { + wp_send_json_error( + array( + 'message' => 'Void failed. ' . $exception->getMessage(), + ) + ); + $this->logger->error( 'Void failed. ' . $exception->getMessage() ); + return; + } + + wp_send_json_success(); + } + + /** + * Returns the list of items for the wc_create_refund data, + * making all items refunded (max qty, total, taxes). + * + * @param WC_Order $wc_order The WC order. + */ + protected function refund_items( WC_Order $wc_order ): array { + $refunded_items = array(); + foreach ( $wc_order->get_items( array( 'line_item', 'fee', 'shipping' ) ) as $item ) { + // Some methods like get_taxes() are not defined in WC_Order_Item. + if ( + ! $item instanceof WC_Order_Item_Product + && ! $item instanceof WC_Order_Item_Fee + && ! $item instanceof WC_Order_Item_Shipping + ) { + continue; + } + + $taxes = array(); + $item_taxes = $item->get_taxes(); + /** + * The type is not really guaranteed in the code. + * + * @psalm-suppress RedundantConditionGivenDocblockType + */ + if ( is_array( $item_taxes ) && isset( $item_taxes['total'] ) ) { + $taxes = $item_taxes['total']; + } + + $refunded_items[ $item->get_id() ] = array( + 'qty' => $item->get_type() === 'line_item' ? $item->get_quantity() : 0, + 'refund_total' => $item->get_total(), + 'refund_tax' => $taxes, + ); + } + return $refunded_items; + } + + /** + * Creates a full refund. + * + * @param WC_Order $wc_order The WC order. + */ + private function make_refunded( WC_Order $wc_order ): void { + wc_create_refund( + array( + 'amount' => $wc_order->get_total(), + 'reason' => __( 'Voided authorization', 'woocommerce-paypal-payments' ), + 'order_id' => $wc_order->get_id(), + 'line_items' => $this->refund_items( $wc_order ), + 'refund_payment' => false, + 'restock_items' => (bool) apply_filters( 'woocommerce_paypal_payments_void_restock_items', true ), + ) + ); + + $wc_order->set_status( 'refunded' ); + } +} + diff --git a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php index 25e4c5eed..523fcf00e 100644 --- a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php @@ -33,9 +33,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater; class RefundProcessor { use RefundMetaTrait; - private const REFUND_MODE_REFUND = 'refund'; - private const REFUND_MODE_VOID = 'void'; - private const REFUND_MODE_UNKNOWN = 'unknown'; + public const REFUND_MODE_REFUND = 'refund'; + public const REFUND_MODE_VOID = 'void'; + public const REFUND_MODE_UNKNOWN = 'unknown'; /** * The order endpoint. @@ -72,12 +72,20 @@ class RefundProcessor { */ private $refund_fees_updater; + /** + * The methods that can be refunded. + * + * @var array + */ + private $allowed_refund_payment_methods; + /** * RefundProcessor constructor. * * @param OrderEndpoint $order_endpoint The order endpoint. * @param PaymentsEndpoint $payments_endpoint The payments endpoint. * @param RefundFeesUpdater $refund_fees_updater The refund fees updater. + * @param array $allowed_refund_payment_methods The methods that can be refunded. * @param string $prefix The prefix. * @param LoggerInterface $logger The logger. */ @@ -85,15 +93,17 @@ class RefundProcessor { OrderEndpoint $order_endpoint, PaymentsEndpoint $payments_endpoint, RefundFeesUpdater $refund_fees_updater, + array $allowed_refund_payment_methods, string $prefix, LoggerInterface $logger ) { - $this->order_endpoint = $order_endpoint; - $this->payments_endpoint = $payments_endpoint; - $this->refund_fees_updater = $refund_fees_updater; - $this->prefix = $prefix; - $this->logger = $logger; + $this->order_endpoint = $order_endpoint; + $this->payments_endpoint = $payments_endpoint; + $this->refund_fees_updater = $refund_fees_updater; + $this->allowed_refund_payment_methods = $allowed_refund_payment_methods; + $this->prefix = $prefix; + $this->logger = $logger; } /** @@ -109,11 +119,7 @@ class RefundProcessor { */ public function process( WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool { try { - $allowed_refund_payment_methods = apply_filters( - 'woocommerce_paypal_payments_allowed_refund_payment_methods', - array( PayPalGateway::ID, CreditCardGateway::ID, CardButtonGateway::ID, PayUponInvoiceGateway::ID ) - ); - if ( ! in_array( $wc_order->get_payment_method(), $allowed_refund_payment_methods, true ) ) { + if ( ! in_array( $wc_order->get_payment_method(), $this->allowed_refund_payment_methods, true ) ) { return true; } @@ -134,7 +140,7 @@ class RefundProcessor { ) ); - $mode = $this->determine_refund_mode( $payments ); + $mode = $this->determine_refund_mode( $order ); switch ( $mode ) { case self::REFUND_MODE_REFUND: @@ -226,11 +232,13 @@ class RefundProcessor { /** * Determines the refunding mode. * - * @param Payments $payments The order payments state. + * @param Order $order The order. * * @return string One of the REFUND_MODE_ constants. */ - private function determine_refund_mode( Payments $payments ): string { + public function determine_refund_mode( Order $order ): string { + $payments = $this->get_payments( $order ); + $authorizations = $payments->authorizations(); if ( $authorizations ) { foreach ( $authorizations as $authorization ) { diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 2471af4e7..25f36d23e 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -21,7 +21,9 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; +use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint; +use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait; use WC_Order; use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; @@ -90,6 +92,7 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul $this->register_columns( $c ); $this->register_checkout_paypal_address_preset( $c ); $this->register_wc_tasks( $c ); + $this->register_void_button( $c ); add_action( 'woocommerce_sections_checkout', @@ -870,4 +873,33 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul }, ); } + + /** + * Registers the assets and ajax endpoint for the void button. + * + * @param ContainerInterface $container The container. + */ + protected function register_void_button( ContainerInterface $container ): void { + add_action( + 'admin_enqueue_scripts', + static function () use ( $container ) { + $assets = $container->get( 'wcgateway.void-button.assets' ); + assert( $assets instanceof VoidButtonAssets ); + + if ( $assets->should_register() ) { + $assets->register(); + } + } + ); + + add_action( + 'wc_ajax_' . VoidOrderEndpoint::ENDPOINT, + static function () use ( $container ) { + $endpoint = $container->get( 'wcgateway.void-button.endpoint' ); + assert( $endpoint instanceof VoidOrderEndpoint ); + + $endpoint->handle_request(); + } + ); + } } diff --git a/modules/ppcp-wc-gateway/webpack.config.js b/modules/ppcp-wc-gateway/webpack.config.js index 394e549fe..196b8b2b1 100644 --- a/modules/ppcp-wc-gateway/webpack.config.js +++ b/modules/ppcp-wc-gateway/webpack.config.js @@ -10,6 +10,7 @@ module.exports = { 'gateway-settings': path.resolve('./resources/js/gateway-settings.js'), 'fraudnet': path.resolve('./resources/js/fraudnet.js'), 'oxxo': path.resolve('./resources/js/oxxo.js'), + 'void-button': path.resolve('./resources/js/void-button.js'), 'gateway-settings-style': path.resolve('./resources/css/gateway-settings.scss'), 'common-style': path.resolve('./resources/css/common.scss'), }, From 0611098b9280c5841fd3ba1f948d2cea75c3cf24 Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Wed, 9 Oct 2024 09:36:29 +0200 Subject: [PATCH 051/271] Place all task registration logic inside the init action --- .../ppcp-wc-gateway/src/WCGatewayModule.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 2471af4e7..583b02914 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -848,21 +848,19 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul * @return void */ protected function register_wc_tasks( ContainerInterface $container ): void { - $simple_redirect_tasks = $container->get( 'wcgateway.settings.wc-tasks.simple-redirect-tasks' ); - if ( empty( $simple_redirect_tasks ) ) { - return; - } - - $task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' ); - assert( $task_registrar instanceof TaskRegistrarInterface ); - - $logger = $container->get( 'woocommerce.logger.woocommerce' ); - assert( $logger instanceof LoggerInterface ); - add_action( 'init', - static function () use ( $simple_redirect_tasks, $task_registrar, $logger ): void { + static function () use ( $container ): void { + $logger = $container->get( 'woocommerce.logger.woocommerce' ); + assert( $logger instanceof LoggerInterface ); try { + $simple_redirect_tasks = $container->get( 'wcgateway.settings.wc-tasks.simple-redirect-tasks' ); + if ( empty( $simple_redirect_tasks ) ) { + return; + } + $task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' ); + assert( $task_registrar instanceof TaskRegistrarInterface ); + $task_registrar->register( $simple_redirect_tasks ); } catch ( Exception $exception ) { $logger->error( "Failed to create a task in the 'Things to do next' section of WC. " . $exception->getMessage() ); From e95321cf82db67461f6894686624c63021016e08 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:04:21 +0200 Subject: [PATCH 052/271] =?UTF-8?q?=F0=9F=90=9B=20Fix=20incorrect=20valida?= =?UTF-8?q?tion=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 2 +- .../ppcp-button/resources/js/modules/Renderer/PaymentButton.js | 1 + modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 89634747a..ef94197e0 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -220,7 +220,7 @@ class ApplePayButton extends PaymentButton { registerValidationRules( invalidIf, validIf ) { invalidIf( () => - [ 'TEST', 'PRODUCTION' ].includes( + ! [ 'TEST', 'PRODUCTION' ].includes( this.buttonConfig.environment ), `Invalid environment: ${ this.buttonConfig.environment }` diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 35d938b34..aa6b80301 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -683,6 +683,7 @@ export default class PaymentButton { * @param {(condition: () => boolean, errorMessage: string) => void} invalidIf - Registers a validation step that fails if the condition returns true. * @param {(condition: () => boolean) => void} validIf - Registers a validation step that passes if the condition returns true. */ + // eslint-disable-next-line no-unused-vars registerValidationRules( invalidIf, validIf ) {} /** diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 0cd2bab70..83416b149 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -218,7 +218,7 @@ class GooglepayButton extends PaymentButton { registerValidationRules( invalidIf, validIf ) { invalidIf( () => - [ 'TEST', 'PRODUCTION' ].includes( + ! [ 'TEST', 'PRODUCTION' ].includes( this.buttonConfig.environment ), `Invalid environment: ${ this.buttonConfig.environment }` From 90542293d875958b7b69a29a8e76ece3a5697b43 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:04:43 +0200 Subject: [PATCH 053/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20duplicate=20get?= =?UTF-8?q?ters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index ef94197e0..9476e53d5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -189,31 +189,6 @@ class ApplePayButton extends PaymentButton { return input?.value || this.buttonConfig.nonce; } - /** - * 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 ) - ); - } - - /** - * 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; - } - /** * @inheritDoc */ From d1607e3ebce5953996614a295f1714fae2da7a89 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:07:54 +0200 Subject: [PATCH 054/271] =?UTF-8?q?=F0=9F=94=A5=20Remove=20incorrect=20val?= =?UTF-8?q?idation=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 9476e53d5..4f6531936 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -193,14 +193,6 @@ class ApplePayButton extends PaymentButton { * @inheritDoc */ registerValidationRules( invalidIf, validIf ) { - invalidIf( - () => - ! [ 'TEST', 'PRODUCTION' ].includes( - this.buttonConfig.environment - ), - `Invalid environment: ${ this.buttonConfig.environment }` - ); - validIf( () => this.isPreview ); invalidIf( From 8aa4d47b68993574c71b299aee589bb945e4eb9f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:30:08 +0200 Subject: [PATCH 055/271] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Start=20ApplePaySe?= =?UTF-8?q?ssion=20after=20handlers=20were=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple Pay SDK docs hint that “begin()” must be called after setting up the event handlers. --- .../ppcp-applepay/resources/js/ApplepayButton.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4f6531936..d03e21b5f 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -319,14 +319,13 @@ class ApplePayButton extends PaymentButton { } /** - * Starts an Apple Pay session. + * Starts an Apple Pay session, which means that the user interacted with the Apple Pay button. * * @param {Object} paymentRequest The payment request object. */ applePaySession( paymentRequest ) { this.log( 'applePaySession', paymentRequest ); const session = new ApplePaySession( 4, paymentRequest ); - session.begin(); if ( this.requiresShipping ) { session.onshippingmethodselected = @@ -337,6 +336,16 @@ class ApplePayButton extends PaymentButton { session.onvalidatemerchant = this.onValidateMerchant( session ); session.onpaymentauthorized = this.onPaymentAuthorized( session ); + + /** + * This starts the merchant validation process and displays the payment sheet + * {@see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778001-begin} + * + * After calling the `begin` method, the browser invokes your `onvalidatemerchant` handler + * {@see https://applepaydemo.apple.com/apple-pay-js-api} + */ + session.begin(); + return session; } From ca93e8e56dfefd564a2c586d17bf6f2142b51ed7 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:31:36 +0200 Subject: [PATCH 056/271] =?UTF-8?q?=F0=9F=9A=A7=20Remove=20more=20legacy?= =?UTF-8?q?=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d03e21b5f..39dadd9fa 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -2,8 +2,6 @@ /* global PayPalCommerceGateway */ import { createAppleErrors } from './Helper/applePayError'; -import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import FormValidator from '../../../ppcp-button/resources/js/modules/Helper/FormValidator'; import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; @@ -234,7 +232,6 @@ class ApplePayButton extends PaymentButton { } super.init(); - this.initEventHandlers(); if ( this.isSeparateGateway ) { document @@ -283,41 +280,6 @@ class ApplePayButton extends PaymentButton { this.transactionInfo = await this.contextHandler.transactionInfo(); } - initEventHandlers() { - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapperId = `#${ this.wrapperId }`; - - if ( wrapperId === ppcpButtonWrapper ) { - throw new Error( - `[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapperId }"` - ); - } - - const syncButtonVisibility = () => { - if ( ! this.isEligible ) { - return; - } - - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapperId, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapperId, - ! $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(); - } - /** * Starts an Apple Pay session, which means that the user interacted with the Apple Pay button. * From 74e806535e3d2bea821db082c410ace699276bdb Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:35:53 +0200 Subject: [PATCH 057/271] =?UTF-8?q?=F0=9F=9A=A7=20Clean=20up=20the=20init?= =?UTF-8?q?=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 39dadd9fa..1e67e32b8 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -233,24 +233,6 @@ class ApplePayButton extends PaymentButton { super.init(); - if ( this.isSeparateGateway ) { - document - .querySelectorAll( '#ppc-button-applepay-container' ) - .forEach( ( el ) => el.remove() ); - } - - 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( () => { const button = this.addButton(); if ( ! button ) { @@ -261,8 +243,6 @@ class ApplePayButton extends PaymentButton { evt.preventDefault(); this.onButtonClick(); } ); - } ); - } } reinit() { @@ -276,10 +256,6 @@ class ApplePayButton extends PaymentButton { this.init(); } - async fetchTransactionInfo() { - this.transactionInfo = await this.contextHandler.transactionInfo(); - } - /** * Starts an Apple Pay session, which means that the user interacted with the Apple Pay button. * From a4b3f2a079b08e023063656ab92afe0263fc8c2e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 14:39:13 +0200 Subject: [PATCH 058/271] =?UTF-8?q?=F0=9F=9A=A7=20Migrate=20eligibility=20?= =?UTF-8?q?check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 1e67e32b8..630be7420 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -232,6 +232,7 @@ class ApplePayButton extends PaymentButton { } super.init(); + this.checkEligibility(); const button = this.addButton(); @@ -256,6 +257,27 @@ class ApplePayButton extends PaymentButton { this.init(); } + /** + * Re-check if the current session is eligible for Apple Pay. + */ + checkEligibility() { + if ( this.isPreview ) { + this.isEligible = true; + return; + } + + try { + if ( ! window.ApplePaySession?.canMakePayments() ) { + this.isEligible = false; + return; + } + + this.isEligible = !! this.#applePayConfig.isEligible; + } catch ( error ) { + this.isEligible = false; + } + } + /** * Starts an Apple Pay session, which means that the user interacted with the Apple Pay button. * From e0f0ec973bdd2b5cba870319b64159d369babb36 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 15:02:14 +0200 Subject: [PATCH 059/271] =?UTF-8?q?=F0=9F=9A=A7=20Migrate=20the=20addButto?= =?UTF-8?q?n=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 62 ++++++++----------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 630be7420..611718a10 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -233,17 +233,6 @@ class ApplePayButton extends PaymentButton { super.init(); this.checkEligibility(); - - const button = this.addButton(); - - if ( ! button ) { - return; - } - - button.addEventListener( 'click', ( evt ) => { - evt.preventDefault(); - this.onButtonClick(); - } ); } reinit() { @@ -310,40 +299,39 @@ class ApplePayButton extends PaymentButton { } /** - * Adds an Apple Pay purchase button. - * - * @return {HTMLElement|null} The newly created `` element. Null on failure. + * Applies CSS classes and inline styling to the payment button wrapper. */ - addButton() { - this.log( 'addButton' ); + applyWrapperStyles() { + super.applyWrapperStyles(); - const wrapper = this.wrapperElement; - const style = this.buttonStyle; - const id = 'apple-' + this.wrapperId; + const { height } = this.style; - if ( ! wrapper ) { - return null; - } + if ( height ) { + const wrapper = this.wrapperElement; - const ppcpStyle = this.ppcpStyle; - - wrapper.innerHTML = ``; - wrapper.classList.remove( 'ppcp-button-rect', 'ppcp-button-pill' ); - wrapper.classList.add( - `ppcp-button-${ ppcpStyle.shape }`, - 'ppcp-button-apm', - 'ppcp-button-applepay' - ); - - if ( ppcpStyle.height ) { wrapper.style.setProperty( '--apple-pay-button-height', - `${ ppcpStyle.height }px` + `${ height }px` ); - wrapper.style.height = `${ ppcpStyle.height }px`; - } - return wrapper.querySelector( 'apple-pay-button' ); + wrapper.style.height = `${ height }px`; + } + } + + /** + * Creates the payment button and calls `this.insertButton()` to make the button visible in the + * correct wrapper. + */ + addButton() { + const { color, type, language } = this.style; + + const button = document.createElement( 'apple-pay-button' ); + button.id = 'apple-' + this.wrapperId; + button.setAttribute( 'buttonstyle', color ); + button.setAttribute( 'type', type ); + button.setAttribute( 'locale', language ); + + this.insertButton( button ); } //------------------------ From b6111e1774092b7e63d0584fa7af9587ddf24959 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 9 Oct 2024 15:04:03 +0200 Subject: [PATCH 060/271] =?UTF-8?q?=F0=9F=90=9B=20Add=20missing=20event=20?= =?UTF-8?q?handler=20to=20payment=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 611718a10..7ed20b9cf 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -331,6 +331,11 @@ class ApplePayButton extends PaymentButton { button.setAttribute( 'type', type ); button.setAttribute( 'locale', language ); + button.addEventListener( 'click', ( evt ) => { + evt.preventDefault(); + this.onButtonClick(); + } ); + this.insertButton( button ); } From 7c111906ab400affa81ee1fa767002f5cbaa6df2 Mon Sep 17 00:00:00 2001 From: inpsyde-maticluznar Date: Thu, 10 Oct 2024 07:22:22 +0200 Subject: [PATCH 061/271] Position the card icons beside the label instead of beneath the fields on the checkout form. --- .../resources/js/Components/card-fields.js | 6 --- .../js/advanced-card-checkout-block.js | 45 ++++++++++++------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/Components/card-fields.js b/modules/ppcp-blocks/resources/js/Components/card-fields.js index 2726bc12e..05d41b315 100644 --- a/modules/ppcp-blocks/resources/js/Components/card-fields.js +++ b/modules/ppcp-blocks/resources/js/Components/card-fields.js @@ -19,11 +19,9 @@ export function CardFields( { config, eventRegistration, emitResponse, - components, } ) { const { onPaymentSetup } = eventRegistration; const { responseTypes } = emitResponse; - const { PaymentMethodIcons } = components; const [ cardFieldsForm, setCardFieldsForm ] = useState(); const getCardFieldsForm = ( cardFieldsForm ) => { @@ -95,10 +93,6 @@ export function CardFields( { } } > - , - content: , - edit: , - ariaLabel: config.title, - canMakePayment: () => { - return true; - }, - supports: { - showSavedCards: true, - features: config.supports, - }, -} ); +const Label = ({components, config}) => { + const {PaymentMethodIcons} = components; + return <> + + + +} + +registerPaymentMethod({ + name: config.id, + label: