From fc805a4369c5e4bb35a133af765a7730e74af8d2 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 6 Aug 2024 17:45:53 +0200
Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20most=20of=20the=20d?=
=?UTF-8?q?isplay=20logic=20to=20base=20class?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The PaymentButton base class now handles display logic that is shared between different APMs
---
.../js/modules/Helper/PaymentButtonHelpers.js | 69 +++
.../js/modules/Renderer/PaymentButton.js | 418 +++++++++++++++-
.../resources/js/GooglepayButton.js | 459 ++----------------
modules/ppcp-googlepay/src/Assets/Button.php | 7 +-
4 files changed, 539 insertions(+), 414 deletions(-)
diff --git a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js
index 19ecfc001..f9a066a23 100644
--- a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js
+++ b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js
@@ -15,6 +15,54 @@ export const ButtonEvents = Object.freeze( {
REDRAW: 'ppcp_redraw_method',
} );
+/**
+ *
+ * @param {string} defaultId - Default wrapper ID.
+ * @param {string} miniCartId - Wrapper inside the mini-cart.
+ * @param {string} smartButtonId - ID of the smart button wrapper.
+ * @param {string} blockId - Block wrapper ID (express checkout, block cart).
+ * @param {string} gatewayId - Gateway wrapper ID (classic checkout).
+ * @return {{MiniCart, Gateway, Block, SmartButton, Default}} List of all wrapper IDs, by context.
+ */
+export function combineWrapperIds(
+ defaultId = '',
+ miniCartId = '',
+ smartButtonId = '',
+ blockId = '',
+ gatewayId = ''
+) {
+ const sanitize = ( id ) => id.replace( /^#/, '' );
+
+ return {
+ Default: sanitize( defaultId ),
+ SmartButton: sanitize( smartButtonId ),
+ Block: sanitize( blockId ),
+ Gateway: sanitize( gatewayId ),
+ MiniCart: sanitize( miniCartId ),
+ };
+}
+
+/**
+ * Returns full payment button styles by combining the global ppcpConfig with
+ * payment-method-specific styling provided via buttonConfig.
+ *
+ * @param {Object} ppcpConfig - Global plugin configuration.
+ * @param {Object} buttonConfig - Payment method specific configuration.
+ * @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context.
+ */
+export function combineStyles( ppcpConfig, buttonConfig ) {
+ return {
+ Default: {
+ ...ppcpConfig.style,
+ ...buttonConfig.style,
+ },
+ MiniCart: {
+ ...ppcpConfig.mini_cart_style,
+ ...buttonConfig.mini_cart_style,
+ },
+ };
+}
+
/**
* Verifies if the given event name is a valid Payment Button event.
*
@@ -46,3 +94,24 @@ export function dispatchButtonEvent( { event, paymentMethod = '' } ) {
document.body.dispatchEvent( new Event( fullEventName ) );
}
+
+/**
+ * Adds an event listener for the provided button event.
+ *
+ * @param {Object} options - The options for the event listener.
+ * @param {string} options.event - Event to observe.
+ * @param {string} [options.paymentMethod] - The payment method name (optional).
+ * @param {Function} options.callback - The callback function to execute when the event is triggered.
+ * @throws {Error} Throws an error if the event is invalid.
+ */
+export function observeButtonEvent( { event, paymentMethod = '', callback } ) {
+ if ( ! isValidButtonEvent( event ) ) {
+ throw new Error( `Invalid event: ${ event }` );
+ }
+
+ const fullEventName = paymentMethod
+ ? `${ event }-${ paymentMethod }`
+ : event;
+
+ document.body.addEventListener( fullEventName, callback );
+}
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
index 20459f52e..8275d1ee4 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
@@ -1,5 +1,30 @@
import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger';
import { apmButtonsInit } from '../Helper/ApmButtons';
+import { PaymentContext } from '../Helper/CheckoutMethodState';
+import {
+ ButtonEvents,
+ dispatchButtonEvent,
+ observeButtonEvent,
+} from '../Helper/PaymentButtonHelpers';
+
+/**
+ * Collection of all available styling options for this button.
+ *
+ * @typedef {Object} StylesCollection
+ * @property {string} Default - Default button styling.
+ * @property {string} MiniCart - Styles for mini-cart button.
+ */
+
+/**
+ * Collection of all available wrapper IDs that are possible for the button.
+ *
+ * @typedef {Object} WrapperCollection
+ * @property {string} Default - Default button wrapper.
+ * @property {string} Gateway - Wrapper for separate gateway.
+ * @property {string} Block - Wrapper for block checkout button.
+ * @property {string} MiniCart - Wrapper for mini-cart button.
+ * @property {string} SmartButton - Wrapper for smart button container.
+ */
/**
* Base class for APM payment buttons, like GooglePay and ApplePay.
@@ -12,6 +37,11 @@ export default class PaymentButton {
*/
#logger;
+ /**
+ * @type {string}
+ */
+ #methodId;
+
/**
* Whether the payment button is initialized.
*
@@ -21,27 +51,105 @@ export default class PaymentButton {
/**
* The button's context.
+ *
+ * @type {string}
*/
#context;
+ /**
+ * Object containing the IDs of all possible wrapper elements that might contain this
+ * button; only one wrapper is relevant, depending on the value of the context.
+ *
+ * @type {Object}
+ */
+ #wrappers;
+
+ /**
+ * @type {StylesCollection}
+ */
+ #styles;
+
+ /**
+ * APM relevant configuration; e.g., configuration of the GooglePay button
+ */
#buttonConfig;
+ /**
+ * Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc.
+ */
#ppcpConfig;
- constructor( gatewayName, context, buttonConfig, ppcpConfig ) {
- this.#logger = new ConsoleLogger( gatewayName, context );
+ /**
+ * Whether the current browser/website support the payment method.
+ *
+ * @type {boolean}
+ */
+ #isEligible = false;
+
+ /**
+ * Whether this button is visible. Modified by `show()` and `hide()`
+ *
+ * @type {boolean}
+ */
+ #isVisible = true;
+
+ /**
+ * The currently visible payment button.
+ *
+ * @see {PaymentButton.insertButton}
+ * @type {HTMLElement|null}
+ */
+ #button = null;
+
+ /**
+ * Initialize the payment button instance.
+ *
+ * @param {string} methodId - Payment method ID (slug, e.g., "ppcp-googlepay").
+ * @param {string} context - Button context name.
+ * @param {WrapperCollection} wrappers - Button wrapper IDs, by context.
+ * @param {StylesCollection} styles - Button styles, by context.
+ * @param {Object} buttonConfig - Payment button specific configuration.
+ * @param {Object} ppcpConfig - Plugin wide configuration object.
+ */
+ constructor(
+ methodId,
+ context,
+ wrappers,
+ styles,
+ buttonConfig,
+ ppcpConfig
+ ) {
+ const methodName = methodId.replace( /^ppcp?-/, '' );
+
+ this.#methodId = methodId;
+
+ this.#logger = new ConsoleLogger( methodName, context );
this.#logger.enabled = !! buttonConfig?.is_debug;
this.#context = context;
+ this.#wrappers = wrappers;
+ this.#styles = styles;
this.#buttonConfig = buttonConfig;
this.#ppcpConfig = ppcpConfig;
apmButtonsInit( ppcpConfig );
+ this.initEventListeners();
}
/**
- * Whether the payment button was fully initialized. Read-only.
+ * Internal ID of the payment gateway.
*
+ * @readonly
+ * @return {string} The internal gateway ID.
+ */
+ get methodId() {
+ return this.#methodId;
+ }
+
+ /**
+ * Whether the payment button was fully initialized.
+ *
+ * @readonly
* @return {boolean} True indicates, that the button was fully initialized.
*/
get isInitialized() {
@@ -49,16 +157,188 @@ export default class PaymentButton {
}
/**
- * The button's context. Read-only.
+ * The button's context.
*
* TODO: Convert the string to a context-object (primitive obsession smell)
*
+ * @readonly
* @return {string} The button context.
*/
get context() {
return this.#context;
}
+ /**
+ * Button wrapper details.
+ *
+ * @readonly
+ * @return {WrapperCollection} Wrapper IDs.
+ */
+ get wrappers() {
+ return this.#wrappers;
+ }
+
+ /**
+ * Returns the context-relevant button style object.
+ *
+ * @readonly
+ * @return {string} Styling options.
+ */
+ get style() {
+ if ( PaymentContext.MiniCart === this.context ) {
+ return this.#styles.MiniCart;
+ }
+
+ return this.#styles.Default;
+ }
+
+ /**
+ * Returns the context-relevant wrapper ID.
+ *
+ * @readonly
+ * @return {string} The wrapper-element's ID (without the `#` prefix).
+ */
+ get wrapperId() {
+ if ( PaymentContext.MiniCart === this.context ) {
+ return this.wrappers.MiniCart;
+ } else if ( this.isSeparateGateway ) {
+ return this.wrappers.Gateway;
+ } else if ( PaymentContext.Blocks.includes( this.context ) ) {
+ return this.wrappers.Block;
+ }
+
+ return this.wrappers.Default;
+ }
+
+ /**
+ * Determines if the current payment button should be rendered as a stand-alone gateway.
+ * The return value `false` usually means, that the payment button is bundled with all available
+ * payment buttons.
+ *
+ * The decision depends on the button context (placement) and the plugin settings.
+ *
+ * @return {boolean} True, if the current button represents a stand-alone gateway.
+ */
+ get isSeparateGateway() {
+ return (
+ this.#buttonConfig.is_wc_gateway_enabled &&
+ PaymentContext.Gateways.includes( this.context )
+ );
+ }
+
+ /**
+ * Determines if the current button instance has valid and complete configuration details.
+ * Used during initialization to decide if the button can be initialized or should be skipped.
+ *
+ * Can be implemented by the derived class.
+ *
+ * @return {boolean} True indicates the config is valid and initialization can continue.
+ */
+ get isConfigValid() {
+ return true;
+ }
+
+ /**
+ * Whether the browser can accept this payment method.
+ *
+ * @return {boolean} True, if payments are technically possible.
+ */
+ get isEligible() {
+ return this.#isEligible;
+ }
+
+ /**
+ * Changes the eligibility state of this button component.
+ *
+ * @param {boolean} newState Whether the browser can accept payments.
+ */
+ set isEligible( newState ) {
+ if ( newState === this.#isEligible ) {
+ return;
+ }
+
+ this.#isEligible = newState;
+ this.triggerRedraw();
+ }
+
+ /**
+ * The visibility state of the button.
+ * This flag does not reflect actual visibility on the page, but rather, if the button
+ * is intended/allowed to be displayed, in case all other checks pass.
+ *
+ * @return {boolean} True indicates, that the button can be displayed.
+ */
+ get isVisible() {
+ return this.#isVisible;
+ }
+
+ /**
+ * Change the visibility of the button.
+ *
+ * A visible button does not always force the button to render on the page. It only means, that
+ * the button is allowed or not allowed to render, if certain other conditions are met.
+ *
+ * @param {boolean} newState Whether rendering the button is allowed.
+ */
+ set isVisible( newState ) {
+ if ( this.#isVisible === newState ) {
+ return;
+ }
+
+ this.#isVisible = newState;
+ this.triggerRedraw();
+ }
+
+ /**
+ * Returns the HTML element that wraps the current button
+ *
+ * @readonly
+ * @return {HTMLElement|null} The wrapper element, or null.
+ */
+ get wrapperElement() {
+ return document.getElementById( this.wrapperId );
+ }
+
+ /**
+ * Checks whether the main button-wrapper is present in the current DOM.
+ *
+ * @readonly
+ * @return {boolean} True, if the button context (wrapper element) is found.
+ */
+ get isPresent() {
+ return this.wrapperElement instanceof HTMLElement;
+ }
+
+ /**
+ * Returns an array of HTMLElements that belong to the payment button.
+ *
+ * @readonly
+ * @return {HTMLElement[]} List of payment button wrapper elements.
+ */
+ get allElements() {
+ const selectors = [];
+
+ // Payment button (Pay now, smart button block)
+ selectors.push( `#${ this.wrapperId }` );
+
+ // Block Checkout: Express checkout button.
+ if ( PaymentContext.Blocks.includes( this.context ) ) {
+ selectors.push( `#${ this.wrappers.Block }` );
+ }
+
+ // Classic Checkout: Separate gateway.
+ if ( this.isSeparateGateway ) {
+ selectors.push(
+ `.wc_payment_method.payment_method_${ this.methodId }`
+ );
+ }
+
+ this.log( 'Wrapper Elements:', selectors );
+ return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) =>
+ Array.from( document.querySelectorAll( selector ) )
+ );
+ }
+
/**
* Log a debug detail to the browser console.
*
@@ -98,4 +378,134 @@ export default class PaymentButton {
reinit() {
this.#isInitialized = false;
}
+
+ triggerRedraw() {
+ dispatchButtonEvent( {
+ event: ButtonEvents.REDRAW,
+ paymentMethod: this.methodId,
+ } );
+ }
+
+ /**
+ * Attaches event listeners to show or hide the payment button when needed.
+ */
+ initEventListeners() {
+ // Refresh the button - this might show, hide or re-create the payment button.
+ observeButtonEvent( {
+ event: ButtonEvents.REDRAW,
+ paymentMethod: this.methodId,
+ callback: () => this.refresh(),
+ } );
+
+ // Events relevant for buttons inside a payment gateway.
+ if ( PaymentContext.Gateways.includes( this.context ) ) {
+ // Hide the button right after the user selected _any_ gateway.
+ observeButtonEvent( {
+ event: ButtonEvents.INVALIDATE,
+ callback: () => ( this.isVisible = false ),
+ } );
+
+ // Show the button (again) when the user selected the current gateway.
+ observeButtonEvent( {
+ event: ButtonEvents.RENDER,
+ paymentMethod: this.methodId,
+ callback: () => ( this.isVisible = true ),
+ } );
+ }
+ }
+
+ /**
+ * Refreshes the payment button on the page.
+ */
+ refresh() {
+ const showButtonWrapper = () => {
+ this.log( 'Show' );
+
+ const styleSelectors = `style[data-hide-gateway="${ this.methodId }"]`;
+
+ document
+ .querySelectorAll( styleSelectors )
+ .forEach( ( el ) => el.remove() );
+
+ this.allElements.forEach( ( element ) => {
+ element.style.display = 'block';
+ } );
+ };
+
+ const hideButtonWrapper = () => {
+ this.log( 'Hide' );
+
+ this.allElements.forEach( ( element ) => {
+ element.style.display = 'none';
+ } );
+ };
+
+ // Refresh or hide the actual payment button.
+ if ( this.isVisible ) {
+ this.addButton();
+ } else {
+ this.removeButton();
+ }
+
+ // Show the wrapper or gateway entry, i.e. add space for the button.
+ if ( this.isEligible && this.isPresent ) {
+ showButtonWrapper();
+ } else {
+ hideButtonWrapper();
+ }
+ }
+
+ /**
+ * Prepares the button wrapper element and inserts the provided payment button into the DOM.
+ *
+ * @param {HTMLElement} button - The button element to inject.
+ */
+ insertButton( button ) {
+ if ( ! this.isPresent ) {
+ return;
+ }
+
+ if ( this.#button ) {
+ this.#button.remove();
+ }
+
+ this.#button = button;
+ this.log( 'addButton', button );
+
+ const wrapper = this.wrapperElement;
+ const { shape, height } = this.style;
+ const methodSlug = this.methodId.replace( /^ppcp?-/, '' );
+
+ wrapper.classList.add(
+ `ppcp-button-${ shape }`,
+ 'ppcp-button-apm',
+ `ppcp-button-${ methodSlug }`
+ );
+
+ if ( height ) {
+ wrapper.style.height = `${ height }px`;
+ }
+
+ wrapper.appendChild( button );
+ }
+
+ /**
+ * Removes the payment button from the DOM.
+ */
+ removeButton() {
+ if ( ! this.isPresent ) {
+ return;
+ }
+
+ this.log( 'removeButton' );
+
+ if ( this.#button ) {
+ this.#button.remove();
+ }
+ this.#button = null;
+
+ const wrapper = this.wrapperElement;
+
+ wrapper.innerHTML = '';
+ }
}
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
index f778ff8e2..347eb819b 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
@@ -1,12 +1,13 @@
/* global google */
+import {
+ combineStyles,
+ combineWrapperIds,
+} from '../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers';
import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton';
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
import UpdatePaymentData from './Helper/UpdatePaymentData';
-import {
- PaymentMethods,
- PaymentContext as CONTEXT,
-} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
+import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
/**
* Plugin-specific styling.
@@ -28,24 +29,6 @@ import {
*/
class GooglepayButton extends PaymentButton {
- #wrapperId = '';
- #ppcpButtonWrapperId = '';
-
- /**
- * Whether the current client support the payment button.
- * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()`
- *
- * @type {boolean}
- */
- #isEligible = false;
-
- /**
- * Whether this button is visible. Modified by `show()` and `hide()`
- *
- * @type {boolean}
- */
- #isVisible = false;
-
/**
* Client reference, provided by the Google Pay JS SDK.
* @see https://developers.google.com/pay/api/web/reference/client
@@ -59,221 +42,54 @@ class GooglepayButton extends PaymentButton {
ppcpConfig,
contextHandler
) {
- super( 'GooglePayButton', context, buttonConfig, ppcpConfig );
+ const wrappers = combineWrapperIds(
+ buttonConfig.button.wrapper,
+ buttonConfig.button.mini_cart_wrapper,
+ ppcpConfig.button.wrapper,
+ 'express-payment-method-ppcp-googlepay',
+ 'ppc-button-ppcp-googlepay'
+ );
+
+ console.log( ppcpConfig.button, buttonConfig.button );
+
+ const styles = combineStyles( ppcpConfig.button, buttonConfig.button );
+
+ if ( 'buy' === styles.MiniCart.type ) {
+ styles.MiniCart.type = 'pay';
+ }
+
+ super(
+ PaymentMethods.GOOGLEPAY,
+ context,
+ wrappers,
+ styles,
+ buttonConfig,
+ ppcpConfig
+ );
- this.externalHandler = externalHandler;
this.buttonConfig = buttonConfig;
- this.ppcpConfig = ppcpConfig;
this.contextHandler = contextHandler;
- this.refresh = this.refresh.bind( this );
-
this.log( 'Create instance' );
}
/**
- * Determines if the current payment button should be rendered as a stand-alone gateway.
- * The return value `false` usually means, that the payment button is bundled with all available
- * payment buttons.
- *
- * The decision depends on the button context (placement) and the plugin settings.
- *
- * @return {boolean} True, if the current button represents a stand-alone gateway.
+ * @inheritDoc
*/
- get isSeparateGateway() {
- return (
- this.buttonConfig.is_wc_gateway_enabled &&
- CONTEXT.Gateways.includes( this.context )
- );
- }
+ get isConfigValid() {
+ const validEnvs = [ 'PRODUCTION', 'TEST' ];
- /**
- * Returns the wrapper ID for the current button context.
- * The ID varies for the MiniCart context.
- *
- * @return {string} The wrapper-element's ID (without the `#` prefix).
- */
- get wrapperId() {
- if ( ! this.#wrapperId ) {
- let id;
-
- if ( CONTEXT.MiniCart === this.context ) {
- id = this.buttonConfig.button.mini_cart_wrapper;
- } else if ( this.isSeparateGateway ) {
- id = 'ppc-button-ppcp-googlepay';
- } else {
- id = this.buttonConfig.button.wrapper;
- }
-
- this.#wrapperId = id.replace( /^#/, '' );
+ if ( ! validEnvs.includes( this.buttonConfig.environment ) ) {
+ this.error( 'Invalid environment.', this.buttonConfig.environment );
+ return false;
}
- return this.#wrapperId;
- }
-
- /**
- * Returns the wrapper ID for the ppcpButton
- *
- * @return {string} The wrapper-element's ID (without the `#` prefix).
- */
- get ppcpButtonWrapperId() {
- if ( ! this.#ppcpButtonWrapperId ) {
- let id;
-
- if ( CONTEXT.MiniCart === this.context ) {
- id = this.ppcpConfig.button.mini_cart_wrapper;
- } else if ( CONTEXT.Blocks.includes( this.context ) ) {
- id = 'express-payment-method-ppcp-gateway-paypal';
- } else {
- id = this.ppcpConfig.button.wrapper;
- }
-
- this.#ppcpButtonWrapperId = id.replace( /^#/, '' );
+ if ( ! typeof this.contextHandler?.validateContext() ) {
+ this.error( 'Invalid context handler.', this.contextHandler );
+ return false;
}
- return this.#ppcpButtonWrapperId;
- }
-
- /**
- * Returns the context-relevant PPCP style object.
- * The style for the MiniCart context can be different.
- *
- * The PPCP style are custom style options, that are provided by this plugin.
- *
- * @return {PPCPStyle} The style object.
- */
- get ppcpStyle() {
- if ( CONTEXT.MiniCart === this.context ) {
- return this.ppcpConfig.button.mini_cart_style;
- }
-
- return this.ppcpConfig.button.style;
- }
-
- /**
- * Returns default style options that are propagated to and rendered by the Google Pay button.
- *
- * These styles are the official style options provided by the Google Pay SDK.
- *
- * @return {GooglePayStyle} The style object.
- */
- get buttonStyle() {
- let style;
-
- if ( CONTEXT.MiniCart === this.context ) {
- style = this.buttonConfig.button.mini_cart_style;
-
- // Handle incompatible types.
- if ( style.type === 'buy' ) {
- style.type = 'pay';
- }
- } else {
- style = this.buttonConfig.button.style;
- }
-
- return {
- type: style.type,
- language: style.language,
- color: style.color,
- };
- }
-
- /**
- * Returns the HTML element that wraps the current button
- *
- * @return {HTMLElement|null} The wrapper element, or null.
- */
- get wrapperElement() {
- return document.getElementById( this.wrapperId );
- }
-
- /**
- * Returns an array of HTMLElements that belong to the payment button.
- *
- * @return {HTMLElement[]} List of payment button wrapper elements.
- */
- get allElements() {
- const selectors = [];
-
- // Payment button (Pay now, smart button block)
- selectors.push( `#${ this.wrapperId }` );
-
- // Block Checkout: Express checkout button.
- if ( CONTEXT.Blocks.includes( this.context ) ) {
- selectors.push( '#express-payment-method-ppcp-googlepay' );
- }
-
- // Classic Checkout: Google Pay gateway.
- if ( CONTEXT.Gateways === this.context ) {
- selectors.push(
- '.wc_payment_method.payment_method_ppcp-googlepay'
- );
- }
-
- this.log( 'Wrapper Elements:', selectors );
- return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) =>
- Array.from( document.querySelectorAll( selector ) )
- );
- }
-
- /**
- * Checks whether the main button-wrapper is present in the current DOM.
- *
- * @return {boolean} True, if the button context (wrapper element) is found.
- */
- get isPresent() {
- return this.wrapperElement instanceof HTMLElement;
- }
-
- /**
- * The visibility state of the button.
- * This flag does not reflect actual visibility on the page, but rather, if the button
- * is intended/allowed to be displayed, in case all other checks pass.
- *
- * @return {boolean} True indicates, that the button can be displayed
- */
- get isVisible() {
- return this.#isVisible;
- }
-
- /**
- * Change the visibility of the button.
- *
- * A visible button does not always force the button to render on the page. It only means, that
- * the button is allowed or not allowed to render, if certain other conditions are met.
- *
- * @param {boolean} newState Whether rendering the button is allowed.
- */
- set isVisible( newState ) {
- if ( this.#isVisible === newState ) {
- return;
- }
-
- this.#isVisible = newState;
- this.refresh();
- }
-
- /**
- * Whether the browser can accept Google Pay payments.
- *
- * @return {boolean} True, if payments are technically possible.
- */
- get isEligible() {
- return this.#isEligible;
- }
-
- /**
- * Changes the eligibility state of this button component.
- *
- * @param {boolean} newState Whether the browser can accept payments.
- */
- set isEligible( newState ) {
- if ( newState === this.#isEligible ) {
- return;
- }
-
- this.#isEligible = newState;
- this.refresh();
+ return true;
}
init( config = null, transactionInfo = null ) {
@@ -288,27 +104,24 @@ class GooglepayButton extends PaymentButton {
}
if ( ! this.googlePayConfig || ! this.transactionInfo ) {
- this.error(
- 'Init called without providing config or transactionInfo'
- );
+ this.error( 'Missing config or transactionInfo during init.' );
return;
}
- if ( ! this.validateConfig() ) {
+ if ( ! this.isConfigValid ) {
return;
}
- if ( ! this.contextHandler.validateContext() ) {
- return;
- }
-
- super.init();
-
this.allowedPaymentMethods = config.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
+ super.init();
this.initClient();
- this.initEventHandlers();
+
+ if ( ! this.isPresent ) {
+ this.log( 'Payment wrapper not found', this.wrapperId );
+ return;
+ }
this.paymentsClient
.isReadyToPay(
@@ -319,17 +132,7 @@ class GooglepayButton extends PaymentButton {
)
.then( ( response ) => {
this.log( 'PaymentsClient.isReadyToPay response:', response );
-
- /**
- * In case the button wrapper element is not present in the DOM yet, wait for it
- * to appear. Only proceed, if a button wrapper is found on this page.
- *
- * Not sure if this is needed, or if we can directly test for `this.isPresent`
- * without any delay.
- */
- this.waitForWrapper( () => {
- this.isEligible = !! response.result;
- } );
+ this.isEligible = !! response.result;
} )
.catch( ( err ) => {
console.error( err );
@@ -346,30 +149,6 @@ class GooglepayButton extends PaymentButton {
this.init();
}
- validateConfig() {
- if (
- [ 'PRODUCTION', 'TEST' ].indexOf(
- this.buttonConfig.environment
- ) === -1
- ) {
- console.error(
- '[GooglePayButton] Invalid environment.',
- this.buttonConfig.environment
- );
- return false;
- }
-
- if ( ! this.contextHandler ) {
- console.error(
- '[GooglePayButton] Invalid context handler.',
- this.contextHandler
- );
- return false;
- }
-
- return true;
- }
-
initClient() {
const callbacks = {
onPaymentAuthorized: this.onPaymentAuthorized.bind( this ),
@@ -394,51 +173,6 @@ class GooglepayButton extends PaymentButton {
} );
}
- initEventHandlers() {
- if ( CONTEXT.Gateways.includes( this.context ) ) {
- document.body.addEventListener( 'ppcp_invalidate_methods', () => {
- this.isVisible = false;
- } );
-
- document.body.addEventListener(
- `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`,
- () => {
- this.isVisible = true;
- }
- );
- } else {
- /**
- * Review: The following logic appears to be unnecessary. Is it still required?
- * /
- const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`;
- const wrapper = `#${ this.wrapperId }`;
- if ( wrapper === ppcpButtonWrapper ) {
- throw new Error(
- `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"`
- );
- }
- const syncButtonVisibility = () => {
- const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper );
- setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) );
- setEnabled(
- wrapper,
- ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' )
- );
- };
- jQuery( document ).on(
- 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
- ( ev, data ) => {
- if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) {
- syncButtonVisibility();
- }
- }
- );
- syncButtonVisibility();
- //
- */
- }
- }
-
buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) {
this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods );
@@ -448,25 +182,11 @@ class GooglepayButton extends PaymentButton {
}
/**
- * Add a Google Pay purchase button
+ * Add a Google Pay purchase button.
*/
addButton() {
- this.log( 'addButton' );
-
- const wrapper = this.wrapperElement;
const baseCardPaymentMethod = this.baseCardPaymentMethod;
- const { color, type, language } = this.buttonStyle;
- const { shape, height } = this.ppcpStyle;
-
- wrapper.classList.add(
- `ppcp-button-${ shape }`,
- 'ppcp-button-apm',
- 'ppcp-button-googlepay'
- );
-
- if ( height ) {
- wrapper.style.height = `${ height }px`;
- }
+ const { color, type, language } = this.style;
/**
* @see https://developers.google.com/pay/api/web/reference/client#createButton
@@ -480,84 +200,7 @@ class GooglepayButton extends PaymentButton {
buttonSizeMode: 'fill',
} );
- this.log( 'Insert Button', {
- wrapper,
- button,
- } );
-
- wrapper.replaceChildren( button );
- }
-
- /**
- * Waits for the current button's wrapper element to become available in the DOM.
- *
- * Not sure if still needed, or if a simple `this.isPresent` check is sufficient.
- *
- * @param {Function} callback Function to call when the wrapper element was detected. Only
- * called on success.
- * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec
- * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec
- */
- waitForWrapper( callback, delay = 100, timeout = 2000 ) {
- let interval = 0;
- const startTime = Date.now();
-
- const stop = () => {
- if ( interval ) {
- clearInterval( interval );
- }
- interval = 0;
- };
-
- const checkElement = () => {
- if ( this.isPresent ) {
- stop();
- callback();
- return;
- }
-
- const timeElapsed = Date.now() - startTime;
-
- if ( timeElapsed > timeout ) {
- stop();
- this.error( 'Wrapper not found:', this.wrapperId );
- }
- };
-
- interval = setInterval( checkElement, delay );
- }
-
- /**
- * Refreshes the payment button on the page.
- */
- refresh() {
- const showButtonWrapper = () => {
- this.log( 'Show' );
-
- // Classic Checkout: Make the Google Pay gateway visible.
- document
- .querySelectorAll( 'style#ppcp-hide-google-pay' )
- .forEach( ( el ) => el.remove() );
-
- this.allElements.forEach( ( element ) => {
- element.style.display = 'block';
- } );
- };
-
- const hideButtonWrapper = () => {
- this.log( 'Hide' );
-
- this.allElements.forEach( ( element ) => {
- element.style.display = 'none';
- } );
- };
-
- if ( this.isVisible && this.isEligible && this.isPresent ) {
- showButtonWrapper();
- this.addButton();
- } else {
- hideButtonWrapper();
- }
+ this.insertButton( button );
}
//------------------------
diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php
index 98bff2c97..2f87a487d 100644
--- a/modules/ppcp-googlepay/src/Assets/Button.php
+++ b/modules/ppcp-googlepay/src/Assets/Button.php
@@ -345,9 +345,12 @@ class Button implements ButtonInterface {
* @return void
*/
protected function hide_gateway_until_eligible() : void {
+
?>
-