mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge trunk
This commit is contained in:
commit
96e434d4b0
77 changed files with 1571 additions and 831 deletions
|
@ -27,7 +27,6 @@ web_environment:
|
|||
- ADMIN_PASS=admin
|
||||
- ADMIN_EMAIL=admin@example.com
|
||||
- WC_VERSION=7.7.2
|
||||
- PCP_BLOCKS_ENABLED=1
|
||||
|
||||
# Key features of ddev's config.yaml:
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ PRODUCT_ID=123
|
|||
|
||||
SUBSCRIPTION_URL="/product/sub"
|
||||
|
||||
APM_ID="sofort"
|
||||
|
||||
WP_MERCHANT_USER="admin"
|
||||
WP_MERCHANT_PASSWORD="admin"
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
if (!defined('PAYPAL_INTEGRATION_DATE')) {
|
||||
define('PAYPAL_INTEGRATION_DATE', '2023-06-02');
|
||||
}
|
||||
if (!defined('EP_PAGES')) {
|
||||
define('EP_PAGES', 4096);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 2.2.0 - TBD =
|
||||
* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
|
||||
* Fix - Use order currency instead of shop currency on order-pay page #1363
|
||||
* Fix - Do not show broken card button gateway when no checkout location #1358
|
||||
* Fix - Smart buttons not greyed out/removed on single product when deselecting product variation #1469
|
||||
* Fix - Type error with advanced columns pro #1367
|
||||
* Fix - Undefined array key 0 when checking $retry_errors in process_payment method #1375
|
||||
* Fix - Advanced Card Processing gateway becomes invisible post-plugin update unless admin pages are accessed once #1432
|
||||
* Fix - Incompatibility with WooCommerce One Page Checkout (or similar use cases) in Version 2.1.0 #1473
|
||||
* Fix - Prevent Repetitive Token Migration and Database Overload After 2.1.0 Update #1461
|
||||
* Enhancement - Remove feature flag requirement for express cart/checkout block integration #1483
|
||||
* Enhancement - Add notice when shop currency is unsupported #1433
|
||||
* Enhancement - Improve ACDC error message when empty fields #1360
|
||||
* Enhancement - Do not exclude free items #1362
|
||||
* Enhancement - Trigger WC checkout_error event #1384
|
||||
* Enhancement - Update wording in buttons previews #1408
|
||||
* Enhancement - Filter to conditionally block the PayPal buttons #1485
|
||||
* Enhancement - Display funding source on the admin order page #1450
|
||||
* Enhancement - Update system report plugin status for Vaulting #1471
|
||||
* Enhancement - Revert Elementor Pro Checkout hook compatibility #1482
|
||||
|
||||
= 2.1.0 - 2023-06-13 =
|
||||
* Fix - Performance issue #1182
|
||||
* Fix - Webhooks not registered when onboarding with manual credentials #1223
|
||||
|
|
|
@ -26,14 +26,8 @@ return function ( string $root_dir ): iterable {
|
|||
( require "$modules_dir/ppcp-vaulting/module.php" )(),
|
||||
( require "$modules_dir/ppcp-order-tracking/module.php" )(),
|
||||
( require "$modules_dir/ppcp-uninstall/module.php" )(),
|
||||
( require "$modules_dir/ppcp-blocks/module.php" )(),
|
||||
);
|
||||
|
||||
if ( apply_filters(
|
||||
'woocommerce_paypal_payments_blocks_enabled',
|
||||
getenv( 'PCP_BLOCKS_ENABLED' ) === '1'
|
||||
) ) {
|
||||
$modules[] = ( require "$modules_dir/ppcp-blocks/module.php" )();
|
||||
}
|
||||
|
||||
return $modules;
|
||||
};
|
||||
|
|
|
@ -9,10 +9,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use WC_Session_Handler;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
|
||||
|
||||
/**
|
||||
* Class PurchaseUnitFactory
|
||||
|
@ -110,7 +112,7 @@ class PurchaseUnitFactory {
|
|||
$items = array_filter(
|
||||
$this->item_factory->from_wc_order( $order ),
|
||||
function ( Item $item ): bool {
|
||||
return $item->unit_amount()->value() > 0;
|
||||
return $item->unit_amount()->value() >= 0;
|
||||
}
|
||||
);
|
||||
$shipping = $this->shipping_factory->from_wc_order( $order );
|
||||
|
@ -166,7 +168,7 @@ class PurchaseUnitFactory {
|
|||
$items = array_filter(
|
||||
$this->item_factory->from_wc_cart( $cart ),
|
||||
function ( Item $item ): bool {
|
||||
return $item->unit_amount()->value() > 0;
|
||||
return $item->unit_amount()->value() >= 0;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -187,7 +189,14 @@ class PurchaseUnitFactory {
|
|||
|
||||
$payee = $this->payee_repository->payee();
|
||||
|
||||
$custom_id = '';
|
||||
$custom_id = '';
|
||||
$session = WC()->session;
|
||||
if ( $session instanceof WC_Session_Handler ) {
|
||||
$session_id = $session->get_customer_unique_id();
|
||||
if ( $session_id ) {
|
||||
$custom_id = CustomIds::CUSTOMER_ID_PREFIX . $session_id;
|
||||
}
|
||||
}
|
||||
$invoice_id = '';
|
||||
$soft_descriptor = '';
|
||||
$purchase_unit = new PurchaseUnit(
|
||||
|
|
|
@ -16,6 +16,7 @@ const PayPalComponent = ({
|
|||
emitResponse,
|
||||
activePaymentMethod,
|
||||
shippingData,
|
||||
isEditing,
|
||||
}) => {
|
||||
const {onPaymentSetup, onCheckoutAfterProcessingWithError} = eventRegistration;
|
||||
const {responseTypes} = emitResponse;
|
||||
|
@ -125,6 +126,10 @@ const PayPalComponent = ({
|
|||
};
|
||||
|
||||
const handleClick = (data, actions) => {
|
||||
if (isEditing) {
|
||||
return actions.reject();
|
||||
}
|
||||
|
||||
window.ppcpFundingSource = data.fundingSource;
|
||||
|
||||
onClick();
|
||||
|
@ -259,8 +264,8 @@ if (config.scriptData.continuation) {
|
|||
registerMethod({
|
||||
name: config.id,
|
||||
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>,
|
||||
content: <PayPalComponent/>,
|
||||
edit: <b>TODO: editing</b>,
|
||||
content: <PayPalComponent isEditing={false}/>,
|
||||
edit: <PayPalComponent isEditing={true}/>,
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: () => config.enabled,
|
||||
supports: {
|
||||
|
|
|
@ -28,6 +28,8 @@ const cardsSpinner = new Spinner('#ppcp-hosted-fields');
|
|||
const bootstrap = () => {
|
||||
const checkoutFormSelector = 'form.woocommerce-checkout';
|
||||
|
||||
const context = PayPalCommerceGateway.context;
|
||||
|
||||
const errorHandler = new ErrorHandler(
|
||||
PayPalCommerceGateway.labels.error.generic,
|
||||
document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper')
|
||||
|
@ -58,7 +60,7 @@ const bootstrap = () => {
|
|||
}
|
||||
});
|
||||
|
||||
const onSmartButtonClick = (data, actions) => {
|
||||
const onSmartButtonClick = async (data, actions) => {
|
||||
window.ppcpFundingSource = data.fundingSource;
|
||||
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
|
||||
requiredFields.each((i, input) => {
|
||||
|
@ -120,13 +122,21 @@ const bootstrap = () => {
|
|||
freeTrialHandler.handle();
|
||||
return actions.reject();
|
||||
}
|
||||
|
||||
if (context === 'checkout' && !PayPalCommerceGateway.funding_sources_without_redirect.includes(data.fundingSource)) {
|
||||
try {
|
||||
await formSaver.save(form);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSmartButtonsInit = () => {
|
||||
buttonsSpinner.unblock();
|
||||
};
|
||||
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit);
|
||||
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages);
|
||||
const context = PayPalCommerceGateway.context;
|
||||
if (context === 'mini-cart' || context === 'product') {
|
||||
if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
|
||||
const miniCartBootstrap = new MiniCartBootstap(
|
||||
|
|
|
@ -102,6 +102,9 @@ class CheckoutActionHandler {
|
|||
} else {
|
||||
errorHandler.message(data.data.message);
|
||||
}
|
||||
|
||||
// fire WC event for other plugins
|
||||
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
|
||||
}
|
||||
|
||||
throw {type: 'create-order-error', data: data.data};
|
||||
|
|
|
@ -40,6 +40,10 @@ class FreeTrialHandler {
|
|||
if (errors.length > 0) {
|
||||
this.spinner.unblock();
|
||||
this.errorHandler.messages(errors);
|
||||
|
||||
// fire WC event for other plugins
|
||||
jQuery( document.body ).trigger( 'checkout_error' , [ this.errorHandler.currentHtml() ] );
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import ButtonsToggleListener from '../Helper/ButtonsToggleListener';
|
||||
import Product from '../Entity/Product';
|
||||
import onApprove from '../OnApproveHandler/onApproveForContinue';
|
||||
import {payerData} from "../Helper/PayerData";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import CartActionHandler from '../ActionHandler/CartActionHandler';
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {setVisible} from "../Helper/Hiding";
|
||||
import {subscriptionHasPlan} from "../Helper/Subscriptions";
|
||||
|
||||
|
@ -7,6 +8,10 @@ class CartBootstrap {
|
|||
this.gateway = gateway;
|
||||
this.renderer = renderer;
|
||||
this.errorHandler = errorHandler;
|
||||
|
||||
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
|
||||
this.handleButtonStatus();
|
||||
}, true);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -15,9 +20,11 @@ class CartBootstrap {
|
|||
}
|
||||
|
||||
this.render();
|
||||
this.handleButtonStatus();
|
||||
|
||||
jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
|
||||
this.render();
|
||||
this.handleButtonStatus();
|
||||
|
||||
fetch(
|
||||
this.gateway.ajax.cart_script_params.endpoint,
|
||||
|
@ -41,10 +48,18 @@ class CartBootstrap {
|
|||
});
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
BootstrapHelper.handleButtonStatus(this);
|
||||
}
|
||||
|
||||
shouldRender() {
|
||||
return document.querySelector(this.gateway.button.wrapper) !== null && subscriptionHasPlan();
|
||||
}
|
||||
|
||||
shouldEnable() {
|
||||
return BootstrapHelper.shouldEnable(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const actionHandler = new CartActionHandler(
|
||||
PayPalCommerceGateway,
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
isSavedCardSelected, ORDER_BUTTON_SELECTOR,
|
||||
PaymentMethods
|
||||
} from "../Helper/CheckoutMethodState";
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {disable, enable} from "../Helper/ButtonDisabler";
|
||||
|
||||
class CheckoutBootstap {
|
||||
constructor(gateway, renderer, messages, spinner, errorHandler) {
|
||||
|
@ -15,10 +17,15 @@ class CheckoutBootstap {
|
|||
this.errorHandler = errorHandler;
|
||||
|
||||
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
|
||||
|
||||
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
|
||||
this.handleButtonStatus();
|
||||
}, true);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.render();
|
||||
this.handleButtonStatus();
|
||||
|
||||
// Unselect saved card.
|
||||
// WC saves form values, so with our current UI it would be a bit weird
|
||||
|
@ -28,6 +35,7 @@ class CheckoutBootstap {
|
|||
|
||||
jQuery(document.body).on('updated_checkout', () => {
|
||||
this.render()
|
||||
this.handleButtonStatus();
|
||||
});
|
||||
|
||||
jQuery(document.body).on('updated_checkout payment_method_selected', () => {
|
||||
|
@ -43,6 +51,10 @@ class CheckoutBootstap {
|
|||
this.updateUi();
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
BootstrapHelper.handleButtonStatus(this);
|
||||
}
|
||||
|
||||
shouldRender() {
|
||||
if (document.querySelector(this.gateway.button.cancel_wrapper)) {
|
||||
return false;
|
||||
|
@ -51,6 +63,10 @@ class CheckoutBootstap {
|
|||
return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null;
|
||||
}
|
||||
|
||||
shouldEnable() {
|
||||
return BootstrapHelper.shouldEnable(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.shouldRender()) {
|
||||
return;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import CartActionHandler from '../ActionHandler/CartActionHandler';
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
|
||||
class MiniCartBootstap {
|
||||
constructor(gateway, renderer, errorHandler) {
|
||||
|
@ -15,9 +16,22 @@ class MiniCartBootstap {
|
|||
this.errorHandler,
|
||||
);
|
||||
this.render();
|
||||
this.handleButtonStatus();
|
||||
|
||||
jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => {
|
||||
this.render();
|
||||
this.handleButtonStatus();
|
||||
});
|
||||
|
||||
this.renderer.onButtonsInit(this.gateway.button.mini_cart_wrapper, () => {
|
||||
this.handleButtonStatus();
|
||||
}, true);
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
BootstrapHelper.handleButtonStatus(this, {
|
||||
wrapper: this.gateway.button.mini_cart_wrapper,
|
||||
skipMessages: true
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -26,6 +40,12 @@ class MiniCartBootstap {
|
|||
|| document.querySelector(this.gateway.hosted_fields.mini_cart_wrapper) !== null;
|
||||
}
|
||||
|
||||
shouldEnable() {
|
||||
return BootstrapHelper.shouldEnable(this, {
|
||||
isDisabled: !!this.gateway.button.is_mini_cart_disabled
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.shouldRender()) {
|
||||
return;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import UpdateCart from "../Helper/UpdateCart";
|
||||
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
|
||||
import {hide, show, setVisible} from "../Helper/Hiding";
|
||||
import ButtonsToggleListener from "../Helper/ButtonsToggleListener";
|
||||
import {hide, show} from "../Helper/Hiding";
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {subscriptionHasPlan} from "../Helper/Subscriptions";
|
||||
|
||||
class SingleProductBootstap {
|
||||
|
@ -11,78 +11,88 @@ class SingleProductBootstap {
|
|||
this.messages = messages;
|
||||
this.errorHandler = errorHandler;
|
||||
this.mutationObserver = new MutationObserver(this.handleChange.bind(this));
|
||||
this.formSelector = 'form.cart';
|
||||
|
||||
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
|
||||
this.handleChange();
|
||||
}, true);
|
||||
}
|
||||
|
||||
variations() {
|
||||
if (!this.hasVariations()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attributes = [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
|
||||
(element) => {
|
||||
return {
|
||||
value: element.value,
|
||||
name: element.name
|
||||
}
|
||||
}
|
||||
);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
hasVariations() {
|
||||
return document.querySelector('form.cart')?.classList.contains('variations_form');
|
||||
form() {
|
||||
return document.querySelector(this.formSelector);
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
const shouldRender = this.shouldRender();
|
||||
setVisible(this.gateway.button.wrapper, shouldRender);
|
||||
setVisible(this.gateway.messages.wrapper, shouldRender);
|
||||
if (!shouldRender) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
init() {
|
||||
const form = document.querySelector('form.cart');
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.addEventListener('change', this.handleChange.bind(this));
|
||||
this.mutationObserver.observe(form, {childList: true, subtree: true});
|
||||
|
||||
const buttonObserver = new ButtonsToggleListener(
|
||||
form.querySelector('.single_add_to_cart_button'),
|
||||
() => {
|
||||
show(this.gateway.button.wrapper);
|
||||
show(this.gateway.messages.wrapper);
|
||||
this.messages.renderWithAmount(this.priceAmount())
|
||||
},
|
||||
() => {
|
||||
hide(this.gateway.button.wrapper);
|
||||
hide(this.gateway.messages.wrapper);
|
||||
},
|
||||
);
|
||||
buttonObserver.init();
|
||||
|
||||
if (!this.shouldRender()) {
|
||||
hide(this.gateway.button.wrapper);
|
||||
this.renderer.disableSmartButtons(this.gateway.button.wrapper);
|
||||
hide(this.gateway.button.wrapper, this.formSelector);
|
||||
hide(this.gateway.messages.wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
||||
this.renderer.enableSmartButtons(this.gateway.button.wrapper);
|
||||
show(this.gateway.button.wrapper);
|
||||
show(this.gateway.messages.wrapper);
|
||||
|
||||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
BootstrapHelper.handleButtonStatus(this, {
|
||||
formSelector: this.formSelector
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
const form = this.form();
|
||||
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
const addToCartButton = form.querySelector('.single_add_to_cart_button');
|
||||
|
||||
if (addToCartButton) {
|
||||
(new MutationObserver(this.handleButtonStatus.bind(this)))
|
||||
.observe(addToCartButton, { attributes : true });
|
||||
}
|
||||
|
||||
if (!this.shouldRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.render();
|
||||
this.handleChange();
|
||||
}
|
||||
|
||||
shouldRender() {
|
||||
return document.querySelector('form.cart') !== null
|
||||
&& !this.priceAmountIsZero()
|
||||
&& !this.isSubscriptionMode()
|
||||
return this.form() !== null
|
||||
&& !this.isWcsattSubscriptionMode()
|
||||
&& subscriptionHasPlan();
|
||||
}
|
||||
|
||||
shouldEnable() {
|
||||
const form = this.form();
|
||||
const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null;
|
||||
|
||||
return BootstrapHelper.shouldEnable(this)
|
||||
&& !this.priceAmountIsZero()
|
||||
&& ((null === addToCartButton) || !addToCartButton.classList.contains('disabled'));
|
||||
}
|
||||
|
||||
priceAmount() {
|
||||
const priceText = [
|
||||
() => document.querySelector('form.cart ins .woocommerce-Price-amount')?.innerText,
|
||||
|
@ -113,16 +123,36 @@ class SingleProductBootstap {
|
|||
if(subscriptionHasPlan()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const price = this.priceAmount();
|
||||
return !price || price === 0;
|
||||
}
|
||||
|
||||
isSubscriptionMode() {
|
||||
isWcsattSubscriptionMode() {
|
||||
// Check "All products for subscriptions" plugin.
|
||||
return document.querySelector('.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked') !== null
|
||||
|| document.querySelector('.wcsatt-options-prompt-label-subscription input[type="radio"]:checked') !== null; // grouped
|
||||
}
|
||||
|
||||
variations() {
|
||||
if (!this.hasVariations()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
|
||||
(element) => {
|
||||
return {
|
||||
value: element.value,
|
||||
name: element.name
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
hasVariations() {
|
||||
return document.querySelector('form.cart')?.classList.contains('variations_form');
|
||||
}
|
||||
|
||||
render() {
|
||||
const actionHandler = new SingleProductActionHandler(
|
||||
this.gateway,
|
||||
|
@ -130,7 +160,7 @@ class SingleProductBootstap {
|
|||
this.gateway.ajax.change_cart.endpoint,
|
||||
this.gateway.ajax.change_cart.nonce,
|
||||
),
|
||||
document.querySelector('form.cart'),
|
||||
this.form(),
|
||||
this.errorHandler,
|
||||
);
|
||||
|
||||
|
|
|
@ -40,6 +40,15 @@ class ErrorHandler {
|
|||
this._scrollToMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String}
|
||||
*/
|
||||
currentHtml()
|
||||
{
|
||||
const messageContainer = this._getMessageContainer();
|
||||
return messageContainer.outerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} text
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import {disable, enable} from "./ButtonDisabler";
|
||||
|
||||
/**
|
||||
* Common Bootstrap methods to avoid code repetition.
|
||||
*/
|
||||
export default class BootstrapHelper {
|
||||
|
||||
static handleButtonStatus(bs, options) {
|
||||
options = options || {};
|
||||
options.wrapper = options.wrapper || bs.gateway.button.wrapper;
|
||||
options.messagesWrapper = options.messagesWrapper || bs.gateway.messages.wrapper;
|
||||
options.skipMessages = options.skipMessages || false;
|
||||
|
||||
if (!bs.shouldEnable()) {
|
||||
bs.renderer.disableSmartButtons(options.wrapper);
|
||||
disable(options.wrapper, options.formSelector || null);
|
||||
|
||||
if (!options.skipMessages) {
|
||||
disable(options.messagesWrapper);
|
||||
}
|
||||
return;
|
||||
}
|
||||
bs.renderer.enableSmartButtons(options.wrapper);
|
||||
enable(options.wrapper);
|
||||
|
||||
if (!options.skipMessages) {
|
||||
enable(options.messagesWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
static shouldEnable(bs, options) {
|
||||
options = options || {};
|
||||
if (typeof options.isDisabled === 'undefined') {
|
||||
options.isDisabled = bs.gateway.button.is_disabled;
|
||||
}
|
||||
|
||||
return bs.shouldRender()
|
||||
&& options.isDisabled !== true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @param selectorOrElement
|
||||
* @returns {Element}
|
||||
*/
|
||||
const getElement = (selectorOrElement) => {
|
||||
if (typeof selectorOrElement === 'string') {
|
||||
return document.querySelector(selectorOrElement);
|
||||
}
|
||||
return selectorOrElement;
|
||||
}
|
||||
|
||||
export const setEnabled = (selectorOrElement, enable, form = null) => {
|
||||
const element = getElement(selectorOrElement);
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
jQuery(element).css({
|
||||
'cursor': '',
|
||||
'-webkit-filter': '',
|
||||
'filter': '',
|
||||
} )
|
||||
.off('mouseup')
|
||||
.find('> *')
|
||||
.css('pointer-events', '');
|
||||
} else {
|
||||
jQuery(element).css({
|
||||
'cursor': 'not-allowed',
|
||||
'-webkit-filter': 'grayscale(100%)',
|
||||
'filter': 'grayscale(100%)',
|
||||
})
|
||||
.on('mouseup', function(event) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (form) {
|
||||
// Trigger form submit to show the error message
|
||||
let $form = jQuery(form);
|
||||
if ($form.find('.single_add_to_cart_button').hasClass('disabled')) {
|
||||
$form.find(':submit').trigger('click');
|
||||
}
|
||||
}
|
||||
})
|
||||
.find('> *')
|
||||
.css('pointer-events', 'none');
|
||||
}
|
||||
};
|
||||
|
||||
export const disable = (selectorOrElement, form = null) => {
|
||||
setEnabled(selectorOrElement, false, form);
|
||||
};
|
||||
|
||||
export const enable = (selectorOrElement) => {
|
||||
setEnabled(selectorOrElement, true);
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* When you can't add something to the cart, the PayPal buttons should not show.
|
||||
* Therefore we listen for changes on the add to cart button and show/hide the buttons accordingly.
|
||||
*/
|
||||
|
||||
class ButtonsToggleListener {
|
||||
constructor(element, showCallback, hideCallback)
|
||||
{
|
||||
this.element = element;
|
||||
this.showCallback = showCallback;
|
||||
this.hideCallback = hideCallback;
|
||||
this.observer = null;
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
const config = { attributes : true };
|
||||
const callback = () => {
|
||||
if (this.element.classList.contains('disabled')) {
|
||||
this.hideCallback();
|
||||
return;
|
||||
}
|
||||
this.showCallback();
|
||||
}
|
||||
this.observer = new MutationObserver(callback);
|
||||
this.observer.observe(this.element, config);
|
||||
callback();
|
||||
}
|
||||
|
||||
disconnect()
|
||||
{
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
export default ButtonsToggleListener;
|
|
@ -4,7 +4,6 @@ export const isChangePaymentPage = () => {
|
|||
}
|
||||
|
||||
export const subscriptionHasPlan = () => {
|
||||
return true;
|
||||
if (PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled && PayPalCommerceGateway.data_client_id.has_subscriptions) {
|
||||
if (PayPalCommerceGateway.subscription_plan_id !== '') {
|
||||
return true;
|
||||
|
|
|
@ -10,6 +10,7 @@ class CreditCardRenderer {
|
|||
this.spinner = spinner;
|
||||
this.cardValid = false;
|
||||
this.formValid = false;
|
||||
this.emptyFields = new Set(['number', 'cvv', 'expirationDate']);
|
||||
this.currentHostedFieldsInstance = null;
|
||||
}
|
||||
|
||||
|
@ -130,7 +131,7 @@ class CreditCardRenderer {
|
|||
return event.fields[key].isValid;
|
||||
});
|
||||
|
||||
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
|
||||
const className = event.cards.length ? this._cardNumberFiledCLassNameByCardType(event.cards[0].type) : '';
|
||||
event.fields.number.isValid
|
||||
? cardNumber.classList.add(className)
|
||||
: this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
|
||||
|
@ -138,6 +139,12 @@ class CreditCardRenderer {
|
|||
this.formValid = formValid;
|
||||
|
||||
});
|
||||
hostedFields.on('empty', (event) => {
|
||||
this.emptyFields.add(event.emittedBy);
|
||||
});
|
||||
hostedFields.on('notEmpty', (event) => {
|
||||
this.emptyFields.delete(event.emittedBy);
|
||||
});
|
||||
|
||||
show(buttonSelector);
|
||||
|
||||
|
@ -249,7 +256,16 @@ class CreditCardRenderer {
|
|||
});
|
||||
} else {
|
||||
this.spinner.unblock();
|
||||
const message = ! this.cardValid ? this.defaultConfig.hosted_fields.labels.card_not_supported : this.defaultConfig.hosted_fields.labels.fields_not_valid;
|
||||
|
||||
let message = this.defaultConfig.labels.error.generic;
|
||||
if (this.emptyFields.size > 0) {
|
||||
message = this.defaultConfig.hosted_fields.labels.fields_empty;
|
||||
} else if (!this.cardValid) {
|
||||
message = this.defaultConfig.hosted_fields.labels.card_not_supported;
|
||||
} else if (!this.formValid) {
|
||||
message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
|
||||
}
|
||||
|
||||
this.errorHandler.message(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ class MessageRenderer {
|
|||
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.optionsFingerprint = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -9,38 +10,58 @@ class MessageRenderer {
|
|||
return;
|
||||
}
|
||||
|
||||
paypal.Messages({
|
||||
const options = {
|
||||
amount: this.config.amount,
|
||||
placement: this.config.placement,
|
||||
style: this.config.style
|
||||
}).render(this.config.wrapper);
|
||||
};
|
||||
|
||||
if (this.optionsEqual(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
paypal.Messages(options).render(this.config.wrapper);
|
||||
|
||||
jQuery(document.body).on('updated_cart_totals', () => {
|
||||
paypal.Messages({
|
||||
amount: this.config.amount,
|
||||
placement: this.config.placement,
|
||||
style: this.config.style
|
||||
}).render(this.config.wrapper);
|
||||
paypal.Messages(options).render(this.config.wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
renderWithAmount(amount) {
|
||||
|
||||
if (! this.shouldRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
amount,
|
||||
placement: this.config.placement,
|
||||
style: this.config.style
|
||||
};
|
||||
|
||||
if (this.optionsEqual(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newWrapper = document.createElement('div');
|
||||
newWrapper.setAttribute('id', this.config.wrapper.replace('#', ''));
|
||||
|
||||
const sibling = document.querySelector(this.config.wrapper).nextSibling;
|
||||
document.querySelector(this.config.wrapper).parentElement.removeChild(document.querySelector(this.config.wrapper));
|
||||
const oldWrapper = document.querySelector(this.config.wrapper);
|
||||
const sibling = oldWrapper.nextSibling;
|
||||
oldWrapper.parentElement.removeChild(oldWrapper);
|
||||
sibling.parentElement.insertBefore(newWrapper, sibling);
|
||||
paypal.Messages({
|
||||
amount,
|
||||
placement: this.config.placement,
|
||||
style: this.config.style
|
||||
}).render(this.config.wrapper);
|
||||
|
||||
paypal.Messages(options).render(this.config.wrapper);
|
||||
}
|
||||
|
||||
optionsEqual(options) {
|
||||
const fingerprint = JSON.stringify(options);
|
||||
|
||||
if (this.optionsFingerprint === fingerprint) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.optionsFingerprint = fingerprint;
|
||||
return false;
|
||||
}
|
||||
|
||||
shouldRender() {
|
||||
|
|
|
@ -7,6 +7,9 @@ class Renderer {
|
|||
this.onSmartButtonClick = onSmartButtonClick;
|
||||
this.onSmartButtonsInit = onSmartButtonsInit;
|
||||
|
||||
this.buttonsOptions = {};
|
||||
this.onButtonsInitListeners = {};
|
||||
|
||||
this.renderedSources = new Set();
|
||||
}
|
||||
|
||||
|
@ -77,7 +80,12 @@ class Renderer {
|
|||
style,
|
||||
...contextConfig,
|
||||
onClick: this.onSmartButtonClick,
|
||||
onInit: this.onSmartButtonsInit,
|
||||
onInit: (data, actions) => {
|
||||
if (this.onSmartButtonsInit) {
|
||||
this.onSmartButtonsInit(data, actions);
|
||||
}
|
||||
this.handleOnButtonsInit(wrapper, data, actions);
|
||||
},
|
||||
});
|
||||
if (!btn.isEligible()) {
|
||||
return;
|
||||
|
@ -106,6 +114,44 @@ class Renderer {
|
|||
enableCreditCardFields() {
|
||||
this.creditCardRenderer.enableFields();
|
||||
}
|
||||
|
||||
onButtonsInit(wrapper, handler, reset) {
|
||||
this.onButtonsInitListeners[wrapper] = reset ? [] : (this.onButtonsInitListeners[wrapper] || []);
|
||||
this.onButtonsInitListeners[wrapper].push(handler);
|
||||
}
|
||||
|
||||
handleOnButtonsInit(wrapper, data, actions) {
|
||||
|
||||
this.buttonsOptions[wrapper] = {
|
||||
data: data,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
if (this.onButtonsInitListeners[wrapper]) {
|
||||
for (let handler of this.onButtonsInitListeners[wrapper]) {
|
||||
if (typeof handler === 'function') {
|
||||
handler({
|
||||
wrapper: wrapper,
|
||||
...this.buttonsOptions[wrapper]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disableSmartButtons(wrapper) {
|
||||
if (!this.buttonsOptions[wrapper]) {
|
||||
return;
|
||||
}
|
||||
this.buttonsOptions[wrapper].actions.disable();
|
||||
}
|
||||
|
||||
enableSmartButtons(wrapper) {
|
||||
if (!this.buttonsOptions[wrapper]) {
|
||||
return;
|
||||
}
|
||||
this.buttonsOptions[wrapper].actions.enable();
|
||||
}
|
||||
}
|
||||
|
||||
export default Renderer;
|
||||
|
|
|
@ -66,23 +66,6 @@ return array(
|
|||
|
||||
return $dummy_ids[ $shop_country ] ?? $container->get( 'button.client_id' );
|
||||
},
|
||||
'button.is_paypal_continuation' => static function( ContainerInterface $container ): bool {
|
||||
$session_handler = $container->get( 'session.handler' );
|
||||
|
||||
$order = $session_handler->order();
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
$source = $order->payment_source();
|
||||
if ( $source && $source->card() ) {
|
||||
return false; // Ignore for DCC.
|
||||
}
|
||||
if ( 'card' === $session_handler->funding_source() ) {
|
||||
return false; // Ignore for card buttons.
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
|
||||
|
@ -124,6 +107,7 @@ return array(
|
|||
$container->get( 'button.basic-checkout-validation-enabled' ),
|
||||
$container->get( 'button.early-wc-checkout-validation-enabled' ),
|
||||
$container->get( 'button.pay-now-contexts' ),
|
||||
$container->get( 'wcgateway.funding-sources-without-redirect' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
@ -175,6 +159,7 @@ return array(
|
|||
$container->get( 'button.early-wc-checkout-validation-enabled' ),
|
||||
$container->get( 'button.pay-now-contexts' ),
|
||||
$container->get( 'button.handle-shipping-in-paypal' ),
|
||||
$container->get( 'wcgateway.funding-sources-without-redirect' ),
|
||||
$logger
|
||||
);
|
||||
},
|
||||
|
@ -183,8 +168,7 @@ return array(
|
|||
$state = $container->get( 'onboarding.state' );
|
||||
$order_processor = $container->get( 'wcgateway.order-processor' );
|
||||
$session_handler = $container->get( 'session.handler' );
|
||||
$prefix = $container->get( 'api.prefix' );
|
||||
return new EarlyOrderHandler( $state, $order_processor, $session_handler, $prefix );
|
||||
return new EarlyOrderHandler( $state, $order_processor, $session_handler );
|
||||
},
|
||||
'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint {
|
||||
$request_data = $container->get( 'button.request-data' );
|
||||
|
@ -214,7 +198,9 @@ return array(
|
|||
);
|
||||
},
|
||||
'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
|
||||
return new CheckoutFormSaver();
|
||||
return new CheckoutFormSaver(
|
||||
$container->get( 'session.handler' )
|
||||
);
|
||||
},
|
||||
'button.endpoint.save-checkout-form' => static function ( ContainerInterface $container ): SaveCheckoutFormEndpoint {
|
||||
return new SaveCheckoutFormEndpoint(
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets;
|
|||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WC_Product;
|
||||
use WC_Product_Variation;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
|
@ -173,6 +174,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
*/
|
||||
private $pay_now_contexts;
|
||||
|
||||
/**
|
||||
* The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $funding_sources_without_redirect;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -208,6 +216,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled.
|
||||
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
|
||||
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
|
||||
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -229,6 +238,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
bool $basic_checkout_validation_enabled,
|
||||
bool $early_validation_enabled,
|
||||
array $pay_now_contexts,
|
||||
array $funding_sources_without_redirect,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
|
@ -250,6 +260,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -796,8 +807,6 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @return array
|
||||
*/
|
||||
public function script_data(): array {
|
||||
global $wp;
|
||||
|
||||
$is_free_trial_cart = $this->is_free_trial_cart();
|
||||
|
||||
$url_params = $this->url_params();
|
||||
|
@ -859,10 +868,12 @@ class SmartButton implements SmartButtonInterface {
|
|||
'bn_codes' => $this->bn_codes(),
|
||||
'payer' => $this->payerData(),
|
||||
'button' => array(
|
||||
'wrapper' => '#ppc-button-' . PayPalGateway::ID,
|
||||
'mini_cart_wrapper' => '#ppc-button-minicart',
|
||||
'cancel_wrapper' => '#ppcp-cancel',
|
||||
'mini_cart_style' => array(
|
||||
'wrapper' => '#ppc-button-' . PayPalGateway::ID,
|
||||
'is_disabled' => $this->is_button_disabled(),
|
||||
'mini_cart_wrapper' => '#ppc-button-minicart',
|
||||
'is_mini_cart_disabled' => $this->is_button_disabled( 'mini-cart' ),
|
||||
'cancel_wrapper' => '#ppcp-cancel',
|
||||
'mini_cart_style' => array(
|
||||
'layout' => $this->style_for_context( 'layout', 'mini-cart' ),
|
||||
'color' => $this->style_for_context( 'color', 'mini-cart' ),
|
||||
'shape' => $this->style_for_context( 'shape', 'mini-cart' ),
|
||||
|
@ -870,7 +881,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ),
|
||||
'height' => $this->settings->has( 'button_mini-cart_height' ) && $this->settings->get( 'button_mini-cart_height' ) ? $this->normalize_height( (int) $this->settings->get( 'button_mini-cart_height' ) ) : 35,
|
||||
),
|
||||
'style' => array(
|
||||
'style' => array(
|
||||
'layout' => $this->style_for_context( 'layout', $this->context() ),
|
||||
'color' => $this->style_for_context( 'color', $this->context() ),
|
||||
'shape' => $this->style_for_context( 'shape', $this->context() ),
|
||||
|
@ -894,6 +905,10 @@ class SmartButton implements SmartButtonInterface {
|
|||
'credit_card_number' => '',
|
||||
'cvv' => '',
|
||||
'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ),
|
||||
'fields_empty' => __(
|
||||
'Card payment details are missing. Please fill in all required fields.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'fields_not_valid' => __(
|
||||
'Unfortunately, your credit card details are not valid.',
|
||||
'woocommerce-paypal-payments'
|
||||
|
@ -935,11 +950,12 @@ class SmartButton implements SmartButtonInterface {
|
|||
// phpcs:ignore WordPress.WP.I18n
|
||||
'shipping_field' => _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ),
|
||||
),
|
||||
'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0,
|
||||
'order_id' => 'pay-now' === $this->context() ? $this->get_order_pay_id() : 0,
|
||||
'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
|
||||
'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
|
||||
'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
|
||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
||||
);
|
||||
|
||||
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
|
||||
|
@ -1001,13 +1017,26 @@ class SmartButton implements SmartButtonInterface {
|
|||
);
|
||||
if (
|
||||
$this->environment->current_environment_is( Environment::SANDBOX )
|
||||
&& defined( 'WP_DEBUG' ) && \WP_DEBUG && is_user_logged_in()
|
||||
&& defined( 'WP_DEBUG' ) && \WP_DEBUG
|
||||
&& WC()->customer instanceof \WC_Customer && WC()->customer->get_billing_country()
|
||||
&& 2 === strlen( WC()->customer->get_billing_country() )
|
||||
) {
|
||||
$params['buyer-country'] = WC()->customer->get_billing_country();
|
||||
}
|
||||
|
||||
if ( 'pay-now' === $this->context() ) {
|
||||
$wc_order_id = $this->get_order_pay_id();
|
||||
if ( $wc_order_id ) {
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( $wc_order instanceof WC_Order ) {
|
||||
$currency = $wc_order->get_currency();
|
||||
if ( $currency ) {
|
||||
$params['currency'] = $currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$disable_funding = $this->settings->has( 'disable_funding' )
|
||||
? $this->settings->get( 'disable_funding' )
|
||||
: array();
|
||||
|
@ -1335,6 +1364,51 @@ class SmartButton implements SmartButtonInterface {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_button_disabled( string $context = null ): bool {
|
||||
if ( null === $context ) {
|
||||
$context = $this->context();
|
||||
}
|
||||
|
||||
if ( 'product' === $context ) {
|
||||
$product = wc_get_product();
|
||||
|
||||
/**
|
||||
* Allows to decide if the button should be disabled for a given product
|
||||
*/
|
||||
$is_disabled = apply_filters(
|
||||
'woocommerce_paypal_payments_product_buttons_disabled',
|
||||
null,
|
||||
$product
|
||||
);
|
||||
|
||||
if ( $is_disabled !== null ) {
|
||||
return $is_disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to decide if the button should be disabled globally or on a given context
|
||||
*/
|
||||
$is_disabled = apply_filters(
|
||||
'woocommerce_paypal_payments_buttons_disabled',
|
||||
null,
|
||||
$context
|
||||
);
|
||||
|
||||
if ( $is_disabled !== null ) {
|
||||
return $is_disabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all payment tokens for the user, via API or cached if already queried.
|
||||
*
|
||||
|
@ -1402,4 +1476,19 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
return $this->context() === 'product' ? $product_intent : $other_context_intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of WC order on the order-pay page, or 0.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_order_pay_id(): int {
|
||||
global $wp;
|
||||
|
||||
if ( ! isset( $wp->query_vars['order-pay'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return absint( $wp->query_vars['order-pay'] );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,6 +152,13 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
*/
|
||||
private $handle_shipping_in_paypal;
|
||||
|
||||
/**
|
||||
* The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $funding_sources_without_redirect;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -175,6 +182,7 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
|
||||
* @param string[] $pay_now_contexts The contexts that should have the Pay Now button.
|
||||
* @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
|
||||
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -191,23 +199,25 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
bool $early_validation_enabled,
|
||||
array $pay_now_contexts,
|
||||
bool $handle_shipping_in_paypal,
|
||||
array $funding_sources_without_redirect,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
|
||||
$this->logger = $logger;
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
|
||||
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -288,6 +298,11 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
}
|
||||
|
||||
if ( 'checkout' === $data['context'] ) {
|
||||
if ( $payment_method === PayPalGateway::ID && ! in_array( $funding_source, $this->funding_sources_without_redirect, true ) ) {
|
||||
$this->session_handler->replace_order( $order );
|
||||
$this->session_handler->replace_funding_source( $funding_source );
|
||||
}
|
||||
|
||||
if (
|
||||
! $this->early_order_handler->should_create_early_order()
|
||||
|| $this->registration_needed
|
||||
|
|
|
@ -10,11 +10,30 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use WC_Checkout;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
||||
/**
|
||||
* Class CheckoutFormSaver
|
||||
*/
|
||||
class CheckoutFormSaver extends WC_Checkout {
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* CheckoutFormSaver constructor.
|
||||
*
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
*/
|
||||
public function __construct(
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the form data to the WC customer and session.
|
||||
*
|
||||
|
@ -28,5 +47,7 @@ class CheckoutFormSaver extends WC_Checkout {
|
|||
$data = $this->get_posted_data();
|
||||
|
||||
$this->update_session( $data );
|
||||
|
||||
$this->session_handler->replace_checkout_form( $data );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
|
||||
trait ContextTrait {
|
||||
|
||||
/**
|
||||
|
@ -18,6 +20,13 @@ trait ContextTrait {
|
|||
*/
|
||||
protected function context(): string {
|
||||
if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
|
||||
|
||||
// Do this check here instead of reordering outside conditions.
|
||||
// In order to have more control over the context.
|
||||
if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) {
|
||||
return 'checkout';
|
||||
}
|
||||
|
||||
return 'product';
|
||||
}
|
||||
|
||||
|
@ -56,6 +65,12 @@ trait ContextTrait {
|
|||
return false;
|
||||
}
|
||||
|
||||
if ( ! $order->status()->is( OrderStatus::APPROVED )
|
||||
&& ! $order->status()->is( OrderStatus::COMPLETED )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = $order->payment_source();
|
||||
if ( $source && $source->card() ) {
|
||||
return false; // Ignore for DCC.
|
||||
|
|
|
@ -15,15 +15,12 @@ use WooCommerce\PayPalCommerce\Onboarding\State;
|
|||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
|
||||
|
||||
/**
|
||||
* Class EarlyOrderHandler
|
||||
*/
|
||||
class EarlyOrderHandler {
|
||||
|
||||
use PrefixTrait;
|
||||
|
||||
/**
|
||||
* The State.
|
||||
*
|
||||
|
@ -51,19 +48,16 @@ class EarlyOrderHandler {
|
|||
* @param State $state The State.
|
||||
* @param OrderProcessor $order_processor The Order Processor.
|
||||
* @param SessionHandler $session_handler The Session Handler.
|
||||
* @param string $prefix The Prefix.
|
||||
*/
|
||||
public function __construct(
|
||||
State $state,
|
||||
OrderProcessor $order_processor,
|
||||
SessionHandler $session_handler,
|
||||
string $prefix
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
|
||||
$this->state = $state;
|
||||
$this->order_processor = $order_processor;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +95,7 @@ class EarlyOrderHandler {
|
|||
$order_id = false;
|
||||
foreach ( $order->purchase_units() as $purchase_unit ) {
|
||||
if ( $purchase_unit->custom_id() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
|
||||
$order_id = (int) $this->sanitize_custom_id( $purchase_unit->custom_id() );
|
||||
$order_id = (int) $purchase_unit->custom_id();
|
||||
}
|
||||
}
|
||||
if ( $order_id === $resume_order_id ) {
|
||||
|
|
|
@ -339,14 +339,6 @@ class CompatModule implements ModuleInterface {
|
|||
},
|
||||
5
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_checkout_button_renderer_hook',
|
||||
function(): string {
|
||||
return 'woocommerce_review_order_after_submit';
|
||||
},
|
||||
5
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Session\Cancellation;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
||||
/**
|
||||
* Class CancelController
|
||||
*/
|
||||
class CancelController {
|
||||
use ContextTrait;
|
||||
|
||||
public const NONCE = 'ppcp-cancel';
|
||||
|
||||
|
@ -50,7 +52,7 @@ class CancelController {
|
|||
/**
|
||||
* Runs the controller.
|
||||
*/
|
||||
public function run() {
|
||||
public function run(): void {
|
||||
$param_name = self::NONCE;
|
||||
if ( isset( $_GET[ $param_name ] ) && // Input var ok.
|
||||
wp_verify_nonce(
|
||||
|
@ -61,20 +63,10 @@ class CancelController {
|
|||
$this->session_handler->destroy_session_data();
|
||||
}
|
||||
|
||||
$order = $this->session_handler->order();
|
||||
if ( ! $order ) {
|
||||
if ( ! $this->is_paypal_continuation() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$source = $order->payment_source();
|
||||
if ( $source && $source->card() ) {
|
||||
return; // Ignore for DCC.
|
||||
}
|
||||
|
||||
if ( 'card' === $this->session_handler->funding_source() ) {
|
||||
return; // Ignore for card buttons.
|
||||
}
|
||||
|
||||
$url = add_query_arg( array( $param_name => wp_create_nonce( self::NONCE ) ), wc_get_checkout_url() );
|
||||
add_action(
|
||||
'woocommerce_review_order_after_submit',
|
||||
|
|
|
@ -63,13 +63,13 @@ class CancelView {
|
|||
printf(
|
||||
// translators: %3$ is funding source like "PayPal" or "Venmo", other placeholders are html tags for a link.
|
||||
esc_html__(
|
||||
'You are currently paying with %3$s. If you want to cancel
|
||||
this process, please click %1$shere%2$s.',
|
||||
'You are currently paying with %3$s. %4$s%1$sChoose another payment method%2$s.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'<a href="' . esc_url( $url ) . '">',
|
||||
'</a>',
|
||||
esc_html( $name )
|
||||
esc_html( $name ),
|
||||
'<br/>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
|
75
modules/ppcp-session/src/MemoryWcSession.php
Normal file
75
modules/ppcp-session/src/MemoryWcSession.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
/**
|
||||
* A WC_Session_Handler subclass for loading the session when it is normally not available (e.g. in webhooks).
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Session
|
||||
*
|
||||
* phpcs:disable Generic.Commenting.DocComment
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Session;
|
||||
|
||||
use WC_Session_Handler;
|
||||
|
||||
/**
|
||||
* MemoryWcSession class.
|
||||
*/
|
||||
class MemoryWcSession extends WC_Session_Handler {
|
||||
/**
|
||||
* The session data (from WC()->session->get_session).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
/**
|
||||
* The customer ID.
|
||||
*
|
||||
* @var string|int
|
||||
*/
|
||||
private static $customer_id;
|
||||
|
||||
/**
|
||||
* Enqueues this session handler with the given data to be used by WC.
|
||||
*
|
||||
* @param array $session_data The session data (from WC()->session->get_session).
|
||||
* @param int|string $customer_id The customer ID.
|
||||
*/
|
||||
public static function replace_session_handler( array $session_data, $customer_id ): void {
|
||||
self::$data = $session_data;
|
||||
self::$customer_id = $customer_id;
|
||||
|
||||
add_filter(
|
||||
'woocommerce_session_handler',
|
||||
function () {
|
||||
return MemoryWcSession::class;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function init_session_cookie() {
|
||||
$this->_customer_id = self::$customer_id;
|
||||
$this->_data = self::$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function get_session_data() {
|
||||
return self::$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
public function forget_session() {
|
||||
self::$data = array();
|
||||
|
||||
parent::forget_session();
|
||||
}
|
||||
}
|
|
@ -47,6 +47,13 @@ class SessionHandler {
|
|||
*/
|
||||
private $funding_source = null;
|
||||
|
||||
/**
|
||||
* The checkout form data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $checkout_form = array();
|
||||
|
||||
/**
|
||||
* Returns the order.
|
||||
*
|
||||
|
@ -55,6 +62,8 @@ class SessionHandler {
|
|||
public function order() {
|
||||
$this->load_session();
|
||||
|
||||
do_action( 'ppcp_session_get_order', $this->order, $this );
|
||||
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
|
@ -71,6 +80,30 @@ class SessionHandler {
|
|||
$this->store_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checkout form data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function checkout_form(): array {
|
||||
$this->load_session();
|
||||
|
||||
return $this->checkout_form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the checkout form data.
|
||||
*
|
||||
* @param array $checkout_form The checkout form data.
|
||||
*/
|
||||
public function replace_checkout_form( array $checkout_form ): void {
|
||||
$this->load_session();
|
||||
|
||||
$this->checkout_form = $checkout_form;
|
||||
|
||||
$this->store_session();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BN Code.
|
||||
*
|
||||
|
@ -151,6 +184,7 @@ class SessionHandler {
|
|||
$this->bn_code = '';
|
||||
$this->insufficient_funding_tries = 0;
|
||||
$this->funding_source = null;
|
||||
$this->checkout_form = array();
|
||||
$this->store_session();
|
||||
return $this;
|
||||
}
|
||||
|
@ -188,6 +222,7 @@ class SessionHandler {
|
|||
if ( ! is_string( $this->funding_source ) ) {
|
||||
$this->funding_source = null;
|
||||
}
|
||||
$this->checkout_form = $data['checkout_form'] ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,6 +237,7 @@ class SessionHandler {
|
|||
'bn_code' => $obj->bn_code,
|
||||
'insufficient_funding_tries' => $obj->insufficient_funding_tries,
|
||||
'funding_source' => $obj->funding_source,
|
||||
'checkout_form' => $obj->checkout_form,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Session;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController;
|
||||
|
@ -19,6 +24,12 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
|||
* Class SessionModule
|
||||
*/
|
||||
class SessionModule implements ModuleInterface {
|
||||
/**
|
||||
* A flag to avoid multiple requests to reload order.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $reloaded_order = false;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
@ -46,6 +57,45 @@ class SessionModule implements ModuleInterface {
|
|||
$controller->run();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'ppcp_session_get_order',
|
||||
function ( ?Order $order, SessionHandler $session_handler ) use ( $c ): void {
|
||||
if ( ! isset( WC()->session ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->reloaded_order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $order->status()->is( OrderStatus::APPROVED )
|
||||
|| $order->status()->is( OrderStatus::COMPLETED )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order_endpoint = $c->get( 'api.endpoint.order' );
|
||||
assert( $order_endpoint instanceof OrderEndpoint );
|
||||
|
||||
$this->reloaded_order = true;
|
||||
|
||||
try {
|
||||
$session_handler->replace_order( $order_endpoint->order( $order->id() ) );
|
||||
} catch ( Throwable $exception ) {
|
||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||
assert( $logger instanceof LoggerInterface );
|
||||
|
||||
$logger->warning( 'Failed to reload PayPal order in the session: ' . $exception->getMessage() );
|
||||
}
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -120,11 +120,19 @@ class StatusReportModule implements ModuleInterface {
|
|||
'value' => $this->bool_to_html( ! $last_webhook_storage->is_empty() ),
|
||||
),
|
||||
array(
|
||||
'label' => esc_html__( 'Vault enabled', 'woocommerce-paypal-payments' ),
|
||||
'exported_label' => 'Vault enabled',
|
||||
'description' => esc_html__( 'Whether vaulting is enabled on PayPal account or not.', 'woocommerce-paypal-payments' ),
|
||||
'label' => esc_html__( 'PayPal Vault enabled', 'woocommerce-paypal-payments' ),
|
||||
'exported_label' => 'PayPal Vault enabled',
|
||||
'description' => esc_html__( 'Whether vaulting option is enabled on Standard Payments settings or not.', 'woocommerce-paypal-payments' ),
|
||||
'value' => $this->bool_to_html(
|
||||
$this->vault_enabled( $bearer )
|
||||
$settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' )
|
||||
),
|
||||
),
|
||||
array(
|
||||
'label' => esc_html__( 'ACDC Vault enabled', 'woocommerce-paypal-payments' ),
|
||||
'exported_label' => 'ACDC Vault enabled',
|
||||
'description' => esc_html__( 'Whether vaulting option is enabled on Advanced Card Processing settings or not.', 'woocommerce-paypal-payments' ),
|
||||
'value' => $this->bool_to_html(
|
||||
$settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' )
|
||||
),
|
||||
),
|
||||
array(
|
||||
|
@ -192,21 +200,6 @@ class StatusReportModule implements ModuleInterface {
|
|||
return $token->is_valid() && $current_state === $state::STATE_ONBOARDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* It returns whether vaulting is enabled or not.
|
||||
*
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @return bool
|
||||
*/
|
||||
private function vault_enabled( Bearer $bearer ): bool {
|
||||
try {
|
||||
$token = $bearer->bearer();
|
||||
return $token->vaulting_available();
|
||||
} catch ( RuntimeException $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if reference transactions are enabled in account.
|
||||
*
|
||||
|
|
|
@ -33,6 +33,7 @@ return array(
|
|||
'woocommerce-ppcp-version',
|
||||
WebhookSimulation::OPTION_ID,
|
||||
WebhookRegistrar::KEY,
|
||||
'ppcp_payment_tokens_migration_initialized',
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -40,6 +41,7 @@ return array(
|
|||
return array(
|
||||
'woocommerce_paypal_payments_check_pui_payment_captured',
|
||||
'woocommerce_paypal_payments_check_saved_payment',
|
||||
'woocommerce_paypal_payments_payment_tokens_migration',
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -178,6 +178,9 @@ class VaultingModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Allows running migration externally via `do_action('pcp_migrate_payment_tokens')`.
|
||||
*/
|
||||
add_action(
|
||||
'pcp_migrate_payment_tokens',
|
||||
function() use ( $container ) {
|
||||
|
@ -218,6 +221,10 @@ class VaultingModule implements ModuleInterface {
|
|||
* @return void
|
||||
*/
|
||||
public function migrate_payment_tokens( LoggerInterface $logger ): void {
|
||||
$initialized = get_option( 'ppcp_payment_tokens_migration_initialized', null );
|
||||
if ( $initialized ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
$customers = new WP_User_Query(
|
||||
|
@ -235,12 +242,19 @@ class VaultingModule implements ModuleInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
$logger->info( 'Starting payment tokens migration for ' . (string) count( $customers ) . ' users' );
|
||||
$logger->info( 'Identified ' . (string) count( $customers ) . ' users with payment tokens. Initiating token migration.' );
|
||||
update_option( 'ppcp_payment_tokens_migration_initialized', true );
|
||||
|
||||
$interval_in_seconds = 5;
|
||||
$timestamp = time();
|
||||
|
||||
foreach ( $customers as $id ) {
|
||||
$tokens = array_filter( get_user_meta( $id, 'ppcp-vault-token' ) );
|
||||
$skip_empty_key_migration = apply_filters( 'ppcp_skip_payment_tokens_empty_key_migration', true );
|
||||
if ( empty( $tokens ) && $skip_empty_key_migration ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function already exist in WooCommerce
|
||||
*
|
||||
|
|
|
@ -57,6 +57,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
|
||||
|
@ -209,6 +210,12 @@ return array(
|
|||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return new ConnectAdminNotice( $state, $settings );
|
||||
},
|
||||
'wcgateway.notice.currency-unsupported' => static function ( ContainerInterface $container ): UnsupportedCurrencyAdminNotice {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
$shop_currency = $container->get( 'api.shop.currency' );
|
||||
$supported_currencies = $container->get( 'api.supported-currencies' );
|
||||
return new UnsupportedCurrencyAdminNotice( $state, $shop_currency, $supported_currencies );
|
||||
},
|
||||
'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
|
||||
return new GatewayWithoutPayPalAdminNotice(
|
||||
CreditCardGateway::ID,
|
||||
|
@ -224,7 +231,8 @@ return array(
|
|||
$container->get( 'onboarding.state' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'wcgateway.is-wc-payments-page' ),
|
||||
$container->get( 'wcgateway.is-ppcp-settings-page' )
|
||||
$container->get( 'wcgateway.is-ppcp-settings-page' ),
|
||||
$container->get( 'wcgateway.settings.status' )
|
||||
);
|
||||
},
|
||||
'wcgateway.notice.authorize-order-action' =>
|
||||
|
@ -911,6 +919,12 @@ return array(
|
|||
'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
|
||||
);
|
||||
},
|
||||
/**
|
||||
* The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
*/
|
||||
'wcgateway.funding-sources-without-redirect' => static function( ContainerInterface $container ): array {
|
||||
return array( 'paypal', 'paylater', 'venmo', 'card' );
|
||||
},
|
||||
'wcgateway.settings.funding-sources' => static function( ContainerInterface $container ): array {
|
||||
return array_diff_key(
|
||||
$container->get( 'wcgateway.all-funding-sources' ),
|
||||
|
@ -946,11 +960,11 @@ return array(
|
|||
'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint {
|
||||
$gateway = $container->get( 'wcgateway.paypal-gateway' );
|
||||
$endpoint = $container->get( 'api.endpoint.order' );
|
||||
$prefix = $container->get( 'api.prefix' );
|
||||
return new ReturnUrlEndpoint(
|
||||
$gateway,
|
||||
$endpoint,
|
||||
$prefix
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Checkout;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||
|
@ -21,6 +22,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
|||
* Class DisableGateways
|
||||
*/
|
||||
class DisableGateways {
|
||||
use ContextTrait;
|
||||
|
||||
/**
|
||||
* The Session Handler.
|
||||
|
@ -93,8 +95,11 @@ class DisableGateways {
|
|||
unset( $methods[ CreditCardGateway::ID ] );
|
||||
}
|
||||
|
||||
if ( ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) && ! $this->session_handler->order() && is_checkout() ) {
|
||||
unset( $methods[ PayPalGateway::ID ] );
|
||||
if ( ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) ) {
|
||||
unset( $methods[ CardButtonGateway::ID ] );
|
||||
if ( ! $this->session_handler->order() && is_checkout() ) {
|
||||
unset( $methods[ PayPalGateway::ID ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->needs_to_disable_gateways() ) {
|
||||
|
@ -144,20 +149,6 @@ class DisableGateways {
|
|||
* @return bool
|
||||
*/
|
||||
private function needs_to_disable_gateways(): bool {
|
||||
$order = $this->session_handler->order();
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = $order->payment_source();
|
||||
if ( $source && $source->card() ) {
|
||||
return false; // DCC.
|
||||
}
|
||||
|
||||
if ( 'card' === $this->session_handler->funding_source() ) {
|
||||
return false; // Card buttons.
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->is_paypal_continuation();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
|
||||
|
||||
/**
|
||||
* Class ReturnUrlEndpoint
|
||||
*/
|
||||
class ReturnUrlEndpoint {
|
||||
|
||||
use PrefixTrait;
|
||||
const ENDPOINT = 'ppc-return-url';
|
||||
|
||||
/**
|
||||
|
@ -36,17 +37,38 @@ class ReturnUrlEndpoint {
|
|||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The session handler
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
protected $session_handler;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* ReturnUrlEndpoint constructor.
|
||||
*
|
||||
* @param PayPalGateway $gateway The PayPal Gateway.
|
||||
* @param OrderEndpoint $order_endpoint The Order Endpoint.
|
||||
* @param string $prefix The prefix.
|
||||
* @param PayPalGateway $gateway The PayPal Gateway.
|
||||
* @param OrderEndpoint $order_endpoint The Order Endpoint.
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct( PayPalGateway $gateway, OrderEndpoint $order_endpoint, string $prefix ) {
|
||||
$this->gateway = $gateway;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->prefix = $prefix;
|
||||
public function __construct(
|
||||
PayPalGateway $gateway,
|
||||
OrderEndpoint $order_endpoint,
|
||||
SessionHandler $session_handler,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->gateway = $gateway;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,13 +85,25 @@ class ReturnUrlEndpoint {
|
|||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
$order = $this->order_endpoint->order( $token );
|
||||
|
||||
$wc_order_id = $this->sanitize_custom_id( $order->purchase_units()[0]->custom_id() );
|
||||
$wc_order_id = (int) $order->purchase_units()[0]->custom_id();
|
||||
if ( ! $wc_order_id ) {
|
||||
// We cannot finish processing here without WC order, but at least go into the continuation mode.
|
||||
if ( $order->status()->is( OrderStatus::APPROVED )
|
||||
|| $order->status()->is( OrderStatus::COMPLETED )
|
||||
) {
|
||||
$this->session_handler->replace_order( $order );
|
||||
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
$this->logger->warning( "Return URL endpoint $token: no WC order ID." );
|
||||
exit();
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
|
||||
$this->logger->warning( "Return URL endpoint $token: WC order $wc_order_id not found." );
|
||||
exit();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ use Exception;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
@ -253,8 +254,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
|
|||
|
||||
$funding_source = $this->session_handler->funding_source();
|
||||
if ( $funding_source ) {
|
||||
$this->title = $this->funding_source_renderer->render_name( $funding_source );
|
||||
$this->description = $this->funding_source_renderer->render_description( $funding_source );
|
||||
$order = $this->session_handler->order();
|
||||
if ( $order &&
|
||||
( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::COMPLETED ) )
|
||||
) {
|
||||
$this->title = $this->funding_source_renderer->render_name( $funding_source );
|
||||
$this->description = $this->funding_source_renderer->render_description( $funding_source );
|
||||
}
|
||||
}
|
||||
|
||||
$this->init_form_fields();
|
||||
|
@ -554,12 +560,15 @@ class PayPalGateway extends \WC_Payment_Gateway {
|
|||
'INSTRUMENT_DECLINED' => __( 'Instrument declined.', 'woocommerce-paypal-payments' ),
|
||||
'PAYER_ACTION_REQUIRED' => __( 'Payer action required, possibly overcharge.', 'woocommerce-paypal-payments' ),
|
||||
);
|
||||
$retry_errors = array_filter(
|
||||
array_keys( $retry_keys_messages ),
|
||||
function ( string $key ) use ( $error ): bool {
|
||||
return $error->has_detail( $key );
|
||||
}
|
||||
$retry_errors = array_values(
|
||||
array_filter(
|
||||
array_keys( $retry_keys_messages ),
|
||||
function ( string $key ) use ( $error ): bool {
|
||||
return $error->has_detail( $key );
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if ( $retry_errors ) {
|
||||
$retry_error_key = $retry_errors[0];
|
||||
|
||||
|
|
|
@ -13,11 +13,16 @@ use WC_Payment_Gateway;
|
|||
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
|
||||
/**
|
||||
* Creates the admin message about the gateway being enabled without the PayPal gateway.
|
||||
*/
|
||||
class GatewayWithoutPayPalAdminNotice {
|
||||
private const NOTICE_OK = '';
|
||||
private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway';
|
||||
private const NOTICE_DISABLED_LOCATION = 'disabled_location';
|
||||
|
||||
/**
|
||||
* The gateway ID.
|
||||
*
|
||||
|
@ -53,27 +58,37 @@ class GatewayWithoutPayPalAdminNotice {
|
|||
*/
|
||||
private $is_ppcp_settings_page;
|
||||
|
||||
/**
|
||||
* The Settings status helper.
|
||||
*
|
||||
* @var SettingsStatus|null
|
||||
*/
|
||||
protected $settings_status;
|
||||
|
||||
/**
|
||||
* ConnectAdminNotice constructor.
|
||||
*
|
||||
* @param string $id The gateway ID.
|
||||
* @param State $state The state.
|
||||
* @param ContainerInterface $settings The settings.
|
||||
* @param bool $is_payments_page Whether the current page is the WC payment page.
|
||||
* @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
|
||||
* @param string $id The gateway ID.
|
||||
* @param State $state The state.
|
||||
* @param ContainerInterface $settings The settings.
|
||||
* @param bool $is_payments_page Whether the current page is the WC payment page.
|
||||
* @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
|
||||
* @param SettingsStatus|null $settings_status The Settings status helper.
|
||||
*/
|
||||
public function __construct(
|
||||
string $id,
|
||||
State $state,
|
||||
ContainerInterface $settings,
|
||||
bool $is_payments_page,
|
||||
bool $is_ppcp_settings_page
|
||||
bool $is_ppcp_settings_page,
|
||||
?SettingsStatus $settings_status = null
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->state = $state;
|
||||
$this->settings = $settings;
|
||||
$this->is_payments_page = $is_payments_page;
|
||||
$this->is_ppcp_settings_page = $is_ppcp_settings_page;
|
||||
$this->settings_status = $settings_status;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,8 +97,25 @@ class GatewayWithoutPayPalAdminNotice {
|
|||
* @return Message|null
|
||||
*/
|
||||
public function message(): ?Message {
|
||||
if ( ! $this->should_display() ) {
|
||||
return null;
|
||||
$notice_type = $this->check();
|
||||
|
||||
switch ( $notice_type ) {
|
||||
case self::NOTICE_DISABLED_GATEWAY:
|
||||
/* translators: %1$s the gateway name, %2$s URL. */
|
||||
$text = __(
|
||||
'%1$s cannot be used without the PayPal gateway. <a href="%2$s">Enable the PayPal gateway</a>.',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
break;
|
||||
case self::NOTICE_DISABLED_LOCATION:
|
||||
/* translators: %1$s the gateway name, %2$s URL. */
|
||||
$text = __(
|
||||
'%1$s cannot be used without enabling the Checkout location for the PayPal gateway. <a href="%2$s">Enable the Checkout location</a>.',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
$gateway = $this->get_gateway();
|
||||
|
@ -94,11 +126,7 @@ class GatewayWithoutPayPalAdminNotice {
|
|||
$name = $gateway->get_method_title();
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %1$s the gateway name, %2$s URL. */
|
||||
__(
|
||||
'%1$s cannot be used without the PayPal gateway. <a href="%2$s">Enable the PayPal gateway</a>.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$text,
|
||||
$name,
|
||||
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' )
|
||||
);
|
||||
|
@ -106,22 +134,33 @@ class GatewayWithoutPayPalAdminNotice {
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether the message should be displayed.
|
||||
* Checks whether one of the messages should be displayed.
|
||||
*
|
||||
* @return bool
|
||||
* @return string One of the NOTICE_* constants.
|
||||
*/
|
||||
protected function should_display(): bool {
|
||||
protected function check(): string {
|
||||
if ( State::STATE_ONBOARDED !== $this->state->current_state() ||
|
||||
( ! $this->is_payments_page && ! $this->is_ppcp_settings_page ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
|
||||
return false;
|
||||
return self::NOTICE_OK;
|
||||
}
|
||||
|
||||
$gateway = $this->get_gateway();
|
||||
$gateway = $this->get_gateway();
|
||||
$gateway_enabled = $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
|
||||
|
||||
return $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
|
||||
if ( ! $gateway_enabled ) {
|
||||
return self::NOTICE_OK;
|
||||
}
|
||||
|
||||
$paypal_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
|
||||
if ( ! $paypal_enabled ) {
|
||||
return self::NOTICE_DISABLED_GATEWAY;
|
||||
}
|
||||
|
||||
if ( $this->settings_status && ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) ) {
|
||||
return self::NOTICE_DISABLED_LOCATION;
|
||||
}
|
||||
|
||||
return self::NOTICE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* Registers the admin message about unsupported currency set in WC shop settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcGateway\Notice
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Notice;
|
||||
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class UnsupportedCurrencyAdminNotice
|
||||
*/
|
||||
class UnsupportedCurrencyAdminNotice {
|
||||
|
||||
/**
|
||||
* The state.
|
||||
*
|
||||
* @var State
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* The supported currencies.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_currencies;
|
||||
|
||||
/**
|
||||
* The shop currency.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $shop_currency;
|
||||
|
||||
/**
|
||||
* UnsupportedCurrencyAdminNotice constructor.
|
||||
*
|
||||
* @param State $state The state.
|
||||
* @param string $shop_currency The shop currency.
|
||||
* @param array $supported_currencies The supported currencies.
|
||||
*/
|
||||
public function __construct( State $state, string $shop_currency, array $supported_currencies ) {
|
||||
$this->state = $state;
|
||||
$this->shop_currency = $shop_currency;
|
||||
$this->supported_currencies = $supported_currencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message.
|
||||
*
|
||||
* @return Message|null
|
||||
*/
|
||||
public function unsupported_currency_message() {
|
||||
if ( ! $this->should_display() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %1$s the shop currency, 2$s the gateway name. */
|
||||
__(
|
||||
'Attention: Your current WooCommerce store currency (%1$s) is not supported by PayPal. Please update your store currency to one that is supported by PayPal to ensure smooth transactions. Visit the <a href="%2$s">PayPal currency support page</a> for more information on supported currencies.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$this->shop_currency,
|
||||
'https://developer.paypal.com/api/rest/reference/currency-codes/'
|
||||
);
|
||||
return new Message( $message, 'warning' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the message should display.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_display(): bool {
|
||||
return $this->state->current_state() === State::STATE_ONBOARDED && ! $this->currency_supported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the currency is supported by PayPal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function currency_supported(): bool {
|
||||
$currency = $this->shop_currency;
|
||||
$supported_currencies = $this->supported_currencies;
|
||||
return in_array( $currency, $supported_currencies, true );
|
||||
}
|
||||
}
|
|
@ -166,7 +166,7 @@ class OrderProcessor {
|
|||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ?: wc_clean( wp_unslash( $_POST['paypal_order_id'] ?? '' ) );
|
||||
$order = $this->session_handler->order();
|
||||
if ( ! $order && is_string( $order_id ) ) {
|
||||
if ( ! $order && is_string( $order_id ) && $order_id ) {
|
||||
$order = $this->order_endpoint->order( $order_id );
|
||||
}
|
||||
if ( ! $order ) {
|
||||
|
@ -178,7 +178,7 @@ class OrderProcessor {
|
|||
$wc_order->get_id()
|
||||
)
|
||||
);
|
||||
$this->last_error = __( 'Could not retrieve order. This browser may not be supported. Please try again with a different browser.', 'woocommerce-paypal-payments' );
|
||||
$this->last_error = __( 'Could not retrieve order. Maybe it was already completed or this browser is not supported. Please check your email or try again with a different browser.', 'woocommerce-paypal-payments' );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,11 +33,12 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
|
||||
$selected_country = $container->get( 'api.shop.country' );
|
||||
$default_messaging_flex_color = $selected_country === 'US' ? 'white-no-border' : 'white';
|
||||
|
||||
$render_preview_element = function ( string $id, string $type ): string {
|
||||
$button_message = __( 'Pay Later Button Preview', 'woocommerce-paypal-payments' );
|
||||
$messaging_message = __( 'Pay Later Messaging Preview', 'woocommerce-paypal-payments' );
|
||||
$render_preview_element = function ( string $id, string $type, string $message ): string {
|
||||
return '
|
||||
<div class="ppcp-preview ppcp-' . $type . '-preview pay-later">
|
||||
<h4>' . __( 'Preview', 'woocommerce-paypal-payments' ) . '</h4>
|
||||
<h4>' . $message . '</h4>
|
||||
<div id="' . $id . '" class="ppcp-' . $type . '-preview-inner"></div>
|
||||
</div>';
|
||||
};
|
||||
|
@ -82,7 +83,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
),
|
||||
'pay_later_button_preview' => array(
|
||||
'type' => 'ppcp-text',
|
||||
'text' => $render_preview_element( 'ppcpPayLaterButtonPreview', 'button' ),
|
||||
'text' => $render_preview_element( 'ppcpPayLaterButtonPreview', 'button', $button_message ),
|
||||
'screens' => array( State::STATE_ONBOARDED ),
|
||||
'requirements' => array( 'messages' ),
|
||||
'gateway' => Settings::PAY_LATER_TAB_ID,
|
||||
|
@ -245,7 +246,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
),
|
||||
'pay_later_general_message_preview' => array(
|
||||
'type' => 'ppcp-text',
|
||||
'text' => $render_preview_element( 'ppcpGeneralMessagePreview', 'message' ),
|
||||
'text' => $render_preview_element( 'ppcpGeneralMessagePreview', 'message', $messaging_message ),
|
||||
'screens' => array( State::STATE_ONBOARDED ),
|
||||
'requirements' => array( 'messages' ),
|
||||
'gateway' => Settings::PAY_LATER_TAB_ID,
|
||||
|
@ -369,7 +370,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
),
|
||||
'pay_later_product_message_preview' => array(
|
||||
'type' => 'ppcp-text',
|
||||
'text' => $render_preview_element( 'ppcpProductMessagePreview', 'message' ),
|
||||
'text' => $render_preview_element( 'ppcpProductMessagePreview', 'message', $messaging_message ),
|
||||
'screens' => array( State::STATE_ONBOARDED ),
|
||||
'requirements' => array( 'messages' ),
|
||||
'gateway' => Settings::PAY_LATER_TAB_ID,
|
||||
|
@ -493,7 +494,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
),
|
||||
'pay_later_cart_message_preview' => array(
|
||||
'type' => 'ppcp-text',
|
||||
'text' => $render_preview_element( 'ppcpCartMessagePreview', 'message' ),
|
||||
'text' => $render_preview_element( 'ppcpCartMessagePreview', 'message', $messaging_message ),
|
||||
'screens' => array( State::STATE_ONBOARDED ),
|
||||
'requirements' => array( 'messages' ),
|
||||
'gateway' => Settings::PAY_LATER_TAB_ID,
|
||||
|
@ -617,7 +618,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
),
|
||||
'pay_later_checkout_message_preview' => array(
|
||||
'type' => 'ppcp-text',
|
||||
'text' => $render_preview_element( 'ppcpCheckoutMessagePreview', 'message' ),
|
||||
'text' => $render_preview_element( 'ppcpCheckoutMessagePreview', 'message', $messaging_message ),
|
||||
'screens' => array( State::STATE_ONBOARDED ),
|
||||
'requirements' => array( 'messages' ),
|
||||
'gateway' => Settings::PAY_LATER_TAB_ID,
|
||||
|
|
|
@ -31,7 +31,7 @@ return function ( ContainerInterface $container, array $fields ): array {
|
|||
$render_preview_element = function ( string $id ): string {
|
||||
return '
|
||||
<div class="ppcp-preview ppcp-button-preview">
|
||||
<h4>' . __( 'Preview', 'woocommerce-paypal-payments' ) . '</h4>
|
||||
<h4>' . __( 'Button Styling Preview', 'woocommerce-paypal-payments' ) . '</h4>
|
||||
<div id="' . $id . '" class="ppcp-button-preview-inner"></div>
|
||||
</div>';
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\HeaderRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
|
||||
|
@ -197,6 +198,13 @@ class WCGatewayModule implements ModuleInterface {
|
|||
$notices[] = $connect_message;
|
||||
}
|
||||
|
||||
$notice = $c->get( 'wcgateway.notice.currency-unsupported' );
|
||||
assert( $notice instanceof UnsupportedCurrencyAdminNotice );
|
||||
$unsupported_currency_message = $notice->unsupported_currency_message();
|
||||
if ( $unsupported_currency_message ) {
|
||||
$notices[] = $unsupported_currency_message;
|
||||
}
|
||||
|
||||
foreach ( array(
|
||||
$c->get( 'wcgateway.notice.dcc-without-paypal' ),
|
||||
$c->get( 'wcgateway.notice.card-button-without-paypal' ),
|
||||
|
@ -278,6 +286,15 @@ class WCGatewayModule implements ModuleInterface {
|
|||
$settings->set( 'products_dcc_enabled', false );
|
||||
$settings->set( 'products_pui_enabled', false );
|
||||
$settings->persist();
|
||||
|
||||
// Update caches.
|
||||
$dcc_status = $c->get( 'wcgateway.helper.dcc-product-status' );
|
||||
assert( $dcc_status instanceof DCCProductStatus );
|
||||
$dcc_status->dcc_is_active();
|
||||
|
||||
$pui_status = $c->get( 'wcgateway.pay-upon-invoice-product-status' );
|
||||
assert( $pui_status instanceof PayUponInvoiceProductStatus );
|
||||
$pui_status->pui_is_active();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -615,7 +632,7 @@ class WCGatewayModule implements ModuleInterface {
|
|||
* @var OrderTablePaymentStatusColumn $payment_status_column
|
||||
*/
|
||||
$payment_status_column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
|
||||
$payment_status_column->render( $column, intval( $wc_order_id ) );
|
||||
$payment_status_column->render( (string) $column, intval( $wc_order_id ) );
|
||||
},
|
||||
10,
|
||||
2
|
||||
|
|
|
@ -82,12 +82,18 @@ return array(
|
|||
$payment_token_factory = $container->get( 'vaulting.payment-token-factory' );
|
||||
|
||||
return array(
|
||||
new CheckoutOrderApproved( $logger, $prefix, $order_endpoint ),
|
||||
new CheckoutOrderCompleted( $logger, $prefix ),
|
||||
new CheckoutOrderApproved(
|
||||
$logger,
|
||||
$order_endpoint,
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wcgateway.funding-source.renderer' ),
|
||||
$container->get( 'wcgateway.order-processor' )
|
||||
),
|
||||
new CheckoutOrderCompleted( $logger ),
|
||||
new CheckoutPaymentApprovalReversed( $logger ),
|
||||
new PaymentCaptureRefunded( $logger, $prefix ),
|
||||
new PaymentCaptureReversed( $logger, $prefix ),
|
||||
new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ),
|
||||
new PaymentCaptureRefunded( $logger ),
|
||||
new PaymentCaptureReversed( $logger ),
|
||||
new PaymentCaptureCompleted( $logger, $order_endpoint ),
|
||||
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory ),
|
||||
new VaultPaymentTokenDeleted( $logger ),
|
||||
new PaymentCapturePending( $logger ),
|
||||
|
|
18
modules/ppcp-webhooks/src/CustomIds.php
Normal file
18
modules/ppcp-webhooks/src/CustomIds.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
/**
|
||||
* The constants for handling custom_id in the webhook requests.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Webhooks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks;
|
||||
|
||||
/**
|
||||
* Interface CustomIds
|
||||
*/
|
||||
interface CustomIds {
|
||||
|
||||
public const CUSTOMER_ID_PREFIX = 'pcp_customer_';
|
||||
}
|
|
@ -17,6 +17,7 @@ use WP_REST_Response;
|
|||
* Class BillingPlanPricingChangeActivated
|
||||
*/
|
||||
class BillingPlanPricingChangeActivated implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -64,9 +65,8 @@ class BillingPlanPricingChangeActivated implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$plan_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
|
@ -92,7 +92,6 @@ class BillingPlanPricingChangeActivated implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use WP_REST_Response;
|
|||
* Class BillingPlanUpdated
|
||||
*/
|
||||
class BillingPlanUpdated implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -64,9 +65,8 @@ class BillingPlanUpdated implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$plan_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
|
@ -109,7 +109,6 @@ class BillingPlanUpdated implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use WP_REST_Response;
|
|||
* Class BillingSubscriptionCancelled
|
||||
*/
|
||||
class BillingSubscriptionCancelled implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -64,9 +65,8 @@ class BillingSubscriptionCancelled implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$subscription_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
|
@ -87,7 +87,6 @@ class BillingSubscriptionCancelled implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use WP_REST_Response;
|
|||
* Class CatalogProductUpdated
|
||||
*/
|
||||
class CatalogProductUpdated implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -64,9 +65,8 @@ class CatalogProductUpdated implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$product_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
|
@ -104,7 +104,6 @@ class CatalogProductUpdated implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,25 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
use WC_Checkout;
|
||||
use WC_Order;
|
||||
use WC_Session_Handler;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Session\MemoryWcSession;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
|
||||
/**
|
||||
* Class CheckoutOrderApproved
|
||||
*/
|
||||
class CheckoutOrderApproved implements RequestHandler {
|
||||
|
||||
use PrefixTrait;
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -36,17 +43,48 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* The funding source renderer.
|
||||
*
|
||||
* @var FundingSourceRenderer
|
||||
*/
|
||||
protected $funding_source_renderer;
|
||||
|
||||
/**
|
||||
* The processor for orders.
|
||||
*
|
||||
* @var OrderProcessor
|
||||
*/
|
||||
protected $order_processor;
|
||||
|
||||
/**
|
||||
* CheckoutOrderApproved constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $prefix The prefix.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
|
||||
* @param OrderProcessor $order_processor The Order Processor.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger, string $prefix, OrderEndpoint $order_endpoint ) {
|
||||
$this->logger = $logger;
|
||||
$this->prefix = $prefix;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
OrderEndpoint $order_endpoint,
|
||||
SessionHandler $session_handler,
|
||||
FundingSourceRenderer $funding_source_renderer,
|
||||
OrderProcessor $order_processor
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->funding_source_renderer = $funding_source_renderer;
|
||||
$this->order_processor = $order_processor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,114 +117,94 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
$custom_ids = array_filter(
|
||||
array_map(
|
||||
static function ( array $purchase_unit ): string {
|
||||
return isset( $purchase_unit['custom_id'] ) ?
|
||||
(string) $purchase_unit['custom_id'] : '';
|
||||
},
|
||||
isset( $request['resource']['purchase_units'] ) ?
|
||||
(array) $request['resource']['purchase_units'] : array()
|
||||
),
|
||||
static function ( string $order_id ): bool {
|
||||
return ! empty( $order_id );
|
||||
}
|
||||
);
|
||||
|
||||
if ( empty( $custom_ids ) ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'No order for webhook event %s was found.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
$order_id = isset( $request['resource']['id'] ) ? $request['resource']['id'] : null;
|
||||
if ( ! $order_id ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'No order ID in webhook event %s.',
|
||||
$request['id'] ?: ''
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
try {
|
||||
$order = isset( $request['resource']['id'] ) ?
|
||||
$this->order_endpoint->order( $request['resource']['id'] ) : null;
|
||||
if ( ! $order ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'No paypal payment for webhook event %s was found.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
$order = $this->order_endpoint->order( $order_id );
|
||||
|
||||
$wc_orders = array();
|
||||
|
||||
$wc_order_ids = $this->get_wc_order_ids_from_request( $request );
|
||||
if ( empty( $wc_order_ids ) ) {
|
||||
$customer_ids = $this->get_wc_customer_ids_from_request( $request );
|
||||
if ( empty( $customer_ids ) ) {
|
||||
return $this->no_custom_ids_response( $request );
|
||||
}
|
||||
|
||||
$customer_id = $customer_ids[0];
|
||||
|
||||
if ( $order->status()->is( OrderStatus::COMPLETED ) ) {
|
||||
$this->logger->info( "Order {$order->id()} already completed." );
|
||||
return $this->success_response();
|
||||
}
|
||||
|
||||
$wc_session = new WC_Session_Handler();
|
||||
|
||||
$session_data = $wc_session->get_session( $customer_id );
|
||||
if ( ! is_array( $session_data ) ) {
|
||||
return $this->failure_response( "Failed to get session data {$customer_id}" );
|
||||
}
|
||||
|
||||
MemoryWcSession::replace_session_handler( $session_data, $customer_id );
|
||||
|
||||
wc_load_cart();
|
||||
WC()->cart->get_cart_from_session();
|
||||
WC()->cart->calculate_shipping();
|
||||
|
||||
$form = $this->session_handler->checkout_form();
|
||||
|
||||
$checkout = new WC_Checkout();
|
||||
$wc_order_id = $checkout->create_order( $form );
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( ! $wc_order instanceof WC_Order ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'Failed to create WC order in webhook event %s.',
|
||||
$request['id'] ?: ''
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
if ( $order->intent() === 'CAPTURE' ) {
|
||||
$order = $this->order_endpoint->capture( $order );
|
||||
$funding_source = $this->session_handler->funding_source();
|
||||
if ( $funding_source ) {
|
||||
$wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
|
||||
}
|
||||
} catch ( RuntimeException $error ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'Could not capture payment for webhook event %s.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
$wc_order_ids = array_map(
|
||||
array(
|
||||
$this,
|
||||
'sanitize_custom_id',
|
||||
),
|
||||
$custom_ids
|
||||
);
|
||||
$args = array(
|
||||
'post__in' => $wc_order_ids,
|
||||
'limit' => -1,
|
||||
);
|
||||
$wc_orders = wc_get_orders( $args );
|
||||
if ( ! $wc_orders ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal order Id.
|
||||
__( 'Order for PayPal order %s not found.', 'woocommerce-paypal-payments' ),
|
||||
isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
|
||||
if ( is_numeric( $customer_id ) ) {
|
||||
$wc_order->set_customer_id( (int) $customer_id );
|
||||
}
|
||||
|
||||
$wc_order->save();
|
||||
|
||||
$wc_orders[] = $wc_order;
|
||||
|
||||
add_action(
|
||||
'shutdown',
|
||||
function () use ( $customer_id ): void {
|
||||
$session = WC()->session;
|
||||
assert( $session instanceof WC_Session_Handler );
|
||||
|
||||
/**
|
||||
* Wrong type-hint.
|
||||
*
|
||||
* @psalm-suppress InvalidScalarArgument
|
||||
*/
|
||||
$session->delete_session( $customer_id );
|
||||
$session->forget_session();
|
||||
}
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
} else {
|
||||
$wc_orders = $this->get_wc_orders_from_custom_ids( $wc_order_ids );
|
||||
if ( ! $wc_orders ) {
|
||||
return $this->no_wc_orders_response( $request );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $wc_orders as $wc_order ) {
|
||||
|
@ -197,31 +215,24 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( $order->intent() === 'CAPTURE' ) {
|
||||
$wc_order->payment_complete();
|
||||
} else {
|
||||
$wc_order->update_status(
|
||||
'on-hold',
|
||||
__( 'Payment can be captured.', 'woocommerce-paypal-payments' )
|
||||
|
||||
if ( ! $this->order_processor->process( $wc_order ) ) {
|
||||
return $this->failure_response(
|
||||
sprintf(
|
||||
'Failed to process WC order %s: %s.',
|
||||
(string) $wc_order->get_id(),
|
||||
$this->order_processor->last_error()
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->logger->log(
|
||||
'info',
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
// translators: %s is the order ID.
|
||||
__(
|
||||
'Order %s has been updated through PayPal',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'WC order %s has been processed after approval in PayPal.',
|
||||
(string) $wc_order->get_id()
|
||||
),
|
||||
array(
|
||||
'request' => $request,
|
||||
'order' => $wc_order,
|
||||
)
|
||||
);
|
||||
}
|
||||
$response['success'] = true;
|
||||
return rest_ensure_response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class CheckoutOrderCompleted implements RequestHandler {
|
||||
|
||||
use PrefixTrait, RequestHandlerTrait;
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -32,11 +32,9 @@ class CheckoutOrderCompleted implements RequestHandler {
|
|||
* CheckoutOrderCompleted constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $prefix The prefix.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger, string $prefix ) {
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,16 +67,14 @@ class CheckoutOrderCompleted implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
|
||||
$custom_ids = $this->get_custom_ids_from_request( $request );
|
||||
$custom_ids = $this->get_wc_order_ids_from_request( $request );
|
||||
if ( empty( $custom_ids ) ) {
|
||||
return $this->no_custom_ids_from_request( $request, $response );
|
||||
return $this->no_custom_ids_response( $request );
|
||||
}
|
||||
|
||||
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
|
||||
if ( ! $wc_orders ) {
|
||||
return $this->no_wc_orders_from_custom_ids( $request, $response );
|
||||
return $this->no_wc_orders_response( $request );
|
||||
}
|
||||
|
||||
foreach ( $wc_orders as $wc_order ) {
|
||||
|
@ -93,17 +89,12 @@ class CheckoutOrderCompleted implements RequestHandler {
|
|||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
// translators: %s is the order ID.
|
||||
__(
|
||||
'Order %s has been updated through PayPal',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'Order %s has been updated through PayPal',
|
||||
(string) $wc_order->get_id()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class CheckoutPaymentApprovalReversed implements RequestHandler {
|
||||
|
||||
use RequestHandlerTrait, PrefixTrait;
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -66,26 +66,20 @@ class CheckoutPaymentApprovalReversed implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
|
||||
$custom_ids = $this->get_custom_ids_from_request( $request );
|
||||
$custom_ids = $this->get_wc_order_ids_from_request( $request );
|
||||
if ( empty( $custom_ids ) ) {
|
||||
return $this->no_custom_ids_from_request( $request, $response );
|
||||
return $this->no_custom_ids_response( $request );
|
||||
}
|
||||
|
||||
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
|
||||
if ( ! $wc_orders ) {
|
||||
return $this->no_wc_orders_from_custom_ids( $request, $response );
|
||||
return $this->no_wc_orders_response( $request );
|
||||
}
|
||||
|
||||
foreach ( $wc_orders as $wc_order ) {
|
||||
if ( in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
|
||||
$error_message = sprintf(
|
||||
// translators: %1$s is the order id.
|
||||
__(
|
||||
'Failed to capture order %1$s through PayPal.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'Failed to capture order %1$s through PayPal.',
|
||||
(string) $wc_order->get_id()
|
||||
);
|
||||
|
||||
|
@ -95,7 +89,6 @@ class CheckoutPaymentApprovalReversed implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class PaymentCaptureCompleted implements RequestHandler {
|
||||
|
||||
use PrefixTrait, TransactionIdHandlingTrait;
|
||||
use TransactionIdHandlingTrait, RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -42,16 +42,13 @@ class PaymentCaptureCompleted implements RequestHandler {
|
|||
* PaymentCaptureCompleted constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $prefix The prefix.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
string $prefix,
|
||||
OrderEndpoint $order_endpoint
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->prefix = $prefix;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
}
|
||||
|
||||
|
@ -83,33 +80,24 @@ class PaymentCaptureCompleted implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( \WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
|
||||
$webhook_id = (string) ( $request['id'] ?? '' );
|
||||
|
||||
$resource = $request['resource'];
|
||||
if ( ! is_array( $resource ) ) {
|
||||
$message = 'Resource data not found in webhook request.';
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_order_id = isset( $resource['custom_id'] ) ?
|
||||
$this->sanitize_custom_id( (string) $resource['custom_id'] ) : 0;
|
||||
$wc_order_id = isset( $resource['custom_id'] ) ? (string) $resource['custom_id'] : 0;
|
||||
if ( ! $wc_order_id ) {
|
||||
$message = sprintf( 'No order for webhook event %s was found.', $webhook_id );
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
|
||||
$message = sprintf( 'No order for webhook event %s was found.', $webhook_id );
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$order_id = $resource['supplementary_data']['related_ids']['order_id'] ?? null;
|
||||
|
@ -120,8 +108,7 @@ class PaymentCaptureCompleted implements RequestHandler {
|
|||
do_action( 'ppcp_payment_capture_completed_webhook_handler', $wc_order, $order_id );
|
||||
|
||||
if ( $wc_order->get_status() !== 'on-hold' ) {
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
$wc_order->add_order_note(
|
||||
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
|
||||
|
@ -130,19 +117,10 @@ class PaymentCaptureCompleted implements RequestHandler {
|
|||
$wc_order->payment_complete();
|
||||
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' );
|
||||
$wc_order->save();
|
||||
$this->logger->log(
|
||||
'info',
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
// translators: %s is the order ID.
|
||||
__(
|
||||
'Order %s has been updated through PayPal',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'Order %s has been updated through PayPal',
|
||||
(string) $wc_order->get_id()
|
||||
),
|
||||
array(
|
||||
'request' => $request,
|
||||
'order' => $wc_order,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -159,7 +137,6 @@ class PaymentCaptureCompleted implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class PaymentCapturePending implements RequestHandler {
|
||||
|
||||
use PrefixTrait;
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -66,36 +66,21 @@ class PaymentCapturePending implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
$order_id = $request['resource'] !== null && isset( $request['resource']['custom_id'] )
|
||||
? $this->sanitize_custom_id( $request['resource']['custom_id'] )
|
||||
? $request['resource']['custom_id']
|
||||
: 0;
|
||||
if ( ! $order_id ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'No order for webhook event %s was found.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'No order for webhook event %s was found.',
|
||||
$request['id'] !== null && isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$resource = $request['resource'];
|
||||
if ( ! is_array( $resource ) ) {
|
||||
$message = 'Resource data not found in webhook request.';
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $order_id );
|
||||
|
@ -105,10 +90,7 @@ class PaymentCapturePending implements RequestHandler {
|
|||
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
|
||||
);
|
||||
|
||||
$this->logger->warning( $message );
|
||||
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
if ( $wc_order->get_status() === 'pending' ) {
|
||||
|
@ -116,7 +98,6 @@ class PaymentCapturePending implements RequestHandler {
|
|||
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,10 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
|
@ -20,7 +22,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class PaymentCaptureRefunded implements RequestHandler {
|
||||
|
||||
use PrefixTrait, TransactionIdHandlingTrait, RefundMetaTrait;
|
||||
use TransactionIdHandlingTrait, RefundMetaTrait, RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -33,11 +35,9 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
* PaymentCaptureRefunded constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $prefix The prefix.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger, string $prefix ) {
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,59 +68,32 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
$order_id = isset( $request['resource']['custom_id'] ) ?
|
||||
$this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
|
||||
$request['resource']['custom_id'] : 0;
|
||||
$refund_id = (string) ( $request['resource']['id'] ?? '' );
|
||||
if ( ! $order_id ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'No order for webhook event %s was found.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'No order for webhook event %s was found.',
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $order_id );
|
||||
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
|
||||
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal refund Id.
|
||||
__( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ),
|
||||
'Order for PayPal refund %s not found.',
|
||||
$refund_id
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$already_added_refunds = $this->get_refunds_meta( $wc_order );
|
||||
if ( in_array( $refund_id, $already_added_refunds, true ) ) {
|
||||
$this->logger->info( "Refund {$refund_id} is already handled." );
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
|
||||
/**
|
||||
* The WooCommerce order.
|
||||
*
|
||||
* @var \WC_Order $wc_order
|
||||
*/
|
||||
$refund = wc_create_refund(
|
||||
array(
|
||||
'order_id' => $wc_order->get_id(),
|
||||
|
@ -128,37 +101,21 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
)
|
||||
);
|
||||
if ( is_wp_error( $refund ) ) {
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
sprintf(
|
||||
// translators: %s is the order id.
|
||||
__( 'Order %s could not be refunded', 'woocommerce-paypal-payments' ),
|
||||
(string) $wc_order->get_id()
|
||||
),
|
||||
array(
|
||||
'request' => $request,
|
||||
'error' => $refund,
|
||||
)
|
||||
assert( $refund instanceof WP_Error );
|
||||
$message = sprintf(
|
||||
'Order %1$s could not be refunded. %2$s',
|
||||
(string) $wc_order->get_id(),
|
||||
$refund->get_error_message()
|
||||
);
|
||||
|
||||
$response['message'] = $refund->get_error_message();
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$this->logger->log(
|
||||
'info',
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
// translators: %1$s is the order id %2$s is the amount which has been refunded.
|
||||
__(
|
||||
'Order %1$s has been refunded with %2$s through PayPal',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'Order %1$s has been refunded with %2$s through PayPal',
|
||||
(string) $wc_order->get_id(),
|
||||
(string) $refund->get_amount()
|
||||
),
|
||||
array(
|
||||
'request' => $request,
|
||||
'order' => $wc_order,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -167,7 +124,6 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
$this->add_refund_to_meta( $wc_order, $refund_id );
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use Psr\Log\LoggerInterface;
|
|||
*/
|
||||
class PaymentCaptureReversed implements RequestHandler {
|
||||
|
||||
use PrefixTrait;
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -32,11 +32,9 @@ class PaymentCaptureReversed implements RequestHandler {
|
|||
* PaymentCaptureReversed constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $prefix The prefix.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger, string $prefix ) {
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,45 +69,23 @@ class PaymentCaptureReversed implements RequestHandler {
|
|||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
$order_id = isset( $request['resource']['custom_id'] ) ?
|
||||
$this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
|
||||
$request['resource']['custom_id'] : 0;
|
||||
if ( ! $order_id ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__(
|
||||
'No order for webhook event %s was found.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'No order for webhook event %s was found.',
|
||||
isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $order_id );
|
||||
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal refund Id.
|
||||
__( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ),
|
||||
'Order for PayPal refund %s not found.',
|
||||
isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response['message'] = $message;
|
||||
return rest_ensure_response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,33 +93,20 @@ class PaymentCaptureReversed implements RequestHandler {
|
|||
*/
|
||||
$note = apply_filters( 'ppcp_payment_capture_reversed_webhook_update_status_note', '', $wc_order, $request['event_type'] );
|
||||
|
||||
/**
|
||||
* The WooCommerce order.
|
||||
*
|
||||
* @var \WC_Order $wc_order
|
||||
*/
|
||||
$response['success'] = (bool) $wc_order->update_status( 'cancelled', $note );
|
||||
$is_success = $wc_order->update_status( 'cancelled', $note );
|
||||
if ( ! $is_success ) {
|
||||
$message = sprintf(
|
||||
'Failed to cancel order %1$s cancelled through PayPal',
|
||||
(string) $wc_order->get_id()
|
||||
);
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$message = $response['success'] ? sprintf(
|
||||
// translators: %1$s is the order id.
|
||||
__(
|
||||
'Order %1$s has been cancelled through PayPal',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
(string) $wc_order->get_id()
|
||||
) : sprintf(
|
||||
// translators: %1$s is the order id.
|
||||
__( 'Failed to cancel order %1$s through PayPal', 'woocommerce-paypal-payments' ),
|
||||
$message = sprintf(
|
||||
'Order %1$s has been cancelled through PayPal',
|
||||
(string) $wc_order->get_id()
|
||||
);
|
||||
$this->logger->log(
|
||||
$response['success'] ? 'info' : 'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
'order' => $wc_order,
|
||||
)
|
||||
);
|
||||
return rest_ensure_response( $response );
|
||||
$this->logger->info( $message );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class PaymentSaleCompleted implements RequestHandler {
|
||||
|
||||
use TransactionIdHandlingTrait;
|
||||
use TransactionIdHandlingTrait, RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -67,17 +67,14 @@ class PaymentSaleCompleted implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$billing_agreement_id = wc_clean( wp_unslash( $request['resource']['billing_agreement_id'] ?? '' ) );
|
||||
if ( ! $billing_agreement_id ) {
|
||||
$message = 'Could not retrieve billing agreement id for subscription.';
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$args = array(
|
||||
|
@ -99,7 +96,6 @@ class PaymentSaleCompleted implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use WP_REST_Response;
|
|||
*/
|
||||
class PaymentSaleRefunded implements RequestHandler {
|
||||
|
||||
use TransactionIdHandlingTrait, RefundMetaTrait;
|
||||
use TransactionIdHandlingTrait, RefundMetaTrait, RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -68,16 +68,15 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
if ( is_null( $request['resource'] ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$refund_id = (string) ( $request['resource']['id'] ?? '' );
|
||||
$transaction_id = $request['resource']['sale_id'] ?? '';
|
||||
$total_refunded_amount = $request['resource']['total_refunded_amount']['value'] ?? '';
|
||||
if ( ! $refund_id || ! $transaction_id || ! $total_refunded_amount ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
$args = array(
|
||||
|
@ -90,7 +89,7 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
$wc_orders = wc_get_orders( $args );
|
||||
|
||||
if ( ! is_array( $wc_orders ) ) {
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response();
|
||||
}
|
||||
|
||||
foreach ( $wc_orders as $wc_order ) {
|
||||
|
@ -102,24 +101,17 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
);
|
||||
|
||||
if ( $refund instanceof WP_Error ) {
|
||||
$this->logger->warning(
|
||||
sprintf(
|
||||
// translators: %s is the order id.
|
||||
__( 'Order %s could not be refunded', 'woocommerce-paypal-payments' ),
|
||||
(string) $wc_order->get_id()
|
||||
)
|
||||
$message = sprintf(
|
||||
'Order %s could not be refunded. %s',
|
||||
(string) $wc_order->get_id(),
|
||||
$refund->get_error_message()
|
||||
);
|
||||
|
||||
$response['message'] = $refund->get_error_message();
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$order_refunded_message = sprintf(
|
||||
// translators: %1$s is the order id %2$s is the amount which has been refunded.
|
||||
__(
|
||||
'Order %1$s has been refunded with %2$s through PayPal.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'Order %1$s has been refunded with %2$s through PayPal.',
|
||||
(string) $wc_order->get_id(),
|
||||
(string) $total_refunded_amount
|
||||
);
|
||||
|
@ -130,7 +122,6 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
$this->add_refund_to_meta( $wc_order, $refund_id );
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Trait which helps to remove the prefix of IDs.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Webhooks\Handler
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
/**
|
||||
* Trait PrefixTrait
|
||||
*/
|
||||
trait PrefixTrait {
|
||||
|
||||
|
||||
/**
|
||||
* The prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix = '';
|
||||
|
||||
/**
|
||||
* Removes the prefix from a given Id.
|
||||
*
|
||||
* @param string $custom_id The custom id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function sanitize_custom_id( string $custom_id ): int {
|
||||
|
||||
$id = $custom_id;
|
||||
if ( strlen( $this->prefix ) > 0 && 0 === strpos( $id, $this->prefix ) ) {
|
||||
$id = substr( $id, strlen( $this->prefix ) );
|
||||
}
|
||||
return (int) $id;
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
use stdClass;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
|
@ -19,22 +19,78 @@ trait RequestHandlerTrait {
|
|||
/**
|
||||
* Get available custom ids from the given request
|
||||
*
|
||||
* @param \WP_REST_Request $request The request.
|
||||
* @return array
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_custom_ids_from_request( WP_REST_Request $request ): array {
|
||||
return array_filter(
|
||||
array_map(
|
||||
$resource = $request['resource'];
|
||||
if ( ! is_array( $resource ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$ids = array();
|
||||
if ( isset( $resource['custom_id'] ) && ! empty( $resource['custom_id'] ) ) {
|
||||
$ids[] = $resource['custom_id'];
|
||||
} elseif ( isset( $resource['purchase_units'] ) ) {
|
||||
$ids = array_map(
|
||||
static function ( array $purchase_unit ): string {
|
||||
return isset( $purchase_unit['custom_id'] ) ?
|
||||
(string) $purchase_unit['custom_id'] : '';
|
||||
return $purchase_unit['custom_id'] ?? '';
|
||||
},
|
||||
$request['resource'] !== null && isset( $request['resource']['purchase_units'] ) ?
|
||||
(array) $request['resource']['purchase_units'] : array()
|
||||
),
|
||||
static function ( string $order_id ): bool {
|
||||
return ! empty( $order_id );
|
||||
}
|
||||
(array) $resource['purchase_units']
|
||||
);
|
||||
}
|
||||
|
||||
return array_values(
|
||||
array_filter(
|
||||
$ids,
|
||||
function ( string $id ): bool {
|
||||
return ! empty( $id );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available WC order ids from the given request.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_wc_order_ids_from_request( WP_REST_Request $request ): array {
|
||||
$ids = $this->get_custom_ids_from_request( $request );
|
||||
|
||||
return array_values(
|
||||
array_filter(
|
||||
$ids,
|
||||
function ( string $id ): bool {
|
||||
return strpos( $id, CustomIds::CUSTOMER_ID_PREFIX ) === false;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available WC customer ids from the given request.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_wc_customer_ids_from_request( WP_REST_Request $request ): array {
|
||||
$ids = $this->get_custom_ids_from_request( $request );
|
||||
|
||||
$customer_ids = array_values(
|
||||
array_filter(
|
||||
$ids,
|
||||
function ( string $id ): bool {
|
||||
return strpos( $id, CustomIds::CUSTOMER_ID_PREFIX ) === 0;
|
||||
}
|
||||
)
|
||||
);
|
||||
return array_map(
|
||||
function ( string $str ): string {
|
||||
return (string) substr( $str, strlen( CustomIds::CUSTOMER_ID_PREFIX ) );
|
||||
},
|
||||
$customer_ids
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,13 +101,7 @@ trait RequestHandlerTrait {
|
|||
* @return WC_Order[]
|
||||
*/
|
||||
protected function get_wc_orders_from_custom_ids( array $custom_ids ): array {
|
||||
$order_ids = array_map(
|
||||
array(
|
||||
$this,
|
||||
'sanitize_custom_id',
|
||||
),
|
||||
$custom_ids
|
||||
);
|
||||
$order_ids = $custom_ids;
|
||||
$args = array(
|
||||
'post__in' => $order_ids,
|
||||
'limit' => -1,
|
||||
|
@ -62,49 +112,62 @@ trait RequestHandlerTrait {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return and log response for no custom ids found in request.
|
||||
* Logs and returns response for no custom ids found in request.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @param array $response The response.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
protected function no_custom_ids_from_request( WP_REST_Request $request, array $response ): WP_REST_Response {
|
||||
protected function no_custom_ids_response( WP_REST_Request $request ): WP_REST_Response {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal webhook Id.
|
||||
__( 'No order for webhook event %s was found.', 'woocommerce-paypal-payments' ),
|
||||
$request['id'] !== null && isset( $request['id'] ) ? $request['id'] : ''
|
||||
);
|
||||
|
||||
return $this->log_and_return_response( $message, $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return and log response for no WC orders found in response.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @param array $response The response.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
protected function no_wc_orders_from_custom_ids( WP_REST_Request $request, array $response ): WP_REST_Response {
|
||||
$message = sprintf(
|
||||
// translators: %s is the PayPal order Id.
|
||||
__( 'WC order for PayPal order %s not found.', 'woocommerce-paypal-payments' ),
|
||||
'WC order ID was not found in webhook event %s for PayPal order %s.',
|
||||
(string) ( $request['id'] ?? '' ),
|
||||
// Psalm 4.x does not seem to understand ?? with ArrayAccess correctly.
|
||||
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
|
||||
);
|
||||
|
||||
return $this->log_and_return_response( $message, $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return and log response with the given message.
|
||||
* Logs and returns response for no WC orders found via custom ids.
|
||||
*
|
||||
* @param string $message The message.
|
||||
* @param array $response The response.
|
||||
* @param WP_REST_Request $request The request.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
private function log_and_return_response( string $message, array $response ): WP_REST_Response {
|
||||
$this->logger->warning( $message );
|
||||
$response['message'] = $message;
|
||||
protected function no_wc_orders_response( WP_REST_Request $request ): WP_REST_Response {
|
||||
$message = sprintf(
|
||||
'WC order %s not found in webhook event %s for PayPal order %s.',
|
||||
implode( ', ', $this->get_custom_ids_from_request( $request ) ),
|
||||
(string) ( $request['id'] ?? '' ),
|
||||
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
|
||||
);
|
||||
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns success response.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
protected function success_response(): WP_REST_Response {
|
||||
return new WP_REST_Response( array( 'success' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs and returns failure response with the given message.
|
||||
*
|
||||
* @param string $message The message.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
private function failure_response( string $message = '' ): WP_REST_Response {
|
||||
$response = array(
|
||||
'success' => false,
|
||||
);
|
||||
if ( $message ) {
|
||||
$this->logger->warning( $message );
|
||||
$response['message'] = $message;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response );
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ use WP_REST_Response;
|
|||
* Class VaultPaymentTokenCreated
|
||||
*/
|
||||
class VaultPaymentTokenCreated implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -103,16 +104,12 @@ class VaultPaymentTokenCreated implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
|
||||
$customer_id = null !== $request['resource'] && isset( $request['resource']['customer_id'] )
|
||||
? $request['resource']['customer_id']
|
||||
: '';
|
||||
if ( ! $customer_id ) {
|
||||
$message = 'No customer id was found.';
|
||||
$this->logger->warning( $message, array( 'request' => $request ) );
|
||||
$response['message'] = $message;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_customer_id = (int) str_replace( $this->prefix, '', $customer_id );
|
||||
|
@ -150,7 +147,6 @@ class VaultPaymentTokenCreated implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ use WP_REST_Response;
|
|||
* Class VaultPaymentTokenDeleted
|
||||
*/
|
||||
class VaultPaymentTokenDeleted implements RequestHandler {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -65,8 +66,6 @@ class VaultPaymentTokenDeleted implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = array( 'success' => false );
|
||||
|
||||
if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
|
||||
$token_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
|
||||
|
@ -89,7 +88,6 @@ class VaultPaymentTokenDeleted implements RequestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
return new WP_REST_Response( $response );
|
||||
return $this->success_response();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,14 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
|
||||
/**
|
||||
* Class IncomingWebhookEndpoint
|
||||
*/
|
||||
class IncomingWebhookEndpoint {
|
||||
use RequestHandlerTrait;
|
||||
|
||||
const NAMESPACE = 'paypal/v1';
|
||||
const ROUTE = 'incoming';
|
||||
|
@ -211,26 +213,16 @@ class IncomingWebhookEndpoint {
|
|||
if ( $this->simulation->is_simulation_event( $event ) ) {
|
||||
$this->logger->info( 'Received simulated webhook.' );
|
||||
$this->simulation->receive( $event );
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
)
|
||||
);
|
||||
return $this->success_response();
|
||||
}
|
||||
|
||||
foreach ( $this->handlers as $handler ) {
|
||||
if ( $handler->responsible_for_request( $request ) ) {
|
||||
$response = $handler->handle_request( $request );
|
||||
$this->logger->log(
|
||||
'info',
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
// translators: %s is the event type.
|
||||
__( 'Webhook has been handled by %s', 'woocommerce-paypal-payments' ),
|
||||
'Webhook has been handled by %s',
|
||||
( $handler->event_types() ) ? current( $handler->event_types() ) : ''
|
||||
),
|
||||
array(
|
||||
'request' => $request,
|
||||
'response' => $response,
|
||||
)
|
||||
);
|
||||
return $response;
|
||||
|
@ -238,22 +230,10 @@ class IncomingWebhookEndpoint {
|
|||
}
|
||||
|
||||
$message = sprintf(
|
||||
// translators: %s is the request type.
|
||||
__( 'Could not find handler for request type %s', 'woocommerce-paypal-payments' ),
|
||||
$request['event_type']
|
||||
'Could not find handler for request type %s',
|
||||
$request['event_type'] ?: ''
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$message,
|
||||
array(
|
||||
'request' => $request,
|
||||
)
|
||||
);
|
||||
$response = array(
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
);
|
||||
return rest_ensure_response( $response );
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "woocommerce-paypal-payments",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"description": "WooCommerce PayPal Payments",
|
||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||
"license": "GPL-2.0",
|
||||
|
|
23
readme.txt
23
readme.txt
|
@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell,
|
|||
Requires at least: 5.3
|
||||
Tested up to: 6.2
|
||||
Requires PHP: 7.2
|
||||
Stable tag: 2.1.0
|
||||
Stable tag: 2.2.0
|
||||
License: GPLv2
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
|
@ -81,6 +81,27 @@ Follow the steps below to connect the plugin to your PayPal account:
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 2.2.0 - TBD =
|
||||
* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
|
||||
* Fix - Use order currency instead of shop currency on order-pay page #1363
|
||||
* Fix - Do not show broken card button gateway when no checkout location #1358
|
||||
* Fix - Smart buttons not greyed out/removed on single product when deselecting product variation #1469
|
||||
* Fix - Type error with advanced columns pro #1367
|
||||
* Fix - Undefined array key 0 when checking $retry_errors in process_payment method #1375
|
||||
* Fix - Advanced Card Processing gateway becomes invisible post-plugin update unless admin pages are accessed once #1432
|
||||
* Fix - Incompatibility with WooCommerce One Page Checkout (or similar use cases) in Version 2.1.0 #1473
|
||||
* Fix - Prevent Repetitive Token Migration and Database Overload After 2.1.0 Update #1461
|
||||
* Enhancement - Remove feature flag requirement for express cart/checkout block integration #1483
|
||||
* Enhancement - Add notice when shop currency is unsupported #1433
|
||||
* Enhancement - Improve ACDC error message when empty fields #1360
|
||||
* Enhancement - Do not exclude free items #1362
|
||||
* Enhancement - Trigger WC checkout_error event #1384
|
||||
* Enhancement - Update wording in buttons previews #1408
|
||||
* Enhancement - Filter to conditionally block the PayPal buttons #1485
|
||||
* Enhancement - Display funding source on the admin order page #1450
|
||||
* Enhancement - Update system report plugin status for Vaulting #1471
|
||||
* Enhancement - Revert Elementor Pro Checkout hook compatibility #1482
|
||||
|
||||
= 2.1.0 - 2023-06-13 =
|
||||
* Fix - Performance issue #1182
|
||||
* Fix - Webhooks not registered when onboarding with manual credentials #1223
|
||||
|
|
|
@ -258,7 +258,7 @@ class PurchaseUnitFactoryTest extends TestCase
|
|||
{
|
||||
$wcCustomer = Mockery::mock(\WC_Customer::class);
|
||||
expect('WC')
|
||||
->andReturn((object) ['customer' => $wcCustomer]);
|
||||
->andReturn((object) ['customer' => $wcCustomer, 'session' => null]);
|
||||
|
||||
$wcCart = Mockery::mock(\WC_Cart::class);
|
||||
$amount = Mockery::mock(Amount::class);
|
||||
|
@ -322,7 +322,7 @@ class PurchaseUnitFactoryTest extends TestCase
|
|||
public function testWcCartShippingGetsDroppendWhenNoCustomer()
|
||||
{
|
||||
expect('WC')
|
||||
->andReturn((object) ['customer' => null]);
|
||||
->andReturn((object) ['customer' => null, 'session' => null]);
|
||||
|
||||
$wcCart = Mockery::mock(\WC_Cart::class);
|
||||
$amount = Mockery::mock(Amount::class);
|
||||
|
@ -360,7 +360,7 @@ class PurchaseUnitFactoryTest extends TestCase
|
|||
public function testWcCartShippingGetsDroppendWhenNoCountryCode()
|
||||
{
|
||||
expect('WC')
|
||||
->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class)]);
|
||||
->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class), 'session' => null]);
|
||||
|
||||
$wcCart = Mockery::mock(\WC_Cart::class);
|
||||
$amount = Mockery::mock(Amount::class);
|
||||
|
|
|
@ -169,6 +169,7 @@ class CreateOrderEndpointTest extends TestCase
|
|||
false,
|
||||
['checkout'],
|
||||
false,
|
||||
['paypal'],
|
||||
new NullLogger()
|
||||
);
|
||||
return array($payer_factory, $testee);
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
|
|||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
@ -75,6 +77,11 @@ class WcGatewayTest extends TestCase
|
|||
->andReturnUsing(function () {
|
||||
return $this->fundingSource;
|
||||
});
|
||||
$order = Mockery::mock(Order::class);
|
||||
$order->shouldReceive('status')->andReturn(new OrderStatus(OrderStatus::APPROVED));
|
||||
$this->sessionHandler
|
||||
->shouldReceive('order')
|
||||
->andReturn($order);
|
||||
|
||||
$this->settings->shouldReceive('has')->andReturnFalse();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const {test, expect} = require('@playwright/test');
|
||||
const {serverExec} = require("./utils/server");
|
||||
const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout");
|
||||
const {fillCheckoutForm, expectOrderReceivedPage, acceptTerms} = require("./utils/checkout");
|
||||
const {openPaypalPopup, loginIntoPaypal, waitForPaypalShippingList, completePaypalPayment} = require("./utils/paypal-popup");
|
||||
|
||||
const {
|
||||
|
@ -11,9 +11,11 @@ const {
|
|||
PRODUCT_ID,
|
||||
CHECKOUT_URL,
|
||||
CHECKOUT_PAGE_ID,
|
||||
CART_URL,
|
||||
BLOCK_CHECKOUT_URL,
|
||||
BLOCK_CHECKOUT_PAGE_ID,
|
||||
BLOCK_CART_URL,
|
||||
APM_ID,
|
||||
} = process.env;
|
||||
|
||||
async function completeBlockContinuation(page) {
|
||||
|
@ -21,11 +23,25 @@ async function completeBlockContinuation(page) {
|
|||
|
||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
||||
|
||||
await page.locator('.wc-block-components-checkout-place-order-button').click();
|
||||
await Promise.all(
|
||||
page.waitForNavigation(),
|
||||
page.locator('.wc-block-components-checkout-place-order-button').click(),
|
||||
);
|
||||
}
|
||||
|
||||
await page.waitForNavigation();
|
||||
async function expectContinuation(page) {
|
||||
await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked();
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
||||
}
|
||||
|
||||
async function completeContinuation(page) {
|
||||
await expectContinuation(page);
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('#place_order').click(),
|
||||
]);
|
||||
}
|
||||
|
||||
test.describe('Classic checkout', () => {
|
||||
|
@ -44,10 +60,7 @@ test.describe('Classic checkout', () => {
|
|||
|
||||
await fillCheckoutForm(page);
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
page.locator('#place_order').click(),
|
||||
]);
|
||||
await completeContinuation(page);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
@ -77,6 +90,47 @@ test.describe('Classic checkout', () => {
|
|||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('PayPal APM button place order', async ({page}) => {
|
||||
await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID);
|
||||
|
||||
await page.goto(CHECKOUT_URL);
|
||||
|
||||
await fillCheckoutForm(page);
|
||||
|
||||
const popup = await openPaypalPopup(page, {fundingSource: APM_ID});
|
||||
|
||||
await popup.getByText('Continue', { exact: true }).click();
|
||||
await completePaypalPayment(popup, {selector: '[name="Successful"]'});
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('PayPal APM button place order when redirect fails', async ({page}) => {
|
||||
await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID);
|
||||
|
||||
await page.goto(CHECKOUT_URL);
|
||||
|
||||
await fillCheckoutForm(page);
|
||||
|
||||
await page.evaluate('PayPalCommerceGateway.ajax.approve_order = null');
|
||||
|
||||
const popup = await openPaypalPopup(page, {fundingSource: APM_ID});
|
||||
|
||||
await popup.getByText('Continue', { exact: true }).click();
|
||||
await completePaypalPayment(popup, {selector: '[name="Successful"]'});
|
||||
|
||||
await expect(page.locator('.woocommerce-error')).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
await expectContinuation(page);
|
||||
|
||||
await acceptTerms(page);
|
||||
|
||||
await completeContinuation(page);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Block checkout', () => {
|
||||
|
@ -97,6 +151,8 @@ test.describe('Block checkout', () => {
|
|||
await completePaypalPayment(popup);
|
||||
|
||||
await completeBlockContinuation(page);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('PayPal express block cart', async ({page}) => {
|
||||
|
@ -109,6 +165,8 @@ test.describe('Block checkout', () => {
|
|||
await completePaypalPayment(popup);
|
||||
|
||||
await completeBlockContinuation(page);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test.describe('Without review', () => {
|
||||
|
|
|
@ -26,6 +26,10 @@ export const fillCheckoutForm = async (page) => {
|
|||
await differentShippingLocator.uncheck();
|
||||
}
|
||||
|
||||
await acceptTerms(page);
|
||||
}
|
||||
|
||||
export const acceptTerms = async (page) => {
|
||||
const termsLocator = page.locator('[name="terms"]');
|
||||
if (await termsLocator.count() > 0) {
|
||||
await termsLocator.check();
|
||||
|
|
|
@ -8,16 +8,24 @@ const {
|
|||
/**
|
||||
* Opens the PayPal popup by pressing the button, and returns the popup object.
|
||||
* @param page
|
||||
* @param {{timeout: ?int, fundingSource: ?string}} options
|
||||
* @param {boolean} retry Retries the button click if the popup did not appear after timeout.
|
||||
* @param {int} timeout
|
||||
*/
|
||||
export const openPaypalPopup = async (page, retry = true, timeout = 5000) => {
|
||||
export const openPaypalPopup = async (page, options = {}, retry = true) => {
|
||||
options = {
|
||||
...{
|
||||
timeout: 5000,
|
||||
fundingSource: 'paypal',
|
||||
},
|
||||
...options
|
||||
};
|
||||
|
||||
try {
|
||||
await page.locator('.component-frame').scrollIntoViewIfNeeded();
|
||||
|
||||
const [popup] = await Promise.all([
|
||||
page.waitForEvent('popup', {timeout}),
|
||||
page.frameLocator('.component-frame').locator('[data-funding-source="paypal"]').click(),
|
||||
page.waitForEvent('popup', {timeout: options.timeout}),
|
||||
page.frameLocator('.component-frame').locator(`[data-funding-source="${options.fundingSource}"]`).click(),
|
||||
]);
|
||||
|
||||
await popup.waitForLoadState();
|
||||
|
@ -41,7 +49,7 @@ export const openPaypalPopup = async (page, retry = true, timeout = 5000) => {
|
|||
}
|
||||
|
||||
if (retry) {
|
||||
return openPaypalPopup(page, false);
|
||||
return openPaypalPopup(page, options, false);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
@ -83,9 +91,20 @@ export const waitForPaypalShippingList = async (popup) => {
|
|||
await expect(popup.locator('#shippingMethodsDropdown')).toBeVisible({timeout: 15000});
|
||||
}
|
||||
|
||||
export const completePaypalPayment = async (popup) => {
|
||||
/**
|
||||
* @param popup
|
||||
* @param {{timeout: ?int, selector: ?string}} options
|
||||
*/
|
||||
export const completePaypalPayment = async (popup, options) => {
|
||||
options = {
|
||||
...{
|
||||
timeout: 20000,
|
||||
selector: '#payment-submit-btn',
|
||||
},
|
||||
...options
|
||||
};
|
||||
await Promise.all([
|
||||
popup.waitForEvent('close', {timeout: 20000}),
|
||||
popup.click('#payment-submit-btn'),
|
||||
popup.waitForEvent('close', {timeout: options.timeout}),
|
||||
popup.click(options.selector),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
* Plugin Name: WooCommerce PayPal Payments
|
||||
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
||||
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
||||
* Version: 2.1.0
|
||||
* Version: 2.2.0
|
||||
* Author: WooCommerce
|
||||
* Author URI: https://woocommerce.com/
|
||||
* License: GPL-2.0
|
||||
* Requires PHP: 7.2
|
||||
* WC requires at least: 3.9
|
||||
* WC tested up to: 7.7
|
||||
* WC tested up to: 7.8
|
||||
* Text Domain: woocommerce-paypal-payments
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce
|
||||
|
@ -23,7 +23,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
|
||||
define( 'PAYPAL_API_URL', 'https://api.paypal.com' );
|
||||
define( 'PAYPAL_SANDBOX_API_URL', 'https://api.sandbox.paypal.com' );
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2023-06-02' );
|
||||
define( 'PAYPAL_INTEGRATION_DATE', '2023-07-06' );
|
||||
|
||||
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
||||
! defined( 'CONNECT_WOO_SANDBOX_CLIENT_ID' ) && define( 'CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_' );
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue