From 4b8843d93d64606b57d8492d83548fa83f7c81c9 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 12 Jul 2024 13:49:50 +0200 Subject: [PATCH 1/5] Google Pay: Fix the incorrect popup triggering (2645) --- .../resources/js/GooglepayButton.js | 89 +++++++++---------- .../resources/js/GooglepayManager.js | 57 ++++++++++-- .../ppcp-googlepay/resources/js/boot-admin.js | 6 +- 3 files changed, 96 insertions(+), 56 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index b3ca0a935..e94a49e1a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,4 +1,3 @@ -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"; @@ -7,7 +6,7 @@ import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/A class GooglepayButton { - constructor(context, externalHandler, buttonConfig, ppcpConfig) { + constructor(context, externalHandler, buttonConfig, ppcpConfig, contextHandler) { apmButtonsInit(ppcpConfig); this.isInitialized = false; @@ -16,24 +15,18 @@ class GooglepayButton { this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; + this.contextHandler = contextHandler; this.paymentsClient = null; - this.contextHandler = ContextHandlerFactory.create( - this.context, - this.buttonConfig, - this.ppcpConfig, - this.externalHandler - ); - this.log = function() { - if ( this.buttonConfig.is_debug ) { + if (this.buttonConfig.is_debug) { //console.log('[GooglePayButton]', ...arguments); } } } - init(config) { + init(config, transactionInfo) { if (this.isInitialized) { return; } @@ -48,6 +41,7 @@ class GooglepayButton { } this.googlePayConfig = config; + this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; @@ -73,16 +67,16 @@ class GooglepayButton { } this.isInitialized = false; - this.init(this.googlePayConfig); + this.init(this.googlePayConfig, this.transactionInfo); } validateConfig() { - if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { + if (['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); return false; } - if ( !this.contextHandler ) { + if (!this.contextHandler) { console.error('[GooglePayButton] Invalid context handler.', this.contextHandler); return false; } @@ -125,7 +119,7 @@ class GooglepayButton { onPaymentAuthorized: this.onPaymentAuthorized.bind(this) } - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { + if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); } @@ -218,7 +212,7 @@ class GooglepayButton { async onButtonClick() { this.log('onButtonClick', this.context); - const paymentDataRequest = await this.paymentDataRequest(); + const paymentDataRequest = this.paymentDataRequest(); this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. @@ -226,7 +220,7 @@ class GooglepayButton { this.paymentsClient.loadPaymentData(paymentDataRequest); } - async paymentDataRequest() { + paymentDataRequest() { let baseRequest = { apiVersion: 2, apiVersionMinor: 0 @@ -235,11 +229,11 @@ class GooglepayButton { const googlePayConfig = this.googlePayConfig; const paymentDataRequest = Object.assign({}, baseRequest); paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); + paymentDataRequest.transactionInfo = this.transactionInfo; paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; + if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { + paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; paymentDataRequest.shippingAddressRequired = true; paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); paymentDataRequest.shippingOptionRequired = true; @@ -266,37 +260,42 @@ class GooglepayButton { this.log('paymentData', paymentData); return new Promise(async (resolve, reject) => { - let paymentDataRequestUpdate = {}; + try { + let paymentDataRequestUpdate = {}; - const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); - const transactionInfo = await this.contextHandler.transactionInfo(); + const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); + const transactionInfo = this.transactionInfo; - this.log('onPaymentDataChanged:updatedData', updatedData); - this.log('onPaymentDataChanged:transactionInfo', 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; + updatedData.country_code = transactionInfo.countryCode; + updatedData.currency_code = transactionInfo.currencyCode; + updatedData.total_str = transactionInfo.totalPrice; + + // Handle unserviceable address. + if (!(updatedData.shipping_options?.shippingOptions?.length)) { + paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); + resolve(paymentDataRequestUpdate); + return; + } + + switch (paymentData.callbackTrigger) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); + break; + } - // Handle unserviceable address. - if (!(updatedData.shipping_options?.shippingOptions?.length)) { - paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); resolve(paymentDataRequestUpdate); - return; + } catch(error) { + console.error('Error during onPaymentDataChanged:', error); + reject(error); } - - switch (paymentData.callbackTrigger) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - } - - resolve(paymentDataRequestUpdate); }); } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 72475cfe5..e03a61ed8 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,5 +1,6 @@ import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; import GooglepayButton from "./GooglepayButton"; +import ContextHandlerFactory from "./Context/ContextHandlerFactory"; class GooglepayManager { @@ -8,34 +9,72 @@ class GooglepayManager { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.googlePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; this.buttons = []; - buttonModuleWatcher.watchContextBootstrap((bootstrap) => { + buttonModuleWatcher.watchContextBootstrap(async (bootstrap) => { + if (!this.contextHandler) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); + } + const button = new GooglepayButton( bootstrap.context, bootstrap.handler, buttonConfig, ppcpConfig, + this.contextHandler ); this.buttons.push(button); - if (this.googlePayConfig) { - button.init(this.googlePayConfig); + // Initialize button only if googlePayConfig and transactionInfo are already fetched. + if (this.googlePayConfig && this.transactionInfo) { + button.init(this.googlePayConfig, this.transactionInfo); + } else { + await this.init(); + if (this.googlePayConfig && this.transactionInfo) { + button.init(this.googlePayConfig, this.transactionInfo); + } } }); } - init() { - (async () => { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await paypal.Googlepay().config(); + async init() { + try { + if (!this.googlePayConfig) { + // Gets GooglePay configuration of the PayPal merchant. + this.googlePayConfig = await paypal.Googlepay().config(); + } + + if (!this.transactionInfo) { + this.transactionInfo = await this.fetchTransactionInfo(); + } for (const button of this.buttons) { - button.init(this.googlePayConfig); + button.init(this.googlePayConfig, this.transactionInfo); } - })(); + } catch(error) { + console.error('Error during initialization:', error); + } + } + + async fetchTransactionInfo() { + try { + if (!this.contextHandler) { + throw new Error('ContextHandler is not initialized'); + } + return await this.contextHandler.transactionInfo(); + } catch(error) { + console.error('Error fetching transaction info:', error); + throw error; + } } reinit() { diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 3e561e6e4..62fda993e 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -1,6 +1,7 @@ import GooglepayButton from './GooglepayButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; /** * Accessor that creates and returns a single PreviewButtonManager instance. @@ -94,9 +95,10 @@ class GooglePayPreviewButton extends PreviewButton { } createButton(buttonConfig) { - const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig); + const contextHandler = ContextHandlerFactory.create('preview', buttonConfig, this.ppcpConfig, null); + const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig, contextHandler); - button.init(this.apiConfig); + button.init(this.apiConfig, contextHandler.transactionInfo()); } /** From 525fee7edd17c88507cebfe61c068f7613856503 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 10:58:03 +0200 Subject: [PATCH 2/5] Remove the redundant async --- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index e94a49e1a..64ea52062 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -209,7 +209,7 @@ class GooglepayButton { /** * Show Google Pay payment sheet when Google Pay payment button is clicked */ - async onButtonClick() { + onButtonClick() { this.log('onButtonClick', this.context); const paymentDataRequest = this.paymentDataRequest(); From 8b43a06f9f298e465a80b61832b1c3dc5fc26e57 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 11:21:56 +0200 Subject: [PATCH 3/5] Fix JS linting --- .../resources/js/GooglepayButton.js | 866 ++++++++++-------- .../resources/js/GooglepayManager.js | 143 ++- .../ppcp-googlepay/resources/js/boot-admin.js | 174 ++-- 3 files changed, 638 insertions(+), 545 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 64ea52062..53a2f9931 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,397 +1,479 @@ -import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; -import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; -import UpdatePaymentData from "./Helper/UpdatePaymentData"; -import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons"; +import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; +import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; +import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; +import UpdatePaymentData from './Helper/UpdatePaymentData'; +import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; class GooglepayButton { - - constructor(context, externalHandler, buttonConfig, ppcpConfig, contextHandler) { - apmButtonsInit(ppcpConfig); - - this.isInitialized = false; - - this.context = context; - this.externalHandler = externalHandler; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.contextHandler = contextHandler; - - this.paymentsClient = null; - - this.log = function() { - if (this.buttonConfig.is_debug) { - //console.log('[GooglePayButton]', ...arguments); - } - } - } - - init(config, transactionInfo) { - if (this.isInitialized) { - return; - } - this.isInitialized = true; - - if (!this.validateConfig()) { - return; - } - - if (!this.contextHandler.validateContext()) { - return; - } - - this.googlePayConfig = config; - this.transactionInfo = transactionInfo; - this.allowedPaymentMethods = config.allowedPaymentMethods; - this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; - - this.initClient(); - this.initEventHandlers(); - - this.paymentsClient.isReadyToPay( - this.buildReadyToPayRequest(this.allowedPaymentMethods, config) - ) - .then((response) => { - if (response.result) { - this.addButton(this.baseCardPaymentMethod); - } - }) - .catch(function(err) { - console.error(err); - }); - } - - reinit() { - if (!this.googlePayConfig) { - return; - } - - this.isInitialized = false; - this.init(this.googlePayConfig, this.transactionInfo); - } - - validateConfig() { - if (['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { - console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); - return false; - } - - if (!this.contextHandler) { - console.error('[GooglePayButton] Invalid context handler.', this.contextHandler); - return false; - } - - return true; - } - - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - let config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper - } - - if (this.context === 'mini-cart') { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - - // Handle incompatible types. - if (config.buttonStyle.type === 'buy') { - config.buttonStyle.type = 'pay'; - } - } - - if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) { - config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - - initClient() { - const callbacks = { - onPaymentAuthorized: this.onPaymentAuthorized.bind(this) - } - - if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { - callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); - } - - this.paymentsClient = new google.payments.api.PaymentsClient({ - environment: this.buttonConfig.environment, - // add merchant info maybe - paymentDataCallbacks: callbacks - }); - } - - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - - if (wrapper === ppcpButtonWrapper) { - throw new Error(`[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${wrapper}"`); - } - - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper); - setVisible(wrapper, $ppcpButtonWrapper.is(':visible')); - setEnabled(wrapper, !$ppcpButtonWrapper.hasClass('ppcp-disabled')); - } - - jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => { - if (jQuery(data.selector).is(ppcpButtonWrapper)) { - syncButtonVisibility(); - } - }); - - syncButtonVisibility(); - } - - buildReadyToPayRequest(allowedPaymentMethods, baseRequest) { - return Object.assign({}, baseRequest, { - allowedPaymentMethods: allowedPaymentMethods, - }); - } - - /** - * Add a Google Pay purchase button - */ - addButton(baseCardPaymentMethod) { - this.log('addButton', this.context); - - const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); - - this.waitForWrapper(wrapper, () => { - jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); - - if (ppcpStyle.height) { - jQuery(wrapper).css('height', `${ppcpStyle.height}px`) - } - - 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(() => { - const el = document.querySelector(selector); - const timeElapsed = Date.now() - startTime; - - if (el) { - clearInterval(interval); - callback(el); - } else if (timeElapsed > timeout) { - clearInterval(interval); - } - }, delay); - } - - //------------------------ - // Button click - //------------------------ - - /** - * Show Google Pay payment sheet when Google Pay payment button is clicked - */ - onButtonClick() { - this.log('onButtonClick', this.context); - - const paymentDataRequest = this.paymentDataRequest(); - this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); - - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. - - this.paymentsClient.loadPaymentData(paymentDataRequest); - } - - paymentDataRequest() { - let baseRequest = { - apiVersion: 2, - apiVersionMinor: 0 - } - - const googlePayConfig = this.googlePayConfig; - const paymentDataRequest = Object.assign({}, baseRequest); - paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = this.transactionInfo; - paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - - if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; - paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); - paymentDataRequest.shippingOptionRequired = true; - } else { - paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; - } - - return paymentDataRequest; - } - - //------------------------ - // Shipping processing - //------------------------ - - shippingAddressParameters() { - return { - allowedCountryCodes: this.buttonConfig.shipping.countries, - phoneNumberRequired: true - }; - } - - onPaymentDataChanged(paymentData) { - this.log('onPaymentDataChanged', this.context); - this.log('paymentData', paymentData); - - return new Promise(async (resolve, reject) => { - try { - let paymentDataRequestUpdate = {}; - - const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); - const transactionInfo = this.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; - - // Handle unserviceable address. - if (!(updatedData.shipping_options?.shippingOptions?.length)) { - paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); - resolve(paymentDataRequestUpdate); - return; - } - - switch (paymentData.callbackTrigger) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - } - - resolve(paymentDataRequestUpdate); - } catch(error) { - console.error('Error during onPaymentDataChanged:', error); - reject(error); - } - }); - } - - unserviceableShippingAddressError() { - return { - reason: "SHIPPING_ADDRESS_UNSERVICEABLE", - message: "Cannot ship to the selected address", - intent: "SHIPPING_ADDRESS" - }; - } - - calculateNewTransactionInfo(updatedData) { - return { - countryCode: updatedData.country_code, - currencyCode: updatedData.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: updatedData.total_str - }; - } - - - //------------------------ - // Payment process - //------------------------ - - onPaymentAuthorized(paymentData) { - this.log('onPaymentAuthorized', this.context); - return this.processPayment(paymentData); - } - - async processPayment(paymentData) { - this.log('processPayment', this.context); - - return new Promise(async (resolve, reject) => { - try { - let id = await this.contextHandler.createOrder(); - - this.log('processPayment: createOrder', id, this.context); - - const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({ - orderId: id, - paymentMethodData: paymentData.paymentMethodData - }); - - this.log('processPayment: confirmOrder', confirmOrderResponse, this.context); - - /** Capture the Order on the Server */ - if (confirmOrderResponse.status === "APPROVED") { - - let approveFailed = false; - await this.contextHandler.approveOrder({ - orderID: id - }, { // actions mock object. - restart: () => new Promise((resolve, reject) => { - approveFailed = true; - resolve(); - }), - order: { - get: () => new Promise((resolve, reject) => { - resolve(null); - }) - } - }); - - if (!approveFailed) { - resolve(this.processPaymentResponse('SUCCESS')); - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'FAILED TO APPROVE')); - } - - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'TRANSACTION FAILED')); - } - } catch(err) { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', err.message)); - } - }); - } - - processPaymentResponse(state, intent = null, message = null) { - let response = { - transactionState: state, - } - - if (intent || message) { - response.error = { - intent: intent, - message: message, - } - } - - this.log('processPaymentResponse', response, this.context); - - return response; - } - + constructor( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ) { + apmButtonsInit( ppcpConfig ); + + this.isInitialized = false; + + this.context = context; + this.externalHandler = externalHandler; + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.contextHandler = contextHandler; + + this.paymentsClient = null; + + this.log = function () { + if ( this.buttonConfig.is_debug ) { + //console.log('[GooglePayButton]', ...arguments); + } + }; + } + + init( config, transactionInfo ) { + if ( this.isInitialized ) { + return; + } + this.isInitialized = true; + + if ( ! this.validateConfig() ) { + return; + } + + if ( ! this.contextHandler.validateContext() ) { + return; + } + + this.googlePayConfig = config; + this.transactionInfo = transactionInfo; + this.allowedPaymentMethods = config.allowedPaymentMethods; + this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; + + this.initClient(); + this.initEventHandlers(); + + this.paymentsClient + .isReadyToPay( + this.buildReadyToPayRequest( + this.allowedPaymentMethods, + config + ) + ) + .then( ( response ) => { + if ( response.result ) { + this.addButton( this.baseCardPaymentMethod ); + } + } ) + .catch( function ( err ) { + console.error( err ); + } ); + } + + reinit() { + if ( ! this.googlePayConfig ) { + return; + } + + this.isInitialized = false; + this.init( this.googlePayConfig, this.transactionInfo ); + } + + validateConfig() { + if ( + [ 'PRODUCTION', 'TEST' ].indexOf( + this.buttonConfig.environment + ) === -1 + ) { + console.error( + '[GooglePayButton] Invalid environment.', + this.buttonConfig.environment + ); + return false; + } + + if ( ! this.contextHandler ) { + console.error( + '[GooglePayButton] Invalid context handler.', + this.contextHandler + ); + return false; + } + + return true; + } + + /** + * Returns configurations relative to this button context. + */ + contextConfig() { + const config = { + wrapper: this.buttonConfig.button.wrapper, + ppcpStyle: this.ppcpConfig.button.style, + buttonStyle: this.buttonConfig.button.style, + ppcpButtonWrapper: this.ppcpConfig.button.wrapper, + }; + + if ( this.context === 'mini-cart' ) { + config.wrapper = this.buttonConfig.button.mini_cart_wrapper; + config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; + config.buttonStyle = this.buttonConfig.button.mini_cart_style; + config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; + + // Handle incompatible types. + if ( config.buttonStyle.type === 'buy' ) { + config.buttonStyle.type = 'pay'; + } + } + + if ( + [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 + ) { + config.ppcpButtonWrapper = + '#express-payment-method-ppcp-gateway-paypal'; + } + + return config; + } + + initClient() { + const callbacks = { + onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), + }; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + callbacks.onPaymentDataChanged = + this.onPaymentDataChanged.bind( this ); + } + + this.paymentsClient = new google.payments.api.PaymentsClient( { + environment: this.buttonConfig.environment, + // add merchant info maybe + paymentDataCallbacks: callbacks, + } ); + } + + initEventHandlers() { + const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); + } + + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + + syncButtonVisibility(); + } + + buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { + return Object.assign( {}, baseRequest, { + allowedPaymentMethods, + } ); + } + + /** + * Add a Google Pay purchase button + * @param baseCardPaymentMethod + */ + addButton( baseCardPaymentMethod ) { + this.log( 'addButton', this.context ); + + const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); + + this.waitForWrapper( wrapper, () => { + jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); + + if ( ppcpStyle.height ) { + jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); + } + + 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( () => { + const el = document.querySelector( selector ); + const timeElapsed = Date.now() - startTime; + + if ( el ) { + clearInterval( interval ); + callback( el ); + } else if ( timeElapsed > timeout ) { + clearInterval( interval ); + } + }, delay ); + } + + //------------------------ + // Button click + //------------------------ + + /** + * Show Google Pay payment sheet when Google Pay payment button is clicked + */ + onButtonClick() { + this.log( 'onButtonClick', this.context ); + + const paymentDataRequest = this.paymentDataRequest(); + this.log( + 'onButtonClick: paymentDataRequest', + paymentDataRequest, + this.context + ); + + window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. + + this.paymentsClient.loadPaymentData( paymentDataRequest ); + } + + paymentDataRequest() { + const baseRequest = { + apiVersion: 2, + apiVersionMinor: 0, + }; + + const googlePayConfig = this.googlePayConfig; + const paymentDataRequest = Object.assign( {}, baseRequest ); + paymentDataRequest.allowedPaymentMethods = + googlePayConfig.allowedPaymentMethods; + paymentDataRequest.transactionInfo = this.transactionInfo; + paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + paymentDataRequest.callbackIntents = [ + 'SHIPPING_ADDRESS', + 'SHIPPING_OPTION', + 'PAYMENT_AUTHORIZATION', + ]; + paymentDataRequest.shippingAddressRequired = true; + paymentDataRequest.shippingAddressParameters = + this.shippingAddressParameters(); + paymentDataRequest.shippingOptionRequired = true; + } else { + paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ]; + } + + return paymentDataRequest; + } + + //------------------------ + // Shipping processing + //------------------------ + + shippingAddressParameters() { + return { + allowedCountryCodes: this.buttonConfig.shipping.countries, + phoneNumberRequired: true, + }; + } + + onPaymentDataChanged( paymentData ) { + this.log( 'onPaymentDataChanged', this.context ); + this.log( 'paymentData', paymentData ); + + return new Promise( async ( resolve, reject ) => { + try { + const paymentDataRequestUpdate = {}; + + const updatedData = await new UpdatePaymentData( + this.buttonConfig.ajax.update_payment_data + ).update( paymentData ); + const transactionInfo = this.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; + + // Handle unserviceable address. + if ( ! updatedData.shipping_options?.shippingOptions?.length ) { + paymentDataRequestUpdate.error = + this.unserviceableShippingAddressError(); + resolve( paymentDataRequestUpdate ); + return; + } + + switch ( paymentData.callbackTrigger ) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = + updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + } + + resolve( paymentDataRequestUpdate ); + } catch ( error ) { + console.error( 'Error during onPaymentDataChanged:', error ); + reject( error ); + } + } ); + } + + unserviceableShippingAddressError() { + return { + reason: 'SHIPPING_ADDRESS_UNSERVICEABLE', + message: 'Cannot ship to the selected address', + intent: 'SHIPPING_ADDRESS', + }; + } + + calculateNewTransactionInfo( updatedData ) { + return { + countryCode: updatedData.country_code, + currencyCode: updatedData.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: updatedData.total_str, + }; + } + + //------------------------ + // Payment process + //------------------------ + + onPaymentAuthorized( paymentData ) { + this.log( 'onPaymentAuthorized', this.context ); + return this.processPayment( paymentData ); + } + + async processPayment( paymentData ) { + this.log( 'processPayment', this.context ); + + return new Promise( async ( resolve, reject ) => { + try { + const id = await this.contextHandler.createOrder(); + + this.log( 'processPayment: createOrder', id, this.context ); + + const confirmOrderResponse = await widgetBuilder.paypal + .Googlepay() + .confirmOrder( { + orderId: id, + paymentMethodData: paymentData.paymentMethodData, + } ); + + this.log( + 'processPayment: confirmOrder', + confirmOrderResponse, + this.context + ); + + /** Capture the Order on the Server */ + if ( confirmOrderResponse.status === 'APPROVED' ) { + let approveFailed = false; + await this.contextHandler.approveOrder( + { + orderID: id, + }, + { + // actions mock object. + restart: () => + new Promise( ( resolve, reject ) => { + approveFailed = true; + resolve(); + } ), + order: { + get: () => + new Promise( ( resolve, reject ) => { + resolve( null ); + } ), + }, + } + ); + + if ( ! approveFailed ) { + resolve( this.processPaymentResponse( 'SUCCESS' ) ); + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'FAILED TO APPROVE' + ) + ); + } + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'TRANSACTION FAILED' + ) + ); + } + } catch ( err ) { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + err.message + ) + ); + } + } ); + } + + processPaymentResponse( state, intent = null, message = null ) { + const response = { + transactionState: state, + }; + + if ( intent || message ) { + response.error = { + intent, + message, + }; + } + + this.log( 'processPaymentResponse', response, this.context ); + + return response; + } } export default GooglepayButton; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index e03a61ed8..71c07989e 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,88 +1,85 @@ -import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; -import GooglepayButton from "./GooglepayButton"; -import ContextHandlerFactory from "./Context/ContextHandlerFactory"; +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import GooglepayButton from './GooglepayButton'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; class GooglepayManager { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.googlePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; - constructor(buttonConfig, ppcpConfig) { + this.buttons = []; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.googlePayConfig = null; - this.transactionInfo = null; - this.contextHandler = null; + buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { + if ( ! this.contextHandler ) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); + } - this.buttons = []; + const button = new GooglepayButton( + bootstrap.context, + bootstrap.handler, + buttonConfig, + ppcpConfig, + this.contextHandler + ); - buttonModuleWatcher.watchContextBootstrap(async (bootstrap) => { - if (!this.contextHandler) { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); - } + this.buttons.push( button ); - const button = new GooglepayButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - this.contextHandler - ); + // Initialize button only if googlePayConfig and transactionInfo are already fetched. + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } else { + await this.init(); + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } + } ); + } - this.buttons.push(button); + async init() { + try { + if ( ! this.googlePayConfig ) { + // Gets GooglePay configuration of the PayPal merchant. + this.googlePayConfig = await paypal.Googlepay().config(); + } - // Initialize button only if googlePayConfig and transactionInfo are already fetched. - if (this.googlePayConfig && this.transactionInfo) { - button.init(this.googlePayConfig, this.transactionInfo); - } else { - await this.init(); - if (this.googlePayConfig && this.transactionInfo) { - button.init(this.googlePayConfig, this.transactionInfo); - } - } - }); - } + if ( ! this.transactionInfo ) { + this.transactionInfo = await this.fetchTransactionInfo(); + } - async init() { - try { - if (!this.googlePayConfig) { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await paypal.Googlepay().config(); - } + for ( const button of this.buttons ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } catch ( error ) { + console.error( 'Error during initialization:', error ); + } + } - if (!this.transactionInfo) { - this.transactionInfo = await this.fetchTransactionInfo(); - } - - for (const button of this.buttons) { - button.init(this.googlePayConfig, this.transactionInfo); - } - } catch(error) { - console.error('Error during initialization:', error); - } - } - - async fetchTransactionInfo() { - try { - if (!this.contextHandler) { - throw new Error('ContextHandler is not initialized'); - } - return await this.contextHandler.transactionInfo(); - } catch(error) { - console.error('Error fetching transaction info:', error); - throw error; - } - } - - reinit() { - for (const button of this.buttons) { - button.reinit(); - } - } + async fetchTransactionInfo() { + try { + if ( ! this.contextHandler ) { + throw new Error( 'ContextHandler is not initialized' ); + } + return await this.contextHandler.transactionInfo(); + } catch ( error ) { + console.error( 'Error fetching transaction info:', error ); + throw error; + } + } + reinit() { + for ( const button of this.buttons ) { + button.reinit(); + } + } } export default GooglepayManager; diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 62fda993e..2c0c008d2 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -7,110 +7,124 @@ import ContextHandlerFactory from './Context/ContextHandlerFactory'; * Accessor that creates and returns a single PreviewButtonManager instance. */ const buttonManager = () => { - if (!GooglePayPreviewButtonManager.instance) { - GooglePayPreviewButtonManager.instance = new GooglePayPreviewButtonManager(); - } + if ( ! GooglePayPreviewButtonManager.instance ) { + GooglePayPreviewButtonManager.instance = + new GooglePayPreviewButtonManager(); + } - return GooglePayPreviewButtonManager.instance; + return GooglePayPreviewButtonManager.instance; }; - /** * Manages all GooglePay preview buttons on this page. */ class GooglePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'GooglePay', - buttonConfig: window.wc_ppcp_googlepay_admin, - }; + constructor() { + const args = { + methodName: 'GooglePay', + buttonConfig: window.wc_ppcp_googlepay_admin, + }; - super(args); - } + super( args ); + } - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig(payPal) { - const apiMethod = payPal?.Googlepay()?.config; + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Googlepay()?.config; - if (!apiMethod) { - this.error('configuration object cannot be retrieved from PayPal'); - return {}; - } + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } - try { - return await apiMethod(); - } catch (error) { - if (error.message.includes('Not Eligible')) { - this.apiError = 'Not Eligible'; - } - return null; - } - } + try { + return await apiMethod(); + } catch ( error ) { + if ( error.message.includes( 'Not Eligible' ) ) { + this.apiError = 'Not Eligible'; + } + return null; + } + } - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {GooglePayPreviewButton} - */ - createButtonInstance(wrapperId) { - return new GooglePayPreviewButton({ - selector: wrapperId, - apiConfig: this.apiConfig, - }); - } + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {GooglePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new GooglePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + } ); + } } - /** * A single GooglePay preview button instance. */ class GooglePayPreviewButton extends PreviewButton { - constructor(args) { - super(args); + constructor( args ) { + super( args ); - this.selector = `${args.selector}GooglePay`; - this.defaultAttributes = { - button: { - style: { - type: 'pay', - color: 'black', - language: 'en', - }, - }, - }; - } + this.selector = `${ args.selector }GooglePay`; + this.defaultAttributes = { + button: { + style: { + type: 'pay', + color: 'black', + language: 'en', + }, + }, + }; + } - createNewWrapper() { - const element = super.createNewWrapper(); - element.addClass('ppcp-button-googlepay'); + createNewWrapper() { + const element = super.createNewWrapper(); + element.addClass( 'ppcp-button-googlepay' ); - return element; - } + return element; + } - createButton(buttonConfig) { - const contextHandler = ContextHandlerFactory.create('preview', buttonConfig, this.ppcpConfig, null); - const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig, contextHandler); + createButton( buttonConfig ) { + const contextHandler = ContextHandlerFactory.create( + 'preview', + buttonConfig, + this.ppcpConfig, + null + ); + const button = new GooglepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig, + contextHandler + ); - button.init(this.apiConfig, contextHandler.transactionInfo()); - } + button.init( this.apiConfig, contextHandler.transactionInfo() ); + } - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - */ - dynamicPreviewConfig(buttonConfig, ppcpConfig) { - // Merge the current form-values into the preview-button configuration. - if (ppcpConfig.button && buttonConfig.button) { - Object.assign(buttonConfig.button.style, ppcpConfig.button.style); - } - } + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button && buttonConfig.button ) { + Object.assign( buttonConfig.button.style, ppcpConfig.button.style ); + } + } } // Initialize the preview button manager. From 37ca92c57e9a9a83cd42a78626340b9b7e696fba Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 22:38:35 +0200 Subject: [PATCH 4/5] Fix the GooglePay button preview in the settings widget --- modules/ppcp-googlepay/resources/js/boot-admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 5f996bcde..b9f7b3c87 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -111,7 +111,7 @@ class GooglePayPreviewButton extends PreviewButton { contextHandler ); - button.init( this.apiConfig, contextHandler.transactionInfo() ); + button.init( this.apiConfig, null ); } /** From f66e6aff99cf5abf198bf09f55c9704ce44677da Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 17 Jul 2024 12:36:44 +0200 Subject: [PATCH 5/5] Google Pay: Fix the context being stuck on mini-cart --- .../resources/js/GooglepayManager.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 71c07989e..e267f1b8a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -13,14 +13,12 @@ class GooglepayManager { this.buttons = []; buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { - if ( ! this.contextHandler ) { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); - } + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); const button = new GooglepayButton( bootstrap.context,