🐛 Address some errors in preview (Settings page)

This commit is contained in:
Philipp Stracker 2024-08-08 13:10:10 +02:00
parent 7f3636348b
commit 9ac4300995
No known key found for this signature in database
4 changed files with 155 additions and 53 deletions

View file

@ -139,6 +139,12 @@ export default class PaymentButton {
*/
#ppcpConfig;
/**
* A variation of a context handler object, like CheckoutHandler.
* This handler provides a standardized interface for certain standardized checks and actions.
*/
#contextHandler;
/**
* Whether the current browser/website support the payment method.
*
@ -197,7 +203,8 @@ export default class PaymentButton {
}
/**
* Returns a list with all wrapper IDs for the implemented payment method, categorized by context.
* Returns a list with all wrapper IDs for the implemented payment method, categorized by
* context.
*
* @abstract
* @param {Object} buttonConfig - Payment method specific configuration.
@ -210,7 +217,8 @@ export default class PaymentButton {
}
/**
* Returns a list of all button styles for the implemented payment method, categorized by context.
* Returns a list of all button styles for the implemented payment method, categorized by
* context.
*
* @abstract
* @param {Object} buttonConfig - Payment method specific configuration.
@ -229,21 +237,27 @@ export default class PaymentButton {
* to avoid multiple button instances handling the same context.
*
* @private
* @param {string} context - Button context name.
* @param {Object} buttonConfig - Payment button specific configuration.
* @param {Object} ppcpConfig - Plugin wide configuration object.
* @param {string} context - Button context name.
* @param {Object} buttonConfig - Payment button specific configuration.
* @param {Object} ppcpConfig - Plugin wide configuration object.
* @param {Object} contextHandler - Handler object.
*/
constructor( context, buttonConfig, ppcpConfig ) {
constructor( context, buttonConfig, ppcpConfig, contextHandler ) {
if ( this.methodId === PaymentButton.methodId ) {
throw new Error( 'Cannot initialize the PaymentButton base class' );
}
if ( ! buttonConfig ) {
buttonConfig = {};
}
const isDebugging = !! buttonConfig?.is_debug;
const methodName = this.methodId.replace( /^ppcp?-/, '' );
this.#context = context;
this.#buttonConfig = buttonConfig;
this.#ppcpConfig = ppcpConfig;
this.#contextHandler = contextHandler;
this.#wrappers = this.constructor.getWrappers(
this.#buttonConfig,
@ -325,6 +339,31 @@ export default class PaymentButton {
return this.#ppcpConfig;
}
/**
* Access the button's context handler.
* When no context handler was provided (like for a preview button), an empty object is returned.
*
* @return {Object} The context handler instance, or an empty object.
*/
get contextHandler() {
return this.#contextHandler || {};
}
/**
* Whether customers need to provide shipping details during payment.
*
* Can be extended by child classes to take method specific configuration into account.
*
* @return {boolean} True means, shipping fields are displayed and must be filled.
*/
get requiresShipping() {
// Default check: Is shipping enabled in WooCommerce?
return (
'function' === typeof this.contextHandler.shippingAllowed &&
this.contextHandler.shippingAllowed()
);
}
/**
* Button wrapper details.
*
@ -395,6 +434,15 @@ export default class PaymentButton {
return true;
}
/**
* Flags a preview button without actual payment logic.
*
* @return {boolean} True indicates a preview instance that has no payment logic.
*/
get isPreview() {
return PaymentContext.Preview === this.context;
}
/**
* Whether the browser can accept this payment method.
*
@ -514,12 +562,19 @@ export default class PaymentButton {
this.#logger.error( ...args );
}
/**
* Configures the button instance. Must be called before the initial `init()`.
*
* Parameters are defined by the derived class.
*
* @abstract
*/
configure() {}
/**
* Must be named `init()` to simulate "protected" visibility:
* Since the derived class also implements a method with the same name, this method can only
* be called by the derived class, but not from any other code.
*
* @protected
*/
init() {
this.#isInitialized = true;
@ -529,8 +584,6 @@ export default class PaymentButton {
* Must be named `reinit()` to simulate "protected" visibility:
* Since the derived class also implements a method with the same name, this method can only
* be called by the derived class, but not from any other code.
*
* @protected
*/
reinit() {
this.#isInitialized = false;

View file

@ -67,9 +67,9 @@ class GooglepayButton extends PaymentButton {
*/
static getWrappers( buttonConfig, ppcpConfig ) {
return combineWrapperIds(
buttonConfig.button.wrapper,
buttonConfig.button.mini_cart_wrapper,
ppcpConfig.button.wrapper,
buttonConfig?.button?.wrapper || '',
buttonConfig?.button?.mini_cart_wrapper || '',
ppcpConfig?.button?.wrapper || '',
'ppc-button-googlepay-container',
'ppc-button-ppcp-googlepay'
);
@ -79,7 +79,10 @@ class GooglepayButton extends PaymentButton {
* @inheritDoc
*/
static getStyles( buttonConfig, ppcpConfig ) {
const styles = combineStyles( ppcpConfig.button, buttonConfig.button );
const styles = combineStyles(
ppcpConfig?.button || {},
buttonConfig?.button || {}
);
if ( 'buy' === styles.MiniCart.type ) {
styles.MiniCart.type = 'pay';
@ -95,9 +98,11 @@ class GooglepayButton extends PaymentButton {
externalHandler,
contextHandler
) {
super( context, buttonConfig, ppcpConfig );
super( context, buttonConfig, ppcpConfig, contextHandler );
this.contextHandler = contextHandler;
this.onPaymentAuthorized = this.onPaymentAuthorized.bind( this );
this.onPaymentDataChanged = this.onPaymentDataChanged.bind( this );
this.onButtonClick = this.onButtonClick.bind( this );
this.log( 'Create instance' );
}
@ -113,6 +118,21 @@ class GooglepayButton extends PaymentButton {
return false;
}
// Preview buttons only need a valid environment.
if ( this.isPreview ) {
return true;
}
if ( ! this.googlePayConfig ) {
this.error( 'No API configuration - missing configure() call?' );
return false;
}
if ( ! this.transactionInfo ) {
this.error( 'No transactionInfo - missing configure() call?' );
return false;
}
if ( ! typeof this.contextHandler?.validateContext() ) {
this.error( 'Invalid context handler.', this.contextHandler );
return false;
@ -121,6 +141,13 @@ class GooglepayButton extends PaymentButton {
return true;
}
/**
* @inheritDoc
*/
get requiresShipping() {
return super.requiresShipping && this.buttonConfig.shipping?.enabled;
}
/**
* The Google Pay API.
*
@ -140,29 +167,33 @@ class GooglepayButton extends PaymentButton {
return this.#paymentsClient;
}
init( config = null, transactionInfo = null ) {
/**
* 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.
*/
configure( apiConfig, transactionInfo ) {
this.googlePayConfig = apiConfig;
this.transactionInfo = transactionInfo;
this.allowedPaymentMethods = this.googlePayConfig.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
this.log( 'CONFIG', transactionInfo );
}
init() {
// Stop, if the button is already initialized.
if ( this.isInitialized ) {
return;
}
if ( config ) {
this.googlePayConfig = config;
}
if ( transactionInfo ) {
this.transactionInfo = transactionInfo;
}
if ( ! this.googlePayConfig || ! this.transactionInfo ) {
this.error( 'Missing config or transactionInfo during init.' );
return;
}
// Stop, if configuration is invalid.
if ( ! this.isConfigValid ) {
return;
}
this.allowedPaymentMethods = config.allowedPaymentMethods;
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
super.init();
this.#paymentsClient = this.createPaymentsClient();
@ -170,6 +201,7 @@ class GooglepayButton extends PaymentButton {
this.log( 'Payment wrapper not found', this.wrapperId );
return;
}
if ( ! this.paymentsClient ) {
this.log( 'Could not initialize the payments client' );
return;
@ -179,7 +211,7 @@ class GooglepayButton extends PaymentButton {
.isReadyToPay(
this.buildReadyToPayRequest(
this.allowedPaymentMethods,
config
this.googlePayConfig
)
)
.then( ( response ) => {
@ -201,22 +233,34 @@ class GooglepayButton extends PaymentButton {
this.init();
}
/**
* Provides an object with relevant paymentDataCallbacks for the current button instance.
*
* @return {Object} An object containing callbacks for the current scope & configuration.
*/
preparePaymentDataCallbacks() {
const callbacks = {};
// We do not attach any callbacks to preview buttons.
if ( this.isPreview ) {
return callbacks;
}
callbacks.onPaymentAuthorized = this.onPaymentAuthorized;
if ( this.requiresShipping ) {
callbacks.onPaymentDataChanged = this.onPaymentDataChanged;
}
return callbacks;
}
createPaymentsClient() {
if ( ! this.googlePayApi ) {
return null;
}
const callbacks = {
onPaymentAuthorized: this.onPaymentAuthorized.bind( this ),
};
if (
this.buttonConfig.shipping.enabled &&
this.contextHandler.shippingAllowed()
) {
callbacks.onPaymentDataChanged =
this.onPaymentDataChanged.bind( this );
}
const callbacks = this.preparePaymentDataCallbacks();
/**
* Consider providing merchant info here:
@ -253,7 +297,7 @@ class GooglepayButton extends PaymentButton {
* @see https://developers.google.com/pay/api/web/reference/client#createButton
*/
const button = this.paymentsClient.createButton( {
onClick: this.onButtonClick.bind( this ),
onClick: this.onButtonClick,
allowedPaymentMethods: [ baseCardPaymentMethod ],
buttonColor: color || 'black',
buttonType: type || 'pay',
@ -314,10 +358,7 @@ class GooglepayButton extends PaymentButton {
paymentDataRequest.transactionInfo = this.transactionInfo;
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
if (
this.buttonConfig.shipping.enabled &&
this.contextHandler.shippingAllowed()
) {
if ( this.requiresShipping ) {
paymentDataRequest.callbackIntents = [
'SHIPPING_ADDRESS',
'SHIPPING_OPTION',

View file

@ -30,13 +30,19 @@ class GooglepayManager {
this.buttons.push( button );
const initButton = () => {
button.configure( this.googlePayConfig, this.transactionInfo );
button.init();
};
// Initialize button only if googlePayConfig and transactionInfo are already fetched.
if ( this.googlePayConfig && this.transactionInfo ) {
button.init( this.googlePayConfig, this.transactionInfo );
initButton();
} else {
await this.init();
if ( this.googlePayConfig && this.transactionInfo ) {
button.init( this.googlePayConfig, this.transactionInfo );
initButton();
}
}
} );
@ -54,7 +60,8 @@ class GooglepayManager {
}
for ( const button of this.buttons ) {
button.init( this.googlePayConfig, this.transactionInfo );
button.configure( this.googlePayConfig, this.transactionInfo );
button.init();
}
} catch ( error ) {
console.error( 'Error during initialization:', error );

View file

@ -103,7 +103,7 @@ class GooglePayPreviewButton extends PreviewButton {
null
);
const button = new GooglepayButton(
const button = GooglepayButton.createButton(
'preview',
null,
buttonConfig,
@ -111,7 +111,8 @@ class GooglePayPreviewButton extends PreviewButton {
contextHandler
);
button.init( this.apiConfig, null );
button.configure( this.apiConfig, null );
button.init();
}
/**