Merge branch 'trunk' into PCP-1480-order-left-on-hold-after-payment-trough-pui-if-merchant-connected-account-trough-manual-credential-input

This commit is contained in:
Alex P 2023-05-12 15:03:31 +03:00
commit 8dc0079a41
No known key found for this signature in database
GPG key ID: 54487A734A204D71
121 changed files with 2730 additions and 1449 deletions

38
.ddev/commands/web/wp-cleanup Executable file
View file

@ -0,0 +1,38 @@
#!/bin/bash
show-help() {
echo -e "\nDelete all posts -p [post type]"
echo -e "\tExample: ddev wp-cleanup -p shop_order,product"
echo -e "\nDelete all logs -l [wp-content path]"
echo -e "\tExample: ddev wp-cleanup -l uploads/wc-logs\n"
}
delete-posts() {
for post in $(wp post list --post_type=$1 --format=ids --path=.ddev/wordpress); do
wp post delete $post --force --path=.ddev/wordpress
done
}
delete-logs() {
rm .ddev/wordpress/wp-content/$1/*.log
}
declare -i param_counter=0
while getopts "p:l:h" arg; do
case $arg in
p)
delete-posts $OPTARG
param_counter+=1
;;
l)
delete-logs $OPTARG
param_counter+=1
;;
h) show-help ;;
esac
done
if [ $param_counter -eq 0 ]; then
show-help
fi

View file

@ -2,6 +2,8 @@ PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress
BASEURL="https://woocommerce-paypal-payments.ddev.site"
PRODUCT_URL="/product/prod"
CUSTOMER_EMAIL="customer@example.com"
CUSTOMER_PASSWORD="password"

View file

@ -9,6 +9,10 @@ if (!defined('HOUR_IN_SECONDS')) {
define('HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS);
}
if (!defined('ABSPATH')) {
define('ABSPATH', '');
}
/**
* Cancel the next occurrence of a scheduled action.
*

View file

@ -73,6 +73,8 @@ Enable xdebug via `$ ddev xdebug`, and press `Start Listening for PHP Debug Conn
After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path.
It should look [like this](https://i.imgur.com/ofsF1Mc.png).
See [tests/playwright](tests/playwright) for e2e (browser-based) tests.
## Test account setup
You will need a PayPal sandbox merchant and customer accounts to configure the plugin and make test purchases with it.

109
api/order-functions.php Normal file
View file

@ -0,0 +1,109 @@
<?php
/**
* The API for operations with orders.
*
* @package WooCommerce\PayPalCommerce\Api
*
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Api;
use Exception;
use InvalidArgumentException;
use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\PPCP;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
/**
* Returns the PayPal order.
*
* @param string|WC_Order $paypal_id_or_wc_order The ID of PayPal order or a WC order (with the ID in meta).
* @throws InvalidArgumentException When the argument cannot be used for retrieving the order.
* @throws Exception When the operation fails.
*/
function ppcp_get_paypal_order( $paypal_id_or_wc_order ): Order {
if ( $paypal_id_or_wc_order instanceof WC_Order ) {
$paypal_id_or_wc_order = $paypal_id_or_wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
if ( ! $paypal_id_or_wc_order ) {
throw new InvalidArgumentException( 'PayPal order ID not found in meta.' );
}
}
if ( ! is_string( $paypal_id_or_wc_order ) ) {
throw new InvalidArgumentException( 'Invalid PayPal order ID, string expected.' );
}
$order_endpoint = PPCP::container()->get( 'api.endpoint.order' );
assert( $order_endpoint instanceof OrderEndpoint );
return $order_endpoint->order( $paypal_id_or_wc_order );
}
/**
* Captures the PayPal order.
*
* @param WC_Order $wc_order The WC order.
* @throws InvalidArgumentException When the order cannot be captured.
* @throws Exception When the operation fails.
*/
function ppcp_capture_order( WC_Order $wc_order ): void {
$intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) );
if ( $intent !== 'AUTHORIZE' ) {
throw new InvalidArgumentException( 'Only orders with "authorize" intent can be captured.' );
}
$captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) );
if ( $captured ) {
throw new InvalidArgumentException( 'The order is already captured.' );
}
$authorized_payment_processor = PPCP::container()->get( 'wcgateway.processor.authorized-payments' );
assert( $authorized_payment_processor instanceof AuthorizedPaymentsProcessor );
if ( ! $authorized_payment_processor->capture_authorized_payment( $wc_order ) ) {
throw new RuntimeException( 'Capture failed.' );
}
}
/**
* Refunds the PayPal order.
* Note that you can use wc_refund_payment() to trigger the refund in WC and PayPal.
*
* @param WC_Order $wc_order The WC order.
* @param float $amount The refund amount.
* @param string $reason The reason for the refund.
* @return string The PayPal refund ID.
* @throws InvalidArgumentException When the order cannot be refunded.
* @throws Exception When the operation fails.
*/
function ppcp_refund_order( WC_Order $wc_order, float $amount, string $reason = '' ): string {
$order = ppcp_get_paypal_order( $wc_order );
$refund_processor = PPCP::container()->get( 'wcgateway.processor.refunds' );
assert( $refund_processor instanceof RefundProcessor );
return $refund_processor->refund( $order, $wc_order, $amount, $reason );
}
/**
* Voids the authorization.
*
* @param WC_Order $wc_order The WC order.
* @throws InvalidArgumentException When the order cannot be voided.
* @throws Exception When the operation fails.
*/
function ppcp_void_order( WC_Order $wc_order ): void {
$order = ppcp_get_paypal_order( $wc_order );
$refund_processor = PPCP::container()->get( 'wcgateway.processor.refunds' );
assert( $refund_processor instanceof RefundProcessor );
$refund_processor->void( $order );
}

View file

@ -16,10 +16,18 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return function (
string $root_dir,
ContainerInterface ...$additional_containers
array $additional_containers = array(),
array $additional_modules = array()
): ContainerInterface {
/**
* Skip path check.
*
* @psalm-suppress UnresolvableInclude
*/
$modules = ( require "$root_dir/modules.php" )( $root_dir );
$modules = array_merge( $modules, $additional_modules );
/**
* Use this filter to add custom module or remove some of existing ones.
* Modules able to access container, add services and modify existing ones.
@ -39,6 +47,11 @@ return function (
// may want to consider fixing it later (pass proxy as parent to DelegatingContainer)
// for now not fixed since we were using this behavior for long time and fixing it now may break things.
$container = new DelegatingContainer( $provider );
/**
* Skip iterable vs array check.
*
* @psalm-suppress PossiblyInvalidArgument
*/
$app_container = new CachingContainer(
new CompositeContainer(
array_merge(

View file

@ -1,6 +1,18 @@
*** Changelog ***
= 2.0.3 - TBD =
= 2.0.4 - 2023-04-03 =
* Fix - Allow Pay Later in mini-cart #1221
* Fix - Duplicated auth error when credentials become wrong #1229
* Fix - Webhook issues when switching sandbox, and delete all webhooks when unsubscribing #1239
* Fix - High volume of traffic from merchant-integrations endpoint #1273
* Fix - Add content type json to all fetch ajax endpoints #1275
* Enhancement - Remove shortcodes from description #1226
* Enhancement - Handle price suffix with price for product button check #1234
* Enhancement - Show funding source as payment method #1220
* Enhancement - Change "Enabled" to "Available" in status text #1237
* Enhancement - Programmatically capturing/voiding authorized payments #590
= 2.0.3 - 2023-03-14 =
* Fix - `DEVICE_DATA_NOT_AVAILABLE` error message when FraudNet is enabled #1177
* Fix - Redirect to connection tab after manual credentials input #1201
* Fix - Asking for address fields in checkout when not using them #1089
@ -11,6 +23,7 @@
* Fix - PPEC compatibility layer does not take over subscriptions #1193
* Fix - Checkout conflict with "All products for subscriptions" plugin #629
* Fix - Pay Later on order pay page #1214
* Fix - High volume of traffic from merchant-integrations endpoint #1241
* Enhancement - Save checkout form before free trial redirect #1135
* Enhancement - Add filter for controlling the ditching of items/breakdown #1146
* Enhancement - Add patch order data filter #1147

View file

@ -30,7 +30,10 @@
"psr-4": {
"WooCommerce\\PayPalCommerce\\": "src",
"WooCommerce\\PayPalCommerce\\Vendor\\": "lib/packages/"
}
},
"files": [
"api/order-functions.php"
]
},
"autoload-dev": {
"psr-4": {

483
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
@ -177,12 +178,13 @@ return array(
$patch_collection_factory = $container->get( 'api.factory.patch-collection-factory' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
/**
* The settings.
*
* @var Settings $settings
*/
$session_handler = $container->get( 'session.handler' );
assert( $session_handler instanceof SessionHandler );
$bn_code = $session_handler->bn_code();
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$application_context_repository = $container->get( 'api.repository.application-context' );
$subscription_helper = $container->get( 'subscription.helper' );
@ -196,7 +198,8 @@ return array(
$application_context_repository,
$subscription_helper,
$container->get( 'wcgateway.is-fraudnet-enabled' ),
$container->get( 'wcgateway.fraudnet' )
$container->get( 'wcgateway.fraudnet' ),
$bn_code
);
},
'api.endpoint.billing-agreements' => static function ( ContainerInterface $container ): BillingAgreementsEndpoint {

View file

@ -197,6 +197,40 @@ class PaymentTokenEndpoint {
return wp_remote_retrieve_response_code( $response ) === 204;
}
/**
* Deletes payment token by the given id.
*
* @param string $token_id Token id.
* @return bool
*
* @throws RuntimeException If something goes wrong while deleting the token.
*/
public function delete_token_by_id( string $token_id ): bool {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/' . $token_id;
$args = array(
'method' => 'DELETE',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
),
);
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = new RuntimeException(
__( 'Could not delete payment token.', 'woocommerce-paypal-payments' )
);
$this->logger->warning( $error->getMessage() );
throw $error;
}
return wp_remote_retrieve_response_code( $response ) === 204;
}
/**
* Starts the process of PayPal account vaulting (without payment), returns the links for further actions.
*

View file

@ -175,12 +175,12 @@ class WebhookEndpoint {
*
* @param Webhook $hook The webhook to delete.
*
* @return bool
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
public function delete( Webhook $hook ): bool {
public function delete( Webhook $hook ): void {
if ( ! $hook->id() ) {
return false;
return;
}
$bearer = $this->bearer->bearer();
@ -198,7 +198,18 @@ class WebhookEndpoint {
__( 'Not able to delete the webhook.', 'woocommerce-paypal-payments' )
);
}
return wp_remote_retrieve_response_code( $response ) === 204;
$status_code = (int) wp_remote_retrieve_response_code( $response );
if ( 204 !== $status_code ) {
$json = null;
if ( is_array( $response ) ) {
$json = json_decode( $response['body'] );
}
throw new PayPalApiException(
$json,
$status_code
);
}
}
/**

View file

@ -58,7 +58,7 @@ class ItemFactory {
mb_substr( $product->get_name(), 0, 127 ),
new Money( $price, $this->currency ),
$quantity,
substr( wp_strip_all_tags( $product->get_description() ), 0, 127 ) ?: '',
$this->prepare_description( $product->get_description() ),
null,
$product->get_sku(),
( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
@ -130,7 +130,7 @@ class ItemFactory {
mb_substr( $item->get_name(), 0, 127 ),
new Money( $price_without_tax_rounded, $currency ),
$quantity,
substr( wp_strip_all_tags( $product instanceof WC_Product ? $product->get_description() : '' ), 0, 127 ) ?: '',
$product instanceof WC_Product ? $this->prepare_description( $product->get_description() ) : '',
null,
$product instanceof WC_Product ? $product->get_sku() : '',
( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
@ -198,4 +198,15 @@ class ItemFactory {
$category
);
}
/**
* Cleanups the description and prepares it for sending to PayPal.
*
* @param string $description Item description.
* @return string
*/
protected function prepare_description( string $description ): string {
$description = strip_shortcodes( wp_strip_all_tags( $description ) );
return substr( $description, 0, 127 ) ?: '';
}
}

View file

@ -23,7 +23,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -16,6 +16,9 @@ class CartActionHandler {
this.config.bn_codes[this.config.context] : '';
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,

View file

@ -32,6 +32,9 @@ class CheckoutActionHandler {
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
@ -59,6 +62,11 @@ class CheckoutActionHandler {
);
} else {
errorHandler.clear();
if (data.data.refresh) {
jQuery( document.body ).trigger( 'update_checkout' );
}
if (data.data.errors?.length > 0) {
errorHandler.messages(data.data.errors);
} else if (data.data.details?.length > 0) {

View file

@ -49,6 +49,9 @@ class FreeTrialHandler {
const res = await fetch(this.config.ajax.vault_paypal.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.vault_paypal.nonce,

View file

@ -66,6 +66,9 @@ class SingleProductActionHandler {
this.config.bn_codes[this.config.context] : '';
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,

View file

@ -69,9 +69,14 @@ class SingleProductBootstap {
() => {
const priceEl = document.querySelector('.product .woocommerce-Price-amount');
// variable products show price like 10.00 - 20.00 here
if (priceEl && priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount').length === 1) {
// but the second price also can be the suffix with the price incl/excl tax
if (priceEl) {
const allPriceElements = Array.from(priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount'))
.filter(el => !el.parentElement.classList.contains('woocommerce-price-suffix'));
if (allPriceElements.length === 1) {
return priceEl.innerText;
}
}
return null;
},
].map(f => f()).find(val => val);

View file

@ -27,6 +27,9 @@ const storeToken = (token) => {
const dataClientIdAttributeHandler = (script, config) => {
fetch(config.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.nonce

View file

@ -10,6 +10,9 @@ export default class FormSaver {
const res = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,

View file

@ -10,6 +10,9 @@ export default class FormValidator {
const res = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,
@ -20,6 +23,10 @@ export default class FormValidator {
const data = await res.json();
if (!data.success) {
if (data.data.refresh) {
jQuery( document.body ).trigger( 'update_checkout' );
}
if (data.data.errors) {
return data.data.errors;
}

View file

@ -20,6 +20,9 @@ class UpdateCart {
this.endpoint,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,

View file

@ -2,6 +2,9 @@ const onApprove = (context, errorHandler) => {
return (data, actions) => {
return fetch(context.config.ajax.approve_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: context.config.ajax.approve_order.nonce,

View file

@ -5,6 +5,9 @@ const onApprove = (context, errorHandler, spinner) => {
return fetch(context.config.ajax.approve_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: context.config.ajax.approve_order.nonce,

View file

@ -288,12 +288,15 @@ class CreateOrderEndpoint implements EndpointInterface {
return true;
} catch ( ValidationException $error ) {
wp_send_json_error(
array(
$response = array(
'message' => $error->getMessage(),
'errors' => $error->errors(),
)
'refresh' => isset( WC()->session->refresh_totals ),
);
unset( WC()->session->refresh_totals );
wp_send_json_error( $response );
} catch ( \RuntimeException $error ) {
$this->logger->error( 'Order creation failed: ' . $error->getMessage() );

View file

@ -86,12 +86,15 @@ class ValidateCheckoutEndpoint implements EndpointInterface {
return true;
} catch ( ValidationException $exception ) {
wp_send_json_error(
array(
$response = array(
'message' => $exception->getMessage(),
'errors' => $exception->errors(),
)
'refresh' => isset( WC()->session->refresh_totals ),
);
unset( WC()->session->refresh_totals );
wp_send_json_error( $response );
return false;
} catch ( Throwable $error ) {
$this->logger->error( "Form validation execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );

View file

@ -1703,9 +1703,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1723,9 +1723,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -2191,10 +2191,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WP_Theme;
/**
* Class CompatModule
@ -57,6 +58,8 @@ class CompatModule implements ModuleInterface {
$this->migrate_pay_later_settings( $c );
$this->migrate_smart_button_settings( $c );
$this->fix_page_builders();
}
/**
@ -320,4 +323,49 @@ class CompatModule implements ModuleInterface {
}
);
}
/**
* Changes the button rendering place for page builders
* that do not work well with our default places.
*
* @return void
*/
protected function fix_page_builders(): void {
if ( $this->is_elementor_pro_active() || $this->is_divi_theme_active() ) {
add_filter(
'woocommerce_paypal_payments_single_product_renderer_hook',
function(): string {
return 'woocommerce_after_add_to_cart_form';
},
5
);
add_filter(
'woocommerce_paypal_payments_checkout_button_renderer_hook',
function(): string {
return 'woocommerce_review_order_after_submit';
},
5
);
}
}
/**
* Checks whether the Elementor Pro plugins (allowing integrations with WC) is active.
*
* @return bool
*/
protected function is_elementor_pro_active(): bool {
return is_plugin_active( 'elementor-pro/elementor-pro.php' );
}
/**
* Checks whether the Divi theme is currently used.
*
* @return bool
*/
protected function is_divi_theme_active(): bool {
$theme = wp_get_theme();
return $theme->get( 'Name' ) === 'Divi';
}
}

View file

@ -184,14 +184,14 @@ class SubscriptionsHandler {
}
// Are we on the WC > Subscriptions screen?
// phpcs:ignore WordPress.Security.NonceVerification.Missing
// phpcs:ignore WordPress.Security.NonceVerification
$post_type = wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) );
if ( $post_type === 'shop_subscription' ) {
return true;
}
// Are we editing an order or subscription tied to PPEC?
// phpcs:ignore WordPress.Security.NonceVerification.Missing
// phpcs:ignore WordPress.Security.NonceVerification
$order_id = wc_clean( wp_unslash( $_GET['post'] ?? $_POST['post_ID'] ?? '' ) );
if ( $order_id ) {
$order = wc_get_order( $order_id );

View file

@ -1682,9 +1682,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -2155,10 +2155,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -84,6 +84,9 @@ const ppcp_onboarding = {
fetch(PayPalCommerceGatewayOnboarding.pui_endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: PayPalCommerceGatewayOnboarding.pui_nonce,

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Onboarding;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
/**
* Exposes and handles REST routes related to onboarding.
@ -249,7 +250,7 @@ class OnboardingRESTController {
}
$webhook_registrar = $this->container->get( 'webhook.registrar' );
$webhook_registrar->unregister();
assert( $webhook_registrar instanceof WebhookRegistrar );
$webhook_registrar->register();
return array();

View file

@ -1682,9 +1682,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -2155,10 +2155,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -18,6 +18,9 @@ document.addEventListener(
submitButton.setAttribute('disabled', 'disabled');
fetch(config.ajax.tracking_info.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.tracking_info.nonce,

View file

@ -1242,12 +1242,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -1785,9 +1780,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1805,9 +1800,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -2263,10 +2258,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -20,7 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Webhooks\WebhookInfoStorage;
use WooCommerce\PayPalCommerce\Webhooks\WebhookEventStorage;
/**
* Class StatusReportModule
@ -62,7 +62,7 @@ class StatusReportModule implements ModuleInterface {
$messages_apply = $c->get( 'button.helper.messages-apply' );
$last_webhook_storage = $c->get( 'webhook.last-webhook-storage' );
assert( $last_webhook_storage instanceof WebhookInfoStorage );
assert( $last_webhook_storage instanceof WebhookEventStorage );
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -1242,12 +1242,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -1785,9 +1780,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1805,9 +1800,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -2263,10 +2258,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -1,11 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.25.0"
}
]
]
}

View file

@ -1,2 +0,0 @@
node_modules
/assets

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */

File diff suppressed because one or more lines are too long

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -1,42 +0,0 @@
document.addEventListener(
'DOMContentLoaded',
() => {
jQuery('.ppcp-delete-payment-button').click(async (event) => {
event.preventDefault();
jQuery(this).prop('disabled', true);
const token = event.target.id;
const response = await fetch(
PayPalCommerceGatewayVaulting.delete.endpoint,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(
{
nonce: PayPalCommerceGatewayVaulting.delete.nonce,
token,
}
)
}
);
const reportError = error => {
alert(error);
}
if (!response.ok) {
try {
const result = await response.json();
reportError(result.data);
} catch (exc) {
console.error(exc);
reportError(response.status);
}
}
window.location.reload();
});
});

View file

@ -10,8 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Vaulting\Assets\MyAccountPaymentsAssets;
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
return array(
'vaulting.module-url' => static function ( ContainerInterface $container ): string {
@ -20,27 +18,11 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
'vaulting.assets.myaccount-payments' => function( ContainerInterface $container ) : MyAccountPaymentsAssets {
return new MyAccountPaymentsAssets(
$container->get( 'vaulting.module-url' ),
$container->get( 'ppcp.asset-version' )
);
},
'vaulting.payment-tokens-renderer' => static function (): PaymentTokensRenderer {
return new PaymentTokensRenderer();
},
'vaulting.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
$factory = $container->get( 'api.factory.payment-token' );
$endpoint = $container->get( 'api.endpoint.payment-token' );
return new PaymentTokenRepository( $factory, $endpoint );
},
'vaulting.endpoint.delete' => function( ContainerInterface $container ) : DeletePaymentTokenEndpoint {
return new DeletePaymentTokenEndpoint(
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'button.request-data' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'vaulting.payment-token-checker' => function( ContainerInterface $container ) : PaymentTokenChecker {
return new PaymentTokenChecker(
$container->get( 'vaulting.repository.payment-token' ),
@ -70,4 +52,14 @@ return array(
$container->get( 'wcgateway.settings' )
);
},
'vaulting.payment-token-factory' => function( ContainerInterface $container ): PaymentTokenFactory {
return new PaymentTokenFactory();
},
'vaulting.payment-tokens-migration' => function( ContainerInterface $container ): PaymentTokensMigration {
return new PaymentTokensMigration(
$container->get( 'vaulting.payment-token-factory' ),
$container->get( 'vaulting.repository.payment-token' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
);

View file

@ -1,77 +0,0 @@
<?php
/**
* Register and configure assets for My account PayPal payments page.
*
* @package WooCommerce\PayPalCommerce\Vaulting\Assets
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting\Assets;
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
/**
* Class MyAccountPaymentsAssets
*/
class MyAccountPaymentsAssets {
/**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* The assets version.
*
* @var string
*/
private $version;
/**
* MyAccountPaymentsAssets constructor.
*
* @param string $module_url The URL to the module.
* @param string $version The assets version.
*/
public function __construct(
string $module_url,
string $version
) {
$this->module_url = untrailingslashit( $module_url );
$this->version = $version;
}
/**
* Enqueues the necessary scripts.
*
* @return void
*/
public function enqueue(): void {
wp_enqueue_script(
'ppcp-vaulting-myaccount-payments',
untrailingslashit( $this->module_url ) . '/assets/js/myaccount-payments.js',
array( 'jquery' ),
$this->version,
true
);
}
/**
* Localize script.
*/
public function localize() {
wp_localize_script(
'ppcp-vaulting-myaccount-payments',
'PayPalCommerceGatewayVaulting',
array(
'delete' => array(
'endpoint' => \WC_AJAX::get_endpoint( DeletePaymentTokenEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DeletePaymentTokenEndpoint::nonce() ),
),
)
);
}
}

View file

@ -1,95 +0,0 @@
<?php
/**
* The endpoint for deleting payment tokens.
*
* @package WooCommerce\PayPalCommerce\Vaulting\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
/**
* Class DeletePayment
*/
class DeletePaymentTokenEndpoint {
const ENDPOINT = 'ppc-vaulting-delete';
/**
* The repository.
*
* @var PaymentTokenRepository
*/
protected $repository;
/**
* The request data.
*
* @var RequestData
*/
protected $request_data;
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
/**
* DeletePaymentTokenEndpoint constructor.
*
* @param PaymentTokenRepository $repository The repository.
* @param RequestData $request_data The request data.
* @param LoggerInterface $logger The logger.
*/
public function __construct( PaymentTokenRepository $repository, RequestData $request_data, LoggerInterface $logger ) {
$this->repository = $repository;
$this->request_data = $request_data;
$this->logger = $logger;
}
/**
* Returns the nonce for the endpoint.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the incoming request.
*/
public function handle_request() {
try {
$data = $this->request_data->read_request( $this->nonce() );
$tokens = $this->repository->all_for_user_id( get_current_user_id() );
if ( $tokens ) {
foreach ( $tokens as $token ) {
if ( isset( $data['token'] ) && $token->id() === $data['token'] ) {
if ( $this->repository->delete_token( get_current_user_id(), $token ) ) {
wp_send_json_success();
return true;
}
wp_send_json_error( 'Could not delete payment token.' );
return false;
}
}
}
} catch ( Exception $error ) {
$this->logger->error( 'Failed to delete payment: ' . $error->getMessage() );
wp_send_json_error( $error->getMessage(), 403 );
return false;
}
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* WooCommerce Payment token for PayPal ACDC (Advanced Credit and Debit Card).
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WC_Payment_Token;
/**
* Class PaymentTokenACDC
*/
class PaymentTokenACDC extends WC_Payment_Token {
/**
* Token Type String.
*
* @var string
*/
protected $type = 'ACDC';
/**
* Stores Credit Card payment token data.
*
* @var array
*/
protected $extra_data = array(
'last4' => '',
'card_type' => '',
);
/**
* Returns the last four digits.
*
* @param string $context The context.
* @return mixed|null
*/
public function get_last4( $context = 'view' ) {
return $this->get_prop( 'last4', $context );
}
/**
* Set the last four digits.
*
* @param string $last4 Last four digits.
*/
public function set_last4( $last4 ) {
$this->set_prop( 'last4', $last4 );
}
/**
* Returns the card type (mastercard, visa, ...).
*
* @param string $context The context.
* @return string Card type
*/
public function get_card_type( $context = 'view' ) {
return $this->get_prop( 'card_type', $context );
}
/**
* Set the card type (mastercard, visa, ...).
*
* @param string $type Credit card type (mastercard, visa, ...).
*/
public function set_card_type( $type ) {
$this->set_prop( 'card_type', $type );
}
}

View file

@ -0,0 +1,32 @@
<?php
/**
* WooCommerce Payment token factory.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
/**
* Class PaymentTokenFactory
*/
class PaymentTokenFactory {
/**
* Creates a new WC payment token instance of the given type.
*
* @param string $type The type of WC payment token.
*
* @return void|PaymentTokenACDC|PaymentTokenPayPal
*/
public function create( string $type ) {
switch ( $type ) {
case 'paypal':
return new PaymentTokenPayPal();
case 'acdc':
return new PaymentTokenACDC();
}
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* WooCommerce Payment token for PayPal.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WC_Payment_Token;
/**
* Class PaymentTokenPayPal
*/
class PaymentTokenPayPal extends WC_Payment_Token {
/**
* Token Type String.
*
* @var string
*/
protected $type = 'PayPal';
/**
* Extra data.
*
* @var string[]
*/
protected $extra_data = array(
'email' => '',
);
/**
* Get PayPal account email.
*
* @return string PayPal account email.
*/
public function get_email() {
return $this->get_meta( 'email' );
}
/**
* Set PayPal account email.
*
* @param string $email PayPal account email.
*/
public function set_email( $email ) {
$this->add_meta_data( 'email', $email, true );
}
}

View file

@ -20,8 +20,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
class PaymentTokenRepository {
const USER_META = 'ppcp-vault-token';
/**
* The payment token factory.
*
@ -51,27 +49,6 @@ class PaymentTokenRepository {
$this->endpoint = $endpoint;
}
/**
* Return a token for a user.
*
* @param int $id The user id.
*
* @return PaymentToken|null
*/
public function for_user_id( int $id ) {
try {
$token = (array) get_user_meta( $id, self::USER_META, true );
if ( ! $token || ! isset( $token['id'] ) ) {
return $this->fetch_for_user_id( $id );
}
$token = $this->factory->from_array( $token );
return $token;
} catch ( RuntimeException $error ) {
return null;
}
}
/**
* Return all tokens for a user.
*
@ -81,7 +58,6 @@ class PaymentTokenRepository {
public function all_for_user_id( int $id ) {
try {
$tokens = $this->endpoint->for_user( $id );
update_user_meta( $id, self::USER_META, $tokens );
return $tokens;
} catch ( RuntimeException $exception ) {
return array();
@ -97,7 +73,6 @@ class PaymentTokenRepository {
* @return bool
*/
public function delete_token( int $user_id, PaymentToken $token ): bool {
delete_user_meta( $user_id, self::USER_META );
return $this->endpoint->delete_token( $token );
}
@ -121,21 +96,6 @@ class PaymentTokenRepository {
return $this->token_contains_source( $tokens, 'paypal' );
}
/**
* Fetch PaymentToken from PayPal for a user.
*
* @param int $id The user id.
* @return PaymentToken
*/
private function fetch_for_user_id( int $id ): PaymentToken {
$tokens = $this->endpoint->for_user( $id );
$token = current( $tokens );
$token_array = $token->to_array();
update_user_meta( $id, self::USER_META, $token_array );
return $token;
}
/**
* Checks if tokens has the given source.
*

View file

@ -0,0 +1,136 @@
<?php
/**
* The payment tokens migration handler.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class PaymentTokensMigration
*/
class PaymentTokensMigration {
/**
* The payment token factory.
*
* @var PaymentTokenFactory
*/
private $payment_token_factory;
/**
* The payment token repository.
*
* @var PaymentTokenRepository
*/
private $payment_token_repository;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* PaymentTokensMigration constructor.
*
* @param PaymentTokenFactory $payment_token_factory The payment token factory.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
PaymentTokenFactory $payment_token_factory,
PaymentTokenRepository $payment_token_repository,
LoggerInterface $logger
) {
$this->payment_token_factory = $payment_token_factory;
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
}
/**
* Migrates user existing vaulted tokens into WC payment tokens API.
*
* @param int $id WooCommerce customer id.
*/
public function migrate_payment_tokens_for_user( int $id ):void {
$tokens = $this->payment_token_repository->all_for_user_id( $id );
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id );
foreach ( $tokens as $token ) {
if ( $this->token_exist( $wc_tokens, $token ) ) {
continue;
}
if ( isset( $token->source()->card ) ) {
$payment_token_acdc = $this->payment_token_factory->create( 'acdc' );
assert( $payment_token_acdc instanceof PaymentTokenACDC );
$payment_token_acdc->set_token( $token->id() );
$payment_token_acdc->set_user_id( $id );
$payment_token_acdc->set_gateway_id( CreditCardGateway::ID );
$payment_token_acdc->set_last4( $token->source()->card->last_digits );
$payment_token_acdc->set_card_type( $token->source()->card->brand );
try {
$payment_token_acdc->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not save WC payment token credit card {$token->id()} for user {$id}. "
. $exception->getMessage()
);
continue;
}
} elseif ( $token->source()->paypal ) {
$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
assert( $payment_token_paypal instanceof PaymentTokenPayPal );
$payment_token_paypal->set_token( $token->id() );
$payment_token_paypal->set_user_id( $id );
$payment_token_paypal->set_gateway_id( PayPalGateway::ID );
$email = $token->source()->paypal->payer->email_address ?? '';
if ( $email && is_email( $email ) ) {
$payment_token_paypal->set_email( $email );
}
try {
$payment_token_paypal->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not save WC payment token PayPal {$token->id()} for user {$id}. "
. $exception->getMessage()
);
continue;
}
}
}
}
/**
* Checks if given PayPal token exist as WC Payment Token.
*
* @param array $wc_tokens WC Payment Tokens.
* @param PaymentToken $token PayPal Token ID.
* @return bool
*/
private function token_exist( array $wc_tokens, PaymentToken $token ): bool {
foreach ( $wc_tokens as $wc_token ) {
if ( $wc_token->get_token() === $token->id() ) {
return true;
}
}
return false;
}
}

View file

@ -1,83 +0,0 @@
<?php
/**
* The payment tokens renderer.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
/**
* Class PaymentTokensRendered
*/
class PaymentTokensRenderer {
/**
* Render payment tokens.
*
* @param PaymentToken[] $tokens The tokens.
* @return false|string
*/
public function render( array $tokens ) {
ob_start();
?>
<table class="shop_table shop_table_responsive">
<thead>
<tr>
<th><?php echo esc_html__( 'Payment sources', 'woocommerce-paypal-payments' ); ?></th>
<th></th>
</tr>
</thead>
<tbody>
<?php
foreach ( $tokens as $token ) {
$source = $token->source() ?? null;
if ( $source && isset( $source->card ) ) {
?>
<tr>
<td><?php echo esc_attr( $source->card->brand ) . ' ...' . esc_attr( $source->card->last_digits ); ?></td>
<td>
<a class="ppcp-delete-payment-button" id="<?php echo esc_attr( $token->id() ); ?>" href=""><?php echo esc_html__( 'Delete', 'woocommerce-paypal-payments' ); ?></a>
</td>
</tr>
<?php
}
if ( $source && isset( $source->paypal ) ) {
?>
<tr>
<td><?php echo esc_attr( $source->paypal->payer->email_address ); ?></td>
<td>
<a class="ppcp-delete-payment-button" id="<?php echo esc_attr( $token->id() ); ?>" href=""><?php echo esc_html__( 'Delete', 'woocommerce-paypal-payments' ); ?></a>
</td>
</tr>
<?php
}
?>
<?php
}
?>
</tbody>
</table>
<?php
return ob_get_clean();
}
/**
* Render no payments message.
*
* @return false|string
*/
public function render_no_tokens() {
ob_start();
?>
<div class="woocommerce-Message woocommerce-Message--info woocommerce-info">
<?php echo esc_html__( 'No payments available yet.', 'woocommerce-paypal-payments' ); ?>
</div>
<?php
return ob_get_clean();
}
}

View file

@ -9,6 +9,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use RuntimeException;
use WC_Payment_Token;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
@ -16,9 +19,10 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WP_User_Query;
/**
* Class StatusReportModule
@ -43,7 +47,6 @@ class VaultingModule implements ModuleInterface {
* @throws NotFoundException When service could not be found.
*/
public function run( ContainerInterface $container ): void {
$settings = $container->get( 'wcgateway.settings' );
if ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) {
return;
@ -54,61 +57,6 @@ class VaultingModule implements ModuleInterface {
$listener->listen();
add_filter(
'woocommerce_account_menu_items',
function( $menu_links ) {
$menu_links = array_slice( $menu_links, 0, 5, true )
+ array( 'ppcp-paypal-payment-tokens' => __( 'PayPal payments', 'woocommerce-paypal-payments' ) )
+ array_slice( $menu_links, 5, null, true );
return $menu_links;
},
40
);
add_action(
'init',
function () {
add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
}
);
add_action(
'woocommerce_paypal_payments_gateway_migrate',
function () {
add_action(
'init',
function () {
add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
flush_rewrite_rules();
}
);
}
);
add_action(
'woocommerce_paypal_payments_gateway_activate',
function () {
add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
flush_rewrite_rules();
}
);
add_action(
'woocommerce_account_ppcp-paypal-payment-tokens_endpoint',
function () use ( $container ) {
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
$renderer = $container->get( 'vaulting.payment-tokens-renderer' );
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( $tokens ) {
echo wp_kses_post( $renderer->render( $tokens ) );
} else {
echo wp_kses_post( $renderer->render_no_tokens() );
}
}
);
$subscription_helper = $container->get( 'subscription.helper' );
add_action(
'woocommerce_created_customer',
@ -124,27 +72,6 @@ class VaultingModule implements ModuleInterface {
}
);
$asset_loader = $container->get( 'vaulting.assets.myaccount-payments' );
add_action(
'wp_enqueue_scripts',
function () use ( $asset_loader ) {
if ( is_account_page() && $this->is_payments_page() ) {
$asset_loader->enqueue();
$asset_loader->localize();
}
}
);
add_action(
'wc_ajax_' . DeletePaymentTokenEndpoint::ENDPOINT,
static function () use ( $container ) {
$endpoint = $container->get( 'vaulting.endpoint.delete' );
assert( $endpoint instanceof DeletePaymentTokenEndpoint );
$endpoint->handle_request();
}
);
add_action(
'woocommerce_paypal_payments_check_saved_payment',
function ( int $order_id, int $customer_id, string $intent ) use ( $container ) {
@ -156,6 +83,133 @@ class VaultingModule implements ModuleInterface {
);
$this->filterFailedVaultingEmailsForSubscriptionOrders( $container );
add_filter(
'woocommerce_payment_token_class',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function ( $type ) {
if ( $type === 'WC_Payment_Token_ACDC' ) {
return PaymentTokenACDC::class;
}
if ( $type === 'WC_Payment_Token_PayPal' ) {
return PaymentTokenPayPal::class;
}
return $type;
}
);
add_filter(
'woocommerce_payment_methods_list_item',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
function( $item, $payment_token ) {
if ( ! is_array( $item ) || ! is_a( $payment_token, WC_Payment_Token::class ) ) {
return $item;
}
if ( strtolower( $payment_token->get_type() ) === 'acdc' ) {
assert( $payment_token instanceof PaymentTokenACDC );
$item['method']['brand'] = $payment_token->get_card_type() . ' ...' . $payment_token->get_last4();
return $item;
}
if ( strtolower( $payment_token->get_type() ) === 'paypal' ) {
assert( $payment_token instanceof PaymentTokenPayPal );
$item['method']['brand'] = $payment_token->get_email();
return $item;
}
return $item;
},
10,
2
);
add_action(
'wp',
function() use ( $container ) {
global $wp;
if ( isset( $wp->query_vars['delete-payment-method'] ) ) {
$token_id = absint( $wp->query_vars['delete-payment-method'] );
$token = WC_Payment_Tokens::get( $token_id );
if (
is_null( $token )
|| ( $token->get_gateway_id() !== PayPalGateway::ID && $token->get_gateway_id() !== CreditCardGateway::ID )
) {
return;
}
$wpnonce = wc_clean( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
$token_id_string = (string) $token_id;
$action = 'delete-payment-method-' . $token_id_string;
if (
$token->get_user_id() !== get_current_user_id()
|| ! isset( $wpnonce ) || ! is_string( $wpnonce )
|| wp_verify_nonce( $wpnonce, $action ) === false
) {
wc_add_notice( __( 'Invalid payment method.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_account_endpoint_url( 'payment-methods' ) );
exit();
}
try {
$payment_token_endpoint = $container->get( 'api.endpoint.payment-token' );
$payment_token_endpoint->delete_token_by_id( $token->get_token() );
} catch ( RuntimeException $exception ) {
wc_add_notice( __( 'Could not delete payment token. ', 'woocommerce-paypal-payments' ) . $exception->getMessage(), 'error' );
return;
}
}
}
);
add_action(
'woocommerce_paypal_payments_gateway_migrate_on_update',
function () use ( $container ) {
// 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(
array(
'fields' => 'ID',
'limit' => -1,
'meta_key' => 'ppcp-vault-token',
)
);
// phpcs:enable
$migrate = $container->get( 'vaulting.payment-tokens-migration' );
assert( $migrate instanceof PaymentTokensMigration );
foreach ( $customers->get_results() as $id ) {
$migrate->migrate_payment_tokens_for_user( (int) $id );
}
}
);
add_filter(
'woocommerce_available_payment_gateways',
function( array $methods ): array {
global $wp;
if ( isset( $wp->query_vars['add-payment-method'] ) ) {
unset( $methods[ PayPalGateway::ID ] );
}
return $methods;
}
);
}
/**

View file

@ -1,22 +0,0 @@
const path = require('path');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
devtool: isProduction ? 'source-map' : 'eval-source-map',
mode: isProduction ? 'production' : 'development',
target: 'web',
entry: {
'myaccount-payments': path.resolve('./resources/js/myaccount-payments.js'),
},
output: {
path: path.resolve(__dirname, 'assets/'),
filename: 'js/[name].js',
},
module: {
rules: [{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
}]
}
};

View file

@ -1021,12 +1021,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*":
version "0.0.50"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
"@types/estree@^0.0.51":
"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@ -1194,12 +1189,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -1292,18 +1282,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.14.5:
version "4.17.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
dependencies:
caniuse-lite "^1.0.30001259"
electron-to-chromium "^1.3.846"
escalade "^3.1.1"
nanocolors "^0.1.5"
node-releases "^1.1.76"
browserslist@^4.20.2, browserslist@^4.21.3:
browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
@ -1326,11 +1305,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
caniuse-lite@^1.0.30001259:
version "1.0.30001261"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
caniuse-lite@^1.0.30001370:
version "1.0.30001393"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
@ -1455,11 +1429,6 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
electron-to-chromium@^1.3.846:
version "1.3.853"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
electron-to-chromium@^1.4.202:
version "1.4.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
@ -1621,12 +1590,7 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.9:
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -1748,17 +1712,10 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
json5@^2.1.2, json5@^2.2.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1776,9 +1733,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -1820,31 +1777,16 @@ mime-types@^2.1.27:
dependencies:
mime-db "1.49.0"
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanocolors@^0.1.5:
version "0.1.12"
resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-releases@^1.1.76:
version "1.1.76"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@ -2270,10 +2212,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -9,10 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
use WooCommerce\WooCommerce\Logging\Logger\WooCommerceLogger;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -52,41 +49,6 @@ return array(
$settings = $container->get( 'wcgateway.settings' );
return $settings->has( 'prefix' ) ? (string) $settings->get( 'prefix' ) : 'WC-';
},
'api.endpoint.order' => static function ( ContainerInterface $container ): OrderEndpoint {
$order_factory = $container->get( 'api.factory.order' );
$patch_collection_factory = $container->get( 'api.factory.patch-collection-factory' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
/**
* The session handler.
*
* @var SessionHandler $session_handler
*/
$session_handler = $container->get( 'session.handler' );
$bn_code = $session_handler->bn_code();
/**
* The settings.
*
* @var Settings $settings
*/
$settings = $container->get( 'wcgateway.settings' );
$intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$application_context_repository = $container->get( 'api.repository.application-context' );
$subscription_helper = $container->get( 'subscription.helper' );
return new OrderEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$order_factory,
$patch_collection_factory,
$intent,
$logger,
$application_context_repository,
$subscription_helper,
$container->get( 'wcgateway.is-fraudnet-enabled' ),
$container->get( 'wcgateway.fraudnet' ),
$bn_code
);
},
'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface {
if ( ! function_exists( 'wc_get_logger' ) || ! $container->get( 'wcgateway.logging.is-enabled' ) ) {
return new NullLogger();

View file

@ -22,7 +22,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -231,8 +231,11 @@ return array(
'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer {
return new SectionsRenderer(
$container->get( 'wcgateway.current-ppcp-settings-page-id' ),
$container->get( 'wcgateway.settings.sections' ),
$container->get( 'onboarding.state' )
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.helper.dcc-product-status' ),
$container->get( 'api.helpers.dccapplies' ),
$container->get( 'button.helper.messages-apply' ),
$container->get( 'wcgateway.pay-upon-invoice-product-status' )
);
},
'wcgateway.settings.header-renderer' => static function ( ContainerInterface $container ): HeaderRenderer {
@ -241,57 +244,6 @@ return array(
$container->get( 'wcgateway.url' )
);
},
'wcgateway.settings.sections' => static function ( ContainerInterface $container ): array {
$state = $container->get( 'onboarding.state' );
if ( $state->current_state() < State::STATE_ONBOARDED ) {
return array();
}
$sections = array(
Settings::CONNECTION_TAB_ID => __( 'Connection', 'woocommerce-paypal-payments' ),
PayPalGateway::ID => __( 'Standard Payments', 'woocommerce-paypal-payments' ),
Settings::PAY_LATER_TAB_ID => __( 'Pay Later', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'Advanced Card Processing', 'woocommerce-paypal-payments' ),
CardButtonGateway::ID => __( 'Standard Card Button', 'woocommerce-paypal-payments' ),
OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
);
// Remove for all not registered in WC gateways that cannot render anything in this case.
$gateways = WC()->payment_gateways->payment_gateways();
foreach ( array_diff(
array_keys( $sections ),
array( Settings::CONNECTION_TAB_ID, PayPalGateway::ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID )
) as $id ) {
if ( ! isset( $gateways[ $id ] ) ) {
unset( $sections[ $id ] );
}
}
$dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
assert( $dcc_product_status instanceof DCCProductStatus );
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
assert( $dcc_applies instanceof DccApplies );
if ( ! $dcc_product_status->dcc_is_active() || ! $dcc_applies->for_country_currency() ) {
unset( $sections['ppcp-credit-card-gateway'] );
}
$messages_apply = $container->get( 'button.helper.messages-apply' );
assert( $messages_apply instanceof MessagesApply );
if ( ! $messages_apply->for_country() ) {
unset( $sections[ Settings::PAY_LATER_TAB_ID ] );
}
$pui_product_status = $container->get( 'wcgateway.pay-upon-invoice-product-status' );
assert( $pui_product_status instanceof PayUponInvoiceProductStatus );
if ( ! $pui_product_status->pui_is_active() ) {
unset( $sections[ PayUponInvoiceGateway::ID ] );
}
return $sections;
},
'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus {
$settings = $container->get( 'wcgateway.settings' );
return new SettingsStatus( $settings );
@ -341,7 +293,8 @@ return array(
$signup_link_cache,
$signup_link_ids,
$pui_status_cache,
$dcc_status_cache
$dcc_status_cache,
$container->get( 'http.redirector' )
);
},
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
@ -649,7 +602,7 @@ return array(
>',
'</a>'
),
'options' => $container->get( 'wcgateway.all-funding-sources' ),
'options' => $container->get( 'wcgateway.settings.funding-sources' ),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
@ -886,6 +839,17 @@ return array(
'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', 'Name of payment method', 'woocommerce-paypal-payments' ),
'trustly' => _x( 'Trustly', 'Name of payment method', 'woocommerce-paypal-payments' ),
'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
);
},
'wcgateway.settings.funding-sources' => static function( ContainerInterface $container ): array {
return array_diff_key(
$container->get( 'wcgateway.all-funding-sources' ),
array_flip(
array(
'paylater',
)
)
);
},
@ -944,7 +908,8 @@ return array(
$settings,
$partner_endpoint,
$container->get( 'dcc.status-cache' ),
$container->get( 'api.helpers.dccapplies' )
$container->get( 'api.helpers.dccapplies' ),
$container->get( 'onboarding.state' )
);
},
@ -956,9 +921,11 @@ return array(
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new FundingSourceRenderer(
$container->get( 'wcgateway.settings' )
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.all-funding-sources' )
);
},
'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper();
},
@ -1012,7 +979,8 @@ return array(
return new PayUponInvoiceProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'pui.status-cache' )
$container->get( 'pui.status-cache' ),
$container->get( 'onboarding.state' )
);
},
'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice {
@ -1264,7 +1232,7 @@ return array(
$dcc_enabled = $dcc_product_status->dcc_is_active();
$enabled_status_text = esc_html__( 'Status: Enabled', 'woocommerce-paypal-payments' );
$enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$dcc_button_text = $dcc_enabled
@ -1302,7 +1270,7 @@ return array(
$pui_enabled = $pui_product_status->pui_is_active();
$enabled_status_text = esc_html__( 'Status: Enabled', 'woocommerce-paypal-payments' );
$enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$enable_pui_url = $environment->current_environment_is( Environment::PRODUCTION )
@ -1353,24 +1321,10 @@ return array(
assert( $settings instanceof Settings );
$button_locations = $container->get( 'wcgateway.button.locations' );
unset( $button_locations['mini-cart'] );
$smart_button_selected_locations = $settings->has( 'smart_button_locations' ) ? $settings->get( 'smart_button_locations' ) : array();
$pay_later_button_locations = array();
if ( empty( $smart_button_selected_locations ) ) {
return $pay_later_button_locations;
}
foreach ( $button_locations as $location_key => $location ) {
if ( ! in_array( $location_key, $smart_button_selected_locations, true ) ) {
continue;
}
$pay_later_button_locations[ $location_key ] = $location;
}
return $pay_later_button_locations;
return array_intersect_key( $button_locations, array_flip( $smart_button_selected_locations ) );
},
'wcgateway.ppcp-gateways' => static function ( ContainerInterface $container ): array {
return array(

View file

@ -22,13 +22,32 @@ class FundingSourceRenderer {
*/
protected $settings;
/**
* Map funding source ID -> human-readable name.
*
* @var array<string, string>
*/
protected $funding_sources;
/**
* The IDs of the sources belonging to PayPal that do not need to mention "via PayPal".
*
* @var string[]
*/
protected $own_funding_sources = array( 'venmo', 'paylater' );
/**
* FundingSourceRenderer constructor.
*
* @param ContainerInterface $settings The settings.
* @param array<string, string> $funding_sources Map funding source ID -> human-readable name.
*/
public function __construct( ContainerInterface $settings ) {
public function __construct(
ContainerInterface $settings,
array $funding_sources
) {
$this->settings = $settings;
$this->funding_sources = $funding_sources;
}
/**
@ -37,9 +56,17 @@ class FundingSourceRenderer {
* @param string $id The ID of the funding source, such as 'venmo'.
*/
public function render_name( string $id ): string {
if ( 'venmo' === $id ) {
return __( 'Venmo', 'woocommerce-paypal-payments' );
if ( array_key_exists( $id, $this->funding_sources ) ) {
if ( in_array( $id, $this->own_funding_sources, true ) ) {
return $this->funding_sources[ $id ];
}
return sprintf(
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
__( '%s (via PayPal)', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);
}
return $this->settings->has( 'title' ) ?
$this->settings->get( 'title' )
: __( 'PayPal', 'woocommerce-paypal-payments' );
@ -51,9 +78,14 @@ class FundingSourceRenderer {
* @param string $id The ID of the funding source, such as 'venmo'.
*/
public function render_description( string $id ): string {
if ( 'venmo' === $id ) {
return __( 'Pay via Venmo.', 'woocommerce-paypal-payments' );
if ( array_key_exists( $id, $this->funding_sources ) ) {
return sprintf(
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
__( 'Pay via %s.', 'woocommerce-paypal-payments' ),
$this->funding_sources[ $id ]
);
}
return $this->settings->has( 'description' ) ?
$this->settings->get( 'description' )
: __( 'Pay via PayPal.', 'woocommerce-paypal-payments' );

View file

@ -202,7 +202,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->api_shop_country = $api_shop_country;
if ( $this->onboarded ) {
$this->supports = array( 'refunds' );
$this->supports = array( 'refunds', 'tokenization' );
}
if (
defined( 'PPCP_FLAG_SUBSCRIPTION' )
@ -223,6 +223,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
'subscription_payment_method_change_customer',
'subscription_payment_method_change_admin',
'multiple_subscriptions',
'tokenization',
);
}

View file

@ -423,15 +423,25 @@ class PayUponInvoice {
* @psalm-suppress MissingClosureParamType
*/
function ( $methods ) {
if ( ! is_array( $methods ) || State::STATE_ONBOARDED !== $this->state->current_state() ) {
if (
! is_array( $methods )
|| State::STATE_ONBOARDED !== $this->state->current_state()
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|| ! ( is_checkout() || isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true' )
) {
return $methods;
}
if (
! $this->pui_product_status->pui_is_active()
|| ! $this->pui_helper->is_checkout_ready_for_pui()
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|| ( isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true' && ! $this->pui_helper->is_pay_for_order_ready_for_pui() )
) {
unset( $methods[ PayUponInvoiceGateway::ID ] );
}
if (
isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true'
&& ! $this->pui_helper->is_pay_for_order_ready_for_pui()
) {
unset( $methods[ PayUponInvoiceGateway::ID ] );
}
@ -440,31 +450,6 @@ class PayUponInvoice {
}
);
add_action(
'woocommerce_settings_checkout',
function () {
if (
PayUponInvoiceGateway::ID === $this->current_ppcp_settings_page_id
&& ! $this->pui_product_status->pui_is_active()
) {
$gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' );
$gateway_enabled = $gateway_settings['enabled'] ?? '';
if ( 'yes' === $gateway_enabled ) {
$gateway_settings['enabled'] = 'no';
update_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings', $gateway_settings );
$redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-pay-upon-invoice-gateway' );
wp_safe_redirect( $redirect_url );
exit;
}
printf(
'<div class="notice notice-error"><p>%1$s</p></div>',
esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay upon Invoice is selected to try again.', 'woocommerce-paypal-payments' )
);
}
}
);
add_action(
'woocommerce_update_options_checkout_ppcp-pay-upon-invoice-gateway',
function () {
@ -509,6 +494,19 @@ class PayUponInvoice {
</div>
<?php
}
} elseif ( PayUponInvoiceGateway::ID === $this->current_ppcp_settings_page_id ) {
$pui_gateway = WC()->payment_gateways->payment_gateways()[ PayUponInvoiceGateway::ID ];
if ( 'yes' === $pui_gateway->get_option( 'enabled' ) ) {
$pui_gateway->update_option( 'enabled', 'no' );
$redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-pay-upon-invoice-gateway' );
wp_safe_redirect( $redirect_url );
exit;
}
printf(
'<div class="notice notice-error"><p>%1$s</p></div>',
esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay upon Invoice is selected to try again.', 'woocommerce-paypal-payments' )
);
}
}
);

View file

@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@ -57,6 +58,13 @@ class DCCProductStatus {
*/
protected $dcc_applies;
/**
* The onboarding state.
*
* @var State
*/
private $onboarding_state;
/**
* DccProductStatus constructor.
*
@ -64,17 +72,20 @@ class DCCProductStatus {
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
* @param DccApplies $dcc_applies The dcc applies helper.
* @param State $onboarding_state The onboarding state.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
Cache $cache,
DccApplies $dcc_applies
DccApplies $dcc_applies,
State $onboarding_state
) {
$this->settings = $settings;
$this->partners_endpoint = $partners_endpoint;
$this->cache = $cache;
$this->dcc_applies = $dcc_applies;
$this->onboarding_state = $onboarding_state;
}
/**
@ -83,15 +94,19 @@ class DCCProductStatus {
* @return bool
*/
public function dcc_is_active() : bool {
if ( $this->cache->has( self::DCC_STATUS_CACHE_KEY ) ) {
return (bool) $this->cache->get( self::DCC_STATUS_CACHE_KEY );
if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
return false;
}
if ( is_bool( $this->current_status_cache ) ) {
if ( $this->cache->has( self::DCC_STATUS_CACHE_KEY ) ) {
return $this->cache->get( self::DCC_STATUS_CACHE_KEY ) === 'true';
}
if ( $this->current_status_cache === true ) {
return $this->current_status_cache;
}
if ( $this->settings->has( 'products_dcc_enabled' ) && $this->settings->get( 'products_dcc_enabled' ) ) {
if ( $this->settings->has( 'products_dcc_enabled' ) && $this->settings->get( 'products_dcc_enabled' ) === true ) {
$this->current_status_cache = true;
return true;
}
@ -120,7 +135,7 @@ class DCCProductStatus {
$this->settings->set( 'products_dcc_enabled', true );
$this->settings->persist();
$this->current_status_cache = true;
$this->cache->set( self::DCC_STATUS_CACHE_KEY, true, 3 * MONTH_IN_SECONDS );
$this->cache->set( self::DCC_STATUS_CACHE_KEY, 'true', 3 * MONTH_IN_SECONDS );
return true;
}
}
@ -129,7 +144,7 @@ class DCCProductStatus {
if ( $this->dcc_applies->for_country_currency() ) {
$expiration = 3 * HOUR_IN_SECONDS;
}
$this->cache->set( self::DCC_STATUS_CACHE_KEY, false, $expiration );
$this->cache->set( self::DCC_STATUS_CACHE_KEY, 'false', $expiration );
$this->current_status_cache = false;
return false;

View file

@ -13,6 +13,7 @@ use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@ -49,21 +50,31 @@ class PayUponInvoiceProductStatus {
*/
private $partners_endpoint;
/**
* The onboarding status
*
* @var State
*/
private $onboarding_state;
/**
* PayUponInvoiceProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
* @param State $onboarding_state The onboarding state.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
Cache $cache
Cache $cache,
State $onboarding_state
) {
$this->settings = $settings;
$this->partners_endpoint = $partners_endpoint;
$this->cache = $cache;
$this->onboarding_state = $onboarding_state;
}
/**
@ -72,14 +83,18 @@ class PayUponInvoiceProductStatus {
* @return bool
*/
public function pui_is_active() : bool {
if ( $this->cache->has( self::PUI_STATUS_CACHE_KEY ) ) {
return (bool) $this->cache->get( self::PUI_STATUS_CACHE_KEY );
if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
return false;
}
if ( is_bool( $this->current_status_cache ) ) {
if ( $this->cache->has( self::PUI_STATUS_CACHE_KEY ) ) {
return $this->cache->get( self::PUI_STATUS_CACHE_KEY ) === 'true';
}
if ( $this->current_status_cache === true ) {
return $this->current_status_cache;
}
if ( $this->settings->has( 'products_pui_enabled' ) && $this->settings->get( 'products_pui_enabled' ) ) {
if ( $this->settings->has( 'products_pui_enabled' ) && $this->settings->get( 'products_pui_enabled' ) === true ) {
$this->current_status_cache = true;
return true;
}
@ -112,11 +127,11 @@ class PayUponInvoiceProductStatus {
$this->settings->set( 'products_pui_enabled', true );
$this->settings->persist();
$this->current_status_cache = true;
$this->cache->set( self::PUI_STATUS_CACHE_KEY, true, 3 * MONTH_IN_SECONDS );
$this->cache->set( self::PUI_STATUS_CACHE_KEY, 'true', 3 * MONTH_IN_SECONDS );
return true;
}
}
$this->cache->set( self::PUI_STATUS_CACHE_KEY, false, 3 * MONTH_IN_SECONDS );
$this->cache->set( self::PUI_STATUS_CACHE_KEY, 'false', 3 * MONTH_IN_SECONDS );
$this->current_status_cache = false;
return false;

View file

@ -9,7 +9,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@ -74,7 +73,9 @@ class SettingsStatus {
* @return bool true if is enabled, otherwise false.
*/
public function is_pay_later_button_enabled_for_location( string $location ): bool {
return $this->is_pay_later_button_enabled() && $this->is_enabled_for_location( 'pay_later_button_locations', $location );
return $this->is_pay_later_button_enabled() &&
( $this->is_enabled_for_location( 'pay_later_button_locations', $location ) ||
( 'product' === $location && $this->is_enabled_for_location( 'pay_later_button_locations', 'mini-cart' ) ) );
}
/**

View file

@ -11,12 +11,14 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
@ -70,7 +72,7 @@ class RefundProcessor {
/**
* Processes a refund.
*
* @param \WC_Order $wc_order The WooCommerce order.
* @param WC_Order $wc_order The WooCommerce order.
* @param float|null $amount The refund amount.
* @param string $reason The reason for the refund.
*
@ -78,7 +80,7 @@ class RefundProcessor {
*
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing
*/
public function process( \WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
public function process( WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
try {
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
if ( ! $order_id ) {
@ -87,15 +89,7 @@ class RefundProcessor {
$order = $this->order_endpoint->order( $order_id );
$purchase_units = $order->purchase_units();
if ( ! $purchase_units ) {
throw new RuntimeException( 'No purchase units.' );
}
$payments = $purchase_units[0]->payments();
if ( ! $payments ) {
throw new RuntimeException( 'No payments.' );
}
$payments = $this->get_payments( $order );
$this->logger->debug(
sprintf(
@ -109,6 +103,48 @@ class RefundProcessor {
switch ( $mode ) {
case self::REFUND_MODE_REFUND:
$refund_id = $this->refund( $order, $wc_order, $amount, $reason );
$this->add_refund_to_meta( $wc_order, $refund_id );
break;
case self::REFUND_MODE_VOID:
$this->void( $order );
$wc_order->set_status( 'refunded' );
$wc_order->save();
break;
default:
throw new RuntimeException( 'Nothing to refund/void.' );
}
return true;
} catch ( Exception $error ) {
$this->logger->error( 'Refund failed: ' . $error->getMessage() );
return false;
}
}
/**
* Adds a refund to the PayPal order.
*
* @param Order $order The PayPal order.
* @param WC_Order $wc_order The WooCommerce order.
* @param float $amount The refund amount.
* @param string $reason The reason for the refund.
*
* @throws RuntimeException When operation fails.
* @return string The PayPal refund ID.
*/
public function refund(
Order $order,
WC_Order $wc_order,
float $amount,
string $reason = ''
): string {
$payments = $this->get_payments( $order );
$captures = $payments->captures();
if ( ! $captures ) {
throw new RuntimeException( 'No capture.' );
@ -123,12 +159,19 @@ class RefundProcessor {
new Money( $amount, $wc_order->get_currency() )
)
);
$refund_id = $this->payments_endpoint->refund( $refund );
$this->add_refund_to_meta( $wc_order, $refund_id );
return $this->payments_endpoint->refund( $refund );
}
/**
* Voids the authorization.
*
* @param Order $order The PayPal order.
* @throws RuntimeException When operation fails.
*/
public function void( Order $order ): void {
$payments = $this->get_payments( $order );
break;
case self::REFUND_MODE_VOID:
$voidable_authorizations = array_filter(
$payments->authorizations(),
function ( Authorization $authorization ): bool {
@ -142,20 +185,6 @@ class RefundProcessor {
foreach ( $voidable_authorizations as $authorization ) {
$this->payments_endpoint->void( $authorization );
}
$wc_order->set_status( 'refunded' );
$wc_order->save();
break;
default:
throw new RuntimeException( 'Nothing to refund/void.' );
}
return true;
} catch ( Exception $error ) {
$this->logger->error( 'Refund failed: ' . $error->getMessage() );
return false;
}
}
/**
@ -181,4 +210,24 @@ class RefundProcessor {
return self::REFUND_MODE_UNKNOWN;
}
/**
* Returns the payments object or throws.
*
* @param Order $order The order.
* @throws RuntimeException When payment not available.
*/
protected function get_payments( Order $order ): Payments {
$purchase_units = $order->purchase_units();
if ( ! $purchase_units ) {
throw new RuntimeException( 'No purchase units.' );
}
$payments = $purchase_units[0]->payments();
if ( ! $payments ) {
throw new RuntimeException( 'No payments.' );
}
return $payments;
}
}

View file

@ -9,8 +9,16 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
/**
* Class SectionsRenderer
@ -26,13 +34,6 @@ class SectionsRenderer {
*/
protected $page_id;
/**
* Key - page/gateway ID, value - displayed text.
*
* @var array<string, string>
*/
protected $sections;
/**
* The onboarding state.
*
@ -40,17 +41,65 @@ class SectionsRenderer {
*/
private $state;
/**
* The DCC product status
*
* @var DCCProductStatus
*/
private $dcc_product_status;
/**
* The DCC applies
*
* @var DccApplies
*/
private $dcc_applies;
/**
* The messages apply.
*
* @var MessagesApply
*/
private $messages_apply;
/**
* The PUI product status.
*
* @var PayUponInvoiceProductStatus
*/
private $pui_product_status;
/**
* SectionsRenderer constructor.
*
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param array<string, string> $sections Key - page/gateway ID, value - displayed text.
* @param State $state The onboarding state.
*/
public function __construct( string $page_id, array $sections, State $state ) {
/**
* SectionsRenderer constructor.
*
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param State $state The onboarding state.
* @param DCCProductStatus $dcc_product_status The DCC product status.
* @param DccApplies $dcc_applies The DCC applies.
* @param MessagesApply $messages_apply The Messages apply.
* @param PayUponInvoiceProductStatus $pui_product_status The PUI product status.
*/
public function __construct(
string $page_id,
State $state,
DCCProductStatus $dcc_product_status,
DccApplies $dcc_applies,
MessagesApply $messages_apply,
PayUponInvoiceProductStatus $pui_product_status
) {
$this->page_id = $page_id;
$this->sections = $sections;
$this->state = $state;
$this->dcc_product_status = $dcc_product_status;
$this->dcc_applies = $dcc_applies;
$this->messages_apply = $messages_apply;
$this->pui_product_status = $pui_product_status;
}
/**
@ -66,6 +115,8 @@ class SectionsRenderer {
/**
* Renders the Sections tab.
*
* @return string
*/
public function render(): string {
if ( ! $this->should_render() ) {
@ -74,8 +125,8 @@ class SectionsRenderer {
$html = '<nav class="nav-tab-wrapper woo-nav-tab-wrapper">';
foreach ( $this->sections as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $id );
foreach ( $this->sections() as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . (string) $id );
if ( in_array( $id, array( Settings::CONNECTION_TAB_ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID ), true ) ) {
// We need section=ppcp-gateway for the webhooks page because it is not a gateway,
// and for DCC because otherwise it will not render the page if gateway is not available (country/currency).
@ -89,4 +140,46 @@ class SectionsRenderer {
return $html;
}
/**
* Returns sections as Key - page/gateway ID, value - displayed text.
*
* @return array
*/
private function sections(): array {
$sections = array(
Settings::CONNECTION_TAB_ID => __( 'Connection', 'woocommerce-paypal-payments' ),
PayPalGateway::ID => __( 'Standard Payments', 'woocommerce-paypal-payments' ),
Settings::PAY_LATER_TAB_ID => __( 'Pay Later', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'Advanced Card Processing', 'woocommerce-paypal-payments' ),
CardButtonGateway::ID => __( 'Standard Card Button', 'woocommerce-paypal-payments' ),
OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ),
PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
);
// Remove for all not registered in WC gateways that cannot render anything in this case.
$gateways = WC()->payment_gateways->payment_gateways();
foreach ( array_diff(
array_keys( $sections ),
array( Settings::CONNECTION_TAB_ID, PayPalGateway::ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID )
) as $id ) {
if ( ! isset( $gateways[ $id ] ) ) {
unset( $sections[ $id ] );
}
}
if ( ! $this->dcc_product_status->dcc_is_active() || ! $this->dcc_applies->for_country_currency() ) {
unset( $sections['ppcp-credit-card-gateway'] );
}
if ( ! $this->messages_apply->for_country() ) {
unset( $sections[ Settings::PAY_LATER_TAB_ID ] );
}
if ( ! $this->pui_product_status->pui_is_active() ) {
unset( $sections[ PayUponInvoiceGateway::ID ] );
}
return $sections;
}
}

View file

@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
@ -111,6 +112,13 @@ class SettingsListener {
*/
protected $dcc_status_cache;
/**
* The HTTP redirector.
*
* @var RedirectorInterface
*/
protected $redirector;
/**
* SettingsListener constructor.
*
@ -125,6 +133,7 @@ class SettingsListener {
* @param array $signup_link_ids Signup link ids.
* @param Cache $pui_status_cache The PUI status cache.
* @param Cache $dcc_status_cache The DCC status cache.
* @param RedirectorInterface $redirector The HTTP redirector.
*/
public function __construct(
Settings $settings,
@ -137,7 +146,8 @@ class SettingsListener {
Cache $signup_link_cache,
array $signup_link_ids,
Cache $pui_status_cache,
Cache $dcc_status_cache
Cache $dcc_status_cache,
RedirectorInterface $redirector
) {
$this->settings = $settings;
@ -151,14 +161,14 @@ class SettingsListener {
$this->signup_link_ids = $signup_link_ids;
$this->pui_status_cache = $pui_status_cache;
$this->dcc_status_cache = $dcc_status_cache;
$this->redirector = $redirector;
}
/**
* Listens if the merchant ID should be updated.
*/
public function listen_for_merchant_id() {
if ( ! $this->is_valid_site_request() ) {
if ( ! $this->is_valid_site_request() || $this->state->current_state() === State::STATE_ONBOARDED ) {
return;
}
@ -198,12 +208,13 @@ class SettingsListener {
$redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
}
wp_safe_redirect( $redirect_url, 302 );
exit;
$this->redirector->redirect( $redirect_url );
}
/**
* Prevent enabling both Pay Later messaging and PayPal vaulting
*
* @throws RuntimeException When API request fails.
*/
public function listen_for_vaulting_enabled() {
if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) {
@ -221,16 +232,7 @@ class SettingsListener {
$this->settings->set( 'vault_enabled', false );
$this->settings->persist();
add_action(
'admin_notices',
function () use ( $exception ) {
printf(
'<div class="notice notice-error"><p>%1$s</p><p>%2$s</p></div>',
esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
wp_kses_post( __( 'Please verify your API Credentials and try again to connect your PayPal business account. Visit the <a href="https://docs.woocommerce.com/document/woocommerce-paypal-payments/" target="_blank">plugin documentation</a> for more information about the setup.', 'woocommerce-paypal-payments' ) )
);
}
);
throw $exception;
}
/**
@ -335,7 +337,7 @@ class SettingsListener {
}
$redirect_url = false;
if ( self::CREDENTIALS_ADDED === $credentials_change_status ) {
if ( $credentials_change_status && self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) {
$redirect_url = $this->get_onboarding_redirect_url();
}
@ -344,8 +346,7 @@ class SettingsListener {
}
if ( $redirect_url ) {
wp_safe_redirect( $redirect_url, 302 );
exit;
$this->redirector->redirect( $redirect_url );
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
@ -522,6 +523,8 @@ class SettingsListener {
/**
* Prevent enabling tracking if it is not enabled for merchant account.
*
* @throws RuntimeException When API request fails.
*/
public function listen_for_tracking_enabled(): void {
if ( State::STATE_ONBOARDED !== $this->state->current_state() ) {
@ -539,16 +542,7 @@ class SettingsListener {
$this->settings->set( 'tracking_enabled', false );
$this->settings->persist();
add_action(
'admin_notices',
function () use ( $exception ) {
printf(
'<div class="notice notice-error"><p>%1$s</p><p>%2$s</p></div>',
esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
wp_kses_post( __( 'Please verify your API Credentials and try again to connect your PayPal business account. Visit the <a href="https://docs.woocommerce.com/document/woocommerce-paypal-payments/" target="_blank">plugin documentation</a> for more information about the setup.', 'woocommerce-paypal-payments' ) )
);
}
);
throw $exception;
}
}
}

View file

@ -11,6 +11,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
use Psr\Log\LoggerInterface;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WC_Order;
@ -179,7 +181,7 @@ class WCGatewayModule implements ModuleInterface {
$c->get( 'onboarding.environment' ),
$settings_status->is_pay_later_button_enabled(),
$settings->has( 'disable_funding' ) ? $settings->get( 'disable_funding' ) : array(),
$c->get( 'wcgateway.all-funding-sources' )
$c->get( 'wcgateway.settings.funding-sources' )
);
$assets->register_assets();
}
@ -260,6 +262,24 @@ class WCGatewayModule implements ModuleInterface {
}
);
add_action(
'woocommerce_paypal_payments_gateway_migrate_on_update',
static function() use ( $c ) {
$dcc_status_cache = $c->get( 'dcc.status-cache' );
assert( $dcc_status_cache instanceof Cache );
$pui_status_cache = $c->get( 'pui.status-cache' );
assert( $pui_status_cache instanceof Cache );
$dcc_status_cache->delete( DCCProductStatus::DCC_STATUS_CACHE_KEY );
$pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY );
$settings = $c->get( 'wcgateway.settings' );
$settings->set( 'products_dcc_enabled', false );
$settings->set( 'products_pui_enabled', false );
$settings->persist();
}
);
add_action(
'wp_loaded',
function () use ( $c ) {
@ -453,14 +473,30 @@ class WCGatewayModule implements ModuleInterface {
'admin_init',
static function () use ( $container ) {
$listener = $container->get( 'wcgateway.settings.listener' );
/**
* The settings listener.
*
* @var SettingsListener $listener
*/
assert( $listener instanceof SettingsListener );
$listener->listen_for_merchant_id();
try {
$listener->listen_for_vaulting_enabled();
$listener->listen_for_tracking_enabled();
} catch ( RuntimeException $exception ) {
add_action(
'admin_notices',
function () use ( $exception ) {
printf(
'<div class="notice notice-error"><p>%1$s</p><p>%2$s</p></div>',
esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
wp_kses_post(
__(
'Please verify your API Credentials and try again to connect your PayPal business account. Visit the <a href="https://docs.woocommerce.com/document/woocommerce-paypal-payments/" target="_blank">plugin documentation</a> for more information about the setup.',
'woocommerce-paypal-payments'
)
)
);
}
);
}
}
);

View file

@ -1028,12 +1028,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*":
version "0.0.50"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
"@types/estree@^0.0.51":
"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@ -1201,12 +1196,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -1299,18 +1289,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.14.5:
version "4.17.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
dependencies:
caniuse-lite "^1.0.30001259"
electron-to-chromium "^1.3.846"
escalade "^3.1.1"
nanocolors "^0.1.5"
node-releases "^1.1.76"
browserslist@^4.20.2, browserslist@^4.21.3:
browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
@ -1333,11 +1312,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
caniuse-lite@^1.0.30001259:
version "1.0.30001261"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
caniuse-lite@^1.0.30001370:
version "1.0.30001393"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
@ -1462,11 +1436,6 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
electron-to-chromium@^1.3.846:
version "1.3.853"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
electron-to-chromium@^1.4.202:
version "1.4.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
@ -1628,12 +1597,7 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.9:
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -1760,17 +1724,10 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
json5@^2.1.2, json5@^2.2.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1788,9 +1745,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -1832,31 +1789,16 @@ mime-types@^2.1.27:
dependencies:
mime-db "1.49.0"
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanocolors@^0.1.5:
version "0.1.12"
resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-releases@^1.1.76:
version "1.1.76"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@ -2294,10 +2236,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.74",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {

View file

@ -1,3 +1,5 @@
import {setVisibleByClass} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"
document.addEventListener(
'DOMContentLoaded',
() => {
@ -147,5 +149,25 @@ document.addEventListener(
simulateBtn.prop('disabled', false);
}
});
const sandboxCheckbox = document.querySelector('#ppcp-sandbox_on');
if (sandboxCheckbox) {
const setWebhooksVisibility = (show) => {
[
'#field-webhook_status_heading',
'#field-webhooks_list',
'#field-webhooks_resubscribe',
'#field-webhooks_simulate',
].forEach(selector => {
setVisibleByClass(selector, show, 'hide');
});
};
const serverSandboxState = PayPalCommerceGatewayWebhooksStatus.environment === 'sandbox';
setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked);
sandboxCheckbox.addEventListener('click', () => {
setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked);
});
}
}
);

View file

@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultCreditCardCreated;
use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultPaymentTokenCreated;
use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultPaymentTokenDeleted;
use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
@ -73,6 +74,8 @@ return array(
$prefix = $container->get( 'api.prefix' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
$payment_token_factory = $container->get( 'vaulting.payment-token-factory' );
return array(
new CheckoutOrderApproved( $logger, $prefix, $order_endpoint ),
new CheckoutOrderCompleted( $logger, $prefix ),
@ -80,8 +83,8 @@ return array(
new PaymentCaptureRefunded( $logger, $prefix ),
new PaymentCaptureReversed( $logger, $prefix ),
new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ),
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ),
new VaultCreditCardCreated( $logger, $prefix ),
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory ),
new VaultPaymentTokenDeleted( $logger ),
new PaymentCapturePending( $logger ),
);
},
@ -172,7 +175,8 @@ return array(
'webhook.status.assets' => function( ContainerInterface $container ) : WebhooksStatusPageAssets {
return new WebhooksStatusPageAssets(
$container->get( 'webhook.module-url' ),
$container->get( 'ppcp.asset-version' )
$container->get( 'ppcp.asset-version' ),
$container->get( 'onboarding.environment' )
);
},
@ -203,8 +207,8 @@ return array(
);
},
'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookInfoStorage {
return new WebhookInfoStorage( $container->get( 'webhook.last-webhook-storage.key' ) );
'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookEventStorage {
return new WebhookEventStorage( $container->get( 'webhook.last-webhook-storage.key' ) );
},
'webhook.last-webhook-storage.key' => static function ( ContainerInterface $container ): string {
return 'ppcp-last-webhook';

View file

@ -62,8 +62,6 @@ class ResubscribeEndpoint {
// Validate nonce.
$this->request_data->read_request( $this->nonce() );
$this->registrar->unregister();
if ( ! $this->registrar->register() ) {
wp_send_json_error( 'Webhook subscription failed.', 500 );
return false;

View file

@ -10,6 +10,12 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WP_REST_Request;
use WP_REST_Response;
@ -40,17 +46,31 @@ class VaultPaymentTokenCreated implements RequestHandler {
*/
protected $authorized_payments_processor;
/**
* The payment token factory.
*
* @var PaymentTokenFactory
*/
protected $payment_token_factory;
/**
* VaultPaymentTokenCreated constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payment processor.
* @param PaymentTokenFactory $payment_token_factory The payment token factory.
*/
public function __construct( LoggerInterface $logger, string $prefix, AuthorizedPaymentsProcessor $authorized_payments_processor ) {
public function __construct(
LoggerInterface $logger,
string $prefix,
AuthorizedPaymentsProcessor $authorized_payments_processor,
PaymentTokenFactory $payment_token_factory
) {
$this->logger = $logger;
$this->prefix = $prefix;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->payment_token_factory = $payment_token_factory;
}
/**
@ -98,6 +118,38 @@ class VaultPaymentTokenCreated implements RequestHandler {
$wc_customer_id = (int) str_replace( $this->prefix, '', $customer_id );
$this->authorized_payments_processor->capture_authorized_payments_for_customer( $wc_customer_id );
if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
if ( ! is_null( $request['resource']['source'] ) && isset( $request['resource']['source']['card'] ) ) {
$token = new WC_Payment_Token_CC();
$token->set_token( $request['resource']['id'] );
$token->set_user_id( $wc_customer_id );
$token->set_gateway_id( CreditCardGateway::ID );
$token->set_last4( $request['resource']['source']['card']['last_digits'] ?? '' );
$expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' );
$token->set_expiry_year( $expiry[0] ?? '' );
$token->set_expiry_month( $expiry[1] ?? '' );
$token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' );
$token->save();
WC_Payment_Tokens::set_users_default( $wc_customer_id, $token->get_id() );
} elseif ( isset( $request['resource']['source']['paypal'] ) ) {
$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
assert( $payment_token_paypal instanceof PaymentTokenPayPal );
$payment_token_paypal->set_token( $request['resource']['id'] );
$payment_token_paypal->set_user_id( $wc_customer_id );
$payment_token_paypal->set_gateway_id( PayPalGateway::ID );
$email = $request['resource']['source']['paypal']['payer']['email_address'] ?? '';
if ( $email && is_email( $email ) ) {
$payment_token_paypal->set_email( $email );
}
$payment_token_paypal->save();
WC_Payment_Tokens::set_users_default( $wc_customer_id, $payment_token_paypal->get_id() );
}
}
$response['success'] = true;
return new WP_REST_Response( $response );
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Handles the Webhook VAULT.CREDIT-CARD.CREATED
* Handles the Webhook VAULT.PAYMENT-TOKEN.DELETED
*
* @package WooCommerce\PayPalCommerce\Webhooks\Handler
*/
@ -10,37 +10,29 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
use WC_Payment_Tokens;
use WP_REST_Request;
use WP_REST_Response;
/**
* Class VaultCreditCardCreated
* Class VaultPaymentTokenDeleted
*/
class VaultCreditCardCreated implements RequestHandler {
class VaultPaymentTokenDeleted implements RequestHandler {
/**
* The logger.
*
* @var LoggerInterface
*/
protected $logger;
private $logger;
/**
* The prefix.
*
* @var string
*/
protected $prefix;
/**
* VaultCreditCardCreated constructor.
* VaultPaymentTokenDeleted 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;
}
/**
@ -50,7 +42,7 @@ class VaultCreditCardCreated implements RequestHandler {
*/
public function event_types(): array {
return array(
'VAULT.CREDIT-CARD.CREATED',
'VAULT.PAYMENT-TOKEN.DELETED',
);
}
@ -73,15 +65,31 @@ class VaultCreditCardCreated implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
// TODO currently this webhook is not triggered from PayPal, implement it once is available.
$response = array( 'success' => false );
$message = 'VAULT.CREDIT-CARD.CREATED received.';
$this->logger->log( 'info', $message );
$response = array(
'success' => true,
'message' => $message,
if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
$token_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
/**
* Needed for database query.
*
* @psalm-suppress InvalidGlobal
*/
global $wpdb;
$token = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token=%s",
$token_id
)
);
if ( isset( $token->token_id ) ) {
WC_Payment_Tokens::delete( $token->token_id );
}
}
$response['success'] = true;
return new WP_REST_Response( $response );
}
}

View file

@ -77,11 +77,11 @@ class IncomingWebhookEndpoint {
private $simulation;
/**
* The last webhook info storage.
* The last webhook event storage.
*
* @var WebhookInfoStorage
* @var WebhookEventStorage
*/
private $last_webhook_storage;
private $last_webhook_event_storage;
/**
* IncomingWebhookEndpoint constructor.
@ -92,7 +92,7 @@ class IncomingWebhookEndpoint {
* @param bool $verify_request Whether requests need to be verified or not.
* @param WebhookEventFactory $webhook_event_factory The webhook event factory.
* @param WebhookSimulation $simulation The simulation handler.
* @param WebhookInfoStorage $last_webhook_storage The last webhook info storage.
* @param WebhookEventStorage $last_webhook_event_storage The last webhook event storage.
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
*/
public function __construct(
@ -102,7 +102,7 @@ class IncomingWebhookEndpoint {
bool $verify_request,
WebhookEventFactory $webhook_event_factory,
WebhookSimulation $simulation,
WebhookInfoStorage $last_webhook_storage,
WebhookEventStorage $last_webhook_event_storage,
RequestHandler ...$handlers
) {
@ -112,7 +112,7 @@ class IncomingWebhookEndpoint {
$this->logger = $logger;
$this->verify_request = $verify_request;
$this->webhook_event_factory = $webhook_event_factory;
$this->last_webhook_storage = $last_webhook_storage;
$this->last_webhook_event_storage = $last_webhook_event_storage;
$this->simulation = $simulation;
}
@ -186,7 +186,7 @@ class IncomingWebhookEndpoint {
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$event = $this->event_from_request( $request );
$this->last_webhook_storage->save( $event );
$this->last_webhook_event_storage->save( $event );
if ( $this->simulation->is_simulation_event( $event ) ) {
$this->logger->info( 'Received simulated webhook.' );

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Status\Assets;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
@ -33,18 +34,28 @@ class WebhooksStatusPageAssets {
*/
private $version;
/**
* The environment object.
*
* @var Environment
*/
private $environment;
/**
* WebhooksStatusPageAssets constructor.
*
* @param string $module_url The URL to the module.
* @param string $version The assets version.
* @param Environment $environment The environment object.
*/
public function __construct(
string $module_url,
string $version
string $version,
Environment $environment
) {
$this->module_url = untrailingslashit( $module_url );
$this->version = $version;
$this->environment = $environment;
}
/**
@ -103,6 +114,7 @@ class WebhooksStatusPageAssets {
'tooLongDelayMessage' => __( 'Looks like the webhook cannot be received. Check that your website is accessible from the internet.', 'woocommerce-paypal-payments' ),
),
),
'environment' => $this->environment->current_environment(),
);
}

View file

@ -12,9 +12,9 @@ namespace WooCommerce\PayPalCommerce\Webhooks;
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
/**
* Class WebhookInfoStorage
* Class WebhookEventStorage
*/
class WebhookInfoStorage {
class WebhookEventStorage {
/**
* The WP option key.

View file

@ -153,7 +153,6 @@ class WebhookModule implements ModuleInterface {
add_action(
'init',
function () use ( $registrar ) {
$registrar->unregister();
$registrar->register();
}
);

View file

@ -45,11 +45,11 @@ class WebhookRegistrar {
private $rest_endpoint;
/**
* The last webhook info storage.
* The last webhook event storage.
*
* @var WebhookInfoStorage
* @var WebhookEventStorage
*/
private $last_webhook_storage;
private $last_webhook_event_storage;
/**
* The logger.
@ -64,21 +64,21 @@ class WebhookRegistrar {
* @param WebhookFactory $webhook_factory The Webhook factory.
* @param WebhookEndpoint $endpoint The Webhook endpoint.
* @param IncomingWebhookEndpoint $rest_endpoint The WordPress Rest API endpoint.
* @param WebhookInfoStorage $last_webhook_storage The last webhook info storage.
* @param WebhookEventStorage $last_webhook_event_storage The last webhook event storage.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
WebhookFactory $webhook_factory,
WebhookEndpoint $endpoint,
IncomingWebhookEndpoint $rest_endpoint,
WebhookInfoStorage $last_webhook_storage,
WebhookEventStorage $last_webhook_event_storage,
LoggerInterface $logger
) {
$this->webhook_factory = $webhook_factory;
$this->endpoint = $endpoint;
$this->rest_endpoint = $rest_endpoint;
$this->last_webhook_storage = $last_webhook_storage;
$this->last_webhook_event_storage = $last_webhook_event_storage;
$this->logger = $logger;
}
@ -88,6 +88,8 @@ class WebhookRegistrar {
* @return bool
*/
public function register(): bool {
$this->unregister();
$webhook = $this->webhook_factory->for_url_and_events(
$this->rest_endpoint->url(),
$this->rest_endpoint->handled_event_types()
@ -102,7 +104,7 @@ class WebhookRegistrar {
self::KEY,
$created->to_array()
);
$this->last_webhook_storage->clear();
$this->last_webhook_event_storage->clear();
$this->logger->info( 'Webhooks subscribed.' );
return true;
} catch ( RuntimeException $error ) {
@ -113,27 +115,23 @@ class WebhookRegistrar {
/**
* Unregister webhooks with PayPal.
*
* @return bool
*/
public function unregister(): bool {
$data = (array) get_option( self::KEY, array() );
if ( ! $data ) {
return false;
}
public function unregister(): void {
try {
$webhook = $this->webhook_factory->from_array( $data );
$success = $this->endpoint->delete( $webhook );
$webhooks = $this->endpoint->list();
foreach ( $webhooks as $webhook ) {
try {
$this->endpoint->delete( $webhook );
} catch ( RuntimeException $deletion_error ) {
$this->logger->error( "Failed to delete webhook {$webhook->id()}: {$deletion_error->getMessage()}" );
}
}
} catch ( RuntimeException $error ) {
$this->logger->error( 'Failed to delete webhooks: ' . $error->getMessage() );
return false;
}
if ( $success ) {
delete_option( self::KEY );
$this->last_webhook_storage->clear();
$this->last_webhook_event_storage->clear();
$this->logger->info( 'Webhooks deleted.' );
}
return $success;
}
}

View file

@ -1021,12 +1021,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*":
version "0.0.50"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
"@types/estree@^0.0.51":
"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@ -1194,12 +1189,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
acorn@^8.5.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@ -1292,18 +1282,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.14.5:
version "4.17.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
dependencies:
caniuse-lite "^1.0.30001259"
electron-to-chromium "^1.3.846"
escalade "^3.1.1"
nanocolors "^0.1.5"
node-releases "^1.1.76"
browserslist@^4.20.2, browserslist@^4.21.3:
browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
@ -1326,11 +1305,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
caniuse-lite@^1.0.30001259:
version "1.0.30001261"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
caniuse-lite@^1.0.30001370:
version "1.0.30001393"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
@ -1455,11 +1429,6 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
electron-to-chromium@^1.3.846:
version "1.3.853"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
electron-to-chromium@^1.4.202:
version "1.4.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
@ -1621,12 +1590,7 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
graceful-fs@^4.1.2, graceful-fs@^4.2.4:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.9:
graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@ -1748,17 +1712,10 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
json5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
json5@^2.1.2, json5@^2.2.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kind-of@^6.0.2:
version "6.0.3"
@ -1776,9 +1733,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -1820,31 +1777,16 @@ mime-types@^2.1.27:
dependencies:
mime-db "1.49.0"
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanocolors@^0.1.5:
version "0.1.12"
resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-releases@^1.1.76:
version "1.1.76"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@ -2270,10 +2212,10 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74:
version "5.74.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
webpack@^5.76:
version "5.76.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"

View file

@ -1,6 +1,6 @@
{
"name": "woocommerce-paypal-payments",
"version": "2.0.2",
"version": "2.0.4",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",
@ -10,7 +10,6 @@
"install:modules:ppcp-button": "cd modules/ppcp-button && yarn install",
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install",
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install",
"install:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn install",
"install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install",
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
@ -18,7 +17,6 @@
"build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build",
"build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build",
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build",
"build:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn run build",
"build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build",
"build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build",
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
@ -27,7 +25,6 @@
"watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch",
"watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch",
"watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch",
"watch:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn run watch",
"watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch",
"watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch",
"watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",
@ -43,10 +40,9 @@
"ddev:composer-update": "ddev composer update && ddev composer update --lock",
"ddev:unit-tests": "ddev exec phpunit",
"ddev:e2e-tests": "cp -n .env.e2e.example .env.e2e && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist",
"ddev:pw-install": "ddev exec npx playwright install --with-deps",
"ddev:pw-tests-ci": "ddev exec npx playwright test --grep @ci",
"ddev:pw-tests-headed": "ddev exec npx playwright test --headed",
"ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests && yarn run ddev:pw-tests-ci",
"ddev:pw-install": "ddev yarn playwright install --with-deps",
"ddev:pw-tests": "ddev yarn playwright test",
"ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests && yarn run ddev:pw-tests",
"ddev:lint": "yarn ddev:phpcs && yarn ddev:psalm",
"ddev:phpcs": "ddev exec phpcs --parallel=8 -s",
"ddev:psalm": "ddev exec psalm --show-info=false --threads=8 --diff",
@ -54,6 +50,9 @@
"ddev:xdebug-on": "ddev xdebug",
"ddev:xdebug-off": "ddev xdebug",
"ddev:build-package": "ddev yarn build",
"pw-install": "yarn playwright install --with-deps",
"pw-tests": "yarn playwright test",
"pw-tests-headed": "yarn playwright test --headed",
"prebuild": "rm -rf ./vendor && find . -name 'node_modules' -type d -maxdepth 3 -exec rm -rf {} +",
"build": "composer install --no-dev && yarn install && yarn run archive",
"prearchive": "rm -rf $npm_package_name.zip",

View file

@ -42,6 +42,7 @@
</rule>
<arg name="extensions" value="php"/>
<file>api</file>
<file>src</file>
<file>modules</file>
<file>woocommerce-paypal-payments.php</file>

View file

@ -2,9 +2,10 @@ require('dotenv').config({ path: '.env.e2e' });
const config = {
testDir: './tests/playwright',
timeout: 30000,
timeout: 60000,
use: {
baseURL: process.env.BASEURL,
ignoreHTTPSErrors: true,
},
};

View file

@ -23,8 +23,11 @@
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="api"/>
<directory name="src"/>
<directory name="modules" />
<file name="bootstrap.php" />
<file name="woocommerce-paypal-payments.php" />
</projectFiles>
<stubs>

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell,
Requires at least: 5.3
Tested up to: 6.1
Requires PHP: 7.2
Stable tag: 2.0.3
Stable tag: 2.0.4
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -81,7 +81,19 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog ==
= 2.0.3 =
= 2.0.4 - 2023-04-03 =
* Fix - Allow Pay Later in mini-cart #1221
* Fix - Duplicated auth error when credentials become wrong #1229
* Fix - Webhook issues when switching sandbox, and delete all webhooks when unsubscribing #1239
* Fix - High volume of traffic from merchant-integrations endpoint #1273
* Fix - Add content type json to all fetch ajax endpoints #1275
* Enhancement - Remove shortcodes from description #1226
* Enhancement - Handle price suffix with price for product button check #1234
* Enhancement - Show funding source as payment method #1220
* Enhancement - Change "Enabled" to "Available" in status text #1237
* Enhancement - Programmatically capturing/voiding authorized payments #590
= 2.0.3 - 2023-03-14 =
* Fix - `DEVICE_DATA_NOT_AVAILABLE` error message when FraudNet is enabled #1177
* Fix - Redirect to connection tab after manual credentials input #1201
* Fix - Asking for address fields in checkout when not using them #1089
@ -92,6 +104,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - PPEC compatibility layer does not take over subscriptions #1193
* Fix - Checkout conflict with "All products for subscriptions" plugin #629
* Fix - Pay Later on order pay page #1214
* Fix - High volume of traffic from merchant-integrations endpoint #1241
* Enhancement - Save checkout form before free trial redirect #1135
* Enhancement - Add filter for controlling the ditching of items/breakdown #1146
* Enhancement - Add patch order data filter #1147

View file

@ -58,10 +58,9 @@ class FilePathPluginFactory implements FilePathPluginFactoryInterface {
if ( ! function_exists( 'get_plugin_data' ) ) {
/**
* Skip check for WP files and constants.
* Skip check for WP files.
*
* @psalm-suppress UnresolvableInclude
* @psalm-suppress UndefinedConstant
* @psalm-suppress MissingFile
*/
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}

View file

@ -0,0 +1,22 @@
<?php
/**
* HTTP redirection.
*
* @package WooCommerce\PayPalCommerce\Api
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Http;
/**
* Interface for HTTP redirection.
*/
interface RedirectorInterface {
/**
* Starts HTTP redirection and shutdowns.
*
* @param string $location The URL to redirect to.
*/
public function redirect( string $location): void;
}

25
src/Http/WpRedirector.php Normal file
View file

@ -0,0 +1,25 @@
<?php
/**
* HTTP redirection.
*
* @package WooCommerce\PayPalCommerce\Api
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Http;
/**
* Wrapper for HTTP redirection via wp_safe_redirect.
*/
class WpRedirector implements RedirectorInterface {
/**
* Starts HTTP redirection and shutdowns.
*
* @param string $location The URL to redirect to.
*/
public function redirect( string $location ): void {
wp_safe_redirect( $location, 302 );
exit;
}
}

48
src/PPCP.php Normal file
View file

@ -0,0 +1,48 @@
<?php
/**
* Internal global data.
*
* @package WooCommerce\PayPalCommerce
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce;
use LogicException;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* Internal global data.
*/
class PPCP {
/**
* The container with services of the application modules.
*
* @var ContainerInterface|null
*/
private static $container = null;
/**
* The container with services of the application modules.
* Mainly for internal usage.
* The compatibility between different versions of the plugins is not guaranteed.
*
* @throws LogicException When no container.
*/
public static function container(): ContainerInterface {
if ( ! self::$container ) {
throw new LogicException( 'No PPCP container, probably called too early when the plugin is not initialized yet.' );
}
return self::$container;
}
/**
* Init the data.
*
* @param ContainerInterface $container The app container.
*/
public static function init( ContainerInterface $container ): void {
self::$container = $container;
}
}

View file

@ -10,6 +10,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce;
use Dhii\Versions\StringVersionFactory;
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
use WooCommerce\PayPalCommerce\Http\WpRedirector;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WpOop\WordPress\Plugin\PluginInterface;
@ -24,4 +26,8 @@ return array(
return (string) $plugin->getVersion();
},
'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface {
return new WpRedirector();
},
);

View file

@ -0,0 +1,83 @@
<?php
namespace WooCommerce\PayPalCommerce\Api;
use InvalidArgumentException;
use Mockery;
use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
class GetOrderTest extends ModularTestCase
{
private $orderEndpoint;
public function setUp(): void {
parent::setUp();
$this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->bootstrapModule([
'api.endpoint.order' => function () {
return $this->orderEndpoint;
},
]);
}
public function testSuccess(): void {
$this->orderEndpoint
->expects('order')
->with('123abc')
->andReturn(Mockery::mock(Order::class))
->once();
ppcp_get_paypal_order('123abc');
}
public function testSuccessWithOrder(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn('123abc');
$this->orderEndpoint
->expects('order')
->with('123abc')
->andReturn(Mockery::mock(Order::class))
->once();
ppcp_get_paypal_order($wcOrder);
}
public function testOrderWithoutId(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn(false);
$this->expectException(InvalidArgumentException::class);
ppcp_get_paypal_order($wcOrder);
}
public function testFailure(): void {
$this->orderEndpoint
->expects('order')
->with('123abc')
->andThrow(new RuntimeException())
->once();
$this->expectException(RuntimeException::class);
ppcp_get_paypal_order('123abc');
}
public function testInvalidId(): void {
$this->expectException(InvalidArgumentException::class);
ppcp_get_paypal_order(123);
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace WooCommerce\PayPalCommerce\Api;
use InvalidArgumentException;
use Mockery;
use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
class OrderCaptureTest extends ModularTestCase
{
private $authorizedPaymentProcessor;
public function setUp(): void {
parent::setUp();
$this->authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$this->bootstrapModule([
'wcgateway.processor.authorized-payments' => function () {
return $this->authorizedPaymentProcessor;
},
]);
}
public function testSuccess(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::INTENT_META_KEY)
->andReturn('AUTHORIZE');
$wcOrder->expects('get_meta')
->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
->andReturn(false);
$this->authorizedPaymentProcessor
->expects('capture_authorized_payment')
->andReturnTrue()
->once();
ppcp_capture_order($wcOrder);
}
public function testFailure(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::INTENT_META_KEY)
->andReturn('AUTHORIZE');
$wcOrder->expects('get_meta')
->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
->andReturn(false);
$this->authorizedPaymentProcessor
->expects('capture_authorized_payment')
->andReturnFalse()
->once();
$this->expectException(RuntimeException::class);
ppcp_capture_order($wcOrder);
}
public function testNotAuthorize(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->shouldReceive('get_meta')
->with(PayPalGateway::INTENT_META_KEY)
->andReturn('CAPTURE');
$wcOrder->shouldReceive('get_meta')
->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
->andReturn(false);
$this->expectException(InvalidArgumentException::class);
ppcp_capture_order($wcOrder);
}
public function testAlreadyCaptured(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->shouldReceive('get_meta')
->with(PayPalGateway::INTENT_META_KEY)
->andReturn('CAPTURE');
$wcOrder->shouldReceive('get_meta')
->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
->andReturn(true);
$this->expectException(InvalidArgumentException::class);
ppcp_capture_order($wcOrder);
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace WooCommerce\PayPalCommerce\Api;
use InvalidArgumentException;
use Mockery;
use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
class OrderRefundTest extends ModularTestCase
{
private $refundProcessor;
private $orderEndpoint;
public function setUp(): void {
parent::setUp();
$this->refundProcessor = Mockery::mock(RefundProcessor::class);
$this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->bootstrapModule([
'wcgateway.processor.refunds' => function () {
return $this->refundProcessor;
},
'api.endpoint.order' => function () {
return $this->orderEndpoint;
},
]);
}
public function testSuccess(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn('123abc');
$this->orderEndpoint
->expects('order')
->with('123abc')
->andReturn(Mockery::mock(Order::class))
->once();
$this->refundProcessor
->expects('refund')
->andReturn('456qwe')
->once();
$refund_id = ppcp_refund_order($wcOrder, 42.0, 'reason');
$this->assertEquals('456qwe', $refund_id);
}
public function testOrderWithoutId(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn(false);
$this->expectException(InvalidArgumentException::class);
ppcp_refund_order($wcOrder, 42.0, 'reason');
}
public function testFailure(): void {
$wcOrder = Mockery::mock(WC_Order::class);
$wcOrder->expects('get_meta')
->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn('123abc');
$this->orderEndpoint
->expects('order')
->with('123abc')
->andReturn(Mockery::mock(Order::class))
->once();
$this->refundProcessor
->expects('refund')
->andThrow(new RuntimeException())
->once();
$this->expectException(RuntimeException::class);
ppcp_refund_order($wcOrder, 42.0, 'reason');
}
}

Some files were not shown because too many files have changed in this diff Show more