♻️ Simplify PaymentButton creation code

This commit is contained in:
Philipp Stracker 2024-08-07 18:26:02 +02:00
parent 8c811d9f8e
commit 95c7d4f7bc
No known key found for this signature in database
2 changed files with 237 additions and 108 deletions

View file

@ -26,22 +26,53 @@ import {
* @property {string} SmartButton - Wrapper for smart button container. * @property {string} SmartButton - Wrapper for smart button container.
*/ */
/**
* Adds the provided PaymentButton instance to a global payment-button collection.
*
* This is debugging logic that should not be used on a production site.
*
* @param {string} methodName - Used to group the buttons.
* @param {PaymentButton} button - Appended to the button collection.
*/
const addToDebuggingCollection = ( methodName, button ) => {
window.ppcpPaymentButtonList = window.ppcpPaymentButtonList || {};
const collection = window.ppcpPaymentButtonList;
collection[ methodName ] = collection[ methodName ] || [];
collection[ methodName ].push( button );
};
/** /**
* Base class for APM payment buttons, like GooglePay and ApplePay. * Base class for APM payment buttons, like GooglePay and ApplePay.
* *
* This class is not intended for the PayPal button. * This class is not intended for the PayPal button.
*/ */
export default class PaymentButton { export default class PaymentButton {
/**
* Defines the implemented payment method.
*
* Used to identify and address the button internally.
* Overwrite this in the derived class.
*
* @type {string}
*/
static methodId = 'generic';
/**
* CSS class that is added to the payment button wrapper.
*
* Overwrite this in the derived class.
*
* @type {string}
*/
static cssClass = '';
/** /**
* @type {ConsoleLogger} * @type {ConsoleLogger}
*/ */
#logger; #logger;
/**
* @type {string}
*/
#methodId;
/** /**
* Whether the payment button is initialized. * Whether the payment button is initialized.
* *
@ -70,7 +101,7 @@ export default class PaymentButton {
#styles; #styles;
/** /**
* APM relevant configuration; e.g., configuration of the GooglePay button * APM relevant configuration; e.g., configuration of the GooglePay button.
*/ */
#buttonConfig; #buttonConfig;
@ -101,38 +132,72 @@ export default class PaymentButton {
*/ */
#button = null; #button = null;
/**
* Returns a list with all wrapper IDs for the implemented payment method, categorized by context.
*
* @abstract
* @param {Object} buttonConfig - Payment method specific configuration.
* @param {Object} ppcpConfig - Global plugin configuration.
* @return {{MiniCart, Gateway, Block, SmartButton, Default}} The wrapper ID collection.
*/
// eslint-disable-next-line no-unused-vars
static getWrappers( buttonConfig, ppcpConfig ) {
throw new Error( 'Must be implemented in the child class' );
}
/**
* Returns a list of all button styles for the implemented payment method, categorized by context.
*
* @abstract
* @param {Object} buttonConfig - Payment method specific configuration.
* @param {Object} ppcpConfig - Global plugin configuration.
* @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context.
*/
// eslint-disable-next-line no-unused-vars
static getStyles( buttonConfig, ppcpConfig ) {
throw new Error( 'Must be implemented in the child class' );
}
/** /**
* Initialize the payment button instance. * Initialize the payment button instance.
* *
* @param {string} methodId - Payment method ID (slug, e.g., "ppcp-googlepay"). * Do not create new button instances directly; use the `createButton` method instead
* @param {string} context - Button context name. * to avoid multiple button instances handling the same context.
* @param {WrapperCollection} wrappers - Button wrapper IDs, by context. *
* @param {StylesCollection} styles - Button styles, by context. * @private
* @param {Object} buttonConfig - Payment button specific configuration. * @param {string} context - Button context name.
* @param {Object} ppcpConfig - Plugin wide configuration object. * @param {Object} buttonConfig - Payment button specific configuration.
* @param {Object} ppcpConfig - Plugin wide configuration object.
*/ */
constructor( constructor( context, buttonConfig, ppcpConfig ) {
methodId, if ( this.methodId === PaymentButton.methodId ) {
context, throw new Error( 'Cannot initialize the PaymentButton base class' );
wrappers, }
styles,
buttonConfig,
ppcpConfig
) {
const methodName = methodId.replace( /^ppcp?-/, '' );
this.#methodId = methodId; const isDebugging = !! buttonConfig?.is_debug;
const methodName = this.methodId.replace( /^ppcp?-/, '' );
this.#logger = new ConsoleLogger( methodName, context );
this.#logger.enabled = !! buttonConfig?.is_debug;
this.#context = context; this.#context = context;
this.#wrappers = wrappers;
this.#styles = styles;
this.#buttonConfig = buttonConfig; this.#buttonConfig = buttonConfig;
this.#ppcpConfig = ppcpConfig; this.#ppcpConfig = ppcpConfig;
apmButtonsInit( ppcpConfig ); this.#wrappers = this.constructor.getWrappers(
this.#buttonConfig,
this.#ppcpConfig
);
this.#styles = this.constructor.getStyles(
this.#buttonConfig,
this.#ppcpConfig
);
this.#logger = new ConsoleLogger( methodName, context );
if ( isDebugging ) {
this.#logger.enabled = true;
addToDebuggingCollection( methodName, this );
}
apmButtonsInit( this.#ppcpConfig );
this.initEventListeners(); this.initEventListeners();
} }
@ -140,10 +205,20 @@ export default class PaymentButton {
* Internal ID of the payment gateway. * Internal ID of the payment gateway.
* *
* @readonly * @readonly
* @return {string} The internal gateway ID. * @return {string} The internal gateway ID, defined in the derived class.
*/ */
get methodId() { get methodId() {
return this.#methodId; return this.constructor.methodId;
}
/**
* CSS class that is added to the button wrapper.
*
* @readonly
* @return {string} CSS class, defined in the derived class.
*/
get cssClass() {
return this.constructor.cssClass;
} }
/** /**
@ -168,6 +243,24 @@ export default class PaymentButton {
return this.#context; return this.#context;
} }
/**
* Configuration, specific for the implemented payment button.
*
* @return {Object} Configuration object.
*/
get buttonConfig() {
return this.#buttonConfig;
}
/**
* Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc.
*
* @return {Object} Configuration object.
*/
get ppcpConfig() {
return this.#ppcpConfig;
}
/** /**
* Button wrapper details. * Button wrapper details.
* *
@ -380,6 +473,10 @@ export default class PaymentButton {
} }
triggerRedraw() { triggerRedraw() {
if ( this.isEligible && this.isSeparateGateway ) {
this.showPaymentGateway();
}
dispatchButtonEvent( { dispatchButtonEvent( {
event: ButtonEvents.REDRAW, event: ButtonEvents.REDRAW,
paymentMethod: this.methodId, paymentMethod: this.methodId,
@ -418,46 +515,75 @@ export default class PaymentButton {
* Refreshes the payment button on the page. * Refreshes the payment button on the page.
*/ */
refresh() { refresh() {
const showButtonWrapper = () => { if ( ! this.isPresent ) {
this.log( 'Show' ); return;
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. this.applyWrapperStyles();
if ( this.isEligible && this.isPresent ) {
showButtonWrapper(); // Refresh or hide the actual payment button.
if ( this.isEligible && this.isPresent && this.isVisible ) {
this.addButton();
} else { } else {
hideButtonWrapper(); // this.removeButton();
} }
} }
/**
* Makes the custom payment gateway visible by removing initial inline styles from the DOM.
*
* Only relevant on the checkout page, i.e., when `this.isSeparateGateway` is `true`
*/
showPaymentGateway() {
const styleSelectors = `style[data-hide-gateway="${ this.methodId }"]`;
const styles = document.querySelectorAll( styleSelectors );
if ( ! styles.length ) {
return;
}
this.log( 'Show gateway' );
styles.forEach( ( el ) => el.remove() );
}
/**
* Applies CSS classes and inline styling to the payment button wrapper.
*/
applyWrapperStyles() {
const wrapper = this.wrapperElement;
const { shape, height } = this.style;
wrapper.classList.add(
`ppcp-button-${ shape }`,
'ppcp-button-apm',
this.cssClass
);
if ( height ) {
wrapper.style.height = `${ height }px`;
}
// Apply the wrapper visibility.
wrapper.style.display = this.isVisible ? 'block' : 'none';
}
/**
* Creates a new payment button (HTMLElement) and must call `this.insertButton()` to display
* that button in the correct wrapper.
*
* @abstract
*/
addButton() {
throw new Error( 'Must be implemented by the child class' );
}
/** /**
* Prepares the button wrapper element and inserts the provided payment button into the DOM. * Prepares the button wrapper element and inserts the provided payment button into the DOM.
* *
* If a payment button was previously inserted to the wrapper, calling this method again will
* first remove the previous button.
*
* @param {HTMLElement} button - The button element to inject. * @param {HTMLElement} button - The button element to inject.
*/ */
insertButton( button ) { insertButton( button ) {
@ -465,28 +591,16 @@ export default class PaymentButton {
return; return;
} }
this.log( 'addButton', button );
const wrapper = this.wrapperElement;
if ( this.#button ) { if ( this.#button ) {
this.#button.remove(); this.log( 'addButton.removePrevious', this.#button );
wrapper.removeChild( this.#button );
} }
this.#button = button; this.#button = button;
this.log( 'addButton', button ); wrapper.appendChild( this.#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 );
} }
/** /**
@ -500,8 +614,10 @@ export default class PaymentButton {
this.log( 'removeButton' ); this.log( 'removeButton' );
if ( this.#button ) { if ( this.#button ) {
this.#button.remove(); const wrapper = this.wrapperElement;
wrapper.removeChild( this.#button );
this.#button = null;
} }
this.#button = null;
} }
} }

View file

@ -1,5 +1,3 @@
/* global google */
import { import {
combineStyles, combineStyles,
combineWrapperIds, combineWrapperIds,
@ -49,11 +47,47 @@ import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper
*/ */
class GooglepayButton extends PaymentButton { class GooglepayButton extends PaymentButton {
/**
* @inheritDoc
*/
static methodId = PaymentMethods.GOOGLEPAY;
/**
* @inheritDoc
*/
static cssClass = 'google-pay';
/** /**
* Client reference, provided by the Google Pay JS SDK. * Client reference, provided by the Google Pay JS SDK.
*/ */
#paymentsClient = null; #paymentsClient = null;
/**
* @inheritDoc
*/
static getWrappers( buttonConfig, ppcpConfig ) {
return combineWrapperIds(
buttonConfig.button.wrapper,
buttonConfig.button.mini_cart_wrapper,
ppcpConfig.button.wrapper,
'express-payment-method-ppcp-googlepay',
'ppc-button-ppcp-googlepay'
);
}
/**
* @inheritDoc
*/
static getStyles( buttonConfig, ppcpConfig ) {
const styles = combineStyles( ppcpConfig.button, buttonConfig.button );
if ( 'buy' === styles.MiniCart.type ) {
styles.MiniCart.type = 'pay';
}
return styles;
}
constructor( constructor(
context, context,
externalHandler, externalHandler,
@ -61,30 +95,8 @@ class GooglepayButton extends PaymentButton {
ppcpConfig, ppcpConfig,
contextHandler contextHandler
) { ) {
const wrappers = combineWrapperIds( super( context, buttonConfig, ppcpConfig );
buttonConfig.button.wrapper,
buttonConfig.button.mini_cart_wrapper,
ppcpConfig.button.wrapper,
'express-payment-method-ppcp-googlepay',
'ppc-button-ppcp-googlepay'
);
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.buttonConfig = buttonConfig;
this.contextHandler = contextHandler; this.contextHandler = contextHandler;
this.log( 'Create instance' ); this.log( 'Create instance' );
@ -226,10 +238,11 @@ class GooglepayButton extends PaymentButton {
} }
/** /**
* Add a Google Pay purchase button. * Creates the payment button and calls `this.insertButton()` to make the button visible in the
* correct wrapper.
*/ */
addButton() { addButton() {
if ( ! this.isInitialized ) { if ( ! this.isInitialized || ! this.paymentsClient ) {
return; return;
} }