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
|
@ -11,9 +11,10 @@
|
||||||
"Edge >= 14"
|
"Edge >= 14"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@paypal/paypal-js": "^6.0.0",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"formdata-polyfill": "^4.0.10",
|
"deepmerge": "^4.2.2",
|
||||||
"deepmerge": "^4.2.2"
|
"formdata-polyfill": "^4.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.19",
|
"@babel/core": "^7.19",
|
||||||
|
|
|
@ -194,9 +194,6 @@ const bootstrap = () => {
|
||||||
payNowBootstrap.init();
|
payNowBootstrap.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context !== 'checkout') {
|
|
||||||
messageRenderer.render();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
'DOMContentLoaded',
|
'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()
|
createOrder()
|
||||||
{
|
{
|
||||||
this.cartHelper = null;
|
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) => {
|
return (data, actions) => {
|
||||||
this.errorHandler.clear();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle script reload
|
||||||
const newParams = result.data.url_params;
|
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
|
if (reloadRequired) {
|
||||||
setVisible(this.gateway.button.wrapper, !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) {
|
if (this.lastAmount !== result.data.amount) {
|
||||||
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 SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
|
||||||
import {hide, show} from "../Helper/Hiding";
|
import {hide, show} from "../Helper/Hiding";
|
||||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||||
|
import SimulateCart from "../Helper/SimulateCart";
|
||||||
|
import {strRemoveWord, strAddWord} from "../Helper/Utils";
|
||||||
|
|
||||||
class SingleProductBootstap {
|
class SingleProductBootstap {
|
||||||
constructor(gateway, renderer, messages, errorHandler) {
|
constructor(gateway, renderer, messages, errorHandler) {
|
||||||
|
@ -38,10 +40,60 @@ class SingleProductBootstap {
|
||||||
this.handleButtonStatus();
|
this.handleButtonStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleButtonStatus() {
|
handleButtonStatus(simulateCart = true) {
|
||||||
BootstrapHelper.handleButtonStatus(this, {
|
BootstrapHelper.handleButtonStatus(this, {
|
||||||
formSelector: this.formSelector
|
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() {
|
init() {
|
||||||
|
@ -53,12 +105,6 @@ class SingleProductBootstap {
|
||||||
|
|
||||||
form.addEventListener('change', () => {
|
form.addEventListener('change', () => {
|
||||||
this.handleChange();
|
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 });
|
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() {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (! document.querySelector(this.config.wrapper)) {
|
if (! document.querySelector(this.config.wrapper)) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import merge from "deepmerge";
|
import merge from "deepmerge";
|
||||||
|
import {loadScript} from "@paypal/paypal-js";
|
||||||
|
import {keysToCamelCase} from "../Helper/Utils";
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
|
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
|
||||||
|
@ -10,6 +12,8 @@ class Renderer {
|
||||||
this.buttonsOptions = {};
|
this.buttonsOptions = {};
|
||||||
this.onButtonsInitListeners = {};
|
this.onButtonsInitListeners = {};
|
||||||
|
|
||||||
|
this.activeButtons = {};
|
||||||
|
|
||||||
this.renderedSources = new Set();
|
this.renderedSources = new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,32 +72,61 @@ class Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('rendering', wrapper);
|
||||||
|
|
||||||
if (fundingSource) {
|
if (fundingSource) {
|
||||||
contextConfig.fundingSource = fundingSource;
|
contextConfig.fundingSource = fundingSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
const btn = paypal.Buttons({
|
const buttonsOptions = () => {
|
||||||
style,
|
return {
|
||||||
...contextConfig,
|
style,
|
||||||
onClick: this.onSmartButtonClick,
|
...contextConfig,
|
||||||
onInit: (data, actions) => {
|
onClick: this.onSmartButtonClick,
|
||||||
if (this.onSmartButtonsInit) {
|
onInit: (data, actions) => {
|
||||||
this.onSmartButtonsInit(data, actions);
|
if (this.onSmartButtonsInit) {
|
||||||
}
|
this.onSmartButtonsInit(data, actions);
|
||||||
this.handleOnButtonsInit(wrapper, data, actions);
|
}
|
||||||
},
|
this.handleOnButtonsInit(wrapper, data, actions);
|
||||||
});
|
},
|
||||||
if (!btn.isEligible()) {
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ?? '');
|
this.renderedSources.add(wrapper + fundingSource ?? '');
|
||||||
|
|
||||||
|
if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') {
|
||||||
|
buildButtons(paypal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) {
|
isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) {
|
||||||
|
@ -101,9 +134,10 @@ class Renderer {
|
||||||
// this will reduce the risk of breaking with different themes/plugins
|
// 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.
|
// 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.
|
// 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 ?? '');
|
return this.renderedSources.has(wrapper + fundingSource ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,11 +129,12 @@ return array(
|
||||||
if ( ! \WC()->cart ) {
|
if ( ! \WC()->cart ) {
|
||||||
throw new RuntimeException( 'cant initialize endpoint at this moment' );
|
throw new RuntimeException( 'cant initialize endpoint at this moment' );
|
||||||
}
|
}
|
||||||
|
$smart_button = $container->get( 'button.smart-button' );
|
||||||
$cart = WC()->cart;
|
$cart = WC()->cart;
|
||||||
$request_data = $container->get( 'button.request-data' );
|
$request_data = $container->get( 'button.request-data' );
|
||||||
$data_store = \WC_Data_Store::load( 'product' );
|
$data_store = \WC_Data_Store::load( 'product' );
|
||||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||||
return new SimulateCartEndpoint( $cart, $request_data, $data_store, $logger );
|
return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $data_store, $logger );
|
||||||
},
|
},
|
||||||
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
|
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
|
||||||
if ( ! \WC()->cart ) {
|
if ( ! \WC()->cart ) {
|
||||||
|
|
|
@ -1384,10 +1384,10 @@ class SmartButton implements SmartButtonInterface {
|
||||||
* Checks if PayPal buttons/messages should be rendered for the current page.
|
* Checks if PayPal buttons/messages should be rendered for the current page.
|
||||||
*
|
*
|
||||||
* @param string|null $context The context that should be checked, use default otherwise.
|
* @param string|null $context The context that should be checked, use default otherwise.
|
||||||
*
|
* @param float|null $price_total The price total to be considered.
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function is_button_disabled( string $context = null ): bool {
|
public function is_button_disabled( string $context = null, float $price_total = null ): bool {
|
||||||
if ( null === $context ) {
|
if ( null === $context ) {
|
||||||
$context = $this->context();
|
$context = $this->context();
|
||||||
}
|
}
|
||||||
|
@ -1415,7 +1415,8 @@ class SmartButton implements SmartButtonInterface {
|
||||||
$is_disabled = apply_filters(
|
$is_disabled = apply_filters(
|
||||||
'woocommerce_paypal_payments_buttons_disabled',
|
'woocommerce_paypal_payments_buttons_disabled',
|
||||||
null,
|
null,
|
||||||
$context
|
$context,
|
||||||
|
null === $price_total ? $this->get_cart_price_total() : $price_total
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $is_disabled !== null ) {
|
if ( $is_disabled !== null ) {
|
||||||
|
@ -1429,9 +1430,10 @@ class SmartButton implements SmartButtonInterface {
|
||||||
* Checks a filter if pay_later/messages should be rendered on a given location / context.
|
* Checks a filter if pay_later/messages should be rendered on a given location / context.
|
||||||
*
|
*
|
||||||
* @param string $location The location.
|
* @param string $location The location.
|
||||||
|
* @param float|null $price_total The price total to be considered.
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function is_pay_later_filter_enabled_for_location( string $location ): bool {
|
private function is_pay_later_filter_enabled_for_location( string $location, float $price_total = null ): bool {
|
||||||
|
|
||||||
if ( 'product' === $location ) {
|
if ( 'product' === $location ) {
|
||||||
$product = wc_get_product();
|
$product = wc_get_product();
|
||||||
|
@ -1446,8 +1448,7 @@ class SmartButton implements SmartButtonInterface {
|
||||||
$is_disabled = apply_filters(
|
$is_disabled = apply_filters(
|
||||||
'woocommerce_paypal_payments_product_buttons_paylater_disabled',
|
'woocommerce_paypal_payments_product_buttons_paylater_disabled',
|
||||||
null,
|
null,
|
||||||
$product,
|
$product
|
||||||
$product->get_price( 'numeric' )
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $is_disabled !== null ) {
|
if ( $is_disabled !== null ) {
|
||||||
|
@ -1462,7 +1463,7 @@ class SmartButton implements SmartButtonInterface {
|
||||||
'woocommerce_paypal_payments_buttons_paylater_disabled',
|
'woocommerce_paypal_payments_buttons_paylater_disabled',
|
||||||
null,
|
null,
|
||||||
$location,
|
$location,
|
||||||
$this->get_cart_price_total()
|
null === $price_total ? $this->get_cart_price_total() : $price_total
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $is_disabled !== null ) {
|
if ( $is_disabled !== null ) {
|
||||||
|
@ -1476,10 +1477,11 @@ class SmartButton implements SmartButtonInterface {
|
||||||
* Check whether Pay Later button is enabled for a given location.
|
* Check whether Pay Later button is enabled for a given location.
|
||||||
*
|
*
|
||||||
* @param string $location The location.
|
* @param string $location The location.
|
||||||
|
* @param float|null $price_total The price total to be considered.
|
||||||
* @return bool true if is enabled, otherwise false.
|
* @return bool true if is enabled, otherwise false.
|
||||||
*/
|
*/
|
||||||
private function is_pay_later_button_enabled_for_location( string $location ): bool {
|
public function is_pay_later_button_enabled_for_location( string $location, float $price_total = null ): bool {
|
||||||
return $this->is_pay_later_filter_enabled_for_location( $location )
|
return $this->is_pay_later_filter_enabled_for_location( $location, $price_total )
|
||||||
&& $this->settings_status->is_pay_later_button_enabled_for_location( $location );
|
&& $this->settings_status->is_pay_later_button_enabled_for_location( $location );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1488,10 +1490,11 @@ class SmartButton implements SmartButtonInterface {
|
||||||
* Check whether Pay Later message is enabled for a given location.
|
* Check whether Pay Later message is enabled for a given location.
|
||||||
*
|
*
|
||||||
* @param string $location The location setting name.
|
* @param string $location The location setting name.
|
||||||
|
* @param float|null $price_total The price total to be considered.
|
||||||
* @return bool true if is enabled, otherwise false.
|
* @return bool true if is enabled, otherwise false.
|
||||||
*/
|
*/
|
||||||
private function is_pay_later_messaging_enabled_for_location( string $location ): bool {
|
public function is_pay_later_messaging_enabled_for_location( string $location, float $price_total = null ): bool {
|
||||||
return $this->is_pay_later_filter_enabled_for_location( $location )
|
return $this->is_pay_later_filter_enabled_for_location( $location, $price_total )
|
||||||
&& $this->settings_status->is_pay_later_messaging_enabled_for_location( $location );
|
&& $this->settings_status->is_pay_later_messaging_enabled_for_location( $location );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
315
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
315
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||||
|
|
||||||
|
abstract class AbstractCartEndpoint implements EndpointInterface {
|
||||||
|
|
||||||
|
const ENDPOINT = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current cart object.
|
||||||
|
*
|
||||||
|
* @var \WC_Cart
|
||||||
|
*/
|
||||||
|
protected $cart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The product data store.
|
||||||
|
*
|
||||||
|
* @var \WC_Data_Store
|
||||||
|
*/
|
||||||
|
protected $product_data_store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request data helper.
|
||||||
|
*
|
||||||
|
* @var RequestData
|
||||||
|
*/
|
||||||
|
protected $request_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logger.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tag to be added to logs.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $logger_tag = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The added cart item IDs
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $cart_item_keys = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nonce.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function nonce(): string {
|
||||||
|
return static::ENDPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function handle_request(): bool {
|
||||||
|
try {
|
||||||
|
return $this->handle_data();
|
||||||
|
} catch ( Exception $error ) {
|
||||||
|
$this->logger->error( 'Cart ' . $this->logger_tag . ' failed: ' . $error->getMessage() );
|
||||||
|
|
||||||
|
wp_send_json_error(
|
||||||
|
array(
|
||||||
|
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
|
||||||
|
'message' => $error->getMessage(),
|
||||||
|
'code' => $error->getCode(),
|
||||||
|
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the request data.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception On error.
|
||||||
|
*/
|
||||||
|
abstract protected function handle_data(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds products to cart.
|
||||||
|
*
|
||||||
|
* @param array $products Array of products to be added to cart.
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function add_products( array $products ): bool {
|
||||||
|
$this->cart->empty_cart( false );
|
||||||
|
|
||||||
|
$success = true;
|
||||||
|
foreach ( $products as $product ) {
|
||||||
|
if ( $product['product']->is_type( 'booking' ) ) {
|
||||||
|
$success = $success && $this->add_booking_product(
|
||||||
|
$product['product'],
|
||||||
|
$product['booking']
|
||||||
|
);
|
||||||
|
} elseif ( $product['product']->is_type( 'variable' ) ) {
|
||||||
|
$success = $success && $this->add_variable_product(
|
||||||
|
$product['product'],
|
||||||
|
$product['quantity'],
|
||||||
|
$product['variations']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$success = $success && $this->add_product(
|
||||||
|
$product['product'],
|
||||||
|
$product['quantity']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $success ) {
|
||||||
|
$this->handle_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles errors.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function handle_error(): void {
|
||||||
|
|
||||||
|
$message = __(
|
||||||
|
'Something went wrong. Action aborted',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
$errors = wc_get_notices( 'error' );
|
||||||
|
if ( count( $errors ) ) {
|
||||||
|
$message = array_reduce(
|
||||||
|
$errors,
|
||||||
|
static function ( string $add, array $error ): string {
|
||||||
|
return $add . $error['notice'] . ' ';
|
||||||
|
},
|
||||||
|
''
|
||||||
|
);
|
||||||
|
wc_clear_notices();
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_error(
|
||||||
|
array(
|
||||||
|
'name' => '',
|
||||||
|
'message' => $message,
|
||||||
|
'code' => 0,
|
||||||
|
'details' => array(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|false
|
||||||
|
*/
|
||||||
|
protected function products_from_request() {
|
||||||
|
$data = $this->request_data->read_request( $this->nonce() );
|
||||||
|
$products = $this->products_from_data( $data );
|
||||||
|
if ( ! $products ) {
|
||||||
|
wp_send_json_error(
|
||||||
|
array(
|
||||||
|
'name' => '',
|
||||||
|
'message' => __(
|
||||||
|
'Necessary fields not defined. Action aborted.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
'code' => 0,
|
||||||
|
'details' => array(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $products;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns product information from a data array.
|
||||||
|
*
|
||||||
|
* @param array $data The data array.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function products_from_data( array $data ): ?array {
|
||||||
|
|
||||||
|
$products = array();
|
||||||
|
|
||||||
|
if (
|
||||||
|
! isset( $data['products'] )
|
||||||
|
|| ! is_array( $data['products'] )
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
foreach ( $data['products'] as $product ) {
|
||||||
|
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wc_product = wc_get_product( (int) $product['id'] );
|
||||||
|
|
||||||
|
if ( ! $wc_product ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$products[] = array(
|
||||||
|
'product' => $wc_product,
|
||||||
|
'quantity' => (int) $product['quantity'],
|
||||||
|
'variations' => $product['variations'] ?? null,
|
||||||
|
'booking' => $product['booking'] ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $products;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a product to the cart.
|
||||||
|
*
|
||||||
|
* @param \WC_Product $product The Product.
|
||||||
|
* @param int $quantity The Quantity.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception When product could not be added.
|
||||||
|
*/
|
||||||
|
private function add_product( \WC_Product $product, int $quantity ): bool {
|
||||||
|
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
|
||||||
|
|
||||||
|
$this->cart_item_keys[] = $cart_item_key;
|
||||||
|
return false !== $cart_item_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds variations to the cart.
|
||||||
|
*
|
||||||
|
* @param \WC_Product $product The Product.
|
||||||
|
* @param int $quantity The Quantity.
|
||||||
|
* @param array $post_variations The variations.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception When product could not be added.
|
||||||
|
*/
|
||||||
|
private function add_variable_product(
|
||||||
|
\WC_Product $product,
|
||||||
|
int $quantity,
|
||||||
|
array $post_variations
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
$variations = array();
|
||||||
|
foreach ( $post_variations as $key => $value ) {
|
||||||
|
$variations[ $value['name'] ] = $value['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
|
||||||
|
|
||||||
|
// ToDo: Check stock status for variation.
|
||||||
|
$cart_item_key = $this->cart->add_to_cart(
|
||||||
|
$product->get_id(),
|
||||||
|
$quantity,
|
||||||
|
$variation_id,
|
||||||
|
$variations
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->cart_item_keys[] = $cart_item_key;
|
||||||
|
return false !== $cart_item_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds booking to the cart.
|
||||||
|
*
|
||||||
|
* @param \WC_Product $product The Product.
|
||||||
|
* @param array $data Data used by the booking plugin.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws Exception When product could not be added.
|
||||||
|
*/
|
||||||
|
private function add_booking_product(
|
||||||
|
\WC_Product $product,
|
||||||
|
array $data
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cart_item_data = array(
|
||||||
|
'booking' => wc_bookings_get_posted_data( $data, $product ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
|
||||||
|
|
||||||
|
$this->cart_item_keys[] = $cart_item_key;
|
||||||
|
return false !== $cart_item_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function remove_cart_items(): void {
|
||||||
|
foreach ( $this->cart_item_keys as $cart_item_key ) {
|
||||||
|
$this->cart->remove_cart_item( $cart_item_key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -65,11 +65,16 @@ class CartScriptParamsEndpoint implements EndpointInterface {
|
||||||
*/
|
*/
|
||||||
public function handle_request(): bool {
|
public function handle_request(): bool {
|
||||||
try {
|
try {
|
||||||
|
if ( is_callable('wc_maybe_define_constant') ) {
|
||||||
|
wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
|
||||||
|
}
|
||||||
|
|
||||||
$script_data = $this->smart_button->script_data();
|
$script_data = $this->smart_button->script_data();
|
||||||
|
|
||||||
wp_send_json_success(
|
wp_send_json_success(
|
||||||
array(
|
array(
|
||||||
'url_params' => $script_data['url_params'],
|
'url_params' => $script_data['url_params'],
|
||||||
|
'button' => $script_data['button'],
|
||||||
'amount' => WC()->cart->get_total( 'raw' ),
|
'amount' => WC()->cart->get_total( 'raw' ),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,26 +11,15 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||||
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ChangeCartEndpoint
|
* Class ChangeCartEndpoint
|
||||||
*/
|
*/
|
||||||
class ChangeCartEndpoint implements EndpointInterface {
|
class ChangeCartEndpoint extends AbstractCartEndpoint {
|
||||||
|
|
||||||
|
|
||||||
const ENDPOINT = 'ppc-change-cart';
|
const ENDPOINT = 'ppc-change-cart';
|
||||||
|
|
||||||
/**
|
|
||||||
* The current cart object.
|
|
||||||
*
|
|
||||||
* @var \WC_Cart
|
|
||||||
*/
|
|
||||||
private $cart;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current shipping object.
|
* The current shipping object.
|
||||||
*
|
*
|
||||||
|
@ -38,13 +27,6 @@ class ChangeCartEndpoint implements EndpointInterface {
|
||||||
*/
|
*/
|
||||||
private $shipping;
|
private $shipping;
|
||||||
|
|
||||||
/**
|
|
||||||
* The request data helper.
|
|
||||||
*
|
|
||||||
* @var RequestData
|
|
||||||
*/
|
|
||||||
private $request_data;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PurchaseUnit factory.
|
* The PurchaseUnit factory.
|
||||||
*
|
*
|
||||||
|
@ -52,20 +34,6 @@ class ChangeCartEndpoint implements EndpointInterface {
|
||||||
*/
|
*/
|
||||||
private $purchase_unit_factory;
|
private $purchase_unit_factory;
|
||||||
|
|
||||||
/**
|
|
||||||
* The product data store.
|
|
||||||
*
|
|
||||||
* @var \WC_Data_Store
|
|
||||||
*/
|
|
||||||
private $product_data_store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logger.
|
|
||||||
*
|
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChangeCartEndpoint constructor.
|
* ChangeCartEndpoint constructor.
|
||||||
*
|
*
|
||||||
|
@ -91,38 +59,8 @@ class ChangeCartEndpoint implements EndpointInterface {
|
||||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||||
$this->product_data_store = $product_data_store;
|
$this->product_data_store = $product_data_store;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$this->logger_tag = 'updating';
|
||||||
* The nonce.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function nonce(): string {
|
|
||||||
return self::ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the request.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function handle_request(): bool {
|
|
||||||
try {
|
|
||||||
return $this->handle_data();
|
|
||||||
} catch ( Exception $error ) {
|
|
||||||
$this->logger->error( 'Cart updating failed: ' . $error->getMessage() );
|
|
||||||
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
|
|
||||||
'message' => $error->getMessage(),
|
|
||||||
'code' => $error->getCode(),
|
|
||||||
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,195 +69,19 @@ class ChangeCartEndpoint implements EndpointInterface {
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws Exception On error.
|
* @throws Exception On error.
|
||||||
*/
|
*/
|
||||||
private function handle_data(): bool {
|
protected function handle_data(): bool {
|
||||||
$data = $this->request_data->read_request( $this->nonce() );
|
if ( ! $products = $this->products_from_request() ) {
|
||||||
$products = $this->products_from_data( $data );
|
|
||||||
if ( ! $products ) {
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => '',
|
|
||||||
'message' => __(
|
|
||||||
'Necessary fields not defined. Action aborted.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
'code' => 0,
|
|
||||||
'details' => array(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->shipping->reset_shipping();
|
$this->shipping->reset_shipping();
|
||||||
$this->cart->empty_cart( false );
|
|
||||||
$success = true;
|
|
||||||
foreach ( $products as $product ) {
|
|
||||||
if ( $product['product']->is_type( 'booking' ) ) {
|
|
||||||
$success = $success && $this->add_booking_product(
|
|
||||||
$product['product'],
|
|
||||||
$product['booking']
|
|
||||||
);
|
|
||||||
} elseif ( $product['product']->is_type( 'variable' ) ) {
|
|
||||||
$success = $success && $this->add_variable_product(
|
|
||||||
$product['product'],
|
|
||||||
$product['quantity'],
|
|
||||||
$product['variations']
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$success = $success && $this->add_product(
|
|
||||||
$product['product'],
|
|
||||||
$product['quantity']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( ! $success ) {
|
|
||||||
$this->handle_error();
|
|
||||||
return $success;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_send_json_success( $this->generate_purchase_units() );
|
if ( ! $this->add_products($products) ) {
|
||||||
return $success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles errors.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function handle_error(): bool {
|
|
||||||
|
|
||||||
$message = __(
|
|
||||||
'Something went wrong. Action aborted',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
);
|
|
||||||
$errors = wc_get_notices( 'error' );
|
|
||||||
if ( count( $errors ) ) {
|
|
||||||
$message = array_reduce(
|
|
||||||
$errors,
|
|
||||||
static function ( string $add, array $error ): string {
|
|
||||||
return $add . $error['notice'] . ' ';
|
|
||||||
},
|
|
||||||
''
|
|
||||||
);
|
|
||||||
wc_clear_notices();
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => '',
|
|
||||||
'message' => $message,
|
|
||||||
'code' => 0,
|
|
||||||
'details' => array(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns product information from an data array.
|
|
||||||
*
|
|
||||||
* @param array $data The data array.
|
|
||||||
*
|
|
||||||
* @return array|null
|
|
||||||
*/
|
|
||||||
private function products_from_data( array $data ) {
|
|
||||||
|
|
||||||
$products = array();
|
|
||||||
|
|
||||||
if (
|
|
||||||
! isset( $data['products'] )
|
|
||||||
|| ! is_array( $data['products'] )
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
foreach ( $data['products'] as $product ) {
|
|
||||||
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$wc_product = wc_get_product( (int) $product['id'] );
|
|
||||||
|
|
||||||
if ( ! $wc_product ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$products[] = array(
|
|
||||||
'product' => $wc_product,
|
|
||||||
'quantity' => (int) $product['quantity'],
|
|
||||||
'variations' => $product['variations'] ?? null,
|
|
||||||
'booking' => $product['booking'] ?? null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $products;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a product to the cart.
|
|
||||||
*
|
|
||||||
* @param \WC_Product $product The Product.
|
|
||||||
* @param int $quantity The Quantity.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws Exception When product could not be added.
|
|
||||||
*/
|
|
||||||
private function add_product( \WC_Product $product, int $quantity ): bool {
|
|
||||||
return false !== $this->cart->add_to_cart( $product->get_id(), $quantity );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds variations to the cart.
|
|
||||||
*
|
|
||||||
* @param \WC_Product $product The Product.
|
|
||||||
* @param int $quantity The Quantity.
|
|
||||||
* @param array $post_variations The variations.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws Exception When product could not be added.
|
|
||||||
*/
|
|
||||||
private function add_variable_product(
|
|
||||||
\WC_Product $product,
|
|
||||||
int $quantity,
|
|
||||||
array $post_variations
|
|
||||||
): bool {
|
|
||||||
|
|
||||||
$variations = array();
|
|
||||||
foreach ( $post_variations as $key => $value ) {
|
|
||||||
$variations[ $value['name'] ] = $value['value'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
|
|
||||||
|
|
||||||
// ToDo: Check stock status for variation.
|
|
||||||
return false !== $this->cart->add_to_cart(
|
|
||||||
$product->get_id(),
|
|
||||||
$quantity,
|
|
||||||
$variation_id,
|
|
||||||
$variations
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds variations to the cart.
|
|
||||||
*
|
|
||||||
* @param \WC_Product $product The Product.
|
|
||||||
* @param array $data Data used by the booking plugin.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws Exception When product could not be added.
|
|
||||||
*/
|
|
||||||
private function add_booking_product(
|
|
||||||
\WC_Product $product,
|
|
||||||
array $data
|
|
||||||
): bool {
|
|
||||||
|
|
||||||
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cart_item_data = array(
|
wp_send_json_success( $this->generate_purchase_units() );
|
||||||
'booking' => wc_bookings_get_posted_data( $data, $product ),
|
return true;
|
||||||
);
|
|
||||||
|
|
||||||
return false !== $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,95 +11,45 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SimulateCartEndpoint
|
* Class SimulateCartEndpoint
|
||||||
*/
|
*/
|
||||||
class SimulateCartEndpoint implements EndpointInterface {
|
class SimulateCartEndpoint extends AbstractCartEndpoint {
|
||||||
|
|
||||||
const ENDPOINT = 'ppc-simulate-cart';
|
const ENDPOINT = 'ppc-simulate-cart';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cart object.
|
* The SmartButton.
|
||||||
*
|
*
|
||||||
* @var \WC_Cart
|
* @var SmartButton
|
||||||
*/
|
*/
|
||||||
private $cart;
|
private $smart_button;
|
||||||
|
|
||||||
/**
|
|
||||||
* The request data helper.
|
|
||||||
*
|
|
||||||
* @var RequestData
|
|
||||||
*/
|
|
||||||
private $request_data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The product data store.
|
|
||||||
*
|
|
||||||
* @var \WC_Data_Store
|
|
||||||
*/
|
|
||||||
private $product_data_store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logger.
|
|
||||||
*
|
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ChangeCartEndpoint constructor.
|
* ChangeCartEndpoint constructor.
|
||||||
*
|
*
|
||||||
|
* @param SmartButton $smart_button The SmartButton.
|
||||||
* @param \WC_Cart $cart The current WC cart object.
|
* @param \WC_Cart $cart The current WC cart object.
|
||||||
* @param RequestData $request_data The request data helper.
|
* @param RequestData $request_data The request data helper.
|
||||||
* @param \WC_Data_Store $product_data_store The data store for products.
|
* @param \WC_Data_Store $product_data_store The data store for products.
|
||||||
* @param LoggerInterface $logger The logger.
|
* @param LoggerInterface $logger The logger.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
SmartButton $smart_button,
|
||||||
\WC_Cart $cart,
|
\WC_Cart $cart,
|
||||||
RequestData $request_data,
|
RequestData $request_data,
|
||||||
\WC_Data_Store $product_data_store,
|
\WC_Data_Store $product_data_store,
|
||||||
LoggerInterface $logger
|
LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
|
$this->smart_button = $smart_button;
|
||||||
$this->cart = clone $cart;
|
$this->cart = clone $cart;
|
||||||
$this->request_data = $request_data;
|
$this->request_data = $request_data;
|
||||||
$this->product_data_store = $product_data_store;
|
$this->product_data_store = $product_data_store;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$this->logger_tag = 'simulation';
|
||||||
* The nonce.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function nonce(): string {
|
|
||||||
return self::ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the request.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function handle_request(): bool {
|
|
||||||
try {
|
|
||||||
return $this->handle_data();
|
|
||||||
} catch ( Exception $error ) {
|
|
||||||
$this->logger->error( 'Cart simulation failed: ' . $error->getMessage() );
|
|
||||||
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
|
|
||||||
'message' => $error->getMessage(),
|
|
||||||
'code' => $error->getCode(),
|
|
||||||
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,167 +58,44 @@ class SimulateCartEndpoint implements EndpointInterface {
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws Exception On error.
|
* @throws Exception On error.
|
||||||
*/
|
*/
|
||||||
private function handle_data(): bool {
|
protected function handle_data(): bool {
|
||||||
$data = $this->request_data->read_request( $this->nonce() );
|
if ( ! $products = $this->products_from_request() ) {
|
||||||
$products = $this->products_from_data( $data );
|
|
||||||
if ( ! $products ) {
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => '',
|
|
||||||
'message' => __(
|
|
||||||
'Necessary fields not defined. Action aborted.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
'code' => 0,
|
|
||||||
'details' => array(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cart->empty_cart( false );
|
// Set WC default cart as the clone.
|
||||||
$success = true;
|
// Store a reference to the real cart.
|
||||||
foreach ( $products as $product ) {
|
$activeCart = WC()->cart;
|
||||||
$success = $success && ( ! $product['product']->is_type( 'variable' ) ) ?
|
WC()->cart = $this->cart;
|
||||||
$this->add_product( $product['product'], $product['quantity'] )
|
|
||||||
: $this->add_variable_product(
|
if ( ! $this->add_products($products) ) {
|
||||||
$product['product'],
|
return false;
|
||||||
$product['quantity'],
|
|
||||||
$product['variations']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ( ! $success ) {
|
|
||||||
$this->handle_error();
|
|
||||||
return $success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cart->calculate_totals();
|
$this->cart->calculate_totals();
|
||||||
|
$total = (float) $this->cart->get_total( 'numeric' );
|
||||||
|
|
||||||
$total = $this->cart->get_total( 'numeric' );
|
$this->remove_cart_items();
|
||||||
|
|
||||||
|
// Restore cart and unset cart clone
|
||||||
|
WC()->cart = $activeCart;
|
||||||
unset( $this->cart );
|
unset( $this->cart );
|
||||||
|
|
||||||
wp_send_json_success(
|
wp_send_json_success(
|
||||||
array(
|
array(
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
)
|
'funding' => [
|
||||||
);
|
'paylater' => [
|
||||||
return $success;
|
'enabled' => $this->smart_button->is_pay_later_button_enabled_for_location( 'cart', $total ),
|
||||||
}
|
'messaging_enabled' => $this->smart_button->is_pay_later_messaging_enabled_for_location( 'cart', $total ),
|
||||||
|
]
|
||||||
/**
|
],
|
||||||
* Handles errors.
|
'button' => [
|
||||||
*
|
'is_disabled' => $this->smart_button->is_button_disabled( 'cart', $total ),
|
||||||
* @return bool
|
]
|
||||||
*/
|
|
||||||
private function handle_error(): bool {
|
|
||||||
|
|
||||||
$message = __(
|
|
||||||
'Something went wrong. Action aborted',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
);
|
|
||||||
$errors = wc_get_notices( 'error' );
|
|
||||||
if ( count( $errors ) ) {
|
|
||||||
$message = array_reduce(
|
|
||||||
$errors,
|
|
||||||
static function ( string $add, array $error ): string {
|
|
||||||
return $add . $error['notice'] . ' ';
|
|
||||||
},
|
|
||||||
''
|
|
||||||
);
|
|
||||||
wc_clear_notices();
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'name' => '',
|
|
||||||
'message' => $message,
|
|
||||||
'code' => 0,
|
|
||||||
'details' => array(),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns product information from an data array.
|
|
||||||
*
|
|
||||||
* @param array $data The data array.
|
|
||||||
*
|
|
||||||
* @return array|null
|
|
||||||
*/
|
|
||||||
private function products_from_data( array $data ) {
|
|
||||||
|
|
||||||
$products = array();
|
|
||||||
|
|
||||||
if (
|
|
||||||
! isset( $data['products'] )
|
|
||||||
|| ! is_array( $data['products'] )
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
foreach ( $data['products'] as $product ) {
|
|
||||||
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$wc_product = wc_get_product( (int) $product['id'] );
|
|
||||||
|
|
||||||
if ( ! $wc_product ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$products[] = array(
|
|
||||||
'product' => $wc_product,
|
|
||||||
'quantity' => (int) $product['quantity'],
|
|
||||||
'variations' => isset( $product['variations'] ) ? $product['variations'] : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $products;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a product to the cart.
|
|
||||||
*
|
|
||||||
* @param \WC_Product $product The Product.
|
|
||||||
* @param int $quantity The Quantity.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws Exception When product could not be added.
|
|
||||||
*/
|
|
||||||
private function add_product( \WC_Product $product, int $quantity ): bool {
|
|
||||||
return false !== $this->cart->add_to_cart( $product->get_id(), $quantity );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds variations to the cart.
|
|
||||||
*
|
|
||||||
* @param \WC_Product $product The Product.
|
|
||||||
* @param int $quantity The Quantity.
|
|
||||||
* @param array $post_variations The variations.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
* @throws Exception When product could not be added.
|
|
||||||
*/
|
|
||||||
private function add_variable_product(
|
|
||||||
\WC_Product $product,
|
|
||||||
int $quantity,
|
|
||||||
array $post_variations
|
|
||||||
): bool {
|
|
||||||
|
|
||||||
$variations = array();
|
|
||||||
foreach ( $post_variations as $key => $value ) {
|
|
||||||
$variations[ $value['name'] ] = $value['value'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
|
|
||||||
|
|
||||||
// ToDo: Check stock status for variation.
|
|
||||||
return false !== $this->cart->add_to_cart(
|
|
||||||
$product->get_id(),
|
|
||||||
$quantity,
|
|
||||||
$variation_id,
|
|
||||||
$variations
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -956,6 +956,13 @@
|
||||||
"@jridgewell/resolve-uri" "^3.0.3"
|
"@jridgewell/resolve-uri" "^3.0.3"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
|
||||||
|
"@paypal/paypal-js@^6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.0.tgz#a5a9556af29e4a0124049bf9a093606f52b8a951"
|
||||||
|
integrity sha512-FYzjYby9F7tgg4tUxYNseZ6vkeDJcdcjoULsyNhfrWZZjicDpdj5932fZlyUlQXDSR9KlhjXH6H4nPIJ0Lq0Kw==
|
||||||
|
dependencies:
|
||||||
|
promise-polyfill "^8.3.0"
|
||||||
|
|
||||||
"@types/eslint-scope@^3.7.3":
|
"@types/eslint-scope@^3.7.3":
|
||||||
version "3.7.4"
|
version "3.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
||||||
|
@ -1858,6 +1865,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
find-up "^4.0.0"
|
find-up "^4.0.0"
|
||||||
|
|
||||||
|
promise-polyfill@^8.3.0:
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
|
||||||
|
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue