mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 10:55:00 +08:00
* Add paypal script reloading
* Add button reloading in cart and product page * Add checking filters asynchronously
This commit is contained in:
parent
e207a11b7e
commit
4e50d1d6ba
16 changed files with 626 additions and 529 deletions
|
@ -194,9 +194,6 @@ const bootstrap = () => {
|
|||
payNowBootstrap.init();
|
||||
}
|
||||
|
||||
if (context !== 'checkout') {
|
||||
messageRenderer.render();
|
||||
}
|
||||
};
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
|
|
|
@ -98,43 +98,38 @@ class SingleProductActionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
getProducts()
|
||||
{
|
||||
if ( this.isBookingProduct() ) {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
} else if ( this.isGroupedProduct() ) {
|
||||
const products = [];
|
||||
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
|
||||
if (! element.value) {
|
||||
return;
|
||||
}
|
||||
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
|
||||
if (elementName.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const id = parseInt(elementName[1]);
|
||||
const quantity = parseInt(element.value);
|
||||
products.push(new Product(id, quantity, null));
|
||||
})
|
||||
return products;
|
||||
} else {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
const qty = document.querySelector('[name="quantity"]').value;
|
||||
const variations = this.variations();
|
||||
return [new Product(id, qty, variations)];
|
||||
}
|
||||
}
|
||||
|
||||
createOrder()
|
||||
{
|
||||
this.cartHelper = null;
|
||||
|
||||
let getProducts = (() => {
|
||||
if ( this.isBookingProduct() ) {
|
||||
return () => {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
}
|
||||
} else if ( this.isGroupedProduct() ) {
|
||||
return () => {
|
||||
const products = [];
|
||||
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
|
||||
if (! element.value) {
|
||||
return;
|
||||
}
|
||||
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
|
||||
if (elementName.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const id = parseInt(elementName[1]);
|
||||
const quantity = parseInt(element.value);
|
||||
products.push(new Product(id, quantity, null));
|
||||
})
|
||||
return products;
|
||||
}
|
||||
} else {
|
||||
return () => {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
const qty = document.querySelector('[name="quantity"]').value;
|
||||
const variations = this.variations();
|
||||
return [new Product(id, qty, variations)];
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return (data, actions) => {
|
||||
this.errorHandler.clear();
|
||||
|
||||
|
@ -170,7 +165,7 @@ class SingleProductActionHandler {
|
|||
});
|
||||
};
|
||||
|
||||
return this.updateCart.update(onResolve, getProducts());
|
||||
return this.updateCart.update(onResolve, this.getProducts());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,11 +40,21 @@ class CartBootstrap {
|
|||
return;
|
||||
}
|
||||
|
||||
// handle script reload
|
||||
const newParams = result.data.url_params;
|
||||
const reloadRequired = this.gateway.url_params.intent !== newParams.intent;
|
||||
const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams);
|
||||
|
||||
// TODO: should reload the script instead
|
||||
setVisible(this.gateway.button.wrapper, !reloadRequired)
|
||||
if (reloadRequired) {
|
||||
this.gateway.url_params = newParams;
|
||||
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons', this.gateway);
|
||||
}
|
||||
|
||||
// handle button status
|
||||
if ( result.data.button ) {
|
||||
this.gateway.button = result.data.button;
|
||||
}
|
||||
|
||||
this.handleButtonStatus();
|
||||
|
||||
if (this.lastAmount !== result.data.amount) {
|
||||
this.lastAmount = result.data.amount;
|
||||
|
|
|
@ -2,6 +2,8 @@ import UpdateCart from "../Helper/UpdateCart";
|
|||
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
|
||||
import {hide, show} from "../Helper/Hiding";
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import SimulateCart from "../Helper/SimulateCart";
|
||||
import {strRemoveWord, strAddWord} from "../Helper/Utils";
|
||||
|
||||
class SingleProductBootstap {
|
||||
constructor(gateway, renderer, messages, errorHandler) {
|
||||
|
@ -38,10 +40,60 @@ class SingleProductBootstap {
|
|||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
handleButtonStatus(simulateCart = true) {
|
||||
BootstrapHelper.handleButtonStatus(this, {
|
||||
formSelector: this.formSelector
|
||||
});
|
||||
|
||||
if (simulateCart) {
|
||||
//------
|
||||
|
||||
const actionHandler = new SingleProductActionHandler(
|
||||
null,
|
||||
null,
|
||||
this.form(),
|
||||
this.errorHandler,
|
||||
);
|
||||
|
||||
(new SimulateCart(
|
||||
this.gateway.ajax.simulate_cart.endpoint,
|
||||
this.gateway.ajax.simulate_cart.nonce,
|
||||
)).simulate((data) => {
|
||||
|
||||
this.messages.renderWithAmount(data.total);
|
||||
|
||||
let enableFunding = this.gateway.url_params['enable-funding'];
|
||||
let disableFunding = this.gateway.url_params['disable-funding'];
|
||||
|
||||
for (const [fundingSource, funding] of Object.entries(data.funding)) {
|
||||
if (funding.enabled === true) {
|
||||
enableFunding = strAddWord(enableFunding, fundingSource);
|
||||
disableFunding = strRemoveWord(disableFunding, fundingSource);
|
||||
} else if (funding.enabled === false) {
|
||||
enableFunding = strRemoveWord(enableFunding, fundingSource);
|
||||
disableFunding = strAddWord(disableFunding, fundingSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(enableFunding !== this.gateway.url_params['enable-funding']) ||
|
||||
(disableFunding !== this.gateway.url_params['disable-funding'])
|
||||
) {
|
||||
this.gateway.url_params['enable-funding'] = enableFunding;
|
||||
this.gateway.url_params['disable-funding'] = disableFunding;
|
||||
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
|
||||
}
|
||||
|
||||
if (typeof data.button.is_disabled === 'boolean') {
|
||||
this.gateway.button.is_disabled = data.button.is_disabled;
|
||||
}
|
||||
|
||||
this.handleButtonStatus(false);
|
||||
|
||||
}, actionHandler.getProducts());
|
||||
|
||||
//------
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -53,12 +105,6 @@ class SingleProductBootstap {
|
|||
|
||||
form.addEventListener('change', () => {
|
||||
this.handleChange();
|
||||
|
||||
setTimeout(() => { // Wait for the DOM to be fully updated
|
||||
// For the moment renderWithAmount should only be done here to prevent undesired side effects due to priceAmount()
|
||||
// not being correctly formatted in some cases, can be moved to handleButtonStatus() once this issue is fixed
|
||||
this.messages.renderWithAmount(this.priceAmount());
|
||||
}, 100);
|
||||
});
|
||||
this.mutationObserver.observe(form, { childList: true, subtree: true });
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
class SimulateCart {
|
||||
|
||||
constructor(endpoint, nonce)
|
||||
{
|
||||
this.endpoint = endpoint;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param onResolve
|
||||
* @param {Product[]} products
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
simulate(onResolve, products)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(
|
||||
this.endpoint,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: this.nonce,
|
||||
products,
|
||||
})
|
||||
}
|
||||
).then(
|
||||
(result) => {
|
||||
return result.json();
|
||||
}
|
||||
).then((result) => {
|
||||
if (! result.success) {
|
||||
reject(result.data);
|
||||
return;
|
||||
}
|
||||
|
||||
const resolved = onResolve(result.data);
|
||||
resolve(resolved);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SimulateCart;
|
41
modules/ppcp-button/resources/js/modules/Helper/Utils.js
Normal file
41
modules/ppcp-button/resources/js/modules/Helper/Utils.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
export const toCamelCase = (str) => {
|
||||
return str.replace(/([-_]\w)/g, function(match) {
|
||||
return match[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export const keysToCamelCase = (obj) => {
|
||||
let output = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
output[toCamelCase(key)] = obj[key];
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export const strAddWord = (str, word, separator = ',') => {
|
||||
let arr = str.split(separator);
|
||||
if (!arr.includes(word)) {
|
||||
arr.push(word);
|
||||
}
|
||||
return arr.join(separator);
|
||||
};
|
||||
|
||||
export const strRemoveWord = (str, word, separator = ',') => {
|
||||
let arr = str.split(separator);
|
||||
let index = arr.indexOf(word);
|
||||
if (index !== -1) {
|
||||
arr.splice(index, 1);
|
||||
}
|
||||
return arr.join(separator);
|
||||
};
|
||||
|
||||
const Utils = {
|
||||
toCamelCase,
|
||||
keysToCamelCase,
|
||||
strAddWord,
|
||||
strRemoveWord
|
||||
};
|
||||
|
||||
export default Utils;
|
|
@ -44,7 +44,7 @@ class MessageRenderer {
|
|||
|
||||
shouldRender() {
|
||||
|
||||
if (typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
|
||||
if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
|
||||
return false;
|
||||
}
|
||||
if (! document.querySelector(this.config.wrapper)) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import merge from "deepmerge";
|
||||
import {loadScript} from "@paypal/paypal-js";
|
||||
import {keysToCamelCase} from "../Helper/Utils";
|
||||
|
||||
class Renderer {
|
||||
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
|
||||
|
@ -10,6 +12,8 @@ class Renderer {
|
|||
this.buttonsOptions = {};
|
||||
this.onButtonsInitListeners = {};
|
||||
|
||||
this.activeButtons = {};
|
||||
|
||||
this.renderedSources = new Set();
|
||||
}
|
||||
|
||||
|
@ -68,32 +72,61 @@ class Renderer {
|
|||
}
|
||||
|
||||
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
|
||||
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) || 'undefined' === typeof paypal.Buttons ) {
|
||||
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('rendering', wrapper);
|
||||
|
||||
if (fundingSource) {
|
||||
contextConfig.fundingSource = fundingSource;
|
||||
}
|
||||
|
||||
const btn = paypal.Buttons({
|
||||
style,
|
||||
...contextConfig,
|
||||
onClick: this.onSmartButtonClick,
|
||||
onInit: (data, actions) => {
|
||||
if (this.onSmartButtonsInit) {
|
||||
this.onSmartButtonsInit(data, actions);
|
||||
}
|
||||
this.handleOnButtonsInit(wrapper, data, actions);
|
||||
},
|
||||
});
|
||||
if (!btn.isEligible()) {
|
||||
return;
|
||||
const buttonsOptions = () => {
|
||||
return {
|
||||
style,
|
||||
...contextConfig,
|
||||
onClick: this.onSmartButtonClick,
|
||||
onInit: (data, actions) => {
|
||||
if (this.onSmartButtonsInit) {
|
||||
this.onSmartButtonsInit(data, actions);
|
||||
}
|
||||
this.handleOnButtonsInit(wrapper, data, actions);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
btn.render(wrapper);
|
||||
const buildButtons = (paypal) => {
|
||||
const btn = paypal.Buttons(buttonsOptions());
|
||||
|
||||
this.activeButtons[wrapper] = btn;
|
||||
|
||||
if (!btn.isEligible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.render(wrapper);
|
||||
}
|
||||
|
||||
jQuery(wrapper).off('ppcp-reload-buttons');
|
||||
jQuery(wrapper).on('ppcp-reload-buttons', (event, settingsOverride = {}) => {
|
||||
const settings = merge(this.defaultSettings, settingsOverride);
|
||||
const scriptOptions = keysToCamelCase(settings.url_params);
|
||||
|
||||
// if (this.activeButtons[wrapper]) {
|
||||
// this.activeButtons[wrapper].close();
|
||||
// }
|
||||
|
||||
loadScript(scriptOptions).then((paypal) => {
|
||||
buildButtons(paypal);
|
||||
});
|
||||
});
|
||||
|
||||
this.renderedSources.add(wrapper + fundingSource ?? '');
|
||||
|
||||
if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') {
|
||||
buildButtons(paypal);
|
||||
}
|
||||
}
|
||||
|
||||
isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) {
|
||||
|
@ -101,9 +134,10 @@ class Renderer {
|
|||
// this will reduce the risk of breaking with different themes/plugins
|
||||
// and on the cart page (where we also do not need to render separately), which may fully reload this part of the page.
|
||||
// Ideally we should also find a way to detect such full reloads and remove the corresponding keys from the set.
|
||||
if (!hasEnabledSeparateGateways) {
|
||||
return document.querySelector(wrapper).hasChildNodes();
|
||||
}
|
||||
|
||||
// if (!hasEnabledSeparateGateways) {
|
||||
// return document.querySelector(wrapper).hasChildNodes();
|
||||
// }
|
||||
return this.renderedSources.has(wrapper + fundingSource ?? '');
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue