2024-07-29 21:16:53 +02:00
|
|
|
/* global google */
|
|
|
|
|
2024-08-02 16:32:27 +02:00
|
|
|
import ConsoleLogger from '../../../ppcp-button/resources/js/modules/Helper/ConsoleLogger';
|
2024-07-12 12:58:34 +02:00
|
|
|
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';
|
2024-07-30 13:55:24 +02:00
|
|
|
import {
|
|
|
|
PaymentMethods,
|
|
|
|
PaymentContext as CONTEXT,
|
|
|
|
} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
2023-08-29 14:19:27 +01:00
|
|
|
|
2024-07-29 21:16:53 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2023-08-29 14:19:27 +01:00
|
|
|
class GooglepayButton {
|
2024-08-02 16:32:27 +02:00
|
|
|
/**
|
|
|
|
* @type {ConsoleLogger}
|
|
|
|
*/
|
|
|
|
#logger;
|
|
|
|
|
2024-07-29 21:16:53 +02:00
|
|
|
#wrapperId = '';
|
|
|
|
#ppcpButtonWrapperId = '';
|
|
|
|
|
2024-07-29 16:20:47 +02:00
|
|
|
/**
|
2024-07-29 21:16:53 +02:00
|
|
|
* Whether the payment button is initialized.
|
2024-07-29 16:20:47 +02:00
|
|
|
*
|
2024-07-29 21:16:53 +02:00
|
|
|
* @type {boolean}
|
2024-07-29 16:20:47 +02:00
|
|
|
*/
|
2024-07-29 21:16:53 +02:00
|
|
|
#isInitialized = false;
|
2024-07-29 16:20:47 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
/**
|
|
|
|
* Whether the current client support the payment button.
|
|
|
|
* This state is mainly dependent on the response of `PaymentClient.isReadyToPay()`
|
|
|
|
*
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
#isEligible = false;
|
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
/**
|
|
|
|
* Whether this button is visible. Modified by `show()` and `hide()`
|
|
|
|
*
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
#isVisible = false;
|
|
|
|
|
2024-07-29 16:20:47 +02:00
|
|
|
/**
|
|
|
|
* Client reference, provided by the Google Pay JS SDK.
|
|
|
|
* @see https://developers.google.com/pay/api/web/reference/client
|
|
|
|
*/
|
|
|
|
paymentsClient = null;
|
|
|
|
|
2024-07-15 11:21:56 +02:00
|
|
|
constructor(
|
|
|
|
context,
|
|
|
|
externalHandler,
|
|
|
|
buttonConfig,
|
|
|
|
ppcpConfig,
|
|
|
|
contextHandler
|
|
|
|
) {
|
2024-08-02 16:32:27 +02:00
|
|
|
this.#logger = new ConsoleLogger( 'GooglePayButton', context );
|
|
|
|
this.#logger.enabled = !! buttonConfig?.is_debug;
|
2024-07-29 18:05:08 +02:00
|
|
|
|
2024-07-12 12:58:34 +02:00
|
|
|
apmButtonsInit( ppcpConfig );
|
|
|
|
|
|
|
|
this.context = context;
|
|
|
|
this.externalHandler = externalHandler;
|
|
|
|
this.buttonConfig = buttonConfig;
|
|
|
|
this.ppcpConfig = ppcpConfig;
|
2024-07-15 11:21:56 +02:00
|
|
|
this.contextHandler = contextHandler;
|
2024-07-29 18:30:03 +02:00
|
|
|
|
2024-07-30 13:55:24 +02:00
|
|
|
this.refresh = this.refresh.bind( this );
|
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'Create instance' );
|
2024-07-29 18:05:08 +02:00
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-08-02 16:32:27 +02:00
|
|
|
log( ...args ) {
|
|
|
|
this.#logger.log( ...args );
|
|
|
|
}
|
2024-07-29 18:05:08 +02:00
|
|
|
|
2024-08-02 16:32:27 +02:00
|
|
|
error( ...args ) {
|
|
|
|
this.#logger.error( ...args );
|
2024-07-12 12:58:34 +02:00
|
|
|
}
|
|
|
|
|
2024-07-29 21:16:53 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:21:56 +02:00
|
|
|
init( config, transactionInfo ) {
|
2024-07-29 21:16:53 +02:00
|
|
|
if ( this.#isInitialized ) {
|
2024-07-12 12:58:34 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! this.validateConfig() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! this.contextHandler.validateContext() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'Init' );
|
2024-07-29 21:16:53 +02:00
|
|
|
this.#isInitialized = true;
|
2024-07-29 18:30:03 +02:00
|
|
|
|
2024-07-12 12:58:34 +02:00
|
|
|
this.googlePayConfig = config;
|
2024-07-15 11:21:56 +02:00
|
|
|
this.transactionInfo = transactionInfo;
|
2024-07-12 12:58:34 +02:00
|
|
|
this.allowedPaymentMethods = config.allowedPaymentMethods;
|
|
|
|
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
|
|
|
|
|
|
|
|
this.initClient();
|
|
|
|
this.initEventHandlers();
|
|
|
|
|
|
|
|
this.paymentsClient
|
|
|
|
.isReadyToPay(
|
|
|
|
this.buildReadyToPayRequest(
|
|
|
|
this.allowedPaymentMethods,
|
|
|
|
config
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.then( ( response ) => {
|
2024-07-30 12:50:41 +02:00
|
|
|
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;
|
|
|
|
} );
|
2024-07-12 12:58:34 +02:00
|
|
|
} )
|
2024-07-30 12:50:41 +02:00
|
|
|
.catch( ( err ) => {
|
2024-07-12 12:58:34 +02:00
|
|
|
console.error( err );
|
2024-07-30 12:50:41 +02:00
|
|
|
this.isEligible = false;
|
2024-07-12 12:58:34 +02:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
reinit() {
|
|
|
|
if ( ! this.googlePayConfig ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-29 21:16:53 +02:00
|
|
|
this.#isInitialized = false;
|
2024-07-15 11:21:56 +02:00
|
|
|
this.init( this.googlePayConfig, this.transactionInfo );
|
2024-07-12 12:58:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ),
|
|
|
|
};
|
|
|
|
|
|
|
|
if (
|
|
|
|
this.buttonConfig.shipping.enabled &&
|
|
|
|
this.contextHandler.shippingAllowed()
|
|
|
|
) {
|
|
|
|
callbacks.onPaymentDataChanged =
|
|
|
|
this.onPaymentDataChanged.bind( this );
|
|
|
|
}
|
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
/**
|
|
|
|
* Consider providing merchant info here:
|
|
|
|
*
|
|
|
|
* @see https://developers.google.com/pay/api/web/reference/request-objects#PaymentOptions
|
|
|
|
*/
|
2024-07-12 12:58:34 +02:00
|
|
|
this.paymentsClient = new google.payments.api.PaymentsClient( {
|
|
|
|
environment: this.buttonConfig.environment,
|
|
|
|
paymentDataCallbacks: callbacks,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
initEventHandlers() {
|
2024-07-30 13:55:24 +02:00
|
|
|
if ( CONTEXT.Gateways.includes( this.context ) ) {
|
2024-07-31 10:01:42 +02:00
|
|
|
document.body.addEventListener( 'ppcp_invalidate_methods', () => {
|
|
|
|
this.isVisible = false;
|
|
|
|
} );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-30 13:55:24 +02:00
|
|
|
document.body.addEventListener(
|
|
|
|
`ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`,
|
2024-07-31 10:01:42 +02:00
|
|
|
() => {
|
|
|
|
this.isVisible = true;
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
);
|
2024-07-30 13:55:24 +02:00
|
|
|
} else {
|
|
|
|
/**
|
|
|
|
* Review: The following logic appears to be unnecessary. Is it still required?
|
2024-07-31 10:01:42 +02:00
|
|
|
* /
|
|
|
|
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();
|
|
|
|
//
|
2024-07-30 13:55:24 +02:00
|
|
|
*/
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) {
|
2024-07-30 12:50:41 +02:00
|
|
|
this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods );
|
|
|
|
|
2024-07-12 12:58:34 +02:00
|
|
|
return Object.assign( {}, baseRequest, {
|
|
|
|
allowedPaymentMethods,
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a Google Pay purchase button
|
|
|
|
*/
|
2024-07-29 21:16:53 +02:00
|
|
|
addButton() {
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'addButton' );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
const wrapper = this.wrapperElement;
|
|
|
|
const baseCardPaymentMethod = this.baseCardPaymentMethod;
|
|
|
|
const { color, type, language } = this.buttonStyle;
|
|
|
|
const { shape, height } = this.ppcpStyle;
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
wrapper.classList.add(
|
|
|
|
`ppcp-button-${ shape }`,
|
|
|
|
'ppcp-button-apm',
|
|
|
|
'ppcp-button-googlepay'
|
|
|
|
);
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
if ( height ) {
|
|
|
|
wrapper.style.height = `${ height }px`;
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
/**
|
|
|
|
* @see https://developers.google.com/pay/api/web/reference/client#createButton
|
|
|
|
*/
|
|
|
|
const button = this.paymentsClient.createButton( {
|
|
|
|
onClick: this.onButtonClick.bind( this ),
|
|
|
|
allowedPaymentMethods: [ baseCardPaymentMethod ],
|
|
|
|
buttonColor: color || 'black',
|
|
|
|
buttonType: type || 'pay',
|
|
|
|
buttonLocale: language || 'en',
|
|
|
|
buttonSizeMode: 'fill',
|
|
|
|
} );
|
2024-07-29 21:16:53 +02:00
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
this.log( 'Insert Button', {
|
|
|
|
wrapper,
|
|
|
|
button,
|
|
|
|
} );
|
2024-07-29 21:16:53 +02:00
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
wrapper.replaceChildren( button );
|
2024-07-29 21:16:53 +02:00
|
|
|
}
|
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2024-07-31 10:01:42 +02:00
|
|
|
* @param {Function} callback Function to call when the wrapper element was detected. Only
|
|
|
|
* called on success.
|
2024-07-30 12:50:41 +02:00
|
|
|
* @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
|
|
|
|
*/
|
2024-07-29 21:16:53 +02:00
|
|
|
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();
|
2024-08-02 16:32:27 +02:00
|
|
|
this.error( 'Wrapper not found:', this.wrapperId );
|
2024-07-29 21:16:53 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
interval = setInterval( checkElement, delay );
|
|
|
|
}
|
|
|
|
|
2024-07-30 12:50:41 +02:00
|
|
|
/**
|
|
|
|
* Refreshes the payment button on the page.
|
|
|
|
*/
|
|
|
|
refresh() {
|
2024-07-31 10:01:42 +02:00
|
|
|
const showButtonWrapper = () => {
|
|
|
|
this.log( 'Show' );
|
2024-07-30 12:50:41 +02:00
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
// Classic Checkout: Make the Google Pay gateway visible.
|
|
|
|
document
|
|
|
|
.querySelectorAll( 'style#ppcp-hide-google-pay' )
|
|
|
|
.forEach( ( el ) => el.remove() );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
this.allElements.forEach( ( element ) => {
|
|
|
|
element.style.display = 'block';
|
|
|
|
} );
|
|
|
|
};
|
2024-07-29 16:20:47 +02:00
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
const hideButtonWrapper = () => {
|
|
|
|
this.log( 'Hide' );
|
2024-07-29 16:20:47 +02:00
|
|
|
|
2024-07-31 10:01:42 +02:00
|
|
|
this.allElements.forEach( ( element ) => {
|
|
|
|
element.style.display = 'none';
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( this.isVisible && this.isEligible && this.isPresent ) {
|
|
|
|
showButtonWrapper();
|
|
|
|
this.addButton();
|
|
|
|
} else {
|
|
|
|
hideButtonWrapper();
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------
|
|
|
|
// Button click
|
|
|
|
//------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show Google Pay payment sheet when Google Pay payment button is clicked
|
|
|
|
*/
|
2024-07-15 11:21:56 +02:00
|
|
|
onButtonClick() {
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'onButtonClick' );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-15 11:21:56 +02:00
|
|
|
const paymentDataRequest = this.paymentDataRequest();
|
2024-07-15 11:31:48 +02:00
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'onButtonClick: paymentDataRequest', paymentDataRequest );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
// Do this on another place like on create order endpoint handler.
|
|
|
|
window.ppcpFundingSource = 'googlepay';
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
this.paymentsClient.loadPaymentData( paymentDataRequest );
|
|
|
|
}
|
|
|
|
|
2024-07-15 11:21:56 +02:00
|
|
|
paymentDataRequest() {
|
2024-07-12 12:58:34 +02:00
|
|
|
const baseRequest = {
|
|
|
|
apiVersion: 2,
|
|
|
|
apiVersionMinor: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
const googlePayConfig = this.googlePayConfig;
|
|
|
|
const paymentDataRequest = Object.assign( {}, baseRequest );
|
|
|
|
paymentDataRequest.allowedPaymentMethods =
|
|
|
|
googlePayConfig.allowedPaymentMethods;
|
2024-07-15 11:21:56 +02:00
|
|
|
paymentDataRequest.transactionInfo = this.transactionInfo;
|
2024-07-12 12:58:34 +02:00
|
|
|
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
|
|
|
|
|
|
|
|
if (
|
|
|
|
this.buttonConfig.shipping.enabled &&
|
|
|
|
this.contextHandler.shippingAllowed()
|
|
|
|
) {
|
|
|
|
paymentDataRequest.callbackIntents = [
|
|
|
|
'SHIPPING_ADDRESS',
|
|
|
|
'SHIPPING_OPTION',
|
|
|
|
'PAYMENT_AUTHORIZATION',
|
|
|
|
];
|
|
|
|
paymentDataRequest.shippingAddressRequired = true;
|
|
|
|
paymentDataRequest.shippingAddressParameters =
|
|
|
|
this.shippingAddressParameters();
|
|
|
|
paymentDataRequest.shippingOptionRequired = true;
|
|
|
|
} else {
|
|
|
|
paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ];
|
|
|
|
}
|
|
|
|
|
|
|
|
return paymentDataRequest;
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------
|
|
|
|
// Shipping processing
|
|
|
|
//------------------------
|
|
|
|
|
|
|
|
shippingAddressParameters() {
|
|
|
|
return {
|
|
|
|
allowedCountryCodes: this.buttonConfig.shipping.countries,
|
|
|
|
phoneNumberRequired: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
onPaymentDataChanged( paymentData ) {
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'onPaymentDataChanged', paymentData );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
return new Promise( async ( resolve, reject ) => {
|
2024-07-15 11:21:56 +02:00
|
|
|
try {
|
|
|
|
const paymentDataRequestUpdate = {};
|
|
|
|
|
|
|
|
const updatedData = await new UpdatePaymentData(
|
|
|
|
this.buttonConfig.ajax.update_payment_data
|
|
|
|
).update( paymentData );
|
|
|
|
const transactionInfo = this.transactionInfo;
|
|
|
|
|
|
|
|
this.log( 'onPaymentDataChanged:updatedData', updatedData );
|
|
|
|
this.log(
|
|
|
|
'onPaymentDataChanged:transactionInfo',
|
|
|
|
transactionInfo
|
|
|
|
);
|
|
|
|
|
|
|
|
updatedData.country_code = transactionInfo.countryCode;
|
|
|
|
updatedData.currency_code = transactionInfo.currencyCode;
|
|
|
|
updatedData.total_str = transactionInfo.totalPrice;
|
|
|
|
|
|
|
|
// Handle unserviceable address.
|
|
|
|
if ( ! updatedData.shipping_options?.shippingOptions?.length ) {
|
|
|
|
paymentDataRequestUpdate.error =
|
|
|
|
this.unserviceableShippingAddressError();
|
|
|
|
resolve( paymentDataRequestUpdate );
|
|
|
|
return;
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
|
2024-07-15 11:21:56 +02:00
|
|
|
switch ( paymentData.callbackTrigger ) {
|
|
|
|
case 'INITIALIZE':
|
|
|
|
case 'SHIPPING_ADDRESS':
|
|
|
|
paymentDataRequestUpdate.newShippingOptionParameters =
|
|
|
|
updatedData.shipping_options;
|
|
|
|
paymentDataRequestUpdate.newTransactionInfo =
|
|
|
|
this.calculateNewTransactionInfo( updatedData );
|
|
|
|
break;
|
|
|
|
case 'SHIPPING_OPTION':
|
|
|
|
paymentDataRequestUpdate.newTransactionInfo =
|
|
|
|
this.calculateNewTransactionInfo( updatedData );
|
|
|
|
break;
|
|
|
|
}
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
resolve( paymentDataRequestUpdate );
|
2024-07-15 11:21:56 +02:00
|
|
|
} catch ( error ) {
|
|
|
|
console.error( 'Error during onPaymentDataChanged:', error );
|
|
|
|
reject( error );
|
2024-07-12 12:58:34 +02:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
unserviceableShippingAddressError() {
|
|
|
|
return {
|
|
|
|
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
|
|
|
|
message: 'Cannot ship to the selected address',
|
|
|
|
intent: 'SHIPPING_ADDRESS',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
calculateNewTransactionInfo( updatedData ) {
|
|
|
|
return {
|
|
|
|
countryCode: updatedData.country_code,
|
|
|
|
currencyCode: updatedData.currency_code,
|
|
|
|
totalPriceStatus: 'FINAL',
|
|
|
|
totalPrice: updatedData.total_str,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------
|
|
|
|
// Payment process
|
|
|
|
//------------------------
|
|
|
|
|
|
|
|
onPaymentAuthorized( paymentData ) {
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'onPaymentAuthorized' );
|
2024-07-12 12:58:34 +02:00
|
|
|
return this.processPayment( paymentData );
|
|
|
|
}
|
|
|
|
|
|
|
|
async processPayment( paymentData ) {
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'processPayment' );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
return new Promise( async ( resolve, reject ) => {
|
|
|
|
try {
|
|
|
|
const id = await this.contextHandler.createOrder();
|
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'processPayment: createOrder', id );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
const confirmOrderResponse = await widgetBuilder.paypal
|
|
|
|
.Googlepay()
|
|
|
|
.confirmOrder( {
|
|
|
|
orderId: id,
|
|
|
|
paymentMethodData: paymentData.paymentMethodData,
|
|
|
|
} );
|
|
|
|
|
|
|
|
this.log(
|
|
|
|
'processPayment: confirmOrder',
|
2024-07-29 18:30:03 +02:00
|
|
|
confirmOrderResponse
|
2024-07-12 12:58:34 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
/** Capture the Order on the Server */
|
|
|
|
if ( confirmOrderResponse.status === 'APPROVED' ) {
|
|
|
|
let approveFailed = false;
|
|
|
|
await this.contextHandler.approveOrder(
|
|
|
|
{
|
|
|
|
orderID: id,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// actions mock object.
|
|
|
|
restart: () =>
|
|
|
|
new Promise( ( resolve, reject ) => {
|
|
|
|
approveFailed = true;
|
|
|
|
resolve();
|
|
|
|
} ),
|
|
|
|
order: {
|
|
|
|
get: () =>
|
|
|
|
new Promise( ( resolve, reject ) => {
|
|
|
|
resolve( null );
|
|
|
|
} ),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( ! approveFailed ) {
|
|
|
|
resolve( this.processPaymentResponse( 'SUCCESS' ) );
|
|
|
|
} else {
|
|
|
|
resolve(
|
|
|
|
this.processPaymentResponse(
|
|
|
|
'ERROR',
|
|
|
|
'PAYMENT_AUTHORIZATION',
|
|
|
|
'FAILED TO APPROVE'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
resolve(
|
|
|
|
this.processPaymentResponse(
|
|
|
|
'ERROR',
|
|
|
|
'PAYMENT_AUTHORIZATION',
|
|
|
|
'TRANSACTION FAILED'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch ( err ) {
|
|
|
|
resolve(
|
|
|
|
this.processPaymentResponse(
|
|
|
|
'ERROR',
|
|
|
|
'PAYMENT_AUTHORIZATION',
|
|
|
|
err.message
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
processPaymentResponse( state, intent = null, message = null ) {
|
|
|
|
const response = {
|
|
|
|
transactionState: state,
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( intent || message ) {
|
|
|
|
response.error = {
|
|
|
|
intent,
|
|
|
|
message,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-07-29 18:30:03 +02:00
|
|
|
this.log( 'processPaymentResponse', response );
|
2024-07-12 12:58:34 +02:00
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
2023-08-29 14:19:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export default GooglepayButton;
|