2023-09-21 11:58:51 +01:00
|
|
|
import {loadCustomScript} from "@paypal/paypal-js";
|
|
|
|
import GooglepayButton from "./GooglepayButton";
|
2024-06-04 22:10:37 +02:00
|
|
|
import merge from "deepmerge";
|
2023-09-21 11:58:51 +01:00
|
|
|
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
|
|
|
|
|
2024-06-04 22:10:37 +02:00
|
|
|
/**
|
|
|
|
* Button manager instance; we usually only need a single instance of this object.
|
|
|
|
*
|
|
|
|
* @see buttonManager()
|
|
|
|
*/
|
|
|
|
let managerInstance = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default attributes for new buttons.
|
|
|
|
*/
|
|
|
|
const defaultAttributes = {
|
|
|
|
button: {
|
|
|
|
style: {
|
|
|
|
type: 'pay',
|
|
|
|
color: 'black',
|
|
|
|
language: 'en'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Accessor that creates and returns a single PreviewButtonManager instance.
|
|
|
|
*/
|
|
|
|
const buttonManager = () => {
|
|
|
|
if (!managerInstance) {
|
|
|
|
managerInstance = new PreviewButtonManager({
|
|
|
|
// Internal, for logging.
|
|
|
|
name: 'GooglePay',
|
|
|
|
// WooCommerce configuration object.
|
|
|
|
buttonConfig: window.wc_ppcp_googlepay_admin,
|
|
|
|
// Internal widgetBuilder instance.
|
|
|
|
widgetBuilder,
|
|
|
|
// Default button styles.
|
|
|
|
defaultAttributes,
|
|
|
|
// Returns an async function that fetches the button configuration.
|
|
|
|
cbFetchConfig: () => widgetBuilder?.paypal?.Googlepay()?.config,
|
|
|
|
// Returns the CSS selector to render the given button.
|
|
|
|
cbInitButton: (button) => {
|
|
|
|
button.selector = `${button.wrapper}GooglePay`
|
|
|
|
button.customClasses = 'ppcp-button-googlepay'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return managerInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a new object which contains a copy of all public properties of the
|
|
|
|
* input object.
|
|
|
|
*/
|
|
|
|
const cloneObject = obj => JSON.parse(JSON.stringify(obj));
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience function to attach an event handler to the document node.
|
|
|
|
* The callback handler will not receive the first argument (event), as this
|
|
|
|
* argument is not used by our module.
|
|
|
|
*/
|
|
|
|
const onDocumentEvent = (name, handler) => jQuery(document).on(name, (ev, ...args) => handler(...args));
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A single GooglePay preview button instance.
|
|
|
|
*/
|
|
|
|
class PreviewButton {
|
|
|
|
constructor({wrapperSelector, configResponse, defaultAttributes, cbInitButton}) {
|
|
|
|
// Do not clone this object. It's generated by an API and might contain methods.
|
|
|
|
this.configResponse = configResponse;
|
|
|
|
|
|
|
|
this.defaultAttributes = cloneObject(defaultAttributes);
|
|
|
|
this.buttonConfig = {};
|
|
|
|
this.ppcpConfig = {};
|
|
|
|
|
|
|
|
this.wrapper = wrapperSelector;
|
|
|
|
this.selector = wrapperSelector + 'Button';
|
|
|
|
this.customClasses = '';
|
|
|
|
|
|
|
|
cbInitButton(this);
|
|
|
|
|
|
|
|
this.domWrapper = jQuery(this.selector);
|
|
|
|
this.payButton = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
createNewWrapper() {
|
|
|
|
const previewId = this.selector.replace('#', '')
|
|
|
|
const previewClass = `ppcp-button-apm ${this.customClasses}`;
|
|
|
|
|
|
|
|
return jQuery(`<div id="${previewId}" class="${previewClass}">`)
|
|
|
|
}
|
|
|
|
|
|
|
|
config(buttonConfig, ppcpConfig) {
|
|
|
|
if (ppcpConfig) {
|
|
|
|
this.ppcpConfig = cloneObject(ppcpConfig);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buttonConfig) {
|
|
|
|
this.buttonConfig = merge(this.defaultAttributes, buttonConfig)
|
|
|
|
this.buttonConfig.button.wrapper = this.selector
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
this.remove();
|
|
|
|
|
|
|
|
const newDomWrapper = this.createNewWrapper();
|
|
|
|
|
|
|
|
if (this.domWrapper?.length) {
|
|
|
|
this.domWrapper.replaceWith(newDomWrapper);
|
|
|
|
} else {
|
|
|
|
jQuery(this.ppcpConfig.button.wrapper).after(newDomWrapper);
|
|
|
|
}
|
|
|
|
this.domWrapper = newDomWrapper;
|
|
|
|
|
|
|
|
this.payButton = new GooglepayButton(
|
|
|
|
'preview',
|
|
|
|
null,
|
|
|
|
this.buttonConfig,
|
|
|
|
this.ppcpConfig,
|
|
|
|
);
|
|
|
|
|
|
|
|
this.payButton.init(this.configResponse);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
remove() {
|
|
|
|
// The payButton has no remove/cleanup function.
|
|
|
|
this.payButton = null;
|
|
|
|
|
|
|
|
if (this.domWrapper) {
|
|
|
|
this.domWrapper.remove();
|
|
|
|
this.domWrapper = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages all GooglePay preview buttons on this page.
|
|
|
|
*/
|
|
|
|
class PreviewButtonManager {
|
|
|
|
constructor({
|
|
|
|
name,
|
|
|
|
buttonConfig,
|
|
|
|
widgetBuilder,
|
|
|
|
defaultAttributes,
|
|
|
|
cbFetchConfig,
|
|
|
|
cbInitButton
|
|
|
|
}) {
|
|
|
|
this.name = name;
|
|
|
|
this.buttonConfig = buttonConfig;
|
|
|
|
this.widgetBuilder = widgetBuilder;
|
|
|
|
this.defaultAttributes = defaultAttributes;
|
|
|
|
this.cbInitButton = cbInitButton;
|
|
|
|
this.cbFetchConfig = cbFetchConfig;
|
|
|
|
|
|
|
|
this.state = 'enabled'
|
|
|
|
this.buttons = {};
|
|
|
|
this.configResponse = null;
|
|
|
|
|
|
|
|
// Empty promise that resolves instantly when called.
|
|
|
|
this.bootstrapping = Promise.resolve();
|
|
|
|
|
|
|
|
// Add the bootstrap logic to the Promise chain. More `then`s are added by createButton().
|
|
|
|
this.bootstrapping = this.bootstrapping.then(() => this.bootstrap());
|
|
|
|
|
|
|
|
this.registerEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
registerEventListeners() {
|
|
|
|
onDocumentEvent('ppcp_paypal_render_preview', this.createButton.bind(this));
|
|
|
|
onDocumentEvent('DOMContentLoaded', () => this.bootstrapping);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Output an error message to the console, with a module-specific prefix.
|
|
|
|
*/
|
|
|
|
error(message, ...args) {
|
|
|
|
console.error(`${this.name} ${message}`, ...args)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load dependencies and bootstrap the module.
|
|
|
|
* Returns a Promise that resolves once all dependencies were loaded and the module can be
|
|
|
|
* used without limitation.
|
|
|
|
*
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
async bootstrap() {
|
|
|
|
if (!this.buttonConfig || !this.widgetBuilder) {
|
|
|
|
this.error('Button could not be configured.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the custom SDK script.
|
|
|
|
const customScriptPromise = loadCustomScript({url: this.buttonConfig.sdk_url});
|
|
|
|
|
|
|
|
// Wait until PayPal is ready.
|
|
|
|
const paypalPromise = new Promise(resolve => {
|
|
|
|
if (this.widgetBuilder.paypal) {
|
|
|
|
resolve();
|
|
|
|
} else {
|
|
|
|
onDocumentEvent('ppcp-paypal-loaded', resolve);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all([customScriptPromise, paypalPromise]);
|
|
|
|
|
|
|
|
const fetchConfig = this.cbFetchConfig()
|
|
|
|
if (!fetchConfig) {
|
|
|
|
this.error('Button could not be initialized');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.configResponse = await fetchConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
|
|
|
|
*/
|
|
|
|
createButton(ppcpConfig) {
|
|
|
|
if (!ppcpConfig.button.wrapper) {
|
|
|
|
this.error('Button did not provide a selector', ppcpConfig)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const wrapper = ppcpConfig.button.wrapper;
|
|
|
|
|
|
|
|
const createButtonInst = () => {
|
|
|
|
if (!this.buttons[wrapper]) {
|
|
|
|
this.buttons[wrapper] = new PreviewButton({
|
|
|
|
wrapperSelector: wrapper,
|
|
|
|
configResponse: this.configResponse,
|
|
|
|
defaultAttributes: this.defaultAttributes,
|
|
|
|
cbInitButton: this.cbInitButton
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.buttons[wrapper].config(
|
|
|
|
this.buttonConfig,
|
|
|
|
ppcpConfig
|
|
|
|
).render()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.bootstrapping) {
|
|
|
|
this.bootstrapping.then(createButtonInst);
|
|
|
|
} else {
|
|
|
|
createButtonInst();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the button configuration, and re-renders all buttons.
|
|
|
|
*/
|
|
|
|
updateConfig(newConfig) {
|
|
|
|
if (!newConfig || 'object' !== typeof newConfig) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.buttonConfig = merge(this.buttonConfig, newConfig)
|
|
|
|
|
|
|
|
Object.values(this.buttons).forEach(button => button.config(this.buttonConfig))
|
|
|
|
this.renderButtons()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Refreshes all buttons using the latest buttonConfig.
|
|
|
|
*/
|
|
|
|
renderButtons() {
|
|
|
|
if ('enabled' === this.state) {
|
|
|
|
Object.values(this.buttons).forEach(button => button.render())
|
|
|
|
} else {
|
|
|
|
Object.values(this.buttons).forEach(button => button.remove())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables this payment method, which re-creates or refreshes all buttons.
|
|
|
|
*/
|
|
|
|
enable() {
|
|
|
|
if ('enabled' === this.state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.state = 'enabled';
|
|
|
|
this.renderButtons()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disables this payment method, effectively removing all preview buttons.
|
|
|
|
*/
|
|
|
|
disable() {
|
|
|
|
if ('disabled' === this.state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.state = 'disabled';
|
|
|
|
this.renderButtons()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the preview button manager.
|
|
|
|
buttonManager().enable()
|
|
|
|
|
|
|
|
// todo - Expose button manager for testing. Remove this!
|
|
|
|
window.gpay = buttonManager()
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
2023-09-21 11:58:51 +01:00
|
|
|
(function ({
|
|
|
|
buttonConfig,
|
|
|
|
jQuery
|
|
|
|
}) {
|
|
|
|
|
|
|
|
let googlePayConfig;
|
|
|
|
let buttonQueue = [];
|
2023-09-21 15:46:20 +01:00
|
|
|
let activeButtons = {};
|
2023-09-21 11:58:51 +01:00
|
|
|
let bootstrapped = false;
|
|
|
|
|
2023-09-21 15:46:20 +01:00
|
|
|
// React to PayPal config changes.
|
2023-09-21 11:58:51 +01:00
|
|
|
jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => {
|
|
|
|
if (bootstrapped) {
|
|
|
|
createButton(ppcpConfig);
|
|
|
|
} else {
|
|
|
|
buttonQueue.push({
|
|
|
|
ppcpConfig: JSON.parse(JSON.stringify(ppcpConfig))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-09-21 15:46:20 +01:00
|
|
|
// React to GooglePay config changes.
|
|
|
|
jQuery([
|
|
|
|
'#ppcp-googlepay_button_enabled',
|
|
|
|
'#ppcp-googlepay_button_type',
|
|
|
|
'#ppcp-googlepay_button_color',
|
|
|
|
'#ppcp-googlepay_button_language',
|
|
|
|
'#ppcp-googlepay_button_shipping_enabled'
|
|
|
|
].join(',')).on('change', () => {
|
|
|
|
for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
|
|
|
|
createButton(ppcpConfig);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-16 17:56:58 +01:00
|
|
|
// Maybe we can find a more elegant reload method when transitioning from styling modes.
|
|
|
|
jQuery([
|
|
|
|
'#ppcp-smart_button_enable_styling_per_location'
|
|
|
|
].join(',')).on('change', () => {
|
|
|
|
setTimeout(() => {
|
|
|
|
for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
|
|
|
|
createButton(ppcpConfig);
|
|
|
|
}
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
|
2024-06-03 17:19:18 +02:00
|
|
|
const shouldDisplayPreviewButton = function() {
|
|
|
|
// TODO - original condition, which is wrong.
|
|
|
|
return jQuery('#ppcp-googlepay_button_enabled').is(':checked');
|
|
|
|
}
|
|
|
|
|
2023-09-21 15:46:20 +01:00
|
|
|
const applyConfigOptions = function (buttonConfig) {
|
|
|
|
buttonConfig.button = buttonConfig.button || {};
|
|
|
|
buttonConfig.button.style = buttonConfig.button.style || {};
|
|
|
|
buttonConfig.button.style.type = jQuery('#ppcp-googlepay_button_type').val();
|
|
|
|
buttonConfig.button.style.color = jQuery('#ppcp-googlepay_button_color').val();
|
|
|
|
buttonConfig.button.style.language = jQuery('#ppcp-googlepay_button_language').val();
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:58:51 +01:00
|
|
|
const createButton = function (ppcpConfig) {
|
|
|
|
const selector = ppcpConfig.button.wrapper + 'GooglePay';
|
|
|
|
|
2024-06-03 17:19:18 +02:00
|
|
|
if (!shouldDisplayPreviewButton()) {
|
2023-09-21 15:46:20 +01:00
|
|
|
jQuery(selector).remove();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:58:51 +01:00
|
|
|
buttonConfig = JSON.parse(JSON.stringify(buttonConfig));
|
|
|
|
buttonConfig.button.wrapper = selector;
|
2023-09-21 15:46:20 +01:00
|
|
|
applyConfigOptions(buttonConfig);
|
2023-09-21 11:58:51 +01:00
|
|
|
|
2023-12-07 18:20:58 +00:00
|
|
|
const wrapperElement = `<div id="${selector.replace('#', '')}" class="ppcp-button-apm ppcp-button-googlepay"></div>`;
|
2023-09-21 11:58:51 +01:00
|
|
|
|
|
|
|
if (!jQuery(selector).length) {
|
|
|
|
jQuery(ppcpConfig.button.wrapper).after(wrapperElement);
|
|
|
|
} else {
|
|
|
|
jQuery(selector).replaceWith(wrapperElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
const button = new GooglepayButton(
|
|
|
|
'preview',
|
|
|
|
null,
|
|
|
|
buttonConfig,
|
|
|
|
ppcpConfig,
|
|
|
|
);
|
|
|
|
|
|
|
|
button.init(googlePayConfig);
|
2023-09-21 15:46:20 +01:00
|
|
|
|
|
|
|
activeButtons[selector] = ppcpConfig;
|
2023-09-21 11:58:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const bootstrap = async function () {
|
2023-10-12 15:27:55 +01:00
|
|
|
if (!widgetBuilder.paypal) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-21 11:58:51 +01:00
|
|
|
googlePayConfig = await widgetBuilder.paypal.Googlepay().config();
|
|
|
|
|
2023-10-16 15:32:25 +01:00
|
|
|
// We need to set bootstrapped here otherwise googlePayConfig may not be set.
|
|
|
|
bootstrapped = true;
|
|
|
|
|
2023-09-21 11:58:51 +01:00
|
|
|
let options;
|
|
|
|
while (options = buttonQueue.pop()) {
|
|
|
|
createButton(options.ppcpConfig);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
document.addEventListener(
|
|
|
|
'DOMContentLoaded',
|
|
|
|
() => {
|
|
|
|
|
|
|
|
if (typeof (buttonConfig) === 'undefined') {
|
|
|
|
console.error('PayPal button could not be configured.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let paypalLoaded = false;
|
|
|
|
let googlePayLoaded = false;
|
|
|
|
|
|
|
|
const tryToBoot = () => {
|
|
|
|
if (!bootstrapped && paypalLoaded && googlePayLoaded) {
|
|
|
|
bootstrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load GooglePay SDK
|
|
|
|
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
|
|
|
|
googlePayLoaded = true;
|
|
|
|
tryToBoot();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Wait for PayPal to be loaded externally
|
|
|
|
if (typeof widgetBuilder.paypal !== 'undefined') {
|
|
|
|
paypalLoaded = true;
|
|
|
|
tryToBoot();
|
|
|
|
}
|
|
|
|
|
|
|
|
jQuery(document).on('ppcp-paypal-loaded', () => {
|
|
|
|
paypalLoaded = true;
|
|
|
|
tryToBoot();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
})({
|
|
|
|
buttonConfig: window.wc_ppcp_googlepay_admin,
|
|
|
|
jQuery: window.jQuery
|
|
|
|
});
|
2024-06-04 22:10:37 +02:00
|
|
|
|
|
|
|
// */
|