diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
index e3df4c969..aa53b9488 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js
@@ -170,6 +170,11 @@ export default class PaymentButton {
*/
#contextHandler;
+ /**
+ * Button attributes.
+ */
+ #buttonAttributes;
+
/**
* Whether the current browser/website support the payment method.
*
@@ -195,11 +200,12 @@ export default class PaymentButton {
/**
* Factory method to create a new PaymentButton while limiting a single instance per context.
*
- * @param {string} context - Button context name.
- * @param {unknown} externalHandler - Handler object.
- * @param {Object} buttonConfig - Payment button specific configuration.
- * @param {Object} ppcpConfig - Plugin wide configuration object.
- * @param {unknown} contextHandler - Handler object.
+ * @param {string} context - Button context name.
+ * @param {unknown} externalHandler - Handler object.
+ * @param {Object} buttonConfig - Payment button specific configuration.
+ * @param {Object} ppcpConfig - Plugin wide configuration object.
+ * @param {unknown} contextHandler - Handler object.
+ * @param {Object} buttonAttributes - Button attributes.
* @return {PaymentButton} The button instance.
*/
static createButton(
@@ -207,7 +213,8 @@ export default class PaymentButton {
externalHandler,
buttonConfig,
ppcpConfig,
- contextHandler
+ contextHandler,
+ buttonAttributes
) {
const buttonInstances = getInstances();
const instanceKey = `${ this.methodId }.${ context }`;
@@ -218,7 +225,8 @@ export default class PaymentButton {
externalHandler,
buttonConfig,
ppcpConfig,
- contextHandler
+ contextHandler,
+ buttonAttributes
);
buttonInstances.set( instanceKey, button );
@@ -262,18 +270,20 @@ export default class PaymentButton {
* to avoid multiple button instances handling the same context.
*
* @private
- * @param {string} context - Button context name.
- * @param {Object} externalHandler - Handler object.
- * @param {Object} buttonConfig - Payment button specific configuration.
- * @param {Object} ppcpConfig - Plugin wide configuration object.
- * @param {Object} contextHandler - Handler object.
+ * @param {string} context - Button context name.
+ * @param {Object} externalHandler - Handler object.
+ * @param {Object} buttonConfig - Payment button specific configuration.
+ * @param {Object} ppcpConfig - Plugin wide configuration object.
+ * @param {Object} contextHandler - Handler object.
+ * @param {Object} buttonAttributes - Button attributes.
*/
constructor(
context,
externalHandler = null,
buttonConfig = {},
ppcpConfig = {},
- contextHandler = null
+ contextHandler = null,
+ buttonAttributes = {}
) {
if ( this.methodId === PaymentButton.methodId ) {
throw new Error( 'Cannot initialize the PaymentButton base class' );
@@ -291,6 +301,7 @@ export default class PaymentButton {
this.#ppcpConfig = ppcpConfig;
this.#externalHandler = externalHandler;
this.#contextHandler = contextHandler;
+ this.#buttonAttributes = buttonAttributes;
this.#logger = new ConsoleLogger( methodName, context );
@@ -763,15 +774,20 @@ export default class PaymentButton {
const styleSelector = `style[data-hide-gateway="${ this.methodId }"]`;
const wrapperSelector = `#${ this.wrappers.Default }`;
- const paymentMethodLi = document.querySelector(`.wc_payment_method.payment_method_${ this.methodId }`);
+ const paymentMethodLi = document.querySelector(
+ `.wc_payment_method.payment_method_${ this.methodId }`
+ );
document
.querySelectorAll( styleSelector )
.forEach( ( el ) => el.remove() );
- if (paymentMethodLi.style.display === 'none' || paymentMethodLi.style.display === '') {
- paymentMethodLi.style.display = 'block';
- }
+ if (
+ paymentMethodLi.style.display === 'none' ||
+ paymentMethodLi.style.display === ''
+ ) {
+ paymentMethodLi.style.display = 'block';
+ }
document
.querySelectorAll( wrapperSelector )
@@ -843,7 +859,7 @@ export default class PaymentButton {
this.removeButton();
}
- this.log( 'addButton', button );
+ this.log( 'insertButton', button );
this.#button = button;
wrapper.appendChild( this.#button );
diff --git a/modules/ppcp-googlepay/resources/css/styles.scss b/modules/ppcp-googlepay/resources/css/styles.scss
index 3c6fe8912..6fb0015e9 100644
--- a/modules/ppcp-googlepay/resources/css/styles.scss
+++ b/modules/ppcp-googlepay/resources/css/styles.scss
@@ -1,7 +1,6 @@
/* Front end display */
.ppcp-button-apm .gpay-card-info-container-fill .gpay-card-info-container {
outline-offset: -1px;
- border-radius: var(--apm-button-border-radius);
}
/* Admin preview */
diff --git a/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js
index a5caab59d..866b0a7a4 100644
--- a/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js
+++ b/modules/ppcp-googlepay/resources/js/Block/components/GooglepayButton.js
@@ -1,12 +1,15 @@
-import { useState, useEffect } from '@wordpress/element';
+import { useState } from '@wordpress/element';
import useGooglepayApiToGenerateButton from '../hooks/useGooglepayApiToGenerateButton';
import usePayPalScript from '../hooks/usePayPalScript';
import useGooglepayScript from '../hooks/useGooglepayScript';
import useGooglepayConfig from '../hooks/useGooglepayConfig';
-const GooglepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
- const [ buttonHtml, setButtonHtml ] = useState( '' );
- const [ buttonElement, setButtonElement ] = useState( null );
+const GooglepayButton = ( {
+ namespace,
+ buttonConfig,
+ ppcpConfig,
+ buttonAttributes,
+} ) => {
const [ componentFrame, setComponentFrame ] = useState( null );
const isPayPalLoaded = usePayPalScript( namespace, ppcpConfig );
@@ -18,35 +21,45 @@ const GooglepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
const googlepayConfig = useGooglepayConfig( namespace, isGooglepayLoaded );
- useEffect( () => {
- if ( ! buttonElement ) {
- return;
- }
-
- setComponentFrame( buttonElement.ownerDocument );
- }, [ buttonElement ] );
-
- const googlepayButton = useGooglepayApiToGenerateButton(
+ const { button, containerStyles } = useGooglepayApiToGenerateButton(
componentFrame,
namespace,
buttonConfig,
ppcpConfig,
- googlepayConfig
+ googlepayConfig,
+ buttonAttributes
);
- useEffect( () => {
- if ( googlepayButton ) {
- const hideLoader =
- '';
- setButtonHtml( googlepayButton.outerHTML + hideLoader );
- }
- }, [ googlepayButton ] );
-
return (
-
+ <>
+ {
+ if ( ! node ) {
+ return;
+ }
+
+ // Set component frame
+ setComponentFrame( node.ownerDocument );
+
+ // Handle button mounting
+ while ( node.firstChild ) {
+ node.removeChild( node.firstChild );
+ }
+ if ( button ) {
+ node.appendChild( button );
+ }
+ } }
+ />
+ { button && (
+
+ ) }
+ >
);
};
diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js
index 6e214acb2..c4dc71f92 100644
--- a/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js
+++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useButtonStyles.js
@@ -1,19 +1,26 @@
import { useMemo } from '@wordpress/element';
import { combineStyles } from '../../../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers';
-const useButtonStyles = ( buttonConfig, ppcpConfig ) => {
+const useButtonStyles = ( buttonConfig, ppcpConfig, buttonAttributes ) => {
return useMemo( () => {
const styles = combineStyles(
ppcpConfig?.button || {},
buttonConfig?.button || {}
);
- if ( styles.MiniCart && styles.MiniCart.type === 'buy' ) {
+ if ( buttonAttributes && styles.Default ) {
+ styles.Default.height =
+ buttonAttributes.height || styles.Default.height;
+ styles.Default.borderRadius =
+ buttonAttributes.borderRadius || styles.Default.borderRadius;
+ }
+
+ if ( styles.MiniCart?.type === 'buy' ) {
styles.MiniCart.type = 'pay';
}
return styles;
- }, [ buttonConfig, ppcpConfig ] );
+ }, [ buttonConfig, ppcpConfig, buttonAttributes ] );
};
export default useButtonStyles;
diff --git a/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js
index fe5b342a7..295b8c656 100644
--- a/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js
+++ b/modules/ppcp-googlepay/resources/js/Block/hooks/useGooglepayApiToGenerateButton.js
@@ -6,10 +6,16 @@ const useGooglepayApiToGenerateButton = (
namespace,
buttonConfig,
ppcpConfig,
- googlepayConfig
+ googlepayConfig,
+ buttonAttributes
) => {
const [ googlepayButton, setGooglepayButton ] = useState( null );
- const buttonStyles = useButtonStyles( buttonConfig, ppcpConfig );
+
+ const buttonStyles = useButtonStyles(
+ buttonConfig,
+ ppcpConfig,
+ buttonAttributes
+ );
useEffect( () => {
if (
@@ -35,14 +41,13 @@ const useGooglepayApiToGenerateButton = (
buttonType: buttonConfig.buttonType || 'pay',
buttonLocale: buttonConfig.buttonLocale || 'en',
buttonSizeMode: 'fill',
- };
-
- const button = paymentsClient.createButton( {
- ...googlePayButtonOptions,
+ buttonRadius: parseInt( buttonStyles?.Default?.borderRadius ),
onClick: ( event ) => {
event.preventDefault();
},
- } );
+ };
+
+ const button = paymentsClient.createButton( googlePayButtonOptions );
setGooglepayButton( button );
@@ -51,7 +56,15 @@ const useGooglepayApiToGenerateButton = (
};
}, [ namespace, buttonConfig, ppcpConfig, googlepayConfig, buttonStyles ] );
- return googlepayButton;
+ // Return both the button and the styles needed for the container
+ return {
+ button: googlepayButton,
+ containerStyles: {
+ height: buttonStyles?.Default?.height
+ ? `${ buttonStyles.Default.height }px`
+ : '',
+ },
+ };
};
export default useGooglepayApiToGenerateButton;
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
index 97bfa6d2c..2b8fb044d 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
@@ -106,8 +106,40 @@ class GooglepayButton extends PaymentButton {
*/
#transactionInfo = null;
+ /**
+ * The currently visible payment button.
+ *
+ * @type {HTMLElement|null}
+ */
+ #button = null;
+
+ /**
+ * The Google Pay configuration object.
+ *
+ * @type {Object|null}
+ */
googlePayConfig = null;
+ /**
+ * The start time of the configuration process.
+ *
+ * @type {number}
+ */
+ #configureStartTime = 0;
+
+ /**
+ * The maximum time to wait for buttonAttributes before proceeding with initialization.
+ * @type {number}
+ */
+ #maxWaitTime = 1000;
+
+ /**
+ * The stored button attributes.
+ *
+ * @type {null}
+ */
+ #storedButtonAttributes = null;
+
/**
* @inheritDoc
*/
@@ -142,7 +174,8 @@ class GooglepayButton extends PaymentButton {
externalHandler,
buttonConfig,
ppcpConfig,
- contextHandler
+ contextHandler,
+ buttonAttributes
) {
// Disable debug output in the browser console:
// buttonConfig.is_debug = false;
@@ -152,7 +185,8 @@ class GooglepayButton extends PaymentButton {
externalHandler,
buttonConfig,
ppcpConfig,
- contextHandler
+ contextHandler,
+ buttonAttributes
);
this.init = this.init.bind( this );
@@ -249,6 +283,22 @@ class GooglepayButton extends PaymentButton {
);
}
+ // Add buttonAttributes validation
+ if ( this.buttonAttributes ) {
+ if (
+ this.buttonAttributes.height &&
+ isNaN( parseInt( this.buttonAttributes.height ) )
+ ) {
+ return isInvalid( 'Invalid height in buttonAttributes' );
+ }
+ if (
+ this.buttonAttributes.borderRadius &&
+ isNaN( parseInt( this.buttonAttributes.borderRadius ) )
+ ) {
+ return isInvalid( 'Invalid borderRadius in buttonAttributes' );
+ }
+ }
+
if ( ! typeof this.contextHandler?.validateContext() ) {
return isInvalid( 'Invalid context handler.', this.contextHandler );
}
@@ -259,24 +309,74 @@ class GooglepayButton extends PaymentButton {
/**
* Configures the button instance. Must be called before the initial `init()`.
*
- * @param {Object} apiConfig - API configuration.
- * @param {Object} transactionInfo - Transaction details; required before "init" call.
+ * @param {Object} apiConfig - API configuration.
+ * @param {Object} transactionInfo - Transaction details; required before "init" call.
+ * @param {Object} buttonAttributes - Button attributes.
*/
- configure( apiConfig, transactionInfo ) {
+ configure( apiConfig, transactionInfo, buttonAttributes = {} ) {
+ // Start timing on first configure call
+ if ( ! this.#configureStartTime ) {
+ this.#configureStartTime = Date.now();
+ }
+
+ // If valid buttonAttributes, store them
+ if ( buttonAttributes?.height && buttonAttributes?.borderRadius ) {
+ this.#storedButtonAttributes = { ...buttonAttributes };
+ }
+
+ // Use stored attributes if current ones are missing
+ const attributes = buttonAttributes?.height
+ ? buttonAttributes
+ : this.#storedButtonAttributes;
+
+ // Check if we've exceeded wait time
+ const timeWaited = Date.now() - this.#configureStartTime;
+ if ( timeWaited > this.#maxWaitTime ) {
+ this.log(
+ 'GooglePay: Timeout waiting for buttonAttributes - proceeding with initialization'
+ );
+ this.googlePayConfig = apiConfig;
+ this.#transactionInfo = transactionInfo;
+ this.buttonAttributes = attributes || buttonAttributes;
+ this.allowedPaymentMethods =
+ this.googlePayConfig.allowedPaymentMethods;
+ this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
+ this.init();
+ return;
+ }
+
+ // Block any initialization until we have valid buttonAttributes
+ if ( ! attributes?.height || ! attributes?.borderRadius ) {
+ setTimeout(
+ () =>
+ this.configure(
+ apiConfig,
+ transactionInfo,
+ buttonAttributes
+ ),
+ 100
+ );
+ return;
+ }
+
+ // Reset timer for future configure calls
+ this.#configureStartTime = 0;
+
this.googlePayConfig = apiConfig;
this.#transactionInfo = transactionInfo;
-
+ this.buttonAttributes = attributes;
this.allowedPaymentMethods = this.googlePayConfig.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
+ this.init();
}
init() {
- // Use `reinit()` to force a full refresh of an initialized button.
+ // Skip if already initialized
if ( this.isInitialized ) {
return;
}
- // Stop, if configuration is invalid.
+ // Validate configuration
if ( ! this.validateConfiguration() ) {
return;
}
@@ -284,16 +384,6 @@ class GooglepayButton extends PaymentButton {
super.init();
this.#paymentsClient = this.createPaymentsClient();
- if ( ! this.isPresent ) {
- this.log( 'Payment wrapper not found', this.wrapperId );
- return;
- }
-
- if ( ! this.paymentsClient ) {
- this.log( 'Could not initialize the payments client' );
- return;
- }
-
this.paymentsClient
.isReadyToPay(
this.buildReadyToPayRequest(
@@ -371,30 +461,86 @@ class GooglepayButton extends PaymentButton {
}
/**
- * Creates the payment button and calls `this.insertButton()` to make the button visible in the
- * correct wrapper.
+ * Creates the payment button and calls `super.insertButton()` to make the button visible in the correct wrapper.
*/
addButton() {
if ( ! this.paymentsClient ) {
return;
}
+ // If current buttonAttributes are missing, try to use stored ones
+ if (
+ ! this.buttonAttributes?.height &&
+ this.#storedButtonAttributes?.height
+ ) {
+ this.buttonAttributes = { ...this.#storedButtonAttributes };
+ }
+
+ this.removeButton();
+
const baseCardPaymentMethod = this.baseCardPaymentMethod;
const { color, type, language } = this.style;
- /**
- * @see https://developers.google.com/pay/api/web/reference/client#createButton
- */
- const button = this.paymentsClient.createButton( {
+ const buttonOptions = {
+ buttonColor: color || 'black',
+ buttonSizeMode: 'fill',
+ buttonLocale: language || 'en',
+ buttonType: type || 'pay',
+ buttonRadius: parseInt( this.buttonAttributes?.borderRadius, 10 ),
onClick: this.onButtonClick,
allowedPaymentMethods: [ baseCardPaymentMethod ],
- buttonColor: color || 'black',
- buttonType: type || 'pay',
- buttonLocale: language || 'en',
- buttonSizeMode: 'fill',
- } );
+ };
- this.insertButton( button );
+ const button = this.paymentsClient.createButton( buttonOptions );
+ this.#button = button;
+
+ super.insertButton( button );
+ this.applyWrapperStyles();
+ }
+
+ /**
+ * Applies CSS classes and inline styling to the payment button wrapper.
+ * Extends parent implementation to handle Google Pay specific styling.
+ */
+ applyWrapperStyles() {
+ super.applyWrapperStyles();
+
+ const wrapper = this.wrapperElement;
+ if ( ! wrapper ) {
+ return;
+ }
+
+ // Try stored attributes if current ones are missing
+ const attributes = this.buttonAttributes?.height
+ ? this.buttonAttributes
+ : this.#storedButtonAttributes;
+
+ if ( attributes?.height ) {
+ const height = parseInt( attributes.height, 10 );
+ if ( ! isNaN( height ) ) {
+ wrapper.style.height = `${ height }px`;
+ wrapper.style.minHeight = `${ height }px`;
+ }
+ }
+ }
+
+ /**
+ * Removes the payment button from the DOM.
+ */
+ removeButton() {
+ if ( ! this.isPresent || ! this.#button ) {
+ return;
+ }
+
+ this.log( 'removeButton' );
+
+ try {
+ this.wrapperElement.removeChild( this.#button );
+ } catch ( Exception ) {
+ // Ignore this.
+ }
+
+ this.#button = null;
}
//------------------------
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
index f9520d23a..8db6c762c 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
@@ -3,10 +3,11 @@ import GooglepayButton from './GooglepayButton';
import ContextHandlerFactory from './Context/ContextHandlerFactory';
class GooglepayManager {
- constructor( namespace, buttonConfig, ppcpConfig ) {
+ constructor( namespace, buttonConfig, ppcpConfig, buttonAttributes = {} ) {
this.namespace = namespace;
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
+ this.buttonAttributes = buttonAttributes;
this.googlePayConfig = null;
this.transactionInfo = null;
this.contextHandler = null;
@@ -26,13 +27,18 @@ class GooglepayManager {
bootstrap.handler,
buttonConfig,
ppcpConfig,
- this.contextHandler
+ this.contextHandler,
+ this.buttonAttributes
);
this.buttons.push( button );
const initButton = () => {
- button.configure( this.googlePayConfig, this.transactionInfo );
+ button.configure(
+ this.googlePayConfig,
+ this.transactionInfo,
+ this.buttonAttributes
+ );
button.init();
};
@@ -70,7 +76,8 @@ class GooglepayManager {
for ( const button of this.buttons ) {
button.configure(
this.googlePayConfig,
- this.transactionInfo
+ this.transactionInfo,
+ this.buttonAttributes
);
button.init();
}
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js b/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js
index 1e3bd6ebb..548e37f8b 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayManagerBlockEditor.js
@@ -4,11 +4,13 @@ const GooglepayManagerBlockEditor = ( {
namespace,
buttonConfig,
ppcpConfig,
+ buttonAttributes,
} ) => (
);
diff --git a/modules/ppcp-googlepay/resources/js/boot-block.js b/modules/ppcp-googlepay/resources/js/boot-block.js
index 398cc488c..8c63bba14 100644
--- a/modules/ppcp-googlepay/resources/js/boot-block.js
+++ b/modules/ppcp-googlepay/resources/js/boot-block.js
@@ -20,7 +20,7 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
window.PayPalCommerceGateway = ppcpConfig;
}
-const GooglePayComponent = ( { isEditing } ) => {
+const GooglePayComponent = ( { isEditing, buttonAttributes } ) => {
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
const [ googlePayLoaded, setGooglePayLoaded ] = useState( false );
const [ manager, setManager ] = useState( null );
@@ -48,11 +48,18 @@ const GooglePayComponent = ( { isEditing } ) => {
const newManager = new GooglepayManager(
namespace,
buttonConfig,
- ppcpConfig
+ ppcpConfig,
+ buttonAttributes
);
setManager( newManager );
}
- }, [ paypalLoaded, googlePayLoaded, isEditing, manager ] );
+ }, [
+ paypalLoaded,
+ googlePayLoaded,
+ isEditing,
+ manager,
+ buttonAttributes,
+ ] );
if ( isEditing ) {
return (
@@ -60,6 +67,7 @@ const GooglePayComponent = ( { isEditing } ) => {
namespace={ namespace }
buttonConfig={ buttonConfig }
ppcpConfig={ ppcpConfig }
+ buttonAttributes={ buttonAttributes }
/>
);
}
@@ -89,5 +97,6 @@ registerExpressPaymentMethod( {
canMakePayment: () => buttonData.enabled,
supports: {
features,
+ style: [ 'height', 'borderRadius' ],
},
} );