mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-03 08:37:53 +08:00
♻️ Apply latest JS structure from ApplePay Gateway
- Partially addresses the known display bug - Simplifies maintainance between both gateways - Reduces component-internal redundancies
This commit is contained in:
parent
8814b1f636
commit
8286085f59
2 changed files with 299 additions and 123 deletions
|
@ -1,16 +1,61 @@
|
|||
/* global google */
|
||||
/* global jQuery */
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* Plugin-specific styling.
|
||||
*
|
||||
* Note that most properties of this object do not apply to the Google Pay button.
|
||||
*
|
||||
* @typedef {Object} PPCPStyle
|
||||
* @property {string} shape - Outline shape.
|
||||
* @property {?number} height - Button height in pixel.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Style options that are defined by the Google Pay SDK and are required to render the button.
|
||||
*
|
||||
* @typedef {Object} GooglePayStyle
|
||||
* @property {string} type - Defines the button label.
|
||||
* @property {string} color - Button color
|
||||
* @property {string} language - The locale; an empty string will apply the user-agent's language.
|
||||
*/
|
||||
|
||||
/**
|
||||
* List of valid context values that the button can have.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const CONTEXT = {
|
||||
Product: 'product',
|
||||
Cart: 'cart',
|
||||
Checkout: 'checkout',
|
||||
PayNow: 'pay-now',
|
||||
MiniCart: 'mini-cart',
|
||||
BlockCart: 'cart-block',
|
||||
BlockCheckout: 'checkout-block',
|
||||
Preview: 'preview',
|
||||
// Block editor contexts.
|
||||
Blocks: [ 'cart-block', 'checkout-block' ],
|
||||
// Custom gateway contexts.
|
||||
Gateways: [ 'checkout', 'pay-now' ],
|
||||
};
|
||||
|
||||
class GooglepayButton {
|
||||
#wrapperId = '';
|
||||
#ppcpButtonWrapperId = '';
|
||||
|
||||
/**
|
||||
* Reference to the payment button created by this instance.
|
||||
* Whether the payment button is initialized.
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @type {boolean}
|
||||
*/
|
||||
#button;
|
||||
#isInitialized = false;
|
||||
|
||||
/**
|
||||
* Client reference, provided by the Google Pay JS SDK.
|
||||
|
@ -29,8 +74,6 @@ class GooglepayButton {
|
|||
|
||||
apmButtonsInit( ppcpConfig );
|
||||
|
||||
this.isInitialized = false;
|
||||
|
||||
this.context = context;
|
||||
this.externalHandler = externalHandler;
|
||||
this.buttonConfig = buttonConfig;
|
||||
|
@ -54,7 +97,7 @@ class GooglepayButton {
|
|||
* @private
|
||||
*/
|
||||
_initDebug( enableDebugging, context ) {
|
||||
if ( ! enableDebugging ) {
|
||||
if ( ! enableDebugging || this.#isInitialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -70,11 +113,164 @@ class GooglepayButton {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 &&
|
||||
CONTEXT.Gateways.includes( this.context )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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( /^#/, '' );
|
||||
}
|
||||
|
||||
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( /^#/, '' );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
init( config, transactionInfo ) {
|
||||
if ( this.isInitialized ) {
|
||||
if ( this.#isInitialized ) {
|
||||
return;
|
||||
}
|
||||
this.isInitialized = true;
|
||||
|
||||
if ( ! this.validateConfig() ) {
|
||||
return;
|
||||
|
@ -85,6 +281,7 @@ class GooglepayButton {
|
|||
}
|
||||
|
||||
this.log( 'Init' );
|
||||
this.#isInitialized = true;
|
||||
|
||||
this.googlePayConfig = config;
|
||||
this.transactionInfo = transactionInfo;
|
||||
|
@ -103,40 +300,7 @@ class GooglepayButton {
|
|||
)
|
||||
.then( ( response ) => {
|
||||
if ( response.result ) {
|
||||
if (
|
||||
( this.context === 'checkout' ||
|
||||
this.context === 'pay-now' ) &&
|
||||
this.buttonConfig.is_wc_gateway_enabled === '1'
|
||||
) {
|
||||
const wrapper = document.getElementById(
|
||||
'ppc-button-ppcp-googlepay'
|
||||
);
|
||||
|
||||
if ( wrapper ) {
|
||||
const { ppcpStyle, buttonStyle } =
|
||||
this.contextConfig();
|
||||
|
||||
wrapper.classList.add(
|
||||
`ppcp-button-${ ppcpStyle.shape }`,
|
||||
'ppcp-button-apm',
|
||||
'ppcp-button-googlepay'
|
||||
);
|
||||
|
||||
if ( ppcpStyle.height ) {
|
||||
wrapper.style.height = `${ ppcpStyle.height }px`;
|
||||
}
|
||||
|
||||
this.addButtonCheckout(
|
||||
this.baseCardPaymentMethod,
|
||||
wrapper,
|
||||
buttonStyle
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.addButton( this.baseCardPaymentMethod );
|
||||
this.addButton();
|
||||
}
|
||||
} )
|
||||
.catch( function ( err ) {
|
||||
|
@ -149,7 +313,7 @@ class GooglepayButton {
|
|||
return;
|
||||
}
|
||||
|
||||
this.isInitialized = false;
|
||||
this.#isInitialized = false;
|
||||
this.init( this.googlePayConfig, this.transactionInfo );
|
||||
}
|
||||
|
||||
|
@ -177,39 +341,6 @@ class GooglepayButton {
|
|||
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 ),
|
||||
|
@ -235,7 +366,8 @@ class GooglepayButton {
|
|||
}
|
||||
|
||||
initEventHandlers() {
|
||||
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
|
||||
const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`;
|
||||
const wrapper = `#${ this.wrapperId }`;
|
||||
|
||||
if ( wrapper === ppcpButtonWrapper ) {
|
||||
throw new Error(
|
||||
|
@ -272,77 +404,104 @@ class GooglepayButton {
|
|||
|
||||
/**
|
||||
* Add a Google Pay purchase button
|
||||
* @param baseCardPaymentMethod
|
||||
*/
|
||||
addButton( baseCardPaymentMethod ) {
|
||||
addButton() {
|
||||
this.log( 'addButton' );
|
||||
|
||||
const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig();
|
||||
const insertButton = () => {
|
||||
const wrapper = this.wrapperElement;
|
||||
const baseCardPaymentMethod = this.baseCardPaymentMethod;
|
||||
const { color, type, language } = this.buttonStyle;
|
||||
const { shape, height } = this.ppcpStyle;
|
||||
|
||||
this.waitForWrapper( wrapper, () => {
|
||||
// Prevent duplicate payment buttons.
|
||||
this.removeButton();
|
||||
wrapper.classList.add(
|
||||
`ppcp-button-${ shape }`,
|
||||
'ppcp-button-apm',
|
||||
'ppcp-button-googlepay'
|
||||
);
|
||||
|
||||
jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape );
|
||||
|
||||
if ( ppcpStyle.height ) {
|
||||
jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` );
|
||||
if ( height ) {
|
||||
wrapper.style.height = `${ height }px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://developers.google.com/pay/api/web/reference/client#createButton
|
||||
*/
|
||||
this.#button = this.paymentsClient.createButton( {
|
||||
const button = this.paymentsClient.createButton( {
|
||||
onClick: this.onButtonClick.bind( this ),
|
||||
allowedPaymentMethods: [ baseCardPaymentMethod ],
|
||||
buttonColor: buttonStyle.color || 'black',
|
||||
buttonType: buttonStyle.type || 'pay',
|
||||
buttonLocale: buttonStyle.language || 'en',
|
||||
buttonColor: color || 'black',
|
||||
buttonType: type || 'pay',
|
||||
buttonLocale: language || 'en',
|
||||
buttonSizeMode: 'fill',
|
||||
} );
|
||||
|
||||
jQuery( wrapper ).append( this.#button );
|
||||
this.log( 'Insert Button', { wrapper, button } );
|
||||
|
||||
wrapper.replaceChildren( button );
|
||||
this.show();
|
||||
};
|
||||
|
||||
this.waitForWrapper( insertButton );
|
||||
}
|
||||
|
||||
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.log( '!! Wrapper not found:', this.wrapperId );
|
||||
}
|
||||
};
|
||||
|
||||
interval = setInterval( checkElement, delay );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all wrappers that belong to this GooglePayButton instance.
|
||||
*/
|
||||
hide() {
|
||||
this.log( 'Hide' );
|
||||
this.allElements.forEach( ( element ) => {
|
||||
element.style.display = 'none';
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the payment button that was injected via addButton()
|
||||
* Ensures all wrapper elements of this GooglePayButton instance are visible.
|
||||
*/
|
||||
removeButton() {
|
||||
if ( ! this.#button ) {
|
||||
show() {
|
||||
if ( ! this.isPresent ) {
|
||||
this.log( 'Cannot show button, wrapper is not present' );
|
||||
return;
|
||||
}
|
||||
this.log( 'Show' );
|
||||
|
||||
this.#button.remove();
|
||||
this.#button = null;
|
||||
}
|
||||
// Classic Checkout: Make the Google Pay gateway visible.
|
||||
document
|
||||
.querySelectorAll( 'style#ppcp-hide-google-pay' )
|
||||
.forEach( ( el ) => el.remove() );
|
||||
|
||||
addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) {
|
||||
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',
|
||||
this.allElements.forEach( ( element ) => {
|
||||
element.style.display = 'block';
|
||||
} );
|
||||
|
||||
wrapper.appendChild( 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 );
|
||||
}
|
||||
|
||||
//------------------------
|
||||
|
|
|
@ -290,6 +290,7 @@ class Button implements ButtonInterface {
|
|||
$render_placeholder,
|
||||
function () {
|
||||
$this->googlepay_button();
|
||||
$this->hide_gateway_until_eligible();
|
||||
},
|
||||
21
|
||||
);
|
||||
|
@ -303,6 +304,7 @@ class Button implements ButtonInterface {
|
|||
$render_placeholder,
|
||||
function () {
|
||||
$this->googlepay_button();
|
||||
$this->hide_gateway_until_eligible();
|
||||
},
|
||||
21
|
||||
);
|
||||
|
@ -335,6 +337,21 @@ class Button implements ButtonInterface {
|
|||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout).
|
||||
* The style is removed by `GooglepayButton.js` once the eligibility of the payment method
|
||||
* is confirmed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function hide_gateway_until_eligible() : void {
|
||||
?>
|
||||
<style id="ppcp-hide-google-pay">
|
||||
.wc_payment_method.payment_method_ppcp-googlepay { : none}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues scripts/styles.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue