mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
♻️ Create generic base classes for preview buttons
This commit is contained in:
parent
9342086f33
commit
ebb1f6962f
3 changed files with 343 additions and 241 deletions
|
@ -1,7 +1,7 @@
|
|||
import {loadCustomScript} from "@paypal/paypal-js";
|
||||
import GooglepayButton from "./GooglepayButton";
|
||||
import merge from "deepmerge";
|
||||
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
|
||||
import PreviewButton from "../../../ppcp-button/resources/js/modules/Renderer/PreviewButton";
|
||||
import PreviewButtonManager from "../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager";
|
||||
|
||||
/**
|
||||
* Button manager instance; we usually only need a single instance of this object.
|
||||
|
@ -28,23 +28,7 @@ const defaultAttributes = {
|
|||
*/
|
||||
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'
|
||||
}
|
||||
});
|
||||
managerInstance = new GooglePayPreviewButtonManager();
|
||||
}
|
||||
|
||||
return managerInstance;
|
||||
|
@ -54,267 +38,94 @@ const buttonManager = () => {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
class GooglePayPreviewButton extends PreviewButton {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
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;
|
||||
this.selector = `${args.selector}GooglePay`
|
||||
}
|
||||
|
||||
createNewWrapper() {
|
||||
const previewId = this.selector.replace('#', '')
|
||||
const previewClass = `ppcp-button-apm ${this.customClasses}`;
|
||||
|
||||
return jQuery(`<div id="${previewId}" class="${previewClass}">`)
|
||||
const element = super.createNewWrapper();
|
||||
element.addClass('ppcp-button-googlepay');
|
||||
return element;
|
||||
}
|
||||
|
||||
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(
|
||||
createButton() {
|
||||
const button = new GooglepayButton(
|
||||
'preview',
|
||||
null,
|
||||
this.buttonConfig,
|
||||
this.ppcpConfig,
|
||||
);
|
||||
|
||||
this.payButton.init(this.configResponse);
|
||||
return this;
|
||||
}
|
||||
button.init(this.configResponse);
|
||||
|
||||
remove() {
|
||||
// The payButton has no remove/cleanup function.
|
||||
this.payButton = null;
|
||||
|
||||
if (this.domWrapper) {
|
||||
this.domWrapper.remove();
|
||||
this.domWrapper = null;
|
||||
}
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||
constructor() {
|
||||
const args = {
|
||||
// WooCommerce configuration object.
|
||||
buttonConfig: window.wc_ppcp_googlepay_admin,
|
||||
// Internal widgetBuilder instance.
|
||||
widgetBuilder,
|
||||
// Default button styles.
|
||||
defaultAttributes
|
||||
};
|
||||
|
||||
this.state = 'enabled'
|
||||
this.buttons = {};
|
||||
this.configResponse = null;
|
||||
super(args);
|
||||
|
||||
// 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);
|
||||
this.methodName = 'GooglePay';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||
* method.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
* @return {Promise<{}>}
|
||||
*/
|
||||
async bootstrap() {
|
||||
if (!this.buttonConfig || !this.widgetBuilder) {
|
||||
this.error('Button could not be configured.');
|
||||
return;
|
||||
async fetchConfig() {
|
||||
const apiMethod = this.widgetBuilder?.paypal?.Googlepay()?.config
|
||||
|
||||
if (!apiMethod) {
|
||||
this.error('configuration object cannot be retrieved from PayPal');
|
||||
return {};
|
||||
}
|
||||
|
||||
// Load the custom SDK script.
|
||||
const customScriptPromise = loadCustomScript({url: this.buttonConfig.sdk_url});
|
||||
return await apiMethod();
|
||||
}
|
||||
|
||||
// Wait until PayPal is ready.
|
||||
const paypalPromise = new Promise(resolve => {
|
||||
if (this.widgetBuilder.paypal) {
|
||||
resolve();
|
||||
} else {
|
||||
onDocumentEvent('ppcp-paypal-loaded', resolve);
|
||||
}
|
||||
/**
|
||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||
*
|
||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||
* @return {GooglePayPreviewButton}
|
||||
*/
|
||||
createButtonInst(wrapperId) {
|
||||
return new GooglePayPreviewButton({
|
||||
selector: wrapperId,
|
||||
configResponse: this.configResponse,
|
||||
defaultAttributes: this.defaultAttributes
|
||||
});
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
alert('GPay Boot Admin')
|
||||
// Initialize the preview button manager.
|
||||
buttonManager().enable()
|
||||
buttonManager()
|
||||
|
||||
// todo - Expose button manager for testing. Remove this!
|
||||
window.gpay = buttonManager()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue