From 08d8cded5b8360565c066811f31146047dc47730 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 21 Sep 2023 11:58:51 +0100 Subject: [PATCH 01/45] Add GooglePlay preview in admin settings. --- .../js/modules/Renderer/WidgetBuilder.js | 4 +- .../js/Context/ContextHandlerFactory.js | 3 + .../resources/js/Context/PreviewHandler.js | 31 ++++++ .../resources/js/GooglepayButton.js | 4 +- .../ppcp-googlepay/resources/js/boot-admin.js | 98 +++++++++++++++++++ modules/ppcp-googlepay/src/Assets/Button.php | 28 ++++++ .../ppcp-googlepay/src/GooglepayModule.php | 25 +++++ modules/ppcp-googlepay/webpack.config.js | 1 + .../resources/js/gateway-settings.js | 3 +- .../src/Assets/SettingsPageAssets.php | 24 +++-- 10 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js create mode 100644 modules/ppcp-googlepay/resources/js/boot-admin.js diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js index 07d7c057c..75268cb9a 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js @@ -27,6 +27,7 @@ class WidgetBuilder { setPaypal(paypal) { this.paypal = paypal; + jQuery(document).trigger('ppcp-paypal-loaded', paypal); } registerButtons(wrapper, options) { @@ -174,4 +175,5 @@ class WidgetBuilder { } } -export default new WidgetBuilder(); +window.widgetBuilder = window.widgetBuilder || new WidgetBuilder(); +export default window.widgetBuilder; diff --git a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js index 4d2db4260..445ab08ae 100644 --- a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js @@ -4,6 +4,7 @@ import CheckoutHandler from "./CheckoutHandler"; import CartBlockHandler from "./CartBlockHandler"; import CheckoutBlockHandler from "./CheckoutBlockHandler"; import MiniCartHandler from "./MiniCartHandler"; +import PreviewHandler from "./PreviewHandler"; class ContextHandlerFactory { @@ -22,6 +23,8 @@ class ContextHandlerFactory { return new CartBlockHandler(buttonConfig, ppcpConfig, externalActionHandler); case 'checkout-block': return new CheckoutBlockHandler(buttonConfig, ppcpConfig, externalActionHandler); + case 'preview': + return new PreviewHandler(buttonConfig, ppcpConfig, externalActionHandler); } } } diff --git a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js new file mode 100644 index 000000000..a637f078d --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js @@ -0,0 +1,31 @@ +import BaseHandler from "./BaseHandler"; + +class CartHandler extends BaseHandler { + + constructor(buttonConfig, ppcpConfig, externalHandler) { + super(buttonConfig, ppcpConfig, externalHandler); + } + + transactionInfo() { + throw new Error('Transaction info fail. This is just a preview.'); + } + + createOrder() { + throw new Error('Create order fail. This is just a preview.'); + } + + approveOrder(data, actions) { + throw new Error('Approve order fail. This is just a preview.'); + } + + actionHandler() { + throw new Error('Action handler fail. This is just a preview.'); + } + + errorHandler() { + throw new Error('Error handler fail. This is just a preview.'); + } + +} + +export default CartHandler; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 6cd97302f..ef601d82d 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,6 +1,7 @@ import ContextHandlerFactory from "./Context/ContextHandlerFactory"; import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; +import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; class GooglepayButton { @@ -152,6 +153,7 @@ class GooglepayButton { buttonLocale: buttonStyle.language || 'en', buttonSizeMode: 'fill', }); + jQuery(wrapper).append(button); } @@ -207,7 +209,7 @@ class GooglepayButton { console.log('[GooglePayButton] processPayment: createOrder', id, this.context); - const confirmOrderResponse = await paypal.Googlepay().confirmOrder({ + const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({ orderId: id, paymentMethodData: paymentData.paymentMethodData }); diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js new file mode 100644 index 000000000..829eb0a2c --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -0,0 +1,98 @@ +import {loadCustomScript} from "@paypal/paypal-js"; +import GooglepayButton from "./GooglepayButton"; +import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; + +(function ({ + buttonConfig, + jQuery +}) { + + let googlePayConfig; + let buttonQueue = []; + let bootstrapped = false; + + jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => { + if (bootstrapped) { + createButton(ppcpConfig); + } else { + buttonQueue.push({ + ppcpConfig: JSON.parse(JSON.stringify(ppcpConfig)) + }); + } + }); + + const createButton = function (ppcpConfig) { + const selector = ppcpConfig.button.wrapper + 'GooglePay'; + + buttonConfig = JSON.parse(JSON.stringify(buttonConfig)); + buttonConfig.button.wrapper = selector; + + const wrapperElement = `
`; + + if (!jQuery(selector).length) { + jQuery(ppcpConfig.button.wrapper).after(wrapperElement); + } else { + jQuery(selector).replaceWith(wrapperElement); + } + + const button = new GooglepayButton( + 'preview', + null, + buttonConfig, + ppcpConfig, + ); + + button.init(googlePayConfig); + } + + const bootstrap = async function () { + googlePayConfig = await widgetBuilder.paypal.Googlepay().config(); + + let options; + while (options = buttonQueue.pop()) { + createButton(options.ppcpConfig); + } + }; + + document.addEventListener( + 'DOMContentLoaded', + () => { + + if (typeof (buttonConfig) === 'undefined') { + console.error('PayPal button could not be configured.'); + return; + } + + let paypalLoaded = false; + let googlePayLoaded = false; + + const tryToBoot = () => { + if (!bootstrapped && paypalLoaded && googlePayLoaded) { + bootstrapped = true; + bootstrap(); + } + } + + // Load GooglePay SDK + loadCustomScript({ url: buttonConfig.sdk_url }).then(() => { + googlePayLoaded = true; + tryToBoot(); + }); + + // Wait for PayPal to be loaded externally + if (typeof widgetBuilder.paypal !== 'undefined') { + paypalLoaded = true; + tryToBoot(); + } + + jQuery(document).on('ppcp-paypal-loaded', () => { + paypalLoaded = true; + tryToBoot(); + }); + }, + ); + +})({ + buttonConfig: window.wc_ppcp_googlepay_admin, + jQuery: window.jQuery +}); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 48848af56..fadb7b89e 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -358,6 +358,34 @@ class Button implements ButtonInterface { ); } + /** + * Enqueues scripts/styles for admin. + */ + public function enqueue_admin(): void { + wp_register_style( + 'wc-ppcp-googlepay-admin', + untrailingslashit( $this->module_url ) . '/assets/css/styles.css', + array(), + $this->version + ); + wp_enqueue_style( 'wc-ppcp-googlepay-admin' ); + + wp_register_script( + 'wc-ppcp-googlepay-admin', + untrailingslashit( $this->module_url ) . '/assets/js/boot-admin.js', + array(), + $this->version, + true + ); + wp_enqueue_script( 'wc-ppcp-googlepay-admin' ); + + wp_localize_script( + 'wc-ppcp-googlepay-admin', + 'wc_ppcp_googlepay_admin', + $this->script_data() + ); + } + /** * The configuration for the smart buttons. * diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 60485fcf1..b93d58552 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -66,6 +66,21 @@ class GooglepayModule implements ModuleInterface { } ); + add_action( + 'admin_enqueue_scripts', + static function () use ( $c, $button ) { + if ( ! is_admin() ) { + return; + } + /** + * Should add this to the ButtonInterface. + * + * @psalm-suppress UndefinedInterfaceMethod + */ + $button->enqueue_admin(); + } + ); + add_action( 'woocommerce_blocks_payment_method_type_registration', function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void { @@ -75,6 +90,16 @@ class GooglepayModule implements ModuleInterface { } ); + add_action( + 'woocommerce_paypal_payments_admin_gateway_settings', + function( array $settings ) use ( $c, $button ): array { + if ( is_array( $settings['components'] ) ) { + $settings['components'][] = 'googlepay'; + } + return $settings; + } + ); + // Clear product status handling. add_action( 'woocommerce_paypal_payments_clear_apm_product_status', diff --git a/modules/ppcp-googlepay/webpack.config.js b/modules/ppcp-googlepay/webpack.config.js index 2d14144fa..6805309e2 100644 --- a/modules/ppcp-googlepay/webpack.config.js +++ b/modules/ppcp-googlepay/webpack.config.js @@ -11,6 +11,7 @@ module.exports = { entry: { 'boot': path.resolve('./resources/js/boot.js'), 'boot-block': path.resolve('./resources/js/boot-block.js'), + 'boot-admin': path.resolve('./resources/js/boot-admin.js'), "styles": path.resolve('./resources/css/styles.scss') }, output: { diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index bbb455be9..b278839e3 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -88,6 +88,7 @@ document.addEventListener( try { renderer.render({}); + jQuery(document).trigger('ppcp_paypal_render_preview', settings); } catch (err) { console.error(err); } @@ -114,7 +115,7 @@ document.addEventListener( 'client-id': PayPalCommerceGatewaySettings.client_id, 'currency': PayPalCommerceGatewaySettings.currency, 'integration-date': PayPalCommerceGatewaySettings.integration_date, - 'components': ['buttons', 'funding-eligibility', 'messages'], + 'components': PayPalCommerceGatewaySettings.components, 'enable-funding': ['venmo', 'paylater'], }; diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php index 14c07e90f..cb2e10380 100644 --- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php +++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php @@ -211,16 +211,20 @@ class SettingsPageAssets { wp_localize_script( 'ppcp-gateway-settings', 'PayPalCommerceGatewaySettings', - array( - 'is_subscriptions_plugin_active' => $this->subscription_helper->plugin_is_active(), - 'client_id' => $this->client_id, - 'currency' => $this->currency, - 'country' => $this->country, - 'environment' => $this->environment->current_environment(), - 'integration_date' => PAYPAL_INTEGRATION_DATE, - 'is_pay_later_button_enabled' => $this->is_pay_later_button_enabled, - 'disabled_sources' => $this->disabled_sources, - 'all_funding_sources' => $this->all_funding_sources, + apply_filters( + 'woocommerce_paypal_payments_admin_gateway_settings', + array( + 'is_subscriptions_plugin_active' => $this->subscription_helper->plugin_is_active(), + 'client_id' => $this->client_id, + 'currency' => $this->currency, + 'country' => $this->country, + 'environment' => $this->environment->current_environment(), + 'integration_date' => PAYPAL_INTEGRATION_DATE, + 'is_pay_later_button_enabled' => $this->is_pay_later_button_enabled, + 'disabled_sources' => $this->disabled_sources, + 'all_funding_sources' => $this->all_funding_sources, + 'components' => array( 'buttons', 'funding-eligibility', 'messages' ), + ) ) ); } From fd156024d24375a4dc63bbd1eb9e8558b78d99f0 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 21 Sep 2023 15:46:20 +0100 Subject: [PATCH 02/45] Add GooglePay button preview reactiveness to settings. --- .../ppcp-googlepay/resources/js/boot-admin.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 829eb0a2c..c952b7b3b 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -9,8 +9,10 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi let googlePayConfig; let buttonQueue = []; + let activeButtons = {}; let bootstrapped = false; + // React to PayPal config changes. jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => { if (bootstrapped) { createButton(ppcpConfig); @@ -21,11 +23,38 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi } }); + // React to GooglePay config changes. + jQuery([ + '#ppcp-googlepay_button_enabled', + '#ppcp-googlepay_button_type', + '#ppcp-googlepay_button_color', + '#ppcp-googlepay_button_language', + '#ppcp-googlepay_button_shipping_enabled' + ].join(',')).on('change', () => { + for (const [selector, ppcpConfig] of Object.entries(activeButtons)) { + createButton(ppcpConfig); + } + }); + + const applyConfigOptions = function (buttonConfig) { + buttonConfig.button = buttonConfig.button || {}; + buttonConfig.button.style = buttonConfig.button.style || {}; + buttonConfig.button.style.type = jQuery('#ppcp-googlepay_button_type').val(); + buttonConfig.button.style.color = jQuery('#ppcp-googlepay_button_color').val(); + buttonConfig.button.style.language = jQuery('#ppcp-googlepay_button_language').val(); + } + const createButton = function (ppcpConfig) { const selector = ppcpConfig.button.wrapper + 'GooglePay'; + if (!jQuery('#ppcp-googlepay_button_enabled').is(':checked')) { + jQuery(selector).remove(); + return; + } + buttonConfig = JSON.parse(JSON.stringify(buttonConfig)); buttonConfig.button.wrapper = selector; + applyConfigOptions(buttonConfig); const wrapperElement = `
`; @@ -43,6 +72,8 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi ); button.init(googlePayConfig); + + activeButtons[selector] = ppcpConfig; } const bootstrap = async function () { From dc77deea79b493b09d31218e0de43f1c68538f56 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 21 Sep 2023 18:30:43 +0100 Subject: [PATCH 03/45] Add eligibility notice. --- modules/ppcp-googlepay/extensions.php | 11 ++++ modules/ppcp-googlepay/services.php | 19 +++--- .../ppcp-googlepay/src/GooglepayModule.php | 62 +++++++++++++++++++ .../src/Helper/ApmProductStatus.php | 19 +++++- 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 05a57284a..07e29d97b 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -24,6 +24,8 @@ return array( return $fields; } + $is_available = $container->get( 'googlepay.available' ); + $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); $index = array_search( $key, $keys, true ); @@ -66,6 +68,15 @@ return array( ->action_visible( 'googlepay_button_language' ) ->action_visible( 'googlepay_button_shipping_enabled' ) ->to_array(), + $display_manager + ->rule() + ->condition_is_true( $is_available ) + ->action_enable( 'googlepay_button_enabled' ) + ->action_enable( 'googlepay_button_type' ) + ->action_enable( 'googlepay_button_color' ) + ->action_enable( 'googlepay_button_language' ) + ->action_enable( 'googlepay_button_shipping_enabled' ) + ->to_array(), ) ), ), diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index a7944a952..aa9d3f677 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\Googlepay\Assets\BlocksPaymentMethod; use WooCommerce\PayPalCommerce\Googlepay\Assets\Button; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies; @@ -37,7 +38,7 @@ return array( // If GooglePay is configured. 'googlepay.available' => static function ( ContainerInterface $container ): bool { - if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', false ) ) { + if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) { $status = $container->get( 'googlepay.helpers.apm-product-status' ); assert( $status instanceof ApmProductStatus ); /** @@ -48,13 +49,15 @@ return array( return true; }, - 'googlepay.helpers.apm-product-status' => static function( ContainerInterface $container ): ApmProductStatus { - return new ApmProductStatus( - $container->get( 'wcgateway.settings' ), - $container->get( 'api.endpoint.partners' ), - $container->get( 'onboarding.state' ) - ); - }, + 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( + static function( ContainerInterface $container ): ApmProductStatus { + return new ApmProductStatus( + $container->get( 'wcgateway.settings' ), + $container->get( 'api.endpoint.partners' ), + $container->get( 'onboarding.state' ) + ); + } + ), /** * The matrix which countries and currency combinations can be used for GooglePay. diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index b93d58552..5855710ca 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; +use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; @@ -46,6 +48,66 @@ class GooglepayModule implements ModuleInterface { $button->initialize(); if ( ! $c->get( 'googlepay.available' ) ) { + + $apm_status = $c->get( 'googlepay.helpers.apm-product-status' ); + assert( $apm_status instanceof ApmProductStatus ); + + // TODO: refactor the notices. + if ( $apm_status->has_request_failure() ) { + + add_filter( + Repository::NOTICES_FILTER, + /** + * Adds seller status notice. + * + * @param array $notices The notices. + * @return array + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $notices ) use ( $c ): array { + + $message = sprintf( + __( + '

There was an error getting your PayPal seller status. Some features may be disabled.

Certify that you connected to your account via our onboarding process.

', + 'woocommerce-paypal-payments' + ) + ); + + // Name the key so it can be overridden. + $notices['error_product_status'] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' ); + return $notices; + } + ); + + } else { + + add_filter( + Repository::NOTICES_FILTER, + /** + * Adds GooglePay not available notice. + * + * @param array $notices The notices. + * @return array + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $notices ) use ( $c ): array { + + $message = sprintf( + __( + 'Google Pay is not available on your PayPal account.', + 'woocommerce-paypal-payments' + ) + ); + + $notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' ); + return $notices; + } + ); + + } + return; } diff --git a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php index b6046e0e5..307fa8c14 100644 --- a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php +++ b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php @@ -32,6 +32,13 @@ class ApmProductStatus { */ private $current_status = null; + /** + * If there was a request failure. + * + * @var bool + */ + private $has_request_failure = false; + /** * The settings. * @@ -93,7 +100,8 @@ class ApmProductStatus { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { // It may be a transitory error, don't persist the status. - $this->current_status = false; + $this->has_request_failure = true; + $this->current_status = false; return $this->current_status; } @@ -118,6 +126,15 @@ class ApmProductStatus { return $this->current_status; } + /** + * Returns if there was a request failure. + * + * @return bool + */ + public function has_request_failure(): bool { + return $this->has_request_failure; + } + /** * Clears the persisted result to force a recheck. * From b16e2571b123bd57e80694f2bd319965065307a2 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 22 Sep 2023 08:53:35 +0100 Subject: [PATCH 04/45] Refactor GooglePay availability notices. --- modules/ppcp-googlepay/services.php | 7 ++ .../ppcp-googlepay/src/GooglepayModule.php | 74 ++---------- .../src/Helper/AvailabilityNotice.php | 112 ++++++++++++++++++ 3 files changed, 132 insertions(+), 61 deletions(-) create mode 100644 modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index aa9d3f677..fafc41de4 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\Googlepay\Assets\BlocksPaymentMethod; use WooCommerce\PayPalCommerce\Googlepay\Assets\Button; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; +use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( @@ -49,6 +50,12 @@ return array( return true; }, + 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { + return new AvailabilityNotice( + $container->get( 'googlepay.helpers.apm-product-status' ) + ); + }, + 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( static function( ContainerInterface $container ): ApmProductStatus { return new ApmProductStatus( diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 5855710ca..eab601704 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; +use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -39,78 +40,25 @@ class GooglepayModule implements ModuleInterface { */ public function run( ContainerInterface $c ): void { + // Check if the module is applicable, correct country, currency, ... etc. if ( ! $c->get( 'googlepay.eligible' ) ) { return; } + // Load the button handler. $button = $c->get( 'googlepay.button' ); assert( $button instanceof ButtonInterface ); $button->initialize(); + // Check if this merchant can activate / use the buttons. if ( ! $c->get( 'googlepay.available' ) ) { - - $apm_status = $c->get( 'googlepay.helpers.apm-product-status' ); - assert( $apm_status instanceof ApmProductStatus ); - - // TODO: refactor the notices. - if ( $apm_status->has_request_failure() ) { - - add_filter( - Repository::NOTICES_FILTER, - /** - * Adds seller status notice. - * - * @param array $notices The notices. - * @return array - * - * @psalm-suppress MissingClosureParamType - */ - static function ( $notices ) use ( $c ): array { - - $message = sprintf( - __( - '

There was an error getting your PayPal seller status. Some features may be disabled.

Certify that you connected to your account via our onboarding process.

', - 'woocommerce-paypal-payments' - ) - ); - - // Name the key so it can be overridden. - $notices['error_product_status'] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' ); - return $notices; - } - ); - - } else { - - add_filter( - Repository::NOTICES_FILTER, - /** - * Adds GooglePay not available notice. - * - * @param array $notices The notices. - * @return array - * - * @psalm-suppress MissingClosureParamType - */ - static function ( $notices ) use ( $c ): array { - - $message = sprintf( - __( - 'Google Pay is not available on your PayPal account.', - 'woocommerce-paypal-payments' - ) - ); - - $notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' ); - return $notices; - } - ); - - } - + $availability_notice = $c->get( 'googlepay.availability_notice' ); + assert( $availability_notice instanceof AvailabilityNotice ); + $availability_notice->execute(); return; } + // Initializes button rendering. add_action( 'wp', static function () use ( $c, $button ) { @@ -121,6 +69,7 @@ class GooglepayModule implements ModuleInterface { } ); + // Enqueue frontend scripts. add_action( 'wp_enqueue_scripts', static function () use ( $c, $button ) { @@ -128,6 +77,7 @@ class GooglepayModule implements ModuleInterface { } ); + // Enqueue backend scripts. add_action( 'admin_enqueue_scripts', static function () use ( $c, $button ) { @@ -143,6 +93,7 @@ class GooglepayModule implements ModuleInterface { } ); + // Registers buttons on blocks pages. add_action( 'woocommerce_blocks_payment_method_type_registration', function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void { @@ -152,6 +103,7 @@ class GooglepayModule implements ModuleInterface { } ); + // Adds GooglePay component to the backend button preview settings. add_action( 'woocommerce_paypal_payments_admin_gateway_settings', function( array $settings ) use ( $c, $button ): array { @@ -162,7 +114,7 @@ class GooglepayModule implements ModuleInterface { } ); - // Clear product status handling. + // Clears product status when appropriate. add_action( 'woocommerce_paypal_payments_clear_apm_product_status', function( Settings $settings = null ) use ( $c ): void { diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php new file mode 100644 index 000000000..84f7c4abc --- /dev/null +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -0,0 +1,112 @@ +product_status = $product_status; + } + + /** + * Adds availability notice if applicable. + * + * @return void + */ + public function execute(): void { + if ( $this->product_status->has_request_failure() ) { + $this->add_seller_status_failure_notice(); + } else { + $this->add_not_available_notice(); + } + } + + /** + * Adds seller status failure notice. + * + * @return void + */ + private function add_seller_status_failure_notice(): void { + add_filter( + Repository::NOTICES_FILTER, + /** + * Adds seller status notice. + * + * @param array $notices The notices. + * @return array + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $notices ): array { + + $message = sprintf( + __( + '

There was an error getting your PayPal seller status. Some features may be disabled.

Certify that you connected to your account via our onboarding process.

', + 'woocommerce-paypal-payments' + ) + ); + + // Name the key so it can be overridden in other modules. + $notices['error_product_status'] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' ); + return $notices; + } + ); + } + + /** + * Adds not available notice. + * + * @return void + */ + private function add_not_available_notice(): void { + add_filter( + Repository::NOTICES_FILTER, + /** + * Adds GooglePay not available notice. + * + * @param array $notices The notices. + * @return array + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $notices ): array { + + $message = sprintf( + __( + 'Google Pay is not available on your PayPal seller account.', + 'woocommerce-paypal-payments' + ) + ); + + $notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' ); + return $notices; + } + ); + } + +} From 904beafa1f881534ecfd476529432f7bf2854402 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 22 Sep 2023 09:56:23 +0100 Subject: [PATCH 05/45] Fix GooglePay availability notice when not onboarded. --- .../ppcp-googlepay/src/Helper/ApmProductStatus.php | 11 ++++++++++- .../ppcp-googlepay/src/Helper/AvailabilityNotice.php | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php index 307fa8c14..ef93d8d54 100644 --- a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php +++ b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php @@ -83,7 +83,7 @@ class ApmProductStatus { * @return bool */ public function is_active() : bool { - if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) { + if ( ! $this->is_onboarded() ) { return false; } @@ -126,6 +126,15 @@ class ApmProductStatus { return $this->current_status; } + /** + * Returns if the seller is onboarded. + * + * @return bool + */ + public function is_onboarded(): bool { + return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED; + } + /** * Returns if there was a request failure. * diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index 84f7c4abc..2f07d422a 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -39,6 +39,10 @@ class AvailabilityNotice { * @return void */ public function execute(): void { + if ( ! $this->product_status->is_onboarded() ) { + return; + } + if ( $this->product_status->has_request_failure() ) { $this->add_seller_status_failure_notice(); } else { From 1581f34a44d19373713e2375a1e09d094784c8a6 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 22 Sep 2023 16:27:43 +0100 Subject: [PATCH 06/45] Add failure registry to API module. Add product status failure handling to GooglePay. --- modules/ppcp-api-client/services.php | 8 +- modules/ppcp-api-client/src/ApiModule.php | 13 +++ .../src/Endpoint/PartnersEndpoint.php | 19 +++- .../src/Helper/FailureRegistry.php | 88 +++++++++++++++++++ .../src/Helper/OrderTransient.php | 2 +- modules/ppcp-googlepay/services.php | 3 +- .../ppcp-googlepay/src/GooglepayModule.php | 27 +++--- .../src/Helper/ApmProductStatus.php | 47 +++++++--- 8 files changed, 177 insertions(+), 30 deletions(-) create mode 100644 modules/ppcp-api-client/src/Helper/FailureRegistry.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index ec73e16fd..8f31382e3 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts; @@ -120,7 +121,8 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.factory.sellerstatus' ), $container->get( 'api.partner_merchant_id' ), - $container->get( 'api.merchant_id' ) + $container->get( 'api.merchant_id' ), + $container->get( 'api.helper.failure-registry' ) ); }, 'api.factory.sellerstatus' => static function ( ContainerInterface $container ) : SellerStatusFactory { @@ -846,6 +848,10 @@ return array( $purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' ); return new OrderTransient( $cache, $purchase_unit_sanitizer ); }, + 'api.helper.failure-registry' => static function( ContainerInterface $container ): FailureRegistry { + $cache = new Cache( 'ppcp-paypal-api-status-cache' ); + return new FailureRegistry( $cache ); + }, 'api.helper.purchase-unit-sanitizer' => SingletonDecorator::make( static function( ContainerInterface $container ): PurchaseUnitSanitizer { $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index f12e69668..ed09ecddc 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; @@ -81,6 +82,18 @@ class ApiModule implements ModuleInterface { 10, 2 ); + add_action( + 'woocommerce_paypal_payments_clear_apm_product_status', + function () use ( $c ) { + $failure_registry = $c->has( 'api.helper.failure-registry' ) ? $c->get( 'api.helper.failure-registry' ) : null; + + if ( $failure_registry instanceof FailureRegistry ) { + $failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY ); + } + }, + 10, + 2 + ); } /** diff --git a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php index 275f94c97..0172f8af1 100644 --- a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerStatusFactory; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; /** * Class PartnersEndpoint @@ -65,6 +66,13 @@ class PartnersEndpoint { */ private $merchant_id; + /** + * The failure registry. + * + * @var FailureRegistry + */ + private $failure_registry; + /** * PartnersEndpoint constructor. * @@ -74,6 +82,7 @@ class PartnersEndpoint { * @param SellerStatusFactory $seller_status_factory The seller status factory. * @param string $partner_id The partner ID. * @param string $merchant_id The merchant ID. + * @param FailureRegistry $failure_registry The API failure registry. */ public function __construct( string $host, @@ -81,7 +90,8 @@ class PartnersEndpoint { LoggerInterface $logger, SellerStatusFactory $seller_status_factory, string $partner_id, - string $merchant_id + string $merchant_id, + FailureRegistry $failure_registry ) { $this->host = $host; $this->bearer = $bearer; @@ -89,6 +99,7 @@ class PartnersEndpoint { $this->seller_status_factory = $seller_status_factory; $this->partner_id = $partner_id; $this->merchant_id = $merchant_id; + $this->failure_registry = $failure_registry; } /** @@ -125,9 +136,15 @@ class PartnersEndpoint { 'response' => $response, ) ); + + // Register the failure on api failure registry. + $this->failure_registry->add_failure( FailureRegistry::SELLER_STATUS_KEY ); + throw $error; } + $this->failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY ); + $json = json_decode( wp_remote_retrieve_body( $response ) ); $status_code = (int) wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { diff --git a/modules/ppcp-api-client/src/Helper/FailureRegistry.php b/modules/ppcp-api-client/src/Helper/FailureRegistry.php new file mode 100644 index 000000000..a7919758c --- /dev/null +++ b/modules/ppcp-api-client/src/Helper/FailureRegistry.php @@ -0,0 +1,88 @@ +cache = $cache; + } + + /** + * @param string $key + * @param int $seconds + * @return bool + */ + public function has_failure_in_timeframe( string $key, int $seconds ): bool { + $cache_key = $this->cache_key( $key ); + $failure_time = $this->cache->get( $cache_key ); + + if ( ! $failure_time ) { + return false; + } + + $expiration = $failure_time + $seconds; + return $expiration > time(); + } + + /** + * @param string $key + * @return void + */ + public function add_failure( string $key ) { + $cache_key = $this->cache_key( $key ); + $this->cache->set( $cache_key, time(), self::CACHE_TIMEOUT ); + } + + /** + * @param string $key + * @return void + */ + public function clear_failures( string $key ) { + $cache_key = $this->cache_key( $key ); + if ( $this->cache->has( $cache_key ) ) { + $this->cache->delete( $cache_key ); + } + } + + /** + * Build cache key. + * + * @param string $key The cache key. + * @return string|null + */ + private function cache_key( string $key ): ?string { + return implode( '_', array( self::CACHE_KEY, $key ) ); + } + +} diff --git a/modules/ppcp-api-client/src/Helper/OrderTransient.php b/modules/ppcp-api-client/src/Helper/OrderTransient.php index d0c7d4a01..09dac1af9 100644 --- a/modules/ppcp-api-client/src/Helper/OrderTransient.php +++ b/modules/ppcp-api-client/src/Helper/OrderTransient.php @@ -17,7 +17,7 @@ use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; /** - * Class OrderHelper + * Class OrderTransient */ class OrderTransient { const CACHE_KEY = 'order_transient'; diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index fafc41de4..958fbbffb 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -61,7 +61,8 @@ return array( return new ApmProductStatus( $container->get( 'wcgateway.settings' ), $container->get( 'api.endpoint.partners' ), - $container->get( 'onboarding.state' ) + $container->get( 'onboarding.state' ), + $container->get( 'api.helper.failure-registry' ) ); } ), diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index eab601704..e6941670d 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -40,6 +40,17 @@ class GooglepayModule implements ModuleInterface { */ public function run( ContainerInterface $c ): void { + // Clears product status when appropriate. + add_action( + 'woocommerce_paypal_payments_clear_apm_product_status', + function( Settings $settings = null ) use ( $c ): void { + $apm_status = $c->get( 'googlepay.helpers.apm-product-status' ); + assert( $apm_status instanceof ApmProductStatus ); + + $apm_status->clear( $settings ); + } + ); + // Check if the module is applicable, correct country, currency, ... etc. if ( ! $c->get( 'googlepay.eligible' ) ) { return; @@ -113,22 +124,6 @@ class GooglepayModule implements ModuleInterface { return $settings; } ); - - // Clears product status when appropriate. - add_action( - 'woocommerce_paypal_payments_clear_apm_product_status', - function( Settings $settings = null ) use ( $c ): void { - $apm_status = $c->get( 'googlepay.helpers.apm-product-status' ); - assert( $apm_status instanceof ApmProductStatus ); - - if ( ! $settings instanceof Settings ) { - $settings = null; - } - - $apm_status->clear( $settings ); - } - ); - } /** diff --git a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php index ef93d8d54..cf1e6487c 100644 --- a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php +++ b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Helper; use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -60,21 +61,31 @@ class ApmProductStatus { */ private $onboarding_state; + /** + * The API failure registry + * + * @var FailureRegistry + */ + private $api_failure_registry; + /** * ApmProductStatus constructor. * * @param Settings $settings The Settings. * @param PartnersEndpoint $partners_endpoint The Partner Endpoint. * @param State $onboarding_state The onboarding state. + * @param FailureRegistry $api_failure_registry The API failure registry. */ public function __construct( Settings $settings, PartnersEndpoint $partners_endpoint, - State $onboarding_state + State $onboarding_state, + FailureRegistry $api_failure_registry ) { - $this->settings = $settings; - $this->partners_endpoint = $partners_endpoint; - $this->onboarding_state = $onboarding_state; + $this->settings = $settings; + $this->partners_endpoint = $partners_endpoint; + $this->onboarding_state = $onboarding_state; + $this->api_failure_registry = $api_failure_registry; } /** @@ -83,34 +94,47 @@ class ApmProductStatus { * @return bool */ public function is_active() : bool { + + // If not onboarded then makes no sense to check status. if ( ! $this->is_onboarded() ) { return false; } + // If status was already checked on this request return the same result. if ( null !== $this->current_status ) { return $this->current_status; } + // Check if status was checked on previous requests. if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) { $this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) ); return $this->current_status; } - try { - $seller_status = $this->partners_endpoint->seller_status(); - } catch ( Throwable $error ) { - // It may be a transitory error, don't persist the status. + // Check API failure registry to prevent multiple failed API requests. + if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { $this->has_request_failure = true; $this->current_status = false; return $this->current_status; } + // Request seller status via PayPal API. + try { + $seller_status = $this->partners_endpoint->seller_status(); + } catch ( Throwable $error ) { + $this->has_request_failure = true; + $this->current_status = false; + return $this->current_status; + } + + // Check the seller status for the intended capability. foreach ( $seller_status->products() as $product ) { if ( $product->name() !== 'PAYMENT_METHODS' ) { continue; } if ( in_array( self::CAPABILITY_NAME, $product->capabilities(), true ) ) { + // Capability found, persist status and return true. $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED ); $this->settings->persist(); @@ -119,6 +143,7 @@ class ApmProductStatus { } } + // Capability not found, persist status and return false. $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED ); $this->settings->persist(); @@ -158,8 +183,10 @@ class ApmProductStatus { $this->current_status = null; - $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED ); - $settings->persist(); + if ( $settings->has( self::SETTINGS_KEY ) ) { + $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED ); + $settings->persist(); + } } } From 561d43baf6da663b13a3479776a11360b818159d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 25 Sep 2023 10:12:27 +0100 Subject: [PATCH 07/45] Add SellerStatus API caching. Add GooglePay capability notice. --- .../src/Endpoint/PartnersEndpoint.php | 12 ++--- modules/ppcp-applepay/services.php | 3 +- .../src/Assets/AppleProductStatus.php | 47 ++++++++++++++++-- .../src/Helper/AvailabilityNotice.php | 2 +- modules/ppcp-wc-gateway/services.php | 6 ++- .../src/Helper/DCCProductStatus.php | 49 ++++++++++++++++--- .../Helper/PayUponInvoiceProductStatus.php | 47 ++++++++++++++++-- 7 files changed, 140 insertions(+), 26 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php index 0172f8af1..b7624d2de 100644 --- a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php @@ -136,15 +136,9 @@ class PartnersEndpoint { 'response' => $response, ) ); - - // Register the failure on api failure registry. - $this->failure_registry->add_failure( FailureRegistry::SELLER_STATUS_KEY ); - throw $error; } - $this->failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY ); - $json = json_decode( wp_remote_retrieve_body( $response ) ); $status_code = (int) wp_remote_retrieve_response_code( $response ); if ( 200 !== $status_code ) { @@ -157,9 +151,15 @@ class PartnersEndpoint { 'response' => $response, ) ); + + // Register the failure on api failure registry. + $this->failure_registry->add_failure( FailureRegistry::SELLER_STATUS_KEY ); + throw $error; } + $this->failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY ); + $status = $this->seller_status_factory->from_paypal_reponse( $json ); return $status; } diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 1cc6f81d3..fb38dd3a0 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -41,7 +41,8 @@ return array( $container->get( 'wcgateway.settings' ), $container->get( 'api.endpoint.partners' ), $container->get( 'applepay.status-cache' ), - $container->get( 'onboarding.state' ) + $container->get( 'onboarding.state' ), + $container->get( 'api.helper.failure-registry' ) ); }, 'applepay.enabled' => static function ( ContainerInterface $container ): bool { diff --git a/modules/ppcp-applepay/src/Assets/AppleProductStatus.php b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php index e804bc069..ce23b9cfc 100644 --- a/modules/ppcp-applepay/src/Assets/AppleProductStatus.php +++ b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php @@ -13,6 +13,7 @@ use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -36,6 +37,14 @@ class AppleProductStatus { * @var bool|null */ private $current_status_cache; + + /** + * If there was a request failure. + * + * @var bool + */ + private $has_request_failure = false; + /** * The settings. * @@ -57,6 +66,13 @@ class AppleProductStatus { */ private $onboarding_state; + /** + * The API failure registry + * + * @var FailureRegistry + */ + private $api_failure_registry; + /** * PayUponInvoiceProductStatus constructor. * @@ -64,17 +80,20 @@ class AppleProductStatus { * @param PartnersEndpoint $partners_endpoint The Partner Endpoint. * @param Cache $cache The cache. * @param State $onboarding_state The onboarding state. + * @param FailureRegistry $api_failure_registry The API failure registry. */ public function __construct( Settings $settings, PartnersEndpoint $partners_endpoint, Cache $cache, - State $onboarding_state + State $onboarding_state, + FailureRegistry $api_failure_registry ) { - $this->settings = $settings; - $this->partners_endpoint = $partners_endpoint; - $this->cache = $cache; - $this->onboarding_state = $onboarding_state; + $this->settings = $settings; + $this->partners_endpoint = $partners_endpoint; + $this->cache = $cache; + $this->onboarding_state = $onboarding_state; + $this->api_failure_registry = $api_failure_registry; } /** @@ -99,9 +118,17 @@ class AppleProductStatus { return true; } + // Check API failure registry to prevent multiple failed API requests. + if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { + $this->has_request_failure = true; + $this->current_status_cache = false; + return $this->current_status_cache; + } + try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { + $this->has_request_failure = true; $this->current_status_cache = false; return false; } @@ -124,4 +151,14 @@ class AppleProductStatus { $this->current_status_cache = false; return false; } + + /** + * Returns if there was a request failure. + * + * @return bool + */ + public function has_request_failure(): bool { + return $this->has_request_failure; + } + } diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index 2f07d422a..cf772a3c7 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -70,7 +70,7 @@ class AvailabilityNotice { $message = sprintf( __( - '

There was an error getting your PayPal seller status. Some features may be disabled.

Certify that you connected to your account via our onboarding process.

', + '

There was an error getting your PayPal seller status. Some features may be disabled.

Certify that you connected to your PayPal business account via our onboarding process.

', 'woocommerce-paypal-payments' ) ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 8a825b0b1..3805230a0 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1017,7 +1017,8 @@ return array( $partner_endpoint, $container->get( 'dcc.status-cache' ), $container->get( 'api.helpers.dccapplies' ), - $container->get( 'onboarding.state' ) + $container->get( 'onboarding.state' ), + $container->get( 'api.helper.failure-registry' ) ); }, @@ -1097,7 +1098,8 @@ return array( $container->get( 'wcgateway.settings' ), $container->get( 'api.endpoint.partners' ), $container->get( 'pui.status-cache' ), - $container->get( 'onboarding.state' ) + $container->get( 'onboarding.state' ), + $container->get( 'api.helper.failure-registry' ) ); }, 'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice { diff --git a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php index f8d717f08..c501dadac 100644 --- a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -37,6 +38,14 @@ class DCCProductStatus { * @var bool|null */ private $current_status_cache; + + /** + * If there was a request failure. + * + * @var bool + */ + private $has_request_failure = false; + /** * The settings. * @@ -65,6 +74,13 @@ class DCCProductStatus { */ private $onboarding_state; + /** + * The API failure registry + * + * @var FailureRegistry + */ + private $api_failure_registry; + /** * DccProductStatus constructor. * @@ -73,19 +89,22 @@ class DCCProductStatus { * @param Cache $cache The cache. * @param DccApplies $dcc_applies The dcc applies helper. * @param State $onboarding_state The onboarding state. + * @param FailureRegistry $api_failure_registry The API failure registry. */ public function __construct( Settings $settings, PartnersEndpoint $partners_endpoint, Cache $cache, DccApplies $dcc_applies, - State $onboarding_state + State $onboarding_state, + FailureRegistry $api_failure_registry ) { - $this->settings = $settings; - $this->partners_endpoint = $partners_endpoint; - $this->cache = $cache; - $this->dcc_applies = $dcc_applies; - $this->onboarding_state = $onboarding_state; + $this->settings = $settings; + $this->partners_endpoint = $partners_endpoint; + $this->cache = $cache; + $this->dcc_applies = $dcc_applies; + $this->onboarding_state = $onboarding_state; + $this->api_failure_registry = $api_failure_registry; } /** @@ -111,9 +130,17 @@ class DCCProductStatus { return true; } + // Check API failure registry to prevent multiple failed API requests. + if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { + $this->has_request_failure = true; + $this->current_status_cache = false; + return $this->current_status_cache; + } + try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { + $this->has_request_failure = true; $this->current_status_cache = false; return false; } @@ -149,4 +176,14 @@ class DCCProductStatus { $this->current_status_cache = false; return false; } + + /** + * Returns if there was a request failure. + * + * @return bool + */ + public function has_request_failure(): bool { + return $this->has_request_failure; + } + } diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php index df70c0779..7295b7e89 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php @@ -13,6 +13,7 @@ use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -36,6 +37,14 @@ class PayUponInvoiceProductStatus { * @var bool|null */ private $current_status_cache; + + /** + * If there was a request failure. + * + * @var bool + */ + private $has_request_failure = false; + /** * The settings. * @@ -57,6 +66,13 @@ class PayUponInvoiceProductStatus { */ private $onboarding_state; + /** + * The API failure registry + * + * @var FailureRegistry + */ + private $api_failure_registry; + /** * PayUponInvoiceProductStatus constructor. * @@ -64,17 +80,20 @@ class PayUponInvoiceProductStatus { * @param PartnersEndpoint $partners_endpoint The Partner Endpoint. * @param Cache $cache The cache. * @param State $onboarding_state The onboarding state. + * @param FailureRegistry $api_failure_registry The API failure registry. */ public function __construct( Settings $settings, PartnersEndpoint $partners_endpoint, Cache $cache, - State $onboarding_state + State $onboarding_state, + FailureRegistry $api_failure_registry ) { - $this->settings = $settings; - $this->partners_endpoint = $partners_endpoint; - $this->cache = $cache; - $this->onboarding_state = $onboarding_state; + $this->settings = $settings; + $this->partners_endpoint = $partners_endpoint; + $this->cache = $cache; + $this->onboarding_state = $onboarding_state; + $this->api_failure_registry = $api_failure_registry; } /** @@ -99,9 +118,17 @@ class PayUponInvoiceProductStatus { return true; } + // Check API failure registry to prevent multiple failed API requests. + if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) { + $this->has_request_failure = true; + $this->current_status_cache = false; + return $this->current_status_cache; + } + try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { + $this->has_request_failure = true; $this->current_status_cache = false; return false; } @@ -136,4 +163,14 @@ class PayUponInvoiceProductStatus { $this->current_status_cache = false; return false; } + + /** + * Returns if there was a request failure. + * + * @return bool + */ + public function has_request_failure(): bool { + return $this->has_request_failure; + } + } From 26a2b80b1b1ce7399d2c513e4ea3973bddcd6d32 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 25 Sep 2023 11:26:37 +0100 Subject: [PATCH 08/45] Fix lint --- modules/ppcp-api-client/services.php | 2 +- .../src/Helper/FailureRegistry.php | 20 ++++++++++++------- .../src/Assets/AppleProductStatus.php | 3 +-- .../src/Helper/DCCProductStatus.php | 2 +- .../Helper/PayUponInvoiceProductStatus.php | 2 +- psalm-baseline.xml | 5 +++++ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 8f31382e3..a222f0782 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -848,7 +848,7 @@ return array( $purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' ); return new OrderTransient( $cache, $purchase_unit_sanitizer ); }, - 'api.helper.failure-registry' => static function( ContainerInterface $container ): FailureRegistry { + 'api.helper.failure-registry' => static function( ContainerInterface $container ): FailureRegistry { $cache = new Cache( 'ppcp-paypal-api-status-cache' ); return new FailureRegistry( $cache ); }, diff --git a/modules/ppcp-api-client/src/Helper/FailureRegistry.php b/modules/ppcp-api-client/src/Helper/FailureRegistry.php index a7919758c..0dbeb3b5b 100644 --- a/modules/ppcp-api-client/src/Helper/FailureRegistry.php +++ b/modules/ppcp-api-client/src/Helper/FailureRegistry.php @@ -39,12 +39,14 @@ class FailureRegistry { } /** - * @param string $key - * @param int $seconds + * Returns if there was a failure within a given timeframe. + * + * @param string $key The cache key. + * @param int $seconds The timeframe in seconds. * @return bool */ public function has_failure_in_timeframe( string $key, int $seconds ): bool { - $cache_key = $this->cache_key( $key ); + $cache_key = $this->cache_key( $key ); $failure_time = $this->cache->get( $cache_key ); if ( ! $failure_time ) { @@ -56,7 +58,9 @@ class FailureRegistry { } /** - * @param string $key + * Registers a failure. + * + * @param string $key The cache key. * @return void */ public function add_failure( string $key ) { @@ -65,7 +69,9 @@ class FailureRegistry { } /** - * @param string $key + * Clear a given failure. + * + * @param string $key The cache key. * @return void */ public function clear_failures( string $key ) { @@ -79,9 +85,9 @@ class FailureRegistry { * Build cache key. * * @param string $key The cache key. - * @return string|null + * @return string */ - private function cache_key( string $key ): ?string { + private function cache_key( string $key ): string { return implode( '_', array( self::CACHE_KEY, $key ) ); } diff --git a/modules/ppcp-applepay/src/Assets/AppleProductStatus.php b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php index ce23b9cfc..83b6e7942 100644 --- a/modules/ppcp-applepay/src/Assets/AppleProductStatus.php +++ b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php @@ -11,7 +11,6 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets; use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint; -use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Onboarding\State; @@ -128,7 +127,7 @@ class AppleProductStatus { try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { - $this->has_request_failure = true; + $this->has_request_failure = true; $this->current_status_cache = false; return false; } diff --git a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php index c501dadac..772a76e3a 100644 --- a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php @@ -140,7 +140,7 @@ class DCCProductStatus { try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { - $this->has_request_failure = true; + $this->has_request_failure = true; $this->current_status_cache = false; return false; } diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php index 7295b7e89..f1a3221eb 100644 --- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php @@ -128,7 +128,7 @@ class PayUponInvoiceProductStatus { try { $seller_status = $this->partners_endpoint->seller_status(); } catch ( Throwable $error ) { - $this->has_request_failure = true; + $this->has_request_failure = true; $this->current_status_cache = false; return false; } diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1c735db2f..e96206781 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -264,6 +264,11 @@ DAY_IN_SECONDS + + + DAY_IN_SECONDS + + realpath( __FILE__ ) From 43136ecf3c9f93afc99f3ee3fa1a89587b1fe5ca Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 26 Sep 2023 13:58:33 +0100 Subject: [PATCH 09/45] Refactor onboarding link update endpoint refactor. --- .../src/Assets/ApplePayButton.php | 15 +- modules/ppcp-googlepay/src/Assets/Button.php | 20 ++- .../resources/js/onboarding.js | 149 ++++++++++-------- modules/ppcp-onboarding/services.php | 6 +- .../src/Assets/OnboardingAssets.php | 5 +- ...oint.php => UpdateSignupLinksEndpoint.php} | 29 +++- .../ppcp-onboarding/src/OnboardingModule.php | 3 +- .../src/Render/OnboardingOptionsRenderer.php | 2 +- modules/ppcp-wc-gateway/services.php | 10 +- .../Gateway/PayUponInvoice/PayUponInvoice.php | 14 ++ 10 files changed, 168 insertions(+), 85 deletions(-) rename modules/ppcp-onboarding/src/Endpoint/{PayUponInvoiceEndpoint.php => UpdateSignupLinksEndpoint.php} (82%) diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index cee3660a0..98a9e86ed 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -147,6 +147,19 @@ class ApplePayButton implements ButtonInterface { */ public function initialize(): void { add_filter( 'ppcp_onboarding_options', array( $this, 'add_apple_onboarding_option' ), 10, 1 ); + add_filter( + 'ppcp_partner_referrals_option', + function ( array $option ): array { + if ( $option['valid'] ) { + return $option; + } + if ( $option['field'] === 'ppcp-onboarding-apple' ) { + $option['valid'] = true; + $option['value'] = ( $option['value'] ? '1' : '' ); + } + return $option; + } + ); add_filter( 'ppcp_partner_referrals_data', function ( array $data ): array { @@ -206,7 +219,7 @@ class ApplePayButton implements ButtonInterface { $checked = ''; } - return $options . '
  • '; diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index fadb7b89e..f8edf694f 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -127,6 +127,7 @@ class Button implements ButtonInterface { */ public function initialize(): void { add_filter( 'ppcp_onboarding_options', array( $this, 'add_onboarding_options' ), 10, 1 ); + add_filter( 'ppcp_partner_referrals_option', array( $this, 'filter_partner_referrals_option' ), 10, 1 ); add_filter( 'ppcp_partner_referrals_data', array( $this, 'add_partner_referrals_data' ), 10, 1 ); } @@ -150,11 +151,28 @@ class Button implements ButtonInterface { } return $options - . '
  • '; } + /** + * Filters a partner referrals option. + * + * @param array $option The option data. + * @return array + */ + public function filter_partner_referrals_option( array $option ): array { + if ( $option['valid'] ) { + return $option; + } + if ( $option['field'] === 'ppcp-onboarding-google' ) { + $option['valid'] = true; + $option['value'] = ( $option['value'] ? '1' : '' ); + } + return $option; + } + /** * Adds to partner referrals data. * diff --git a/modules/ppcp-onboarding/resources/js/onboarding.js b/modules/ppcp-onboarding/resources/js/onboarding.js index 46c962e81..1e32cd753 100644 --- a/modules/ppcp-onboarding/resources/js/onboarding.js +++ b/modules/ppcp-onboarding/resources/js/onboarding.js @@ -13,69 +13,87 @@ const ppcp_onboarding = { reload: function() { const buttons = document.querySelectorAll(ppcp_onboarding.BUTTON_SELECTOR); - if (0 === buttons.length) { - return; + if (buttons.length > 0) { + // Add event listeners to buttons preventing link clicking if PayPal init failed. + buttons.forEach( + (element) => { + if (element.hasAttribute('data-ppcp-button-initialized')) { + return; + } + + element.addEventListener( + 'click', + (e) => { + if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) { + e.preventDefault(); + } + } + ); + } + ); + + // Clear any previous PayPal scripts. + [ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach( + (scriptID) => { + const scriptTag = document.getElementById(scriptID); + + if (scriptTag) { + scriptTag.parentNode.removeChild(scriptTag); + } + + if ('undefined' !== typeof window.PAYPAL) { + delete window.PAYPAL; + } + } + ); + + // Load PayPal scripts. + const paypalScriptTag = document.createElement('script'); + paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID; + paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url; + document.body.appendChild(paypalScriptTag); + + if (ppcp_onboarding._timeout) { + clearTimeout(ppcp_onboarding._timeout); + } + + ppcp_onboarding._timeout = setTimeout( + () => { + buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); }); + + if ('undefined' !== window.PAYPAL.apps.Signup) { + window.PAYPAL.apps.Signup.render(); + } + }, + 1000 + ); } - // Add event listeners to buttons preventing link clicking if PayPal init failed. - buttons.forEach( - (element) => { - if (element.hasAttribute('data-ppcp-button-initialized')) { - return; - } + const $onboarding_inputs = function () { + return jQuery('*[data-onboarding-option]'); + }; + const onboarding_options = function () { + let options = {}; + $onboarding_inputs().each((index, el) => { + const opt = jQuery(el).data('onboardingOption'); + options[opt] = el.checked; + }); + return options; + } + const disable_onboarding_options = function () { + $onboarding_inputs().each((index, el) => { + el.setAttribute('disabled', 'disabled'); + }); + } + const enable_onboarding_options = function () { + $onboarding_inputs().each((index, el) => { + el.removeAttribute('disabled'); + }); + } + const update_onboarding_options = function () { + const spinner = ''; - element.addEventListener( - 'click', - (e) => { - if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) { - e.preventDefault(); - } - } - ); - } - ); - - // Clear any previous PayPal scripts. - [ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach( - (scriptID) => { - const scriptTag = document.getElementById(scriptID); - - if (scriptTag) { - scriptTag.parentNode.removeChild(scriptTag); - } - - if ('undefined' !== typeof window.PAYPAL) { - delete window.PAYPAL; - } - } - ); - - // Load PayPal scripts. - const paypalScriptTag = document.createElement('script'); - paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID; - paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url; - document.body.appendChild(paypalScriptTag); - - if (ppcp_onboarding._timeout) { - clearTimeout(ppcp_onboarding._timeout); - } - - ppcp_onboarding._timeout = setTimeout( - () => { - buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); }); - - if ('undefined' !== window.PAYPAL.apps.Signup) { - window.PAYPAL.apps.Signup.render(); - } - }, - 1000 - ); - - const onboard_pui = document.querySelector('#ppcp-onboarding-pui'); - const spinner = ''; - onboard_pui?.addEventListener('click', (event) => { - event.preventDefault(); - onboard_pui.setAttribute('disabled', 'disabled'); + disable_onboarding_options(); buttons.forEach((element) => { element.removeAttribute('href'); element.setAttribute('disabled', 'disabled'); @@ -90,7 +108,7 @@ const ppcp_onboarding = { credentials: 'same-origin', body: JSON.stringify({ nonce: PayPalCommerceGatewayOnboarding.pui_nonce, - checked: onboard_pui.checked + settings: onboarding_options() }) }).then((res)=>{ return res.json(); @@ -99,7 +117,6 @@ const ppcp_onboarding = { alert('Could not update signup buttons: ' + JSON.stringify(data)); return; } - buttons.forEach((element) => { for (let [key, value] of Object.entries(data.data.signup_links)) { key = 'connect-to' + key.replace(/-/g, ''); @@ -110,9 +127,13 @@ const ppcp_onboarding = { } } }); - onboard_pui.removeAttribute('disabled'); + enable_onboarding_options(); }); - }) + } + $onboarding_inputs().on('click', (event) => { + event.preventDefault(); + update_onboarding_options(); + }); }, loginSeller: function(env, authCode, sharedId) { diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index 06f2d27a7..27ab1ad25 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; -use WooCommerce\PayPalCommerce\Onboarding\Endpoint\PayUponInvoiceEndpoint; +use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController; @@ -187,8 +187,8 @@ return array( $logger ); }, - 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : PayUponInvoiceEndpoint { - return new PayUponInvoiceEndpoint( + 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { + return new UpdateSignupLinksEndpoint( $container->get( 'wcgateway.settings' ), $container->get( 'button.request-data' ), $container->get( 'onboarding.signup-link-cache' ), diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php index 5cac972dc..422830ecb 100644 --- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php +++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding\Assets; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; +use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -155,8 +156,8 @@ class OnboardingAssets { 'error_messages' => array( 'no_credentials' => __( 'API credentials must be entered to save the settings.', 'woocommerce-paypal-payments' ), ), - 'pui_endpoint' => \WC_AJAX::get_endpoint( 'ppc-pui' ), - 'pui_nonce' => wp_create_nonce( 'ppc-pui' ), + 'pui_endpoint' => \WC_AJAX::get_endpoint( UpdateSignupLinksEndpoint::ENDPOINT ), + 'pui_nonce' => wp_create_nonce( UpdateSignupLinksEndpoint::ENDPOINT ), ); } diff --git a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php similarity index 82% rename from modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php rename to modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php index bc1607aa4..d7487424a 100644 --- a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php @@ -14,14 +14,17 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; +use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** - * Class PayUponInvoiceEndpoint + * Class UpdateSignupLinksEndpoint */ -class PayUponInvoiceEndpoint implements EndpointInterface { +class UpdateSignupLinksEndpoint implements EndpointInterface { + + const ENDPOINT = 'ppc-update-signup-links'; /** * The settings. @@ -66,7 +69,7 @@ class PayUponInvoiceEndpoint implements EndpointInterface { protected $logger; /** - * PayUponInvoiceEndpoint constructor. + * UpdateSignupLinksEndpoint constructor. * * @param Settings $settings The settings. * @param RequestData $request_data The request data. @@ -97,7 +100,7 @@ class PayUponInvoiceEndpoint implements EndpointInterface { * @return string */ public static function nonce(): string { - return 'ppc-pui'; + return self::ENDPOINT; } /** @@ -116,13 +119,23 @@ class PayUponInvoiceEndpoint implements EndpointInterface { try { $data = $this->request_data->read_request( $this->nonce() ); - $this->settings->set( 'ppcp-onboarding-pui', $data['checked'] ); + + foreach ( $data['settings'] ?? array() as $field => $value ) { + $option = apply_filters( 'ppcp_partner_referrals_option', array( + 'field' => $field, + 'value' => $value, + 'valid' => false, + ) ); + + if ( $option['valid'] ) { + $this->settings->set( $field, $value ); + } + } + $this->settings->persist(); foreach ( $this->signup_link_ids as $key ) { - if ( $this->signup_link_cache->has( $key ) ) { - $this->signup_link_cache->delete( $key ); - } + ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete(); } foreach ( $this->signup_link_ids as $key ) { diff --git a/modules/ppcp-onboarding/src/OnboardingModule.php b/modules/ppcp-onboarding/src/OnboardingModule.php index 1f94f0fee..578cda42c 100644 --- a/modules/ppcp-onboarding/src/OnboardingModule.php +++ b/modules/ppcp-onboarding/src/OnboardingModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding; +use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; @@ -96,7 +97,7 @@ class OnboardingModule implements ModuleInterface { ); add_action( - 'wc_ajax_ppc-pui', + 'wc_ajax_' . UpdateSignupLinksEndpoint::ENDPOINT, static function () use ( $c ) { $endpoint = $c->get( 'onboarding.endpoint.pui' ); $endpoint->handle_request(); diff --git a/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php b/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php index f78c817c6..a5d7628ec 100644 --- a/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php +++ b/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php @@ -95,7 +95,7 @@ class OnboardingOptionsRenderer { $checked = ''; } - return '
  • '; } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 3805230a0..8266d70e7 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -200,10 +200,12 @@ return array( return $ppcp_tab ? $ppcp_tab : $section; }, - 'wcgateway.settings' => static function ( ContainerInterface $container ): Settings { - $default_button_locations = $container->get( 'wcgateway.button.default-locations' ); - return new Settings( $default_button_locations ); - }, + 'wcgateway.settings' => SingletonDecorator::make( + static function ( ContainerInterface $container ): Settings { + $default_button_locations = $container->get( 'wcgateway.button.default-locations' ); + return new Settings( $default_button_locations ); + } + ), 'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice { $state = $container->get( 'onboarding.state' ); $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 0d0807659..9da44d069 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -142,6 +142,20 @@ class PayUponInvoice { $this->settings->persist(); } + add_filter( + 'ppcp_partner_referrals_option', + function ( array $option ): array { + if ( $option['valid'] ) { + return $option; + } + if ( $option['field'] === 'ppcp-onboarding-pui' ) { + $option['valid'] = true; + $option['value'] = ( $option['value'] ? '1' : '' ); + } + return $option; + } + ); + add_filter( 'ppcp_partner_referrals_data', function ( array $data ): array { From 82c143c5b6b807ee32d2535f6f13565946cfd7fb Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 27 Sep 2023 08:30:52 +0100 Subject: [PATCH 10/45] Refactor signup link endpoint --- .../resources/js/onboarding.js | 4 ++-- .../src/Assets/OnboardingAssets.php | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-onboarding/resources/js/onboarding.js b/modules/ppcp-onboarding/resources/js/onboarding.js index 1e32cd753..57d30161b 100644 --- a/modules/ppcp-onboarding/resources/js/onboarding.js +++ b/modules/ppcp-onboarding/resources/js/onboarding.js @@ -100,14 +100,14 @@ const ppcp_onboarding = { jQuery(spinner).insertAfter(element); }); - fetch(PayPalCommerceGatewayOnboarding.pui_endpoint, { + fetch(PayPalCommerceGatewayOnboarding.update_signup_links_endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', body: JSON.stringify({ - nonce: PayPalCommerceGatewayOnboarding.pui_nonce, + nonce: PayPalCommerceGatewayOnboarding.update_signup_links_nonce, settings: onboarding_options() }) }).then((res)=>{ diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php index 422830ecb..4b0c9c165 100644 --- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php +++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php @@ -146,18 +146,18 @@ class OnboardingAssets { */ public function get_script_data() { return array( - 'endpoint' => \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ), - 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ), - 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', - 'sandbox_state' => State::get_state_name( $this->state->sandbox_state() ), - 'production_state' => State::get_state_name( $this->state->production_state() ), - 'current_state' => State::get_state_name( $this->state->current_state() ), - 'current_env' => $this->environment->current_environment(), - 'error_messages' => array( + 'endpoint' => \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ), + 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js', + 'sandbox_state' => State::get_state_name( $this->state->sandbox_state() ), + 'production_state' => State::get_state_name( $this->state->production_state() ), + 'current_state' => State::get_state_name( $this->state->current_state() ), + 'current_env' => $this->environment->current_environment(), + 'error_messages' => array( 'no_credentials' => __( 'API credentials must be entered to save the settings.', 'woocommerce-paypal-payments' ), ), - 'pui_endpoint' => \WC_AJAX::get_endpoint( UpdateSignupLinksEndpoint::ENDPOINT ), - 'pui_nonce' => wp_create_nonce( UpdateSignupLinksEndpoint::ENDPOINT ), + 'update_signup_links_endpoint' => \WC_AJAX::get_endpoint( UpdateSignupLinksEndpoint::ENDPOINT ), + 'update_signup_links_nonce' => wp_create_nonce( UpdateSignupLinksEndpoint::ENDPOINT ), ); } From 59219009c3e34781a00d280a7da64e5ae4c0e031 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 27 Sep 2023 08:47:48 +0100 Subject: [PATCH 11/45] Fix lint --- .../src/Endpoint/UpdateSignupLinksEndpoint.php | 13 ++++++++----- modules/ppcp-wc-gateway/services.php | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php index d7487424a..f26e6dc13 100644 --- a/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php @@ -121,11 +121,14 @@ class UpdateSignupLinksEndpoint implements EndpointInterface { $data = $this->request_data->read_request( $this->nonce() ); foreach ( $data['settings'] ?? array() as $field => $value ) { - $option = apply_filters( 'ppcp_partner_referrals_option', array( - 'field' => $field, - 'value' => $value, - 'valid' => false, - ) ); + $option = apply_filters( + 'ppcp_partner_referrals_option', + array( + 'field' => $field, + 'value' => $value, + 'valid' => false, + ) + ); if ( $option['valid'] ) { $this->settings->set( $field, $value ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 8266d70e7..267c5822d 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -200,7 +200,7 @@ return array( return $ppcp_tab ? $ppcp_tab : $section; }, - 'wcgateway.settings' => SingletonDecorator::make( + 'wcgateway.settings' => SingletonDecorator::make( static function ( ContainerInterface $container ): Settings { $default_button_locations = $container->get( 'wcgateway.button.default-locations' ); return new Settings( $default_button_locations ); From b96e556257d8c82555d2b79ad9b5061f2d803895 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 27 Sep 2023 11:12:09 +0100 Subject: [PATCH 12/45] Fix GooglePay notice placement. --- modules/ppcp-googlepay/services.php | 4 +- .../src/Helper/AvailabilityNotice.php | 43 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 958fbbffb..9a483942c 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -52,7 +52,9 @@ return array( 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { return new AvailabilityNotice( - $container->get( 'googlepay.helpers.apm-product-status' ) + $container->get( 'googlepay.helpers.apm-product-status' ), + $container->get( 'wcgateway.is-wc-gateways-list-page' ), + $container->get( 'wcgateway.is-ppcp-settings-page' ) ); }, diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index cf772a3c7..c8b7584ea 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -24,13 +24,35 @@ class AvailabilityNotice { */ private $product_status; + /** + * Indicates if we're on the WooCommerce gateways list page. + * + * @var bool + */ + private $is_wc_gateways_list_page; + + /** + * Indicates if we're on a PPCP Settings page. + * + * @var bool + */ + private $is_ppcp_settings_page; + /** * Class ApmProductStatus constructor. * @param ApmProductStatus $product_status The product status handler. + * @param bool $is_wc_gateways_list_page Indicates if we're on the WooCommerce gateways list page. + * @param bool $is_ppcp_settings_page Indicates if we're on a PPCP Settings page. */ - public function __construct( ApmProductStatus $product_status ) { - $this->product_status = $product_status; + public function __construct( + ApmProductStatus $product_status, + bool $is_wc_gateways_list_page, + bool $is_ppcp_settings_page + ) { + $this->product_status = $product_status; + $this->is_wc_gateways_list_page = $is_wc_gateways_list_page; + $this->is_ppcp_settings_page = $is_ppcp_settings_page; } /** @@ -39,7 +61,7 @@ class AvailabilityNotice { * @return void */ public function execute(): void { - if ( ! $this->product_status->is_onboarded() ) { + if ( ! $this->should_display() ) { return; } @@ -50,6 +72,21 @@ class AvailabilityNotice { } } + /** + * Whether the message should display. + * + * @return bool + */ + protected function should_display(): bool { + if ( ! $this->product_status->is_onboarded() ) { + return false; + } + if ( ! $this->is_wc_gateways_list_page && ! $this->is_ppcp_settings_page ) { + return false; + } + return true; + } + /** * Adds seller status failure notice. * From ad08480b67235b3d50902deb727e14ff22ff617d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 28 Sep 2023 09:32:09 +0100 Subject: [PATCH 13/45] Add GooglePay shipping support POC --- .../src/Assets/ApplePayButton.php | 2 -- .../resources/js/GooglepayButton.js | 23 +++++++++++++++++-- modules/ppcp-googlepay/src/Assets/Button.php | 4 ---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 98a9e86ed..9a3934080 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -178,7 +178,6 @@ class ApplePayButton implements ButtonInterface { $data['products'][0] = 'PAYMENT_METHODS'; } $data['capabilities'][] = 'APPLE_PAY'; - $nonce = $data['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['seller_nonce']; $data['operations'][] = array( 'operation' => 'API_INTEGRATION', 'api_integration_preference' => array( @@ -190,7 +189,6 @@ class ApplePayButton implements ButtonInterface { 'PAYMENT', 'REFUND', ), - 'seller_nonce' => $nonce, ), ), ), diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index ef601d82d..202fb7d89 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -104,7 +104,7 @@ class GooglepayButton { environment: this.buttonConfig.environment, // add merchant info maybe paymentDataCallbacks: { - //onPaymentDataChanged: onPaymentDataChanged, + onPaymentDataChanged: this.onPaymentDataChanged.bind(this), onPaymentAuthorized: this.onPaymentAuthorized.bind(this), } }); @@ -186,15 +186,34 @@ class GooglepayButton { paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; + //paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; + + paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; + + + paymentDataRequest.shippingAddressRequired = true; + paymentDataRequest.shippingAddressParameters = this.getGoogleShippingAddressParameters(); + paymentDataRequest.shippingOptionRequired = true; + return paymentDataRequest; } + getGoogleShippingAddressParameters() { + return { + allowedCountryCodes: ['US'], + phoneNumberRequired: true + }; + } //------------------------ // Payment process //------------------------ + onPaymentDataChanged(paymentData) { + console.log('[GooglePayButton] onPaymentDataChanged', this.context); + console.log('[GooglePayButton] paymentData', paymentData); + } + onPaymentAuthorized(paymentData) { console.log('[GooglePayButton] onPaymentAuthorized', this.context); return this.processPayment(paymentData); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index f8edf694f..eb7de8360 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -198,9 +198,6 @@ class Button implements ButtonInterface { } $data['capabilities'][] = 'GOOGLE_PAY'; - - $nonce = $data['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['seller_nonce']; - $data['operations'][] = array( 'operation' => 'API_INTEGRATION', 'api_integration_preference' => array( @@ -212,7 +209,6 @@ class Button implements ButtonInterface { 'PAYMENT', 'REFUND', ), - 'seller_nonce' => $nonce, ), ), ), From eeaf38e4c425c65c31865dbbf299efd2cf4c342c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 28 Sep 2023 18:04:46 +0100 Subject: [PATCH 14/45] Add GooglePay shipping option --- .../src/Assets/ApplePayButton.php | 2 +- .../resources/js/GooglepayButton.js | 28 +++++++++++-------- modules/ppcp-googlepay/src/Assets/Button.php | 13 +++++---- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 9a3934080..77d1bcf14 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -185,7 +185,7 @@ class ApplePayButton implements ButtonInterface { 'integration_method' => 'PAYPAL', 'integration_type' => 'THIRD_PARTY', 'third_party_details' => array( - 'features' => array( + 'features' => array( 'PAYMENT', 'REFUND', ), diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 202fb7d89..61b21a019 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -100,13 +100,18 @@ class GooglepayButton { } initClient() { + const callbacks = { + onPaymentAuthorized: this.onPaymentAuthorized.bind(this) + } + + if ( this.buttonConfig.enable_shipping ) { + callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); + } + this.paymentsClient = new google.payments.api.PaymentsClient({ environment: this.buttonConfig.environment, // add merchant info maybe - paymentDataCallbacks: { - onPaymentDataChanged: this.onPaymentDataChanged.bind(this), - onPaymentAuthorized: this.onPaymentAuthorized.bind(this), - } + paymentDataCallbacks: callbacks }); } @@ -186,14 +191,15 @@ class GooglepayButton { paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - //paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; - - - paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = this.getGoogleShippingAddressParameters(); - paymentDataRequest.shippingOptionRequired = true; + if ( this.buttonConfig.enable_shipping ) { + paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; + paymentDataRequest.shippingAddressRequired = true; + paymentDataRequest.shippingAddressParameters = this.getGoogleShippingAddressParameters(); + paymentDataRequest.shippingOptionRequired = true; + } else { + paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; + } return paymentDataRequest; } diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index eb7de8360..6524d611f 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -198,14 +198,14 @@ class Button implements ButtonInterface { } $data['capabilities'][] = 'GOOGLE_PAY'; - $data['operations'][] = array( + $data['operations'][] = array( 'operation' => 'API_INTEGRATION', 'api_integration_preference' => array( 'rest_api_integration' => array( 'integration_method' => 'PAYPAL', 'integration_type' => 'THIRD_PARTY', 'third_party_details' => array( - 'features' => array( + 'features' => array( 'PAYMENT', 'REFUND', ), @@ -407,14 +407,17 @@ class Button implements ButtonInterface { */ public function script_data(): array { return array( - 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', - 'sdk_url' => $this->sdk_url, - 'button' => array( + 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', + 'sdk_url' => $this->sdk_url, + 'button' => array( 'wrapper' => '#ppc-button-googlepay-container', 'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary. 'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart', 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), ), + 'enable_shipping' => $this->settings->has( 'googlepay_button_shipping_enabled' ) + ? $this->settings->get( 'googlepay_button_shipping_enabled' ) + : false, ); } From 6b1760fb7250ee7f4799229682789caa22b6c0ed Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 29 Sep 2023 14:22:09 +0100 Subject: [PATCH 15/45] Update comment. Test commit signature. --- modules/ppcp-googlepay/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 9a483942c..cbc2e7fad 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -37,7 +37,7 @@ return array( ); }, - // If GooglePay is configured. + // If GooglePay is configured and onboarded. 'googlepay.available' => static function ( ContainerInterface $container ): bool { if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) { $status = $container->get( 'googlepay.helpers.apm-product-status' ); From c30ddd3b641d94f5e177ae65b135d58ca2c0dd1d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 29 Sep 2023 18:10:06 +0100 Subject: [PATCH 16/45] Add GooglePay shipping support --- .../resources/js/GooglepayButton.js | 90 ++++++++++++++++--- modules/ppcp-googlepay/src/Assets/Button.php | 29 ++++-- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 61b21a019..c70c67c01 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -104,7 +104,7 @@ class GooglepayButton { onPaymentAuthorized: this.onPaymentAuthorized.bind(this) } - if ( this.buttonConfig.enable_shipping ) { + if ( this.buttonConfig.shipping.enabled ) { callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); } @@ -192,10 +192,10 @@ class GooglepayButton { paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - if ( this.buttonConfig.enable_shipping ) { + if ( this.buttonConfig.shipping.enabled ) { paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = this.getGoogleShippingAddressParameters(); + paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); paymentDataRequest.shippingOptionRequired = true; } else { paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; @@ -204,22 +204,92 @@ class GooglepayButton { return paymentDataRequest; } - getGoogleShippingAddressParameters() { + //------------------------ + // Shipping processing + //------------------------ + + shippingAddressParameters() { return { - allowedCountryCodes: ['US'], + allowedCountryCodes: this.buttonConfig.shipping.countries, phoneNumberRequired: true }; } + onPaymentDataChanged(paymentData) { + console.log('[GooglePayButton] onPaymentDataChanged', this.context); + console.log('[GooglePayButton] paymentData', paymentData); + + return new Promise(async (resolve, reject) => { + let paymentDataRequestUpdate = {}; + + // TODO : update shipping in cart and get price + + if(false) { // TODO : on error + paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); + resolve(paymentDataRequestUpdate); + } + + switch (paymentData.callbackTrigger) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = this.shippingOptions(); + let selectedShippingOptionId = paymentDataRequestUpdate.newShippingOptionParameters.defaultSelectedOptionId; + paymentDataRequestUpdate.newTransactionInfo = await this.calculateNewTransactionInfo(selectedShippingOptionId); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = await this.calculateNewTransactionInfo(paymentData.shippingOptionData.id); + break; + } + + resolve(paymentDataRequestUpdate); + }); + } + + unserviceableShippingAddressError() { + return { + reason: "SHIPPING_ADDRESS_UNSERVICEABLE", + message: "Cannot ship to the selected address", + intent: "SHIPPING_ADDRESS" + }; + } + + async calculateNewTransactionInfo(shippingOptionId) { + let newTransactionInfo = await this.contextHandler.transactionInfo(); + + // TODO : update shipping in cart + + newTransactionInfo.totalPrice = Math.floor(10 + Math.random() * 100).toString(); // TODO : testing only + + return newTransactionInfo; + } + + shippingOptions() { + return { + defaultSelectedOptionId: "shipping-001", + shippingOptions: [ + { + "id": "shipping-001", + "label": "Free: Standard shipping", + "description": "Free Shipping delivered in 5 business days." + }, + { + "id": "shipping-002", + "label": "$1.99: Standard shipping", + "description": "Standard shipping delivered in 3 business days." + }, + { + "id": "shipping-003", + "label": "$10: Express shipping", + "description": "Express shipping delivered in 1 business day." + }, + ] + }; + } + //------------------------ // Payment process //------------------------ - onPaymentDataChanged(paymentData) { - console.log('[GooglePayButton] onPaymentDataChanged', this.context); - console.log('[GooglePayButton] paymentData', paymentData); - } - onPaymentAuthorized(paymentData) { console.log('[GooglePayButton] onPaymentAuthorized', this.context); return this.processPayment(paymentData); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 6524d611f..e23bf88de 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Assets; use Exception; use Psr\Log\LoggerInterface; +use WC_Countries; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; @@ -406,18 +407,26 @@ class Button implements ButtonInterface { * @return array */ public function script_data(): array { + $shipping = array( + 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) + ? boolval( $this->settings->get( 'googlepay_button_shipping_enabled' ) ) + : false, + ); + + if ( $shipping['enabled'] ) { + $shipping['countries'] = array_keys( $this->wc_countries()->get_shipping_countries() ); + } + return array( - 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', - 'sdk_url' => $this->sdk_url, - 'button' => array( + 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', + 'sdk_url' => $this->sdk_url, + 'button' => array( 'wrapper' => '#ppc-button-googlepay-container', 'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary. 'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart', 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), ), - 'enable_shipping' => $this->settings->has( 'googlepay_button_shipping_enabled' ) - ? $this->settings->get( 'googlepay_button_shipping_enabled' ) - : false, + 'shipping' => $shipping, ); } @@ -449,4 +458,12 @@ class Button implements ButtonInterface { return $values; } + /** + * Returns a WC_Countries instance to check shipping + * + * @return WC_Countries + */ + private function wc_countries(): WC_Countries { + return new WC_Countries(); + } } From 8c6a58d05cfb3ce76f0d845d9ee5e80aec2ccf2f Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 11 Oct 2023 08:51:22 +0100 Subject: [PATCH 17/45] Allow GooglePay use for non referral merchants --- modules/ppcp-applepay/extensions.php | 29 ++++++------------- modules/ppcp-googlepay/extensions.php | 3 +- modules/ppcp-googlepay/services.php | 8 +++++ .../ppcp-googlepay/src/GooglepayModule.php | 12 ++++++-- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index 64c41b52c..f4fe807bc 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -35,11 +35,11 @@ return array( 'allow_card_button_gateway', array( 'applepay_button_enabled' => array( - 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), - 'type' => 'checkbox', - 'class' => array( 'ppcp-grayed-out-text' ), - 'input_class' => array( 'ppcp-disabled-checkbox' ), - 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' ) + 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'class' => array( 'ppcp-grayed-out-text' ), + 'input_class' => array( 'ppcp-disabled-checkbox' ), + 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' ) . '

    ' . sprintf( // translators: %1$s and %2$s are the opening and closing of HTML tag. @@ -48,21 +48,10 @@ return array( '' ) . '

    ', - 'default' => 'yes', - 'screens' => array( State::STATE_ONBOARDED ), - 'gateway' => 'paypal', - 'requirements' => array(), - 'custom_attributes' => array( - 'data-ppcp-display' => wp_json_encode( - array( - $display_manager - ->rule() - ->condition_element( 'applepay_button_enabled', '1' ) - ->action_enable( 'applepay_button_enabled' ) - ->to_array(), - ) - ), - ), + 'default' => 'yes', + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), ), ) ); diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 07e29d97b..62cc1c029 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -25,6 +25,7 @@ return array( } $is_available = $container->get( 'googlepay.available' ); + $is_referral = $container->get( 'googlepay.is_referral' ); $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); @@ -70,7 +71,7 @@ return array( ->to_array(), $display_manager ->rule() - ->condition_is_true( $is_available ) + ->condition_is_true( $is_available || ! $is_referral ) ->action_enable( 'googlepay_button_enabled' ) ->action_enable( 'googlepay_button_type' ) ->action_enable( 'googlepay_button_color' ) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index cbc2e7fad..1f7cd2db0 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -50,6 +50,14 @@ return array( return true; }, + // We assume it's a referral if we can check product status without API request failures. + 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { + $status = $container->get( 'googlepay.helpers.apm-product-status' ); + assert( $status instanceof ApmProductStatus ); + + return ! $status->has_request_failure(); + }, + 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { return new AvailabilityNotice( $container->get( 'googlepay.helpers.apm-product-status' ), diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index e6941670d..35ce24902 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -10,8 +10,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; -use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; -use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; @@ -62,13 +60,21 @@ class GooglepayModule implements ModuleInterface { $button->initialize(); // Check if this merchant can activate / use the buttons. - if ( ! $c->get( 'googlepay.available' ) ) { + // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability. + if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) { $availability_notice = $c->get( 'googlepay.availability_notice' ); assert( $availability_notice instanceof AvailabilityNotice ); $availability_notice->execute(); return; } + // Show notice and continue if merchant isn't onboarded via a referral. + if ( ! $c->get( 'googlepay.is_referral' ) ) { + $availability_notice = $c->get( 'googlepay.availability_notice' ); + assert( $availability_notice instanceof AvailabilityNotice ); + $availability_notice->execute(); + } + // Initializes button rendering. add_action( 'wp', From 387d677a05ff813b60d83556dd517a54e7598296 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 11 Oct 2023 18:55:42 +0100 Subject: [PATCH 18/45] Add shipping callback --- modules.php | 2 +- .../resources/js/GooglepayButton.js | 53 ++--- .../resources/js/Helper/UpdatePaymentData.js | 38 ++++ modules/ppcp-googlepay/services.php | 8 + modules/ppcp-googlepay/src/Assets/Button.php | 7 + .../Endpoint/UpdatePaymentDataEndpoint.php | 189 ++++++++++++++++++ .../ppcp-googlepay/src/GooglepayModule.php | 26 ++- .../src/Helper/AvailabilityNotice.php | 2 +- 8 files changed, 278 insertions(+), 47 deletions(-) create mode 100644 modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js create mode 100644 modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php diff --git a/modules.php b/modules.php index 7e93c89ea..5b8955527 100644 --- a/modules.php +++ b/modules.php @@ -39,7 +39,7 @@ return function ( string $root_dir ): iterable { if ( apply_filters( //phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores 'woocommerce.feature-flags.woocommerce_paypal_payments.googlepay_enabled', - getenv( 'PCP_GOOGLEPAY_ENABLED' ) === '1' + getenv( 'PCP_GOOGLEPAY_ENABLED' ) !== '0' ) ) { $modules[] = ( require "$modules_dir/ppcp-googlepay/module.php" )(); } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index c70c67c01..43e3997ba 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -2,6 +2,7 @@ import ContextHandlerFactory from "./Context/ContextHandlerFactory"; import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; +import UpdatePaymentData from "./Helper/UpdatePaymentData"; class GooglepayButton { @@ -222,26 +223,31 @@ class GooglepayButton { return new Promise(async (resolve, reject) => { let paymentDataRequestUpdate = {}; - // TODO : update shipping in cart and get price + const updateData = new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data) + const updatedData = await updateData.update(paymentData); - if(false) { // TODO : on error + // Handle unserviceable address. + if(!updatedData.shipping_options || !updatedData.shipping_options.shippingOptions.length) { paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); resolve(paymentDataRequestUpdate); + return; } switch (paymentData.callbackTrigger) { case 'INITIALIZE': case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = this.shippingOptions(); - let selectedShippingOptionId = paymentDataRequestUpdate.newShippingOptionParameters.defaultSelectedOptionId; - paymentDataRequestUpdate.newTransactionInfo = await this.calculateNewTransactionInfo(selectedShippingOptionId); + paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); break; case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = await this.calculateNewTransactionInfo(paymentData.shippingOptionData.id); + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); break; } resolve(paymentDataRequestUpdate); + + // Update WooCommerce checkout form. + jQuery(document.body).trigger('update_checkout'); }); } @@ -253,39 +259,16 @@ class GooglepayButton { }; } - async calculateNewTransactionInfo(shippingOptionId) { - let newTransactionInfo = await this.contextHandler.transactionInfo(); - - // TODO : update shipping in cart - - newTransactionInfo.totalPrice = Math.floor(10 + Math.random() * 100).toString(); // TODO : testing only - - return newTransactionInfo; - } - - shippingOptions() { + calculateNewTransactionInfo(updatedData) { return { - defaultSelectedOptionId: "shipping-001", - shippingOptions: [ - { - "id": "shipping-001", - "label": "Free: Standard shipping", - "description": "Free Shipping delivered in 5 business days." - }, - { - "id": "shipping-002", - "label": "$1.99: Standard shipping", - "description": "Standard shipping delivered in 3 business days." - }, - { - "id": "shipping-003", - "label": "$10: Express shipping", - "description": "Express shipping delivered in 1 business day." - }, - ] + countryCode: updatedData.country_code, + currencyCode: updatedData.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: updatedData.total_str }; } + //------------------------ // Payment process //------------------------ diff --git a/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js new file mode 100644 index 000000000..3d56d9316 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js @@ -0,0 +1,38 @@ + +class UpdatePaymentData { + + constructor(config) { + this.config = config; + } + + update(paymentData) { + return new Promise((resolve, reject) => { + fetch( + this.config.endpoint, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + body: JSON.stringify({ + nonce: this.config.nonce, + paymentData: paymentData, + }) + + } + ) + .then(result => result.json()) + .then(result => { + if (!result.success) { + return; + } + + resolve(result.data); + }); + }); + } + +} + +export default UpdatePaymentData; diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 1f7cd2db0..0cf240b1d 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\Googlepay\Assets\BlocksPaymentMethod; use WooCommerce\PayPalCommerce\Googlepay\Assets\Button; +use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; @@ -174,4 +175,11 @@ return array( return 'https://pay.google.com/gp/p/js/pay.js'; }, + 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { + return new UpdatePaymentDataEndpoint( + $container->get( 'button.request-data' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, + ); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index e23bf88de..8bc1c3d76 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Countries; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -427,6 +428,12 @@ class Button implements ButtonInterface { 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), ), 'shipping' => $shipping, + 'ajax' => array( + 'update_payment_data' => array( + 'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ), + 'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ), + ), + ), ); } diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php new file mode 100644 index 000000000..76f0288ec --- /dev/null +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -0,0 +1,189 @@ +request_data = $request_data; + $this->logger = $logger; + } + + /** + * Returns the nonce. + * + * @return string + */ + public static function nonce(): string { + return self::ENDPOINT; + } + + /** + * Handles the request. + * + * @return bool + * @throws RuntimeException + */ + public function handle_request(): bool { + try { + $data = $this->request_data->read_request( $this->nonce() ); + + // Validate nonce. + if ( + ! isset( $data['nonce'] ) + || ! wp_verify_nonce( $data['nonce'], self::nonce() ) + ) { + throw new RuntimeException( + __( 'Could not validate nonce.', 'woocommerce-paypal-payments' ) + ); + } + + // Validate payment data. + if ( ! isset( $data['paymentData'] ) ) { + throw new RuntimeException( + __( 'No paymentData provided.', 'woocommerce-paypal-payments' ) + ); + } + + $payment_data = $data['paymentData']; + + // Set context as cart. + if ( is_callable( 'wc_maybe_define_constant' ) ) { + wc_maybe_define_constant( 'WOOCOMMERCE_CART', true ); + } + + // Update shipping address. + if ( $payment_data['callbackTrigger'] === 'SHIPPING_ADDRESS' ) { + $customer = WC()->customer; + + if ( $customer ) { + $customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); + $customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); + $customer->set_billing_state( $payment_data['shippingAddress']['locality'] ?? '' ); + + $customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); + $customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); + $customer->set_shipping_state( $payment_data['shippingAddress']['locality'] ?? '' ); + + // Save the data. + $customer->save(); + + WC()->session->set( 'customer', WC()->customer->get_data() ); + } + } + + // Set shipping method. + WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() ); + + $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); + $chosen_shipping_methods[0] = $payment_data['shippingOptionData']['id']; + WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); + + WC()->cart->calculate_shipping(); + WC()->cart->calculate_fees(); + WC()->cart->calculate_totals(); + + $total = (float) WC()->cart->get_total( 'numeric' ); + + // Shop settings. + $base_location = wc_get_base_location(); + $shop_country_code = $base_location['country']; + $currency_code = get_woocommerce_currency(); + + wp_send_json_success( + array( + 'total' => $total, + 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'currency_code' => $currency_code, + 'country_code' => $shop_country_code, + 'shipping_options' => $this->get_shipping_options(), + ) + ); + + return true; + } catch ( Throwable $error ) { + $this->logger->error( "UpdatePaymentDataEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" ); + + wp_send_json_error(); + return false; + } + } + + /** + * Returns the array of available shipping methods. + * + * @return array + */ + public function get_shipping_options(): array { + $shipping_methods = array(); + + $packages = WC()->cart->get_shipping_packages(); + $zone = \WC_Shipping_Zones::get_zone_matching_package( $packages[0] ); + + /** @var \WC_Shipping_Method[] $methods The shipping methods. */ + $methods = $zone->get_shipping_methods( true ); + + foreach ( $methods as $method ) { + if ( ! $method->is_available( $packages[0] ) ) { + continue; + } + + $shipping_methods[] = array( + 'id' => $method->get_rate_id(), + 'label' => $method->get_title(), + 'description' => '', + ); + } + + if ( ! isset( $shipping_methods[0] ) ) { + return array(); + } + + return array( + 'defaultSelectedOptionId' => $shipping_methods[0]['id'], + 'shippingOptions' => $shipping_methods, + ); + } + +} diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 35ce24902..86c967694 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; @@ -59,22 +60,17 @@ class GooglepayModule implements ModuleInterface { assert( $button instanceof ButtonInterface ); $button->initialize(); + // Show notice if there are product availability issues. + $availability_notice = $c->get( 'googlepay.availability_notice' ); + assert( $availability_notice instanceof AvailabilityNotice ); + $availability_notice->execute(); + // Check if this merchant can activate / use the buttons. // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability. if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) { - $availability_notice = $c->get( 'googlepay.availability_notice' ); - assert( $availability_notice instanceof AvailabilityNotice ); - $availability_notice->execute(); return; } - // Show notice and continue if merchant isn't onboarded via a referral. - if ( ! $c->get( 'googlepay.is_referral' ) ) { - $availability_notice = $c->get( 'googlepay.availability_notice' ); - assert( $availability_notice instanceof AvailabilityNotice ); - $availability_notice->execute(); - } - // Initializes button rendering. add_action( 'wp', @@ -130,6 +126,16 @@ class GooglepayModule implements ModuleInterface { return $settings; } ); + + // Initialize AJAX endpoints. + add_action( + 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' ); + assert( $endpoint instanceof UpdatePaymentDataEndpoint ); + $endpoint->handle_request(); + } + ); } /** diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index c8b7584ea..6ea3c0e62 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -67,7 +67,7 @@ class AvailabilityNotice { if ( $this->product_status->has_request_failure() ) { $this->add_seller_status_failure_notice(); - } else { + } elseif ( ! $this->product_status->is_active() ) { $this->add_not_available_notice(); } } From c69f2a99b8dea42a182d9bec93e09ac484b7a298 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 11 Oct 2023 18:58:36 +0100 Subject: [PATCH 19/45] Fix lint --- .../src/Endpoint/UpdatePaymentDataEndpoint.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index 76f0288ec..7cfe6dfea 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -63,7 +63,7 @@ class UpdatePaymentDataEndpoint { * Handles the request. * * @return bool - * @throws RuntimeException + * @throws RuntimeException When a validation fails. */ public function handle_request(): bool { try { @@ -161,7 +161,10 @@ class UpdatePaymentDataEndpoint { $packages = WC()->cart->get_shipping_packages(); $zone = \WC_Shipping_Zones::get_zone_matching_package( $packages[0] ); - /** @var \WC_Shipping_Method[] $methods The shipping methods. */ + /** + * The shipping methods. + * @var \WC_Shipping_Method[] $methods + */ $methods = $zone->get_shipping_methods( true ); foreach ( $methods as $method ) { From 46f579c42f3ec84404426315e1491a26ff8cc0c9 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 12 Oct 2023 08:29:52 +0100 Subject: [PATCH 20/45] Fix lint --- .../src/Endpoint/UpdatePaymentDataEndpoint.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index 7cfe6dfea..a60545290 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -95,6 +95,12 @@ class UpdatePaymentDataEndpoint { // Update shipping address. if ( $payment_data['callbackTrigger'] === 'SHIPPING_ADDRESS' ) { + + /** + * The shipping methods. + * + * @var \WC_Customer|null $customer + */ $customer = WC()->customer; if ( $customer ) { @@ -163,6 +169,7 @@ class UpdatePaymentDataEndpoint { /** * The shipping methods. + * * @var \WC_Shipping_Method[] $methods */ $methods = $zone->get_shipping_methods( true ); From 7d1f8b5187cba232022c708d548827a44a3b3840 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 12 Oct 2023 15:27:55 +0100 Subject: [PATCH 21/45] Refactor options for release preparation --- modules/ppcp-applepay/extensions.php | 59 +++++++++++++--- .../resources/js/ApplepayButton.js | 55 +++++++++------ .../resources/js/ApplepayManager.js | 2 +- .../ppcp-applepay/resources/js/boot-block.js | 10 +-- modules/ppcp-applepay/resources/js/boot.js | 2 +- modules/ppcp-applepay/services.php | 59 ++++++++++++++++ .../src/Assets/ApplePayButton.php | 4 ++ .../src/Assets/DataToAppleButtonScripts.php | 2 + modules/ppcp-googlepay/extensions.php | 70 ++++++++++++++++--- .../resources/js/GooglepayButton.js | 26 ++++--- .../ppcp-googlepay/resources/js/boot-admin.js | 4 ++ modules/ppcp-googlepay/services.php | 49 +++++++++++++ modules/ppcp-googlepay/src/Assets/Button.php | 5 ++ .../Settings/Fields/connection-tab-fields.php | 2 +- 14 files changed, 288 insertions(+), 61 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index f4fe807bc..c0cbc7f34 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -13,10 +13,20 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + + // Eligibility check. + if ( ! $container->has( 'applepay.eligible' ) || ! $container->get( 'applepay.eligible' ) ) { + return $fields; + } + + $is_available = $container->get( 'applepay.enabled' ); + $is_referral = $container->get( 'applepay.is_referral' ); + $insert_after = function ( array $array, string $key, array $new ): array { $keys = array_keys( $array ); $index = array_search( $key, $keys, true ); @@ -27,7 +37,25 @@ return array( $display_manager = $container->get( 'wcgateway.display-manager' ); assert( $display_manager instanceof DisplayManager ); - if ( ! $container->has( 'applepay.eligible' ) || ! $container->get( 'applepay.eligible' ) ) { + // Connection tab fields. + $fields = $insert_after( + $fields, + 'ppcp_dcc_status', + array( + 'applepay_status' => array( + 'title' => __( 'Apple Pay Payments', 'woocommerce-paypal-payments' ), + 'type' => 'ppcp-text', + 'text' => $container->get( 'applepay.settings.connection.status-text' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => Settings::CONNECTION_TAB_ID, + ), + ) + ); + + if ( ! $is_available && $is_referral ) { $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' ); $connection_link = ''; return $insert_after( @@ -35,11 +63,11 @@ return array( 'allow_card_button_gateway', array( 'applepay_button_enabled' => array( - 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), - 'type' => 'checkbox', - 'class' => array( 'ppcp-grayed-out-text' ), - 'input_class' => array( 'ppcp-disabled-checkbox' ), - 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' ) + 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'class' => array( 'ppcp-grayed-out-text' ), + 'input_class' => array( 'ppcp-disabled-checkbox' ), + 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' ) . '

    ' . sprintf( // translators: %1$s and %2$s are the opening and closing of HTML tag. @@ -48,10 +76,21 @@ return array( '' ) . '

    ', - 'default' => 'yes', - 'screens' => array( State::STATE_ONBOARDED ), - 'gateway' => 'paypal', - 'requirements' => array(), + 'default' => 'yes', + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), + 'custom_attributes' => array( + 'data-ppcp-display' => wp_json_encode( + array( + $display_manager + ->rule() + ->condition_is_true( false ) + ->action_enable( 'applepay_button_enabled' ) + ->to_array(), + ) + ), + ), ), ) ); diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index af9720636..a53b1bb4c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -31,10 +31,16 @@ class ApplepayButton { this.updated_contact_info = [] this.selectedShippingMethod = [] this.nonce = document.getElementById('woocommerce-process-checkout-nonce').value + + this.log = (message) => { + if ( this.buttonConfig.is_debug ) { + console.log('[ApplePayButton] ' + message, this); + } + } } init(config) { - console.log('[ApplePayButton] init', config); + this.log('init', config); if (this.isInitialized) { return; } @@ -53,12 +59,12 @@ class ApplepayButton { const id = "#apple-" + this.buttonConfig.button.wrapper; if (this.context === 'mini-cart') { - document.querySelector(id_minicart).addEventListener('click', (evt) => { + document.querySelector(id_minicart)?.addEventListener('click', (evt) => { evt.preventDefault(); this.onButtonClick(); }); } else { - document.querySelector(id).addEventListener('click', (evt) => { + document.querySelector(id)?.addEventListener('click', (evt) => { evt.preventDefault(); this.onButtonClick(); }); @@ -150,7 +156,10 @@ class ApplepayButton { const language = this.buttonConfig.button.lang; const color = this.buttonConfig.button.color; const id = "apple-" + wrapper; - appleContainer.innerHTML = ``; + + if (appleContainer) { + appleContainer.innerHTML = ``; + } jQuery('#' + wrapper).addClass('ppcp-button-' + shape); jQuery(wrapper).append(appleContainer); @@ -180,7 +189,7 @@ class ApplepayButton { console.error(error); } const session = this.applePaySession(paymentDataRequest) - console.log("session", session) + this.log("session", session) const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ? new FormValidator( PayPalCommerceGateway.ajax.validate_checkout.endpoint, @@ -242,7 +251,7 @@ class ApplepayButton { //------------------------ onvalidatemerchant(session) { - console.log("onvalidatemerchant") + this.log("onvalidatemerchant") return (applePayValidateMerchantEvent) => { paypal.Applepay().validateMerchant({ validationUrl: applePayValidateMerchantEvent.validationURL @@ -259,7 +268,7 @@ class ApplepayButton { 'woocommerce-process-checkout-nonce': this.nonce, } }) - console.log('validated') + this.log('validated') }) .catch(validateError => { console.error(validateError); @@ -279,7 +288,7 @@ class ApplepayButton { } onshippingmethodselected(session) { const ajax_url = this.buttonConfig.ajax_url - console.log('[ApplePayButton] onshippingmethodselected'); + this.log('onshippingmethodselected'); return (event) => { const data = this.getShippingMethodData(event); jQuery.ajax({ @@ -291,7 +300,7 @@ class ApplepayButton { if (applePayShippingMethodUpdate.success === false) { response.errors = createAppleErrors(response.errors) } - console.log('shipping method update response', response, applePayShippingMethodUpdate) + this.log('shipping method update response', response, applePayShippingMethodUpdate) this.selectedShippingMethod = event.shippingMethod //order the response shipping methods, so that the selected shipping method is the first one let orderedShippingMethods = response.newShippingMethods.sort((a, b) => { @@ -316,10 +325,10 @@ class ApplepayButton { } onshippingcontactselected(session) { const ajax_url = this.buttonConfig.ajax_url - console.log('[ApplePayButton] onshippingcontactselected', ajax_url, session) + this.log('[ApplePayButton] onshippingcontactselected', ajax_url, session) return (event) => { const data = this.getShippingContactData(event); - console.log('shipping contact selected', data, event) + this.log('shipping contact selected', data, event) jQuery.ajax({ url: ajax_url, method: 'POST', @@ -327,7 +336,7 @@ class ApplepayButton { success: (applePayShippingContactUpdate, textStatus, jqXHR) => { let response = applePayShippingContactUpdate.data this.updated_contact_info = event.shippingContact - console.log('shipping contact update response', response, applePayShippingContactUpdate, this.updated_contact_info) + this.log('shipping contact update response', response, applePayShippingContactUpdate, this.updated_contact_info) if (applePayShippingContactUpdate.success === false) { response.errors = createAppleErrors(response.errors) } @@ -421,7 +430,7 @@ class ApplepayButton { let createOrderInPayPal = actionHandler.createOrder() const processInWooAndCapture = async (data) => { try { - console.log('processInWooAndCapture', data) + this.log('processInWooAndCapture', data) const billingContact = data.billing_contact const shippingContact = data.shipping_contact jQuery.ajax({ @@ -443,7 +452,7 @@ class ApplepayButton { complete: (jqXHR, textStatus) => { }, success: (authorizationResult, textStatus, jqXHR) => { - console.log('success authorizationResult', authorizationResult) + this.log('success authorizationResult', authorizationResult) if (authorizationResult.result === "success") { redirectionUrl = authorizationResult.redirect; //session.completePayment(ApplePaySession.STATUS_SUCCESS) @@ -453,18 +462,18 @@ class ApplepayButton { } }, error: (jqXHR, textStatus, errorThrown) => { - console.log('error authorizationResult', errorThrown) + this.log('error authorizationResult', errorThrown) session.completePayment(ApplePaySession.STATUS_FAILURE) console.warn(textStatus, errorThrown) session.abort() }, }) } catch (error) { - console.log(error) // handle error + this.log(error) // handle error } } createOrderInPayPal([], []).then((orderId) => { - console.log('createOrderInPayPal', orderId) + this.log('createOrderInPayPal', orderId) paypal.Applepay().confirmOrder( { orderId: orderId, @@ -488,31 +497,31 @@ class ApplepayButton { } ); }).catch((error) => { - console.log(error) + console.error(error) session.abort() }) };*/ } /* onPaymentAuthorized(paymentData) { - console.log('[ApplePayButton] onPaymentAuthorized', this.context); + this.log('[ApplePayButton] onPaymentAuthorized', this.context); return this.processPayment(paymentData); } async processPayment(paymentData) { - console.log('[ApplePayButton] processPayment', this.context); + this.log('[ApplePayButton] processPayment', this.context); return new Promise(async (resolve, reject) => { try { let id = await this.contextHandler.createOrder(); - console.log('[ApplePayButton] processPayment: createOrder', id, this.context); + this.log('[ApplePayButton] processPayment: createOrder', id, this.context); const confirmOrderResponse = await paypal.Applepay().confirmOrder({ orderId: id, paymentMethodData: paymentData.paymentMethodData }); - console.log('[ApplePayButton] processPayment: confirmOrder', confirmOrderResponse, this.context); + this.log('[ApplePayButton] processPayment: confirmOrder', confirmOrderResponse, this.context); /!** Capture the Order on the Server *!/ if (confirmOrderResponse.status === "APPROVED") { @@ -554,7 +563,7 @@ class ApplepayButton { } } - console.log('[ApplePayButton] processPaymentResponse', response, this.context); + this.log('processPaymentResponse', response, this.context); return response; }*/ diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 784bdfabd..03acfcace 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -8,7 +8,7 @@ class ApplepayManager { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.ApplePayConfig = null; -console.log('Applepay manager', ppcpConfig, buttonConfig) + //console.log('Applepay manager', ppcpConfig, buttonConfig) this.buttons = []; buttonModuleWatcher.watchContextBootstrap((bootstrap) => { diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index de70f7f13..64f83690a 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -14,13 +14,13 @@ if (typeof window.PayPalCommerceGateway === 'undefined') { window.PayPalCommerceGateway = ppcpConfig; } -console.log('ppcpData', ppcpData); -console.log('ppcpConfig', ppcpConfig); -console.log('buttonData', buttonData); -console.log('buttonConfig', buttonConfig); +//console.log('ppcpData', ppcpData); +//console.log('ppcpConfig', ppcpConfig); +//console.log('buttonData', buttonData); +//console.log('buttonConfig', buttonConfig); const ApplePayComponent = () => { - console.log('ApplePayComponent render'); + //console.log('ApplePayComponent render'); const [bootstrapped, setBootstrapped] = useState(false); const [paypalLoaded, setPaypalLoaded] = useState(false); diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index 7b6d569e3..e9b750b2f 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -25,7 +25,7 @@ import ApplepayManager from "./ApplepayManager"; } const isMiniCart = ppcpConfig.mini_cart_buttons_enabled; const isButton = jQuery('#' + buttonConfig.button.wrapper).length > 0; - console.log('isbutton' ,isButton, buttonConfig.button.wrapper) + //console.log('isbutton' ,isButton, buttonConfig.button.wrapper) // If button wrapper is not present then there is no need to load the scripts. // minicart loads later? if (!isMiniCart && !isButton) { diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index fb38dd3a0..de5c22943 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -16,6 +16,9 @@ use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; use WooCommerce\PayPalCommerce\Applepay\Assets\DataToAppleButtonScripts; use WooCommerce\PayPalCommerce\Applepay\Assets\BlocksPaymentMethod; use WooCommerce\PayPalCommerce\Applepay\Helper\ApmApplies; +use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; +use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -36,6 +39,14 @@ return array( 'applepay.status-cache' => static function( ContainerInterface $container ): Cache { return new Cache( 'ppcp-paypal-apple-status-cache' ); }, + + // We assume it's a referral if we can check product status without API request failures. + 'applepay.is_referral' => static function ( ContainerInterface $container ): bool { + $status = $container->get( 'applepay.apple-product-status' ); + assert( $status instanceof AppleProductStatus ); + + return ! $status->has_request_failure(); + }, 'applepay.apple-product-status' => static function( ContainerInterface $container ): AppleProductStatus { return new AppleProductStatus( $container->get( 'wcgateway.settings' ), @@ -117,4 +128,52 @@ return array( ) ); }, + + 'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; + }, + + 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { + return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; + }, + + 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + $state = $container->get( 'onboarding.state' ); + if ( $state->current_state() < State::STATE_ONBOARDED ) { + return ''; + } + + $product_status = $container->get( 'applepay.apple-product-status' ); + assert( $product_status instanceof AppleProductStatus ); + + $environment = $container->get( 'onboarding.environment' ); + assert( $environment instanceof Environment ); + + $enabled = $product_status->apple_is_active(); + + $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' ); + $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' ); + + $button_text = $enabled + ? esc_html__( 'Settings', 'woocommerce-paypal-payments' ) + : esc_html__( 'Enable Apple Pay', 'woocommerce-paypal-payments' ); + + $enable_url = $environment->current_environment_is( Environment::PRODUCTION ) + ? $container->get( 'applepay.enable-url-live' ) + : $container->get( 'applepay.enable-url-sandbox' ); + + $button_url = $enabled + ? admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway#field-alternative_payment_methods' ) + : $enable_url; + + return sprintf( + '

    %1$s %2$s

    %5$s

    ', + $enabled ? $enabled_status_text : $disabled_status_text, + $enabled ? '' : '', + $enabled ? '_self' : '_blank', + esc_url( $button_url ), + esc_html( $button_text ) + ); + }, + ); diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 77d1bcf14..7bd4d09a8 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -207,6 +207,10 @@ class ApplePayButton implements ButtonInterface { * @return string */ public function add_apple_onboarding_option( $options ): string { + if ( ! apply_filters( 'woocommerce_paypal_payments_apple_pay_onboarding_option', false ) ) { + return $options; + } + $checked = ''; try { $onboard_with_apple = $this->settings->get( 'ppcp-onboarding-apple' ); diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 90f25b6c1..6558ac472 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -127,6 +127,7 @@ class DataToAppleButtonScripts { return array( 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', @@ -180,6 +181,7 @@ class DataToAppleButtonScripts { return array( 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, 'button' => array( 'wrapper' => 'applepay-container', 'mini_cart_wrapper' => 'applepay-container-minicart', diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 62cc1c029..27a904f03 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\Googlepay\Helper\PropertiesDictionary; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( @@ -38,6 +39,66 @@ return array( $display_manager = $container->get( 'wcgateway.display-manager' ); assert( $display_manager instanceof DisplayManager ); + // Connection tab fields. + $fields = $insert_after( + $fields, + 'ppcp_dcc_status', + array( + 'googlepay_status' => array( + 'title' => __( 'Google Pay Payments', 'woocommerce-paypal-payments' ), + 'type' => 'ppcp-text', + 'text' => $container->get( 'googlepay.settings.connection.status-text' ), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array(), + 'gateway' => Settings::CONNECTION_TAB_ID, + ), + ) + ); + + if ( ! $is_available && $is_referral ) { + $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' ); + $connection_link = ''; + return $insert_after( + $fields, + 'allow_card_button_gateway', + array( + 'googlepay_button_enabled' => array( + 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'class' => array( 'ppcp-grayed-out-text' ), + 'input_class' => array( 'ppcp-disabled-checkbox' ), + 'label' => __( 'Enable Google Pay button', 'woocommerce-paypal-payments' ) + . '

    ' + . sprintf( + // translators: %1$s and %2$s are the opening and closing of HTML tag. + __( 'Your PayPal account %1$srequires additional permissions%2$s to enable Google Pay.', 'woocommerce-paypal-payments' ), + $connection_link, + '' + ) + . '

    ', + 'default' => 'yes', + 'screens' => array( State::STATE_ONBOARDED ), + 'gateway' => 'paypal', + 'requirements' => array(), + 'custom_attributes' => array( + 'data-ppcp-display' => wp_json_encode( + array( + $display_manager + ->rule() + ->condition_is_true( false ) + ->action_enable( 'googlepay_button_enabled' ) + ->to_array(), + ) + ), + ), + ), + ) + ); + } + + // Standard Payments tab fields. return $insert_after( $fields, 'allow_card_button_gateway', @@ -69,15 +130,6 @@ return array( ->action_visible( 'googlepay_button_language' ) ->action_visible( 'googlepay_button_shipping_enabled' ) ->to_array(), - $display_manager - ->rule() - ->condition_is_true( $is_available || ! $is_referral ) - ->action_enable( 'googlepay_button_enabled' ) - ->action_enable( 'googlepay_button_type' ) - ->action_enable( 'googlepay_button_color' ) - ->action_enable( 'googlepay_button_language' ) - ->action_enable( 'googlepay_button_shipping_enabled' ) - ->to_array(), ) ), ), diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 43e3997ba..d004b8a41 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -23,7 +23,11 @@ class GooglepayButton { this.externalHandler ); - console.log('[GooglePayButton] new Button', this); + this.log = (message) => { + if ( this.buttonConfig.is_debug ) { + console.log('[GooglePayButton] ' + message, this); + } + } } init(config) { @@ -144,7 +148,7 @@ class GooglepayButton { * Add a Google Pay purchase button */ addButton(baseCardPaymentMethod) { - console.log('[GooglePayButton] addButton', this.context); + this.log('addButton', this.context); const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); @@ -171,10 +175,10 @@ class GooglepayButton { * Show Google Pay payment sheet when Google Pay payment button is clicked */ async onButtonClick() { - console.log('[GooglePayButton] onButtonClick', this.context); + this.log('onButtonClick', this.context); const paymentDataRequest = await this.paymentDataRequest(); - console.log('[GooglePayButton] onButtonClick: paymentDataRequest', paymentDataRequest, this.context); + this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. @@ -217,8 +221,8 @@ class GooglepayButton { } onPaymentDataChanged(paymentData) { - console.log('[GooglePayButton] onPaymentDataChanged', this.context); - console.log('[GooglePayButton] paymentData', paymentData); + this.log('onPaymentDataChanged', this.context); + this.log('paymentData', paymentData); return new Promise(async (resolve, reject) => { let paymentDataRequestUpdate = {}; @@ -274,25 +278,25 @@ class GooglepayButton { //------------------------ onPaymentAuthorized(paymentData) { - console.log('[GooglePayButton] onPaymentAuthorized', this.context); + this.log('onPaymentAuthorized', this.context); return this.processPayment(paymentData); } async processPayment(paymentData) { - console.log('[GooglePayButton] processPayment', this.context); + this.log('processPayment', this.context); return new Promise(async (resolve, reject) => { try { let id = await this.contextHandler.createOrder(); - console.log('[GooglePayButton] processPayment: createOrder', id, this.context); + this.log('processPayment: createOrder', id, this.context); const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({ orderId: id, paymentMethodData: paymentData.paymentMethodData }); - console.log('[GooglePayButton] processPayment: confirmOrder', confirmOrderResponse, this.context); + this.log('processPayment: confirmOrder', confirmOrderResponse, this.context); /** Capture the Order on the Server */ if (confirmOrderResponse.status === "APPROVED") { @@ -339,7 +343,7 @@ class GooglepayButton { } } - console.log('[GooglePayButton] processPaymentResponse', response, this.context); + this.log('processPaymentResponse', response, this.context); return response; } diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index c952b7b3b..5d28db9c8 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -77,6 +77,10 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi } const bootstrap = async function () { + if (!widgetBuilder.paypal) { + return; + } + googlePayConfig = await widgetBuilder.paypal.Googlepay().config(); let options; diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 0cf240b1d..02a9ccc9d 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -18,6 +18,8 @@ use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; +use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( @@ -182,4 +184,51 @@ return array( ); }, + 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; + }, + + 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { + return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; + }, + + 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + $state = $container->get( 'onboarding.state' ); + if ( $state->current_state() < State::STATE_ONBOARDED ) { + return ''; + } + + $product_status = $container->get( 'googlepay.helpers.apm-product-status' ); + assert( $product_status instanceof ApmProductStatus ); + + $environment = $container->get( 'onboarding.environment' ); + assert( $environment instanceof Environment ); + + $enabled = $product_status->is_active(); + + $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' ); + $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' ); + + $button_text = $enabled + ? esc_html__( 'Settings', 'woocommerce-paypal-payments' ) + : esc_html__( 'Enable Google Pay', 'woocommerce-paypal-payments' ); + + $enable_url = $environment->current_environment_is( Environment::PRODUCTION ) + ? $container->get( 'googlepay.enable-url-live' ) + : $container->get( 'googlepay.enable-url-sandbox' ); + + $button_url = $enabled + ? admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway#field-alternative_payment_methods' ) + : $enable_url; + + return sprintf( + '

    %1$s %2$s

    %5$s

    ', + $enabled ? $enabled_status_text : $disabled_status_text, + $enabled ? '' : '', + $enabled ? '_self' : '_blank', + esc_url( $button_url ), + esc_html( $button_text ) + ); + }, + ); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 8bc1c3d76..b4ad9c297 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -142,6 +142,10 @@ class Button implements ButtonInterface { * @psalm-suppress MissingClosureParamType */ public function add_onboarding_options( $options ): string { + if ( ! apply_filters( 'woocommerce_paypal_payments_google_pay_onboarding_option', false ) ) { + return $options; + } + $checked = ''; try { $onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' ); @@ -420,6 +424,7 @@ class Button implements ButtonInterface { return array( 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, 'sdk_url' => $this->sdk_url, 'button' => array( 'wrapper' => '#ppc-button-googlepay-container', diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php index d9d4d2596..067acf434 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php @@ -456,7 +456,7 @@ return function ( ContainerInterface $container, array $fields ): array { 'description' => __( 'If you use your PayPal account with more than one installation, please use a distinct prefix to separate those installations. Please use only English letters and "-", "_" characters.', 'woocommerce-paypal-payments' ), 'maxlength' => 15, 'custom_attributes' => array( - 'pattern' => '[a-zA-Z_-]+', + 'pattern' => '[a-zA-Z_\\-]+', ), 'default' => ( static function (): string { $site_url = get_site_url( get_current_blog_id() ); From 9db41ee34e84cc5fa16899954727915fca12f0f4 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 12 Oct 2023 18:49:27 +0100 Subject: [PATCH 22/45] Fix tests --- modules/ppcp-api-client/src/Helper/FailureRegistry.php | 2 +- modules/ppcp-api-client/src/Helper/OrderTransient.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Helper/FailureRegistry.php b/modules/ppcp-api-client/src/Helper/FailureRegistry.php index 0dbeb3b5b..68889b13c 100644 --- a/modules/ppcp-api-client/src/Helper/FailureRegistry.php +++ b/modules/ppcp-api-client/src/Helper/FailureRegistry.php @@ -17,7 +17,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Helper; */ class FailureRegistry { const CACHE_KEY = 'failure_registry'; - const CACHE_TIMEOUT = DAY_IN_SECONDS; // If necessary we can increase this. + const CACHE_TIMEOUT = 60 * 60 * 24; // DAY_IN_SECONDS, if necessary we can increase this. const SELLER_STATUS_KEY = 'seller_status'; diff --git a/modules/ppcp-api-client/src/Helper/OrderTransient.php b/modules/ppcp-api-client/src/Helper/OrderTransient.php index 09dac1af9..b6b7a0d99 100644 --- a/modules/ppcp-api-client/src/Helper/OrderTransient.php +++ b/modules/ppcp-api-client/src/Helper/OrderTransient.php @@ -21,7 +21,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; */ class OrderTransient { const CACHE_KEY = 'order_transient'; - const CACHE_TIMEOUT = DAY_IN_SECONDS; // If necessary we can increase this. + const CACHE_TIMEOUT = 60 * 60 * 24; // DAY_IN_SECONDS, if necessary we can increase this. /** * The Cache. From 799e06a7dd6f36cee4d31a018e88d45adb2e18cd Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 13 Oct 2023 10:04:17 +0100 Subject: [PATCH 23/45] Add cart simulation adjustments --- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index d004b8a41..f4efc17f0 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -229,6 +229,11 @@ class GooglepayButton { const updateData = new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data) const updatedData = await updateData.update(paymentData); + const transactionInfo = await this.contextHandler.transactionInfo(); + + updatedData.country_code = transactionInfo.countryCode; + updatedData.currency_code = transactionInfo.currencyCode; + updatedData.total_str = transactionInfo.totalPrice; // Handle unserviceable address. if(!updatedData.shipping_options || !updatedData.shipping_options.shippingOptions.length) { From 8526adbceecaa8346a258a8a37b7f750369671b7 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 13 Oct 2023 14:36:11 +0100 Subject: [PATCH 24/45] Add GooglePay shipping support. --- .../resources/js/ApplepayButton.js | 4 +- .../resources/js/Context/BaseHandler.js | 4 + .../js/Context/CheckoutBlockHandler.js | 4 + .../resources/js/Context/CheckoutHandler.js | 4 + .../resources/js/GooglepayButton.js | 17 ++- .../Endpoint/UpdatePaymentDataEndpoint.php | 132 +++++++++++------- 6 files changed, 101 insertions(+), 64 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index a53b1bb4c..aad77d435 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -32,9 +32,9 @@ class ApplepayButton { this.selectedShippingMethod = [] this.nonce = document.getElementById('woocommerce-process-checkout-nonce').value - this.log = (message) => { + this.log = function() { if ( this.buttonConfig.is_debug ) { - console.log('[ApplePayButton] ' + message, this); + console.log('[ApplePayButton]', ...arguments); } } } diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js index 73f4785c9..1074c94ec 100644 --- a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js @@ -10,6 +10,10 @@ class BaseHandler { this.externalHandler = externalHandler; } + shippingAllowed() { + return true; + } + transactionInfo() { return new Promise((resolve, reject) => { diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js index 9295c4302..3d24df9aa 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js @@ -2,6 +2,10 @@ import BaseHandler from "./BaseHandler"; class CheckoutBlockHandler extends BaseHandler{ + shippingAllowed() { + return false; + } + createOrder() { return this.externalHandler.createOrder(); } diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index ed8323a60..3f773a875 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -6,6 +6,10 @@ import FormValidator from "../../../../ppcp-button/resources/js/modules/Helper/F class CheckoutHandler extends BaseHandler { + shippingAllowed() { + return false; + } + transactionInfo() { return new Promise(async (resolve, reject) => { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index f4efc17f0..ede1a2416 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -23,9 +23,9 @@ class GooglepayButton { this.externalHandler ); - this.log = (message) => { + this.log = function() { if ( this.buttonConfig.is_debug ) { - console.log('[GooglePayButton] ' + message, this); + console.log('[GooglePayButton]', ...arguments); } } } @@ -109,7 +109,7 @@ class GooglepayButton { onPaymentAuthorized: this.onPaymentAuthorized.bind(this) } - if ( this.buttonConfig.shipping.enabled ) { + if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); } @@ -197,7 +197,7 @@ class GooglepayButton { paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - if ( this.buttonConfig.shipping.enabled ) { + if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; paymentDataRequest.shippingAddressRequired = true; paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); @@ -227,10 +227,12 @@ class GooglepayButton { return new Promise(async (resolve, reject) => { let paymentDataRequestUpdate = {}; - const updateData = new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data) - const updatedData = await updateData.update(paymentData); + const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); const transactionInfo = await this.contextHandler.transactionInfo(); + this.log('onPaymentDataChanged:updatedData', updatedData); + this.log('onPaymentDataChanged:transactionInfo', transactionInfo); + updatedData.country_code = transactionInfo.countryCode; updatedData.currency_code = transactionInfo.currencyCode; updatedData.total_str = transactionInfo.totalPrice; @@ -254,9 +256,6 @@ class GooglepayButton { } resolve(paymentDataRequestUpdate); - - // Update WooCommerce checkout form. - jQuery(document.body).trigger('update_checkout'); }); } diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index a60545290..63b461738 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -93,38 +93,8 @@ class UpdatePaymentDataEndpoint { wc_maybe_define_constant( 'WOOCOMMERCE_CART', true ); } - // Update shipping address. - if ( $payment_data['callbackTrigger'] === 'SHIPPING_ADDRESS' ) { - - /** - * The shipping methods. - * - * @var \WC_Customer|null $customer - */ - $customer = WC()->customer; - - if ( $customer ) { - $customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); - $customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); - $customer->set_billing_state( $payment_data['shippingAddress']['locality'] ?? '' ); - - $customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); - $customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); - $customer->set_shipping_state( $payment_data['shippingAddress']['locality'] ?? '' ); - - // Save the data. - $customer->save(); - - WC()->session->set( 'customer', WC()->customer->get_data() ); - } - } - - // Set shipping method. - WC()->shipping->calculate_shipping( WC()->cart->get_shipping_packages() ); - - $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); - $chosen_shipping_methods[0] = $payment_data['shippingOptionData']['id']; - WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); + $this->update_addresses( $payment_data ); + $this->update_shipping_method( $payment_data ); WC()->cart->calculate_shipping(); WC()->cart->calculate_fees(); @@ -162,38 +132,94 @@ class UpdatePaymentDataEndpoint { * @return array */ public function get_shipping_options(): array { - $shipping_methods = array(); + $shipping_options = array(); - $packages = WC()->cart->get_shipping_packages(); - $zone = \WC_Shipping_Zones::get_zone_matching_package( $packages[0] ); + $calculated_packages = WC()->shipping->calculate_shipping( + WC()->cart->get_shipping_packages() + ); - /** - * The shipping methods. - * - * @var \WC_Shipping_Method[] $methods - */ - $methods = $zone->get_shipping_methods( true ); + if ( ! isset( $calculated_packages[0] ) && ! isset( $calculated_packages[0]['rates'] ) ) { + return array(); + } - foreach ( $methods as $method ) { - if ( ! $method->is_available( $packages[0] ) ) { - continue; - } - - $shipping_methods[] = array( - 'id' => $method->get_rate_id(), - 'label' => $method->get_title(), - 'description' => '', + foreach ( $calculated_packages[0]['rates'] as $rate ) { + /** + * The shipping rate. + * + * @var \WC_Shipping_Rate $rate + */ + $shipping_options[] = array( + 'id' => $rate->get_id(), + 'label' => $rate->get_label(), + 'description' => html_entity_decode( + wp_strip_all_tags( + wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) ) + ) + ), ); } - if ( ! isset( $shipping_methods[0] ) ) { + if ( ! isset( $shipping_options[0] ) ) { return array(); } return array( - 'defaultSelectedOptionId' => $shipping_methods[0]['id'], - 'shippingOptions' => $shipping_methods, + 'defaultSelectedOptionId' => $shipping_options[0]['id'], + 'shippingOptions' => $shipping_options, ); } + /** + * Update addresses. + * + * @param array $payment_data The payment data. + * @return void + */ + private function update_addresses( array $payment_data ): void { + if ( ( $payment_data['callbackTrigger'] ?? '' ) !== 'SHIPPING_ADDRESS' ) { + return; + } + + /** + * The shipping methods. + * + * @var \WC_Customer|null $customer + */ + $customer = WC()->customer; + + if ( ! $customer ) { + return; + } + + $customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); + $customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); + $customer->set_billing_state( $payment_data['shippingAddress']['locality'] ?? '' ); + + $customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); + $customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); + $customer->set_shipping_state( $payment_data['shippingAddress']['locality'] ?? '' ); + + // Save the data. + $customer->save(); + + WC()->session->set( 'customer', WC()->customer->get_data() ); + } + + /** + * Update shipping method. + * + * @param array $payment_data The payment data. + * @return void + */ + private function update_shipping_method( array $payment_data ): void { + $rate_id = $payment_data['shippingOptionData']['id']; + $calculated_packages = WC()->shipping->calculate_shipping( + WC()->cart->get_shipping_packages() + ); + + if ( $rate_id && isset( $calculated_packages[0]['rates'][ $rate_id ] ) ) { + WC()->session->set( 'chosen_shipping_methods', array( $rate_id ) ); + } + } + } From bdf472c3dcffd0b7ef549309a34b767d1e097d70 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 13 Oct 2023 14:58:17 +0100 Subject: [PATCH 25/45] Fix default shipping method --- .../ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index 63b461738..803a73f3f 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -163,8 +163,10 @@ class UpdatePaymentDataEndpoint { return array(); } + $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); + return array( - 'defaultSelectedOptionId' => $shipping_options[0]['id'], + 'defaultSelectedOptionId' => ( $chosen_shipping_methods[0] ?? null ) ? $chosen_shipping_methods[0] : $shipping_options[0]['id'], 'shippingOptions' => $shipping_options, ); } From babd27977d27e6631bbf3e63b80a561262ec1134 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 13 Oct 2023 18:34:51 +0300 Subject: [PATCH 26/45] Simplify message re-rendering, use data-pp-amount --- .../resources/js/modules/Renderer/MessageRenderer.js | 10 ++-------- .../resources/js/modules/Renderer/WidgetBuilder.js | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js index 85f1475e6..b1261a466 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js @@ -28,15 +28,9 @@ class MessageRenderer { return; } - const newWrapper = document.createElement('div'); - newWrapper.setAttribute('id', this.config.wrapper.replace('#', '')); + const wrapper = document.querySelector(this.config.wrapper); this.currentNumber++; - newWrapper.setAttribute('data-render-number', this.currentNumber); - - const oldWrapper = document.querySelector(this.config.wrapper); - const sibling = oldWrapper.nextSibling; - oldWrapper.parentElement.removeChild(oldWrapper); - sibling.parentElement.insertBefore(newWrapper, sibling); + wrapper.setAttribute('data-render-number', this.currentNumber); widgetBuilder.registerMessages(this.config.wrapper, options); widgetBuilder.renderMessages(this.config.wrapper); diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js index 07d7c057c..e4d13844b 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js @@ -84,11 +84,14 @@ class WidgetBuilder { return; } + const entry = this.messages.get(wrapper); + if (this.hasRendered(wrapper)) { + const element = document.querySelector(wrapper); + element.setAttribute('data-pp-amount', entry.options.amount); return; } - const entry = this.messages.get(wrapper); const btn = this.paypal.Messages(entry.options); btn.render(entry.wrapper); From 886f214dc6391bc9c5933bd1196649ea00ef3882 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 13 Oct 2023 17:57:03 +0100 Subject: [PATCH 27/45] Fix GooglePay button on Pay Now page. --- .../ppcp-button/src/Assets/SmartButton.php | 31 ++++++++++++++++ .../src/Endpoint/CartScriptParamsEndpoint.php | 2 +- .../js/Context/ContextHandlerFactory.js | 4 ++- .../resources/js/Context/PayNowHandler.js | 35 +++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 426bb9251..390ec32f2 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use WC_Order; use WC_Product; use WC_Product_Variation; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; @@ -971,6 +972,10 @@ class SmartButton implements SmartButtonInterface { 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, ); + if ( 'pay-now' === $this->context() ) { + $localize['pay_now'] = $this->pay_now_script_data(); + } + if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { $localize['button']['mini_cart_style']['tagline'] = false; } @@ -991,6 +996,32 @@ class SmartButton implements SmartButtonInterface { return $localize; } + /** + * Returns pay-now payment data. + * + * @return array + */ + private function pay_now_script_data(): array { + $order_id = $this->get_order_pay_id(); + $base_location = wc_get_base_location(); + $shop_country_code = $base_location['country'] ?? ''; + $currency_code = get_woocommerce_currency(); + + $wc_order = wc_get_order( $order_id ); + if ( ! $wc_order instanceof WC_Order ) { + return array(); + } + + $total = (float) $wc_order->get_total( 'numeric' ); + + return array( + 'total' => $total, + 'total_str' => ( new Money( $total, $currency_code ) )->value_str(), + 'currency_code' => $currency_code, + 'country_code' => $shop_country_code, + ); + } + /** * If we can find the payer data for a current customer, we will return it. * diff --git a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php index dbf18e67b..83d4c74cf 100644 --- a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php @@ -76,7 +76,7 @@ class CartScriptParamsEndpoint implements EndpointInterface { // Shop settings. $base_location = wc_get_base_location(); - $shop_country_code = $base_location['country']; + $shop_country_code = $base_location['country'] ?? ''; $currency_code = get_woocommerce_currency(); wp_send_json_success( diff --git a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js index 445ab08ae..8c6bc261d 100644 --- a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js +++ b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js @@ -4,6 +4,7 @@ import CheckoutHandler from "./CheckoutHandler"; import CartBlockHandler from "./CartBlockHandler"; import CheckoutBlockHandler from "./CheckoutBlockHandler"; import MiniCartHandler from "./MiniCartHandler"; +import PayNowHandler from "./PayNowHandler"; import PreviewHandler from "./PreviewHandler"; class ContextHandlerFactory { @@ -15,8 +16,9 @@ class ContextHandlerFactory { case 'cart': return new CartHandler(buttonConfig, ppcpConfig, externalActionHandler); case 'checkout': - case 'pay-now': // same as checkout return new CheckoutHandler(buttonConfig, ppcpConfig, externalActionHandler); + case 'pay-now': + return new PayNowHandler(buttonConfig, ppcpConfig, externalActionHandler); case 'mini-cart': return new MiniCartHandler(buttonConfig, ppcpConfig, externalActionHandler); case 'cart-block': diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js new file mode 100644 index 000000000..add275608 --- /dev/null +++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js @@ -0,0 +1,35 @@ +import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner"; +import BaseHandler from "./BaseHandler"; +import CheckoutActionHandler + from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler"; + +class PayNowHandler extends BaseHandler { + + shippingAllowed() { + return false; + } + + transactionInfo() { + return new Promise(async (resolve, reject) => { + const data = this.ppcpConfig['pay_now']; + + resolve({ + countryCode: data.country_code, + currencyCode: data.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: data.total_str + }); + }); + } + + actionHandler() { + return new CheckoutActionHandler( + this.ppcpConfig, + this.errorHandler(), + new Spinner() + ); + } + +} + +export default PayNowHandler; From 8750cc01ccde27393a407d948bb695da0c4a6e41 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 09:59:17 +0100 Subject: [PATCH 28/45] Commented console logs in DisplayManager --- .../resources/js/common/display-manager/DisplayManager.js | 4 ++-- .../resources/js/common/display-manager/Rule.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js index 522238686..2aede73ee 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js @@ -14,11 +14,11 @@ class DisplayManager { addRule(ruleConfig) { const updateStatus = () => { this.ruleStatus[ruleConfig.key] = this.rules[ruleConfig.key].status; - console.log('ruleStatus', this.ruleStatus); + //console.log('ruleStatus', this.ruleStatus); } this.rules[ruleConfig.key] = new Rule(ruleConfig, updateStatus.bind(this)); - console.log('Rule', this.rules[ruleConfig.key]); + //console.log('Rule', this.rules[ruleConfig.key]); } register() { diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js index 75e88f141..20581b025 100644 --- a/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js +++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js @@ -15,14 +15,14 @@ class Rule { const condition = ConditionFactory.make(conditionConfig, updateStatus); this.conditions[condition.key] = condition; - console.log('Condition', condition); + //console.log('Condition', condition); } for (const actionConfig of this.config.actions) { const action = ActionFactory.make(actionConfig); this.actions[action.key] = action; - console.log('Action', action); + //console.log('Action', action); } } From 993191ff3452c2e714b7b043816653124d4fc193 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 12:07:07 +0100 Subject: [PATCH 29/45] Fix availability notice --- modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index 6ea3c0e62..82c250881 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -65,9 +65,12 @@ class AvailabilityNotice { return; } + // We need to check is active before checking failure requests, otherwise failure status won't be set. + $is_active = $this->product_status->is_active(); + if ( $this->product_status->has_request_failure() ) { $this->add_seller_status_failure_notice(); - } elseif ( ! $this->product_status->is_active() ) { + } elseif ( ! $is_active ) { $this->add_not_available_notice(); } } From b38658aeebb5af67d0f2b7068f9e6a48615dc95c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 15:32:25 +0100 Subject: [PATCH 30/45] Fix GooglePay preview rendering race condition. --- modules/ppcp-googlepay/resources/js/boot-admin.js | 4 +++- modules/ppcp-wc-gateway/resources/js/common.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 5d28db9c8..4cb606559 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -83,6 +83,9 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi googlePayConfig = await widgetBuilder.paypal.Googlepay().config(); + // We need to set bootstrapped here otherwise googlePayConfig may not be set. + bootstrapped = true; + let options; while (options = buttonQueue.pop()) { createButton(options.ppcpConfig); @@ -103,7 +106,6 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi const tryToBoot = () => { if (!bootstrapped && paypalLoaded && googlePayLoaded) { - bootstrapped = true; bootstrap(); } } diff --git a/modules/ppcp-wc-gateway/resources/js/common.js b/modules/ppcp-wc-gateway/resources/js/common.js index e1aad6028..ba763605b 100644 --- a/modules/ppcp-wc-gateway/resources/js/common.js +++ b/modules/ppcp-wc-gateway/resources/js/common.js @@ -16,7 +16,7 @@ document.addEventListener( jQuery( '*[data-ppcp-display]' ).each( (index, el) => { const rules = jQuery(el).data('ppcpDisplay'); - console.log('rules', rules); + // console.log('rules', rules); for (const rule of rules) { displayManager.addRule(rule); From 5233e9caf638749dc3b89f7fb160c5db03682d22 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 15:46:26 +0100 Subject: [PATCH 31/45] Removed duplicated nonce validation --- .../src/Endpoint/UpdatePaymentDataEndpoint.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index 803a73f3f..ef64ec4e6 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -69,16 +69,6 @@ class UpdatePaymentDataEndpoint { try { $data = $this->request_data->read_request( $this->nonce() ); - // Validate nonce. - if ( - ! isset( $data['nonce'] ) - || ! wp_verify_nonce( $data['nonce'], self::nonce() ) - ) { - throw new RuntimeException( - __( 'Could not validate nonce.', 'woocommerce-paypal-payments' ) - ); - } - // Validate payment data. if ( ! isset( $data['paymentData'] ) ) { throw new RuntimeException( From e867488ee2001e5b638bdc369620124ec9c3df43 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 16 Oct 2023 18:36:23 +0300 Subject: [PATCH 32/45] Fix message previews --- modules/ppcp-wc-gateway/resources/js/gateway-settings.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index c20432d48..44bb86021 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -172,11 +172,16 @@ document.addEventListener( function createMessagesPreview(settingsCallback) { const render = (settings) => { - const wrapper = document.querySelector(settings.wrapper); + let wrapper = document.querySelector(settings.wrapper); if (!wrapper) { return; } - wrapper.innerHTML = ''; + // looks like .innerHTML = '' is not enough, PayPal somehow renders with old style + const parent = wrapper.parentElement; + parent.removeChild(wrapper); + wrapper = document.createElement('div'); + wrapper.setAttribute('id', settings.wrapper.replace('#', '')); + parent.appendChild(wrapper); const messageRenderer = new MessageRenderer(settings); From 8847cbbd2182894f60a85008330e3e68ee27b008 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 17:06:55 +0100 Subject: [PATCH 33/45] Fix GooglePay notices. --- modules/ppcp-applepay/extensions.php | 2 +- modules/ppcp-googlepay/extensions.php | 2 +- modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index c0cbc7f34..38955fba0 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -57,7 +57,7 @@ return array( if ( ! $is_available && $is_referral ) { $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' ); - $connection_link = ''; + $connection_link = ''; return $insert_after( $fields, 'allow_card_button_gateway', diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 27a904f03..18c8955be 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -59,7 +59,7 @@ return array( if ( ! $is_available && $is_referral ) { $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' ); - $connection_link = ''; + $connection_link = ''; return $insert_after( $fields, 'allow_card_button_gateway', diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index 82c250881..d865094c1 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -110,13 +110,13 @@ class AvailabilityNotice { $message = sprintf( __( - '

    There was an error getting your PayPal seller status. Some features may be disabled.

    Certify that you connected to your PayPal business account via our onboarding process.

    ', + '

    Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our onboarding process to resolve this.

    Don\'t worry if you cannot use the onboarding process; most functionalities available to your account should work.

    ', 'woocommerce-paypal-payments' ) ); // Name the key so it can be overridden in other modules. - $notices['error_product_status'] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' ); + $notices['error_product_status'] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' ); return $notices; } ); From 9ab11b40e4410b3f151cf989f0abfe2cfc867035 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 16 Oct 2023 17:56:58 +0100 Subject: [PATCH 34/45] Fix GooglePay preview buttons --- modules/ppcp-googlepay/resources/js/boot-admin.js | 11 +++++++++++ .../ppcp-googlepay/src/Helper/AvailabilityNotice.php | 7 +++++-- modules/ppcp-onboarding/resources/js/settings.js | 10 ++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 4cb606559..577733b6b 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -36,6 +36,17 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi } }); + // Maybe we can find a more elegant reload method when transitioning from styling modes. + jQuery([ + '#ppcp-smart_button_enable_styling_per_location' + ].join(',')).on('change', () => { + setTimeout(() => { + for (const [selector, ppcpConfig] of Object.entries(activeButtons)) { + createButton(ppcpConfig); + } + }, 100); + }); + const applyConfigOptions = function (buttonConfig) { buttonConfig.button = buttonConfig.button || {}; buttonConfig.button.style = buttonConfig.button.style || {}; diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php index d865094c1..9cadbc91a 100644 --- a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php +++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php @@ -109,10 +109,13 @@ class AvailabilityNotice { static function ( $notices ): array { $message = sprintf( + // translators: %1$s and %2$s are the opening and closing of HTML
    tag. __( - '

    Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our onboarding process to resolve this.

    Don\'t worry if you cannot use the onboarding process; most functionalities available to your account should work.

    ', + '

    Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our %1$sonboarding process%2$s to resolve this.

    Don\'t worry if you cannot use the %1$sonboarding process%2$s; most functionalities available to your account should work.

    ', 'woocommerce-paypal-payments' - ) + ), + '
    ', + '' ); // Name the key so it can be overridden in other modules. diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js index 44fa692ed..f05f11ed3 100644 --- a/modules/ppcp-onboarding/resources/js/settings.js +++ b/modules/ppcp-onboarding/resources/js/settings.js @@ -19,7 +19,10 @@ document.addEventListener( if (! toggleElement.checked) { group.forEach( (elementToHide) => { - document.querySelector(elementToHide).style.display = 'none'; + const element = document.querySelector(elementToHide); + if (element) { + element.style.display = 'none'; + } }) } toggleElement.addEventListener( @@ -27,7 +30,10 @@ document.addEventListener( (event) => { if (! event.target.checked) { group.forEach( (elementToHide) => { - document.querySelector(elementToHide).style.display = 'none'; + const element = document.querySelector(elementToHide); + if (element) { + element.style.display = 'none'; + } }); return; From 0062f20c7a085768ca5aa4a4d9164ce72a741b89 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 16 Oct 2023 21:39:54 +0300 Subject: [PATCH 35/45] Allow loading messages separately from buttons --- .../ppcp-button/src/Assets/SmartButton.php | 49 ++++++++++++++----- .../ppcp-button/src/Helper/ContextTrait.php | 22 +++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 5e4533fee..c3e9cc046 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -528,8 +528,8 @@ class SmartButton implements SmartButtonInterface { * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded. */ public function should_load_ppcp_script(): bool { - $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); - if ( ! $buttons_enabled ) { + $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); + if ( ! $pcp_gateway_enabled ) { return false; } @@ -537,37 +537,65 @@ class SmartButton implements SmartButtonInterface { return false; } - return $this->should_load_buttons() || $this->can_render_dcc(); + return $this->should_load_buttons() || $this->should_load_messages() || $this->can_render_dcc(); } /** * Determines whether the button component should be loaded. */ public function should_load_buttons() : bool { - $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); - if ( ! $buttons_enabled ) { + $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); + if ( ! $pcp_gateway_enabled ) { return false; } $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() ); $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); - $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() ); switch ( $this->context() ) { case 'checkout': case 'cart': case 'pay-now': - return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location; case 'checkout-block': case 'cart-block': return $smart_button_enabled_for_current_location; case 'product': - return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart; + return $smart_button_enabled_for_current_location || $smart_button_enabled_for_mini_cart; default: return $smart_button_enabled_for_mini_cart; } } + /** + * Determines whether the Pay Later messages component should be loaded. + */ + public function should_load_messages() : bool { + $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ); + if ( ! $pcp_gateway_enabled ) { + return false; + } + + if ( ! $this->messages_apply->for_country() || $this->is_free_trial_cart() ) { + return false; + } + + $location = $this->location(); + + $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $location ); + + switch ( $location ) { + case 'checkout': + case 'cart': + case 'pay-now': + case 'product': + case 'shop': + case 'home': + return $messaging_enabled_for_current_location; + default: + return false; + } + } + /** * Whether DCC fields can be rendered. */ @@ -1159,10 +1187,7 @@ class SmartButton implements SmartButtonInterface { $components[] = 'buttons'; $components[] = 'funding-eligibility'; } - if ( - $this->messages_apply->for_country() - && ! $this->is_free_trial_cart() - ) { + if ( $this->should_load_messages() ) { $components[] = 'messages'; } if ( $this->dcc_is_enabled() ) { diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 77de2a078..291f5e16a 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -54,6 +54,28 @@ trait ContextTrait { return 'mini-cart'; } + /** + * The current location. + * + * @return string + */ + protected function location(): string { + $context = $this->context(); + if ( $context !== 'mini-cart' ) { + return $context; + } + + if ( is_shop() ) { + return 'shop'; + } + + if ( is_front_page() ) { + return 'home'; + } + + return ''; + } + /** * Checks if PayPal payment was already initiated (on the product or cart pages). * From 7da5d8d68a48ca8aa92cfa61018170168788c64c Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 16 Oct 2023 21:41:17 +0300 Subject: [PATCH 36/45] Simplify message wrapper rendering and add shop, home locations --- .../ContextBootstrap/CheckoutBootstap.js | 11 +- .../ppcp-button/src/Assets/SmartButton.php | 100 ++++--- .../ppcp-onboarding/resources/js/settings.js | 4 +- .../resources/js/gateway-settings.js | 2 +- modules/ppcp-wc-gateway/services.php | 8 +- .../Settings/Fields/pay-later-tab-fields.php | 248 ++++++++++++++++++ 6 files changed, 328 insertions(+), 45 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 27f9ca882..e7fae33b0 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -156,8 +156,15 @@ class CheckoutBootstap { } shouldShowMessages() { - return getCurrentPaymentMethod() === PaymentMethods.PAYPAL - && !PayPalCommerceGateway.is_free_trial_cart; + // hide when another method selected only if messages are near buttons + const messagesWrapper = document.querySelector(this.gateway.messages.wrapper); + if (getCurrentPaymentMethod() !== PaymentMethods.PAYPAL && + messagesWrapper && jQuery(messagesWrapper).closest('.ppc-button-wrapper').length + ) { + return false; + } + + return !PayPalCommerceGateway.is_free_trial_cart; } disableCreditCardFields() { diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index c3e9cc046..69905a960 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -382,54 +382,48 @@ class SmartButton implements SmartButtonInterface { * Registers the hooks to render the credit messaging HTML depending on the settings. * * @return bool - * @throws NotFoundException When a setting was not found. */ private function render_message_wrapper_registrar(): bool { if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) { return false; } - $selected_locations = $this->settings->has( 'pay_later_messaging_locations' ) ? $this->settings->get( 'pay_later_messaging_locations' ) : array(); + $location = $this->location(); - $not_enabled_on_cart = ! in_array( 'cart', $selected_locations, true ); + if ( ! $this->settings_status->is_pay_later_messaging_enabled_for_location( $location ) ) { + return false; + } + + $get_hook = function ( string $location ): ?array { + switch ( $location ) { + case 'checkout': + return $this->messages_renderer_hook( $location, 'woocommerce_review_order_after_submit', 11 ); + case 'cart': + return $this->messages_renderer_hook( $location, $this->proceed_to_checkout_button_renderer_hook(), 19 ); + case 'pay-now': + return $this->messages_renderer_hook( 'pay_order', 'woocommerce_pay_order_before_submit', 10 ); + case 'product': + return $this->messages_renderer_hook( $location, $this->single_product_renderer_hook(), 30 ); + case 'shop': + return $this->messages_renderer_hook( $location, 'woocommerce_archive_description', 10 ); + case 'home': + return $this->messages_renderer_hook( $location, 'loop_start', 20 ); + default: + return null; + } + }; + + $hook = $get_hook( $location ); + if ( ! $hook ) { + return false; + } add_action( - $this->proceed_to_checkout_button_renderer_hook(), - function() use ( $not_enabled_on_cart ) { - if ( ! is_cart() || $not_enabled_on_cart ) { - return; - } - $this->message_renderer(); - }, - 19 + $hook['name'], + array( $this, 'message_renderer' ), + $hook['priority'] ); - $not_enabled_on_product_page = ! in_array( 'product', $selected_locations, true ); - if ( - ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) - && ! $not_enabled_on_product_page - && ! is_checkout() - ) { - add_action( - $this->single_product_renderer_hook(), - array( $this, 'message_renderer' ), - 30 - ); - } - - $not_enabled_on_checkout = ! in_array( 'checkout', $selected_locations, true ); - if ( ! $not_enabled_on_checkout ) { - add_action( - $this->checkout_dcc_button_renderer_hook(), - array( $this, 'message_renderer' ), - 11 - ); - add_action( - $this->pay_order_renderer_hook(), - array( $this, 'message_renderer' ), - 15 - ); - } return true; } @@ -707,8 +701,7 @@ class SmartButton implements SmartButtonInterface { } $styling_per_location = $this->settings->has( 'pay_later_enable_styling_per_messaging_location' ) && $this->settings->get( 'pay_later_enable_styling_per_messaging_location' ); - $per_location = is_checkout() ? 'checkout' : ( is_cart() ? 'cart' : 'product' ); - $location = $styling_per_location ? $per_location : 'general'; + $location = $styling_per_location ? $this->location() : 'general'; $setting_name_prefix = "pay_later_{$location}_message"; $layout = $this->settings->has( "{$setting_name_prefix}_layout" ) ? $this->settings->get( "{$setting_name_prefix}_layout" ) : 'text'; @@ -1321,6 +1314,35 @@ class SmartButton implements SmartButtonInterface { return (string) apply_filters( 'woocommerce_paypal_payments_pay_order_dcc_renderer_hook', 'woocommerce_pay_order_after_submit' ); } + /** + * Returns the action name that will be used for rendering Pay Later messages. + * + * @param string $location The location name like 'checkout', 'shop'. See render_message_wrapper_registrar. + * @param string $default_hook The default name of the hook. + * @param int $default_priority The default priority of the hook. + * @return array An array with 'name' and 'priority' keys. + */ + private function messages_renderer_hook( string $location, string $default_hook, int $default_priority ): array { + /** + * The filter returning the action name that will be used for rendering Pay Later messages. + */ + $hook = (string) apply_filters( + "woocommerce_paypal_payments_${location}_messages_renderer_hook", + $default_hook + ); + /** + * The filter returning the action priority that will be used for rendering Pay Later messages. + */ + $priority = (int) apply_filters( + "woocommerce_paypal_payments_${location}_messages_renderer_priority", + $default_priority + ); + return array( + 'name' => $hook, + 'priority' => $priority, + ); + } + /** * Returns action name that PayPal button will use for rendering next to Proceed to checkout button (normally displayed in cart). * diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js index 44fa692ed..7485b3bc3 100644 --- a/modules/ppcp-onboarding/resources/js/settings.js +++ b/modules/ppcp-onboarding/resources/js/settings.js @@ -1,7 +1,7 @@ document.addEventListener( 'DOMContentLoaded', () => { - const payLaterMessagingSelectableLocations = ['product', 'cart', 'checkout']; + const payLaterMessagingSelectableLocations = ['product', 'cart', 'checkout', 'shop', 'home']; const payLaterMessagingAllLocations = payLaterMessagingSelectableLocations.concat('general'); const payLaterMessagingLocationsSelector = '#field-pay_later_messaging_locations'; const payLaterMessagingLocationsSelect = payLaterMessagingLocationsSelector + ' select'; @@ -9,7 +9,7 @@ document.addEventListener( const smartButtonLocationsSelector = '#field-smart_button_locations'; const smartButtonLocationsSelect = smartButtonLocationsSelector + ' select'; - const smartButtonSelectableLocations = payLaterMessagingSelectableLocations.concat('mini-cart'); + const smartButtonSelectableLocations = ['product', 'cart', 'checkout', 'mini-cart']; const groupToggle = (selector, group) => { const toggleElement = document.querySelector(selector); diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 44bb86021..e5b71f8d5 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -274,7 +274,7 @@ document.addEventListener( }, 1000)); loadPaypalScript(oldScriptSettings, () => { - const payLaterMessagingLocations = ['product', 'cart', 'checkout', 'general']; + const payLaterMessagingLocations = ['product', 'cart', 'checkout', 'shop', 'home', 'general']; const paypalButtonLocations = ['product', 'cart', 'checkout', 'mini-cart', 'general']; paypalButtonLocations.forEach((location) => { diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 0044f7ea1..3cd63a056 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1354,7 +1354,13 @@ return array( 'wcgateway.settings.pay-later.messaging-locations' => static function( ContainerInterface $container ): array { $button_locations = $container->get( 'wcgateway.button.locations' ); unset( $button_locations['mini-cart'] ); - return $button_locations; + return array_merge( + $button_locations, + array( + 'shop' => __( 'Shop', 'woocommerce-paypal-payments' ), + 'home' => __( 'Home', 'woocommerce-paypal-payments' ), + ) + ); }, 'wcgateway.button.default-locations' => static function( ContainerInterface $container ): array { return array_keys( $container->get( 'wcgateway.settings.pay-later.messaging-locations' ) ); diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php index 6428b1389..99a3fd3f0 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php +++ b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php @@ -623,6 +623,254 @@ return function ( ContainerInterface $container, array $fields ): array { 'requirements' => array( 'messages' ), 'gateway' => Settings::PAY_LATER_TAB_ID, ), + + // Shop. + 'pay_later_shop_messaging_heading' => array( + 'heading' => __( 'Pay Later Messaging on the Shop page', 'woocommerce-paypal-payments' ), + 'type' => 'ppcp-heading', + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array(), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_layout' => array( + 'title' => __( 'Shop Messaging Layout', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'text', + 'desc_tip' => true, + 'description' => __( 'The layout of the message.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'text' => __( 'Text', 'woocommerce-paypal-payments' ), + 'flex' => __( 'Banner', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_logo' => array( + 'title' => __( 'Shop Messaging Logo', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'inline', + 'desc_tip' => true, + 'description' => __( 'What logo the text message contains. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'primary' => __( 'Primary', 'woocommerce-paypal-payments' ), + 'alternative' => __( 'Alternative', 'woocommerce-paypal-payments' ), + 'inline' => __( 'Inline', 'woocommerce-paypal-payments' ), + 'none' => __( 'None', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_position' => array( + 'title' => __( 'Shop Messaging Logo Position', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'left', + 'desc_tip' => true, + 'description' => __( 'The position of the logo. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'left' => __( 'Left', 'woocommerce-paypal-payments' ), + 'right' => __( 'Right', 'woocommerce-paypal-payments' ), + 'top' => __( 'Top', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_color' => array( + 'title' => __( 'Shop Messaging Text Color', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'black', + 'desc_tip' => true, + 'description' => __( 'The color of the text. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'black' => __( 'Black', 'woocommerce-paypal-payments' ), + 'white' => __( 'White', 'woocommerce-paypal-payments' ), + 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ), + 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_flex_color' => array( + 'title' => __( 'Shop Messaging Color', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => $default_messaging_flex_color, + 'desc_tip' => true, + 'description' => __( 'The color of the text. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'blue' => __( 'Blue', 'woocommerce-paypal-payments' ), + 'black' => __( 'Black', 'woocommerce-paypal-payments' ), + 'white' => __( 'White', 'woocommerce-paypal-payments' ), + 'white-no-border' => __( 'White no border', 'woocommerce-paypal-payments' ), + 'gray' => __( 'Gray', 'woocommerce-paypal-payments' ), + 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ), + 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_flex_ratio' => array( + 'title' => __( 'Shop Messaging Ratio', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => '8x1', + 'desc_tip' => true, + 'description' => __( 'The width/height ratio of the banner. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + '1x1' => __( '1x1', 'woocommerce-paypal-payments' ), + '1x4' => __( '1x4', 'woocommerce-paypal-payments' ), + '8x1' => __( '8x1', 'woocommerce-paypal-payments' ), + '20x1' => __( '20x1', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_shop_message_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpShopMessagePreview', 'message', $messaging_message ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + + // Home. + 'pay_later_home_messaging_heading' => array( + 'heading' => __( 'Pay Later Messaging on the Home page', 'woocommerce-paypal-payments' ), + 'type' => 'ppcp-heading', + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array(), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_layout' => array( + 'title' => __( 'Home Messaging Layout', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'text', + 'desc_tip' => true, + 'description' => __( 'The layout of the message.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'text' => __( 'Text', 'woocommerce-paypal-payments' ), + 'flex' => __( 'Banner', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_logo' => array( + 'title' => __( 'Home Messaging Logo', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'inline', + 'desc_tip' => true, + 'description' => __( 'What logo the text message contains. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'primary' => __( 'Primary', 'woocommerce-paypal-payments' ), + 'alternative' => __( 'Alternative', 'woocommerce-paypal-payments' ), + 'inline' => __( 'Inline', 'woocommerce-paypal-payments' ), + 'none' => __( 'None', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_position' => array( + 'title' => __( 'Home Messaging Logo Position', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'left', + 'desc_tip' => true, + 'description' => __( 'The position of the logo. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'left' => __( 'Left', 'woocommerce-paypal-payments' ), + 'right' => __( 'Right', 'woocommerce-paypal-payments' ), + 'top' => __( 'Top', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_color' => array( + 'title' => __( 'Home Messaging Text Color', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => 'black', + 'desc_tip' => true, + 'description' => __( 'The color of the text. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'black' => __( 'Black', 'woocommerce-paypal-payments' ), + 'white' => __( 'White', 'woocommerce-paypal-payments' ), + 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ), + 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_flex_color' => array( + 'title' => __( 'Home Messaging Color', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => $default_messaging_flex_color, + 'desc_tip' => true, + 'description' => __( 'The color of the text. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + 'blue' => __( 'Blue', 'woocommerce-paypal-payments' ), + 'black' => __( 'Black', 'woocommerce-paypal-payments' ), + 'white' => __( 'White', 'woocommerce-paypal-payments' ), + 'white-no-border' => __( 'White no border', 'woocommerce-paypal-payments' ), + 'gray' => __( 'Gray', 'woocommerce-paypal-payments' ), + 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ), + 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_flex_ratio' => array( + 'title' => __( 'Home Messaging Ratio', 'woocommerce-paypal-payments' ), + 'type' => 'select', + 'class' => array(), + 'input_class' => array( 'wc-enhanced-select' ), + 'default' => '8x1', + 'desc_tip' => true, + 'description' => __( 'The width/height ratio of the banner. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ), + 'options' => array( + '1x1' => __( '1x1', 'woocommerce-paypal-payments' ), + '1x4' => __( '1x4', 'woocommerce-paypal-payments' ), + '8x1' => __( '8x1', 'woocommerce-paypal-payments' ), + '20x1' => __( '20x1', 'woocommerce-paypal-payments' ), + ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), + 'pay_later_home_message_preview' => array( + 'type' => 'ppcp-text', + 'text' => $render_preview_element( 'ppcpHomeMessagePreview', 'message', $messaging_message ), + 'screens' => array( State::STATE_ONBOARDED ), + 'requirements' => array( 'messages' ), + 'gateway' => Settings::PAY_LATER_TAB_ID, + ), ); return array_merge( $fields, $pay_later_fields ); From fa46b88b164e1dd63b061aaccc72e647b4e0592b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 08:44:35 +0300 Subject: [PATCH 37/45] Move checkout pay later messages outside of payment method --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 69905a960..bdfeb46ab 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -397,7 +397,7 @@ class SmartButton implements SmartButtonInterface { $get_hook = function ( string $location ): ?array { switch ( $location ) { case 'checkout': - return $this->messages_renderer_hook( $location, 'woocommerce_review_order_after_submit', 11 ); + return $this->messages_renderer_hook( $location, 'woocommerce_review_order_before_payment', 10 ); case 'cart': return $this->messages_renderer_hook( $location, $this->proceed_to_checkout_button_renderer_hook(), 19 ); case 'pay-now': From 874a4e92ff8c1ab0f72347445b0b340c3757ebf2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 09:21:33 +0300 Subject: [PATCH 38/45] Add hooks to allow rendering messages near buttons like before --- modules/ppcp-button/resources/css/gateway.scss | 4 ++++ modules/ppcp-button/src/Assets/SmartButton.php | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/modules/ppcp-button/resources/css/gateway.scss b/modules/ppcp-button/resources/css/gateway.scss index b563fcab9..f78d1b5b9 100644 --- a/modules/ppcp-button/resources/css/gateway.scss +++ b/modules/ppcp-button/resources/css/gateway.scss @@ -7,3 +7,7 @@ -webkit-filter: grayscale(100%); filter: grayscale(100%); } + +.ppc-button-wrapper #ppcp-messages:first-child { + padding-top: 10px; +} diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index bdfeb46ab..aaf97e13b 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -652,8 +652,24 @@ class SmartButton implements SmartButtonInterface { // The wrapper is needed for the loading spinner, // otherwise jQuery block() prevents buttons rendering. echo '
    '; + + $hook_gateway_id = str_replace( '-', '_', $gateway_id ); + /** + * A hook executed after rendering of the opening tag for the PCP wrapper (before the inner wrapper for the buttons). + * + * For the PayPal gateway the hook name is ppcp_start_button_wrapper_ppcp_gateway. + */ + do_action( 'ppcp_start_button_wrapper_' . $hook_gateway_id ); + echo '
    '; + /** + * A hook executed before rendering of the closing tag for the PCP wrapper (before the inner wrapper for the buttons). + * + * For the PayPal gateway the hook name is ppcp_end_button_wrapper_ppcp_gateway. + */ + do_action( 'ppcp_end_button_wrapper_' . $hook_gateway_id ); + if ( null !== $action_name ) { do_action( $action_name ); } From 9abaebf7dbae058ede37d00f96f630a3b34cb22c Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 09:58:30 +0300 Subject: [PATCH 39/45] Fix context() for checkout ajax refreshes --- .../ppcp-button/src/Helper/ContextTrait.php | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php index 291f5e16a..189796b75 100644 --- a/modules/ppcp-button/src/Helper/ContextTrait.php +++ b/modules/ppcp-button/src/Helper/ContextTrait.php @@ -12,6 +12,27 @@ namespace WooCommerce\PayPalCommerce\Button\Helper; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; trait ContextTrait { + /** + * Checks WC is_checkout() + WC checkout ajax requests. + */ + private function is_checkout(): bool { + if ( is_checkout() ) { + return true; + } + + /** + * The filter returning whether to detect WC checkout ajax requests. + */ + if ( apply_filters( 'ppcp_check_ajax_checkout', true ) ) { + // phpcs:ignore WordPress.Security + $wc_ajax = $_GET['wc-ajax'] ?? ''; + if ( in_array( $wc_ajax, array( 'update_order_review' ), true ) ) { + return true; + } + } + + return false; + } /** * The current context. @@ -23,7 +44,7 @@ trait ContextTrait { // Do this check here instead of reordering outside conditions. // In order to have more control over the context. - if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) { + if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) { return 'checkout'; } @@ -47,7 +68,7 @@ trait ContextTrait { return 'checkout-block'; } - if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) { + if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) { return 'checkout'; } From e4ec8f8630aad69ad064dad9bce85a2308fc7be3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 09:59:12 +0300 Subject: [PATCH 40/45] Fix is_checkout usage --- modules/ppcp-button/src/Assets/SmartButton.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index aaf97e13b..e298c1d1c 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -684,8 +684,10 @@ class SmartButton implements SmartButtonInterface { $product = wc_get_product(); + $location = $this->location(); + if ( - ! is_checkout() && is_a( $product, WC_Product::class ) + $location === 'product' && is_a( $product, WC_Product::class ) /** * The filter returning true if PayPal buttons can be rendered, or false otherwise. */ From 46c22c03e02beb2cfb10f7178f15bfd8c568764a Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 10:00:20 +0300 Subject: [PATCH 41/45] Improve message placement value --- .../ppcp-button/src/Assets/SmartButton.php | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e298c1d1c..17f2e249b 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -703,23 +703,47 @@ class SmartButton implements SmartButtonInterface { * The values for the credit messaging. * * @return array - * @throws NotFoundException When a setting was not found. */ private function message_values(): array { if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) { return array(); } - $placement = is_checkout() ? 'payment' : ( is_cart() ? 'cart' : 'product' ); - $product = wc_get_product(); - $amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0; + $location = $this->location(); + + switch ( $location ) { + case 'checkout': + case 'checkout-block': + case 'pay-now': + $placement = 'payment'; + break; + case 'cart': + case 'cart-block': + $placement = 'cart'; + break; + case 'product': + $placement = 'product'; + break; + case 'shop': + $placement = 'product-list'; + break; + case 'home': + $placement = 'home'; + break; + default: + $placement = 'payment'; + break; + } + + $product = wc_get_product(); + $amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0; if ( is_checkout() || is_cart() ) { $amount = WC()->cart->get_total( 'raw' ); } $styling_per_location = $this->settings->has( 'pay_later_enable_styling_per_messaging_location' ) && $this->settings->get( 'pay_later_enable_styling_per_messaging_location' ); - $location = $styling_per_location ? $this->location() : 'general'; + $location = $styling_per_location ? $location : 'general'; $setting_name_prefix = "pay_later_{$location}_message"; $layout = $this->settings->has( "{$setting_name_prefix}_layout" ) ? $this->settings->get( "{$setting_name_prefix}_layout" ) : 'text'; From 66a65991f09137cac31f60965ad42d3104b22746 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 12:38:19 +0300 Subject: [PATCH 42/45] Fix phpdoc Updates @since from 2563d41d83 --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 97a203276..413b9ebe7 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1176,7 +1176,7 @@ class SmartButton implements SmartButtonInterface { * Filter to add further components from the extensions. * * @internal Matches filter name in APM extension. - * @since TODO + * @since 2.3.0 * * @param array $components The array of components already registered. */ From 84e4c2c40a7c323c097bd484da13f0ae6c4ac52b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 17 Oct 2023 10:47:26 +0100 Subject: [PATCH 43/45] Fix GooglePay mini-cart button loading Fix GooglePay shipping callback Fix GooglePay button on cart updates --- .../resources/js/GooglepayButton.js | 51 +++++++++++++++---- .../resources/js/GooglepayManager.js | 6 +++ modules/ppcp-googlepay/resources/js/boot.js | 15 +++--- .../Endpoint/UpdatePaymentDataEndpoint.php | 8 +-- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index ede1a2416..9d2e1544b 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -60,6 +60,15 @@ class GooglepayButton { }); } + reinit() { + if (!this.googlePayConfig) { + return; + } + + this.isInitialized = false; + this.init(this.googlePayConfig); + } + validateConfig() { if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); @@ -152,19 +161,39 @@ class GooglepayButton { const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); - jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); + jQuery(wrapper).length - const button = - this.paymentsClient.createButton({ - onClick: this.onButtonClick.bind(this), - allowedPaymentMethods: [baseCardPaymentMethod], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', - buttonSizeMode: 'fill', - }); + this.waitForWrapper(wrapper, () => { + jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); - jQuery(wrapper).append(button); + const button = + this.paymentsClient.createButton({ + onClick: this.onButtonClick.bind(this), + allowedPaymentMethods: [baseCardPaymentMethod], + buttonColor: buttonStyle.color || 'black', + buttonType: buttonStyle.type || 'pay', + buttonLocale: buttonStyle.language || 'en', + buttonSizeMode: 'fill', + }); + + jQuery(wrapper).append(button); + }); + } + + waitForWrapper(selector, callback, delay = 100, timeout = 2000) { + const startTime = Date.now(); + const interval = setInterval(function() { + const el = document.querySelector(selector); + const timeElapsed = Date.now() - startTime; + + if (el) { + clearInterval(interval); + callback(el); + } else if (timeElapsed > timeout) { + clearInterval(interval); + console.error('Waiting for wrapper timed out.', selector); + } + }, delay); } //------------------------ diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 95aac2c2d..72475cfe5 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -38,6 +38,12 @@ class GooglepayManager { })(); } + reinit() { + for (const button of this.buttons) { + button.reinit(); + } + } + } export default GooglepayManager; diff --git a/modules/ppcp-googlepay/resources/js/boot.js b/modules/ppcp-googlepay/resources/js/boot.js index e6390c8dd..387822e67 100644 --- a/modules/ppcp-googlepay/resources/js/boot.js +++ b/modules/ppcp-googlepay/resources/js/boot.js @@ -8,11 +8,17 @@ import GooglepayManager from "./GooglepayManager"; jQuery }) { + let manager; + const bootstrap = function () { - const manager = new GooglepayManager(buttonConfig, ppcpConfig); + manager = new GooglepayManager(buttonConfig, ppcpConfig); manager.init(); }; + jQuery(document.body).on('updated_cart_totals updated_checkout', () => { + manager.reinit(); + }); + document.addEventListener( 'DOMContentLoaded', () => { @@ -20,12 +26,7 @@ import GooglepayManager from "./GooglepayManager"; (typeof (buttonConfig) === 'undefined') || (typeof (ppcpConfig) === 'undefined') ) { - console.error('PayPal button could not be configured.'); - return; - } - - // If button wrapper is not present then there is no need to load the scripts. - if (!jQuery(buttonConfig.button.wrapper).length) { + // No PayPal buttons present on this page. return; } diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php index ef64ec4e6..27da5ef48 100644 --- a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php +++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php @@ -168,7 +168,7 @@ class UpdatePaymentDataEndpoint { * @return void */ private function update_addresses( array $payment_data ): void { - if ( ( $payment_data['callbackTrigger'] ?? '' ) !== 'SHIPPING_ADDRESS' ) { + if ( ! in_array( $payment_data['callbackTrigger'] ?? '', array( 'SHIPPING_ADDRESS', 'INITIALIZE' ), true ) ) { return; } @@ -185,11 +185,13 @@ class UpdatePaymentDataEndpoint { $customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); $customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); - $customer->set_billing_state( $payment_data['shippingAddress']['locality'] ?? '' ); + $customer->set_billing_state( '' ); + $customer->set_billing_city( $payment_data['shippingAddress']['locality'] ?? '' ); $customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' ); $customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' ); - $customer->set_shipping_state( $payment_data['shippingAddress']['locality'] ?? '' ); + $customer->set_shipping_state( '' ); + $customer->set_shipping_city( $payment_data['shippingAddress']['locality'] ?? '' ); // Save the data. $customer->save(); From d86a1dd050f8c99ea160a11c6282db691e65c743 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 17 Oct 2023 10:50:38 +0100 Subject: [PATCH 44/45] Fix removed redundant code --- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9d2e1544b..57d29a75d 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -161,8 +161,6 @@ class GooglepayButton { const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); - jQuery(wrapper).length - this.waitForWrapper(wrapper, () => { jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); @@ -182,7 +180,7 @@ class GooglepayButton { waitForWrapper(selector, callback, delay = 100, timeout = 2000) { const startTime = Date.now(); - const interval = setInterval(function() { + const interval = setInterval(() => { const el = document.querySelector(selector); const timeElapsed = Date.now() - startTime; From aaa2025a8286c5436f0b69813b72ae66211640d4 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 17 Oct 2023 13:03:44 +0300 Subject: [PATCH 45/45] Remove @since --- modules/ppcp-button/src/Assets/SmartButton.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 413b9ebe7..53917077c 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1176,7 +1176,6 @@ class SmartButton implements SmartButtonInterface { * Filter to add further components from the extensions. * * @internal Matches filter name in APM extension. - * @since 2.3.0 * * @param array $components The array of components already registered. */