Merge branch 'trunk' into pcp-157-blocks

This commit is contained in:
Alex P 2023-03-17 15:53:53 +02:00
commit fa0afe381a
No known key found for this signature in database
GPG key ID: 54487A734A204D71
41 changed files with 502 additions and 196 deletions

View file

@ -16,10 +16,13 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return function ( return function (
string $root_dir, string $root_dir,
ContainerInterface ...$additional_containers array $additional_containers = array(),
array $additional_modules = array()
): ContainerInterface { ): ContainerInterface {
$modules = ( require "$root_dir/modules.php" )( $root_dir ); $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. * Use this filter to add custom module or remove some of existing ones.
* Modules able to access container, add services and modify existing ones. * Modules able to access container, add services and modify existing ones.

View file

@ -175,12 +175,12 @@ class WebhookEndpoint {
* *
* @param Webhook $hook The webhook to delete. * @param Webhook $hook The webhook to delete.
* *
* @return bool
* @throws RuntimeException If the request fails. * @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() ) { if ( ! $hook->id() ) {
return false; return;
} }
$bearer = $this->bearer->bearer(); $bearer = $this->bearer->bearer();
@ -198,7 +198,18 @@ class WebhookEndpoint {
__( 'Not able to delete the webhook.', 'woocommerce-paypal-payments' ) __( '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 ), mb_substr( $product->get_name(), 0, 127 ),
new Money( $price, $this->currency ), new Money( $price, $this->currency ),
$quantity, $quantity,
substr( wp_strip_all_tags( $product->get_description() ), 0, 127 ) ?: '', $this->prepare_description( $product->get_description() ),
null, null,
$product->get_sku(), $product->get_sku(),
( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS ( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
@ -130,7 +130,7 @@ class ItemFactory {
mb_substr( $item->get_name(), 0, 127 ), mb_substr( $item->get_name(), 0, 127 ),
new Money( $price_without_tax_rounded, $currency ), new Money( $price_without_tax_rounded, $currency ),
$quantity, $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, null,
$product instanceof WC_Product ? $product->get_sku() : '', $product instanceof WC_Product ? $product->get_sku() : '',
( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS ( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
@ -198,4 +198,15 @@ class ItemFactory {
$category $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

@ -69,9 +69,14 @@ class SingleProductBootstap {
() => { () => {
const priceEl = document.querySelector('.product .woocommerce-Price-amount'); const priceEl = document.querySelector('.product .woocommerce-Price-amount');
// variable products show price like 10.00 - 20.00 here // 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 priceEl.innerText;
} }
}
return null; return null;
}, },
].map(f => f()).find(val => val); ].map(f => f()).find(val => val);

View file

@ -1723,9 +1723,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.2" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

@ -184,14 +184,14 @@ class SubscriptionsHandler {
} }
// Are we on the WC > Subscriptions screen? // 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'] ?? '' ) ); $post_type = wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) );
if ( $post_type === 'shop_subscription' ) { if ( $post_type === 'shop_subscription' ) {
return true; return true;
} }
// Are we editing an order or subscription tied to PPEC? // 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'] ?? '' ) ); $order_id = wc_clean( wp_unslash( $_GET['post'] ?? $_POST['post_ID'] ?? '' ) );
if ( $order_id ) { if ( $order_id ) {
$order = wc_get_order( $order_id ); $order = wc_get_order( $order_id );

View file

@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.2" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

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

View file

@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.2" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

@ -1805,9 +1805,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.2" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

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

View file

@ -1805,9 +1805,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.2" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

@ -1776,9 +1776,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.0" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

View file

@ -336,7 +336,8 @@ return array(
$signup_link_cache, $signup_link_cache,
$signup_link_ids, $signup_link_ids,
$pui_status_cache, $pui_status_cache,
$dcc_status_cache $dcc_status_cache,
$container->get( 'http.redirector' )
); );
}, },
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
@ -644,7 +645,7 @@ return array(
>', >',
'</a>' '</a>'
), ),
'options' => $container->get( 'wcgateway.all-funding-sources' ), 'options' => $container->get( 'wcgateway.settings.funding-sources' ),
'screens' => array( 'screens' => array(
State::STATE_START, State::STATE_START,
State::STATE_ONBOARDED, State::STATE_ONBOARDED,
@ -881,6 +882,17 @@ return array(
'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ), 'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', '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' ), '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',
)
)
); );
}, },
@ -952,9 +964,11 @@ return array(
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer { 'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new 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 { 'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper(); return new CheckoutHelper();
}, },
@ -1256,7 +1270,7 @@ return array(
$dcc_enabled = $dcc_product_status->dcc_is_active(); $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' ); $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$dcc_button_text = $dcc_enabled $dcc_button_text = $dcc_enabled
@ -1289,7 +1303,7 @@ return array(
$pui_enabled = $pui_product_status->pui_is_active(); $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' ); $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$enable_pui_url = $environment->current_environment_is( Environment::PRODUCTION ) $enable_pui_url = $environment->current_environment_is( Environment::PRODUCTION )
@ -1340,24 +1354,10 @@ return array(
assert( $settings instanceof Settings ); assert( $settings instanceof Settings );
$button_locations = $container->get( 'wcgateway.button.locations' ); $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(); $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 array_intersect_key( $button_locations, array_flip( $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;
}, },
'wcgateway.ppcp-gateways' => static function ( ContainerInterface $container ): array { 'wcgateway.ppcp-gateways' => static function ( ContainerInterface $container ): array {
return array( return array(

View file

@ -22,13 +22,32 @@ class FundingSourceRenderer {
*/ */
protected $settings; 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. * FundingSourceRenderer constructor.
* *
* @param ContainerInterface $settings The settings. * @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->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'. * @param string $id The ID of the funding source, such as 'venmo'.
*/ */
public function render_name( string $id ): string { public function render_name( string $id ): string {
if ( 'venmo' === $id ) { if ( array_key_exists( $id, $this->funding_sources ) ) {
return __( 'Venmo', 'woocommerce-paypal-payments' ); 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' ) ? return $this->settings->has( 'title' ) ?
$this->settings->get( 'title' ) $this->settings->get( 'title' )
: __( 'PayPal', 'woocommerce-paypal-payments' ); : __( 'PayPal', 'woocommerce-paypal-payments' );
@ -51,9 +78,14 @@ class FundingSourceRenderer {
* @param string $id The ID of the funding source, such as 'venmo'. * @param string $id The ID of the funding source, such as 'venmo'.
*/ */
public function render_description( string $id ): string { public function render_description( string $id ): string {
if ( 'venmo' === $id ) { if ( array_key_exists( $id, $this->funding_sources ) ) {
return __( 'Pay via Venmo.', 'woocommerce-paypal-payments' ); 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' ) ? return $this->settings->has( 'description' ) ?
$this->settings->get( 'description' ) $this->settings->get( 'description' )
: __( 'Pay via PayPal.', 'woocommerce-paypal-payments' ); : __( 'Pay via PayPal.', 'woocommerce-paypal-payments' );

View file

@ -9,7 +9,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper; namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
@ -74,7 +73,9 @@ class SettingsStatus {
* @return bool true if is enabled, otherwise false. * @return bool true if is enabled, otherwise false.
*/ */
public function is_pay_later_button_enabled_for_location( string $location ): bool { 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

@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
@ -111,6 +112,13 @@ class SettingsListener {
*/ */
protected $dcc_status_cache; protected $dcc_status_cache;
/**
* The HTTP redirector.
*
* @var RedirectorInterface
*/
protected $redirector;
/** /**
* SettingsListener constructor. * SettingsListener constructor.
* *
@ -125,6 +133,7 @@ class SettingsListener {
* @param array $signup_link_ids Signup link ids. * @param array $signup_link_ids Signup link ids.
* @param Cache $pui_status_cache The PUI status cache. * @param Cache $pui_status_cache The PUI status cache.
* @param Cache $dcc_status_cache The DCC status cache. * @param Cache $dcc_status_cache The DCC status cache.
* @param RedirectorInterface $redirector The HTTP redirector.
*/ */
public function __construct( public function __construct(
Settings $settings, Settings $settings,
@ -137,7 +146,8 @@ class SettingsListener {
Cache $signup_link_cache, Cache $signup_link_cache,
array $signup_link_ids, array $signup_link_ids,
Cache $pui_status_cache, Cache $pui_status_cache,
Cache $dcc_status_cache Cache $dcc_status_cache,
RedirectorInterface $redirector
) { ) {
$this->settings = $settings; $this->settings = $settings;
@ -151,6 +161,7 @@ class SettingsListener {
$this->signup_link_ids = $signup_link_ids; $this->signup_link_ids = $signup_link_ids;
$this->pui_status_cache = $pui_status_cache; $this->pui_status_cache = $pui_status_cache;
$this->dcc_status_cache = $dcc_status_cache; $this->dcc_status_cache = $dcc_status_cache;
$this->redirector = $redirector;
} }
/** /**
@ -198,12 +209,13 @@ class SettingsListener {
$redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url ); $redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
} }
wp_safe_redirect( $redirect_url, 302 ); $this->redirector->redirect( $redirect_url );
exit;
} }
/** /**
* Prevent enabling both Pay Later messaging and PayPal vaulting * Prevent enabling both Pay Later messaging and PayPal vaulting
*
* @throws RuntimeException When API request fails.
*/ */
public function listen_for_vaulting_enabled() { public function listen_for_vaulting_enabled() {
if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) { if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) {
@ -221,16 +233,7 @@ class SettingsListener {
$this->settings->set( 'vault_enabled', false ); $this->settings->set( 'vault_enabled', false );
$this->settings->persist(); $this->settings->persist();
add_action( throw $exception;
'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' ) )
);
}
);
} }
/** /**
@ -322,16 +325,6 @@ class SettingsListener {
} }
$this->settings->persist(); $this->settings->persist();
if ( $credentials_change_status ) {
if ( in_array(
$credentials_change_status,
array( self::CREDENTIALS_ADDED, self::CREDENTIALS_CHANGED ),
true
) ) {
$this->webhook_registrar->register();
}
}
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY ); $this->cache->delete( PayPalBearer::CACHE_KEY );
} }
@ -345,7 +338,7 @@ class SettingsListener {
} }
$redirect_url = false; $redirect_url = false;
if ( self::CREDENTIALS_ADDED === $credentials_change_status ) { if ( self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) {
$redirect_url = $this->get_onboarding_redirect_url(); $redirect_url = $this->get_onboarding_redirect_url();
} }
@ -354,8 +347,7 @@ class SettingsListener {
} }
if ( $redirect_url ) { if ( $redirect_url ) {
wp_safe_redirect( $redirect_url, 302 ); $this->redirector->redirect( $redirect_url );
exit;
} }
// phpcs:enable WordPress.Security.NonceVerification.Missing // phpcs:enable WordPress.Security.NonceVerification.Missing
@ -532,6 +524,8 @@ class SettingsListener {
/** /**
* Prevent enabling tracking if it is not enabled for merchant account. * Prevent enabling tracking if it is not enabled for merchant account.
*
* @throws RuntimeException When API request fails.
*/ */
public function listen_for_tracking_enabled(): void { public function listen_for_tracking_enabled(): void {
if ( State::STATE_ONBOARDED !== $this->state->current_state() ) { if ( State::STATE_ONBOARDED !== $this->state->current_state() ) {
@ -549,16 +543,7 @@ class SettingsListener {
$this->settings->set( 'tracking_enabled', false ); $this->settings->set( 'tracking_enabled', false );
$this->settings->persist(); $this->settings->persist();
add_action( throw $exception;
'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

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Throwable; use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WC_Order; use WC_Order;
@ -179,7 +180,7 @@ class WCGatewayModule implements ModuleInterface {
$c->get( 'onboarding.environment' ), $c->get( 'onboarding.environment' ),
$settings_status->is_pay_later_button_enabled(), $settings_status->is_pay_later_button_enabled(),
$settings->has( 'disable_funding' ) ? $settings->get( 'disable_funding' ) : array(), $settings->has( 'disable_funding' ) ? $settings->get( 'disable_funding' ) : array(),
$c->get( 'wcgateway.all-funding-sources' ) $c->get( 'wcgateway.settings.funding-sources' )
); );
$assets->register_assets(); $assets->register_assets();
} }
@ -453,14 +454,30 @@ class WCGatewayModule implements ModuleInterface {
'admin_init', 'admin_init',
static function () use ( $container ) { static function () use ( $container ) {
$listener = $container->get( 'wcgateway.settings.listener' ); $listener = $container->get( 'wcgateway.settings.listener' );
/** assert( $listener instanceof SettingsListener );
* The settings listener.
*
* @var SettingsListener $listener
*/
$listener->listen_for_merchant_id(); $listener->listen_for_merchant_id();
try {
$listener->listen_for_vaulting_enabled(); $listener->listen_for_vaulting_enabled();
$listener->listen_for_tracking_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

@ -1,3 +1,5 @@
import {setVisibleByClass} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"
document.addEventListener( document.addEventListener(
'DOMContentLoaded', 'DOMContentLoaded',
() => { () => {
@ -147,5 +149,25 @@ document.addEventListener(
simulateBtn.prop('disabled', false); 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

@ -167,7 +167,8 @@ return array(
'webhook.status.assets' => function( ContainerInterface $container ) : WebhooksStatusPageAssets { 'webhook.status.assets' => function( ContainerInterface $container ) : WebhooksStatusPageAssets {
return new WebhooksStatusPageAssets( return new WebhooksStatusPageAssets(
$container->get( 'webhook.module-url' ), $container->get( 'webhook.module-url' ),
$container->get( 'ppcp.asset-version' ) $container->get( 'ppcp.asset-version' ),
$container->get( 'onboarding.environment' )
); );
}, },
@ -198,8 +199,8 @@ return array(
); );
}, },
'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookInfoStorage { 'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookEventStorage {
return new WebhookInfoStorage( $container->get( 'webhook.last-webhook-storage.key' ) ); return new WebhookEventStorage( $container->get( 'webhook.last-webhook-storage.key' ) );
}, },
'webhook.last-webhook-storage.key' => static function ( ContainerInterface $container ): string { 'webhook.last-webhook-storage.key' => static function ( ContainerInterface $container ): string {
return 'ppcp-last-webhook'; return 'ppcp-last-webhook';

View file

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

View file

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

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Status\Assets; namespace WooCommerce\PayPalCommerce\Webhooks\Status\Assets;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
@ -33,18 +34,28 @@ class WebhooksStatusPageAssets {
*/ */
private $version; private $version;
/**
* The environment object.
*
* @var Environment
*/
private $environment;
/** /**
* WebhooksStatusPageAssets constructor. * WebhooksStatusPageAssets constructor.
* *
* @param string $module_url The URL to the module. * @param string $module_url The URL to the module.
* @param string $version The assets version. * @param string $version The assets version.
* @param Environment $environment The environment object.
*/ */
public function __construct( public function __construct(
string $module_url, string $module_url,
string $version string $version,
Environment $environment
) { ) {
$this->module_url = untrailingslashit( $module_url ); $this->module_url = untrailingslashit( $module_url );
$this->version = $version; $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' ), '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; use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
/** /**
* Class WebhookInfoStorage * Class WebhookEventStorage
*/ */
class WebhookInfoStorage { class WebhookEventStorage {
/** /**
* The WP option key. * The WP option key.

View file

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

View file

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

View file

@ -1776,9 +1776,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.0" version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies: dependencies:
big.js "^5.2.2" big.js "^5.2.2"
emojis-list "^3.0.0" emojis-list "^3.0.0"

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;
}
}

View file

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

View file

@ -43,9 +43,8 @@ class ItemFactoryTest extends TestCase
->expects('get_cart_contents') ->expects('get_cart_contents')
->andReturn($items); ->andReturn($items);
expect('wp_strip_all_tags') expect('wp_strip_all_tags')->andReturnFirstArg();
->with('description') expect('strip_shortcodes')->andReturnFirstArg();
->andReturn('description');
$woocommerce = Mockery::mock(\WooCommerce::class); $woocommerce = Mockery::mock(\WooCommerce::class);
$session = Mockery::mock(\WC_Session::class); $session = Mockery::mock(\WC_Session::class);
@ -99,9 +98,8 @@ class ItemFactoryTest extends TestCase
->expects('get_cart_contents') ->expects('get_cart_contents')
->andReturn($items); ->andReturn($items);
expect('wp_strip_all_tags') expect('wp_strip_all_tags')->andReturnFirstArg();
->with('description') expect('strip_shortcodes')->andReturnFirstArg();
->andReturn('description');
$woocommerce = Mockery::mock(\WooCommerce::class); $woocommerce = Mockery::mock(\WooCommerce::class);
$session = Mockery::mock(\WC_Session::class); $session = Mockery::mock(\WC_Session::class);
@ -130,9 +128,9 @@ class ItemFactoryTest extends TestCase
$product $product
->expects('is_virtual') ->expects('is_virtual')
->andReturn(false); ->andReturn(false);
expect('wp_strip_all_tags')
->with('description') expect('wp_strip_all_tags')->andReturnFirstArg();
->andReturn('description'); expect('strip_shortcodes')->andReturnFirstArg();
$item = Mockery::mock(\WC_Order_Item_Product::class); $item = Mockery::mock(\WC_Order_Item_Product::class);
$item $item
@ -190,9 +188,8 @@ class ItemFactoryTest extends TestCase
->expects('is_virtual') ->expects('is_virtual')
->andReturn(true); ->andReturn(true);
expect('wp_strip_all_tags') expect('wp_strip_all_tags')->andReturnFirstArg();
->with('description') expect('strip_shortcodes')->andReturnFirstArg();
->andReturn('description');
$item = Mockery::mock(\WC_Order_Item_Product::class); $item = Mockery::mock(\WC_Order_Item_Product::class);
$item $item
@ -245,9 +242,8 @@ class ItemFactoryTest extends TestCase
->expects('is_virtual') ->expects('is_virtual')
->andReturn(true); ->andReturn(true);
expect('wp_strip_all_tags') expect('wp_strip_all_tags')->andReturnFirstArg();
->with($description) expect('strip_shortcodes')->andReturnFirstArg();
->andReturn(mb_substr( $description, 0, 127 ));
$item = Mockery::mock(\WC_Order_Item_Product::class); $item = Mockery::mock(\WC_Order_Item_Product::class);
$item $item

View file

@ -5,6 +5,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Exception; use Exception;
use Mockery; use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException; use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
@ -13,6 +14,8 @@ use function Brain\Monkey\Functions\expect;
class ValidateCheckoutEndpointTest extends TestCase class ValidateCheckoutEndpointTest extends TestCase
{ {
use MockeryPHPUnitIntegration;
private $requestData; private $requestData;
private $formValidator; private $formValidator;
private $logger; private $logger;

View file

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Helper;
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
class RedirectorStub implements RedirectorInterface
{
public function redirect(string $location): void
{
throw new StubRedirectionException($location);
}
}

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
class SettingsStub extends Settings
{
/**
* @var array
*/
protected $data;
/**
* @param array $data
*/
public function __construct(array $data) {
$this->data = $data;
}
public function get($id) {
if ( ! $this->has( $id ) ) {
throw new NotFoundException();
}
return $this->data[$id];
}
public function has($id) {
return array_key_exists( $id, $this->data );
}
public function set($id, $value) {
$this->data[$id] = $value;
}
public function persist() {
}
}

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Helper;
use Exception;
class StubRedirectionException extends Exception
{
}

View file

@ -3,10 +3,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce; namespace WooCommerce\PayPalCommerce;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeCachingServiceProvider; use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\DelegatingContainer;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -58,13 +59,28 @@ class ModularTestCase extends TestCase
*/ */
protected function bootstrapModule(array $overriddenServices = []): ContainerInterface protected function bootstrapModule(array $overriddenServices = []): ContainerInterface
{ {
$overridingContainer = new DelegatingContainer(new CompositeCachingServiceProvider([ $overriddenServices = array_merge([
new ServiceProvider($overriddenServices, []), 'http.redirector' => function () {
])); return new RedirectorStub();
}
], $overriddenServices);
$module = new class ($overriddenServices) implements ModuleInterface {
public function __construct(array $services) {
$this->services = $services;
}
public function setup(): ServiceProviderInterface{
return new ServiceProvider($this->services, []);
}
public function run(ContainerInterface $c): void {
}
};
$rootDir = ROOT_DIR; $rootDir = ROOT_DIR;
$bootstrap = require ("$rootDir/bootstrap.php"); $bootstrap = require ("$rootDir/bootstrap.php");
$appContainer = $bootstrap($rootDir, $overridingContainer); $appContainer = $bootstrap($rootDir, [], [$module]);
return $appContainer; return $appContainer;
} }

View file

@ -59,7 +59,10 @@ class WcGatewayTest extends TestCase
$this->environment = Mockery::mock(Environment::class); $this->environment = Mockery::mock(Environment::class);
$this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class); $this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class);
$this->logger = Mockery::mock(LoggerInterface::class); $this->logger = Mockery::mock(LoggerInterface::class);
$this->funding_source_renderer = new FundingSourceRenderer($this->settings); $this->funding_source_renderer = new FundingSourceRenderer(
$this->settings,
['venmo' => 'Venmo', 'paylater' => 'Pay Later', 'blik' => 'BLIK']
);
$this->apiShopCountry = 'DE'; $this->apiShopCountry = 'DE';
$this->onboardingState->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED); $this->onboardingState->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
@ -271,6 +274,8 @@ class WcGatewayTest extends TestCase
return [ return [
[null, 'PayPal', 'Pay via PayPal.'], [null, 'PayPal', 'Pay via PayPal.'],
['venmo', 'Venmo', 'Pay via Venmo.'], ['venmo', 'Venmo', 'Pay via Venmo.'],
['paylater', 'Pay Later', 'Pay via Pay Later.'],
['blik', 'BLIK (via PayPal)', 'Pay via BLIK.'],
['qwerty', 'PayPal', 'Pay via PayPal.'], ['qwerty', 'PayPal', 'Pay via PayPal.'],
]; ];
} }

View file

@ -0,0 +1,60 @@
<?php
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\Helper\SettingsStub;
use WooCommerce\PayPalCommerce\ModularTestCase;
class LocationsTest extends ModularTestCase
{
private $appContainer;
private $settings;
public function setUp(): void {
parent::setUp();
$this->settings = new SettingsStub([]);
$this->appContainer = $this->bootstrapModule([
'wcgateway.settings' => function () {
return $this->settings;
},
]);
}
/**
* @dataProvider payLaterButtonLocationsData
*/
public function testPayLaterButtonLocations(array $selectedLocations, array $expectedResult) {
$this->settings->set('smart_button_locations', $selectedLocations);
$result = $this->appContainer->get('wcgateway.settings.pay-later.button-locations');
self::assertEquals($expectedResult, $result);
}
public function payLaterButtonLocationsData()
{
yield [
['product', 'cart', 'checkout', 'mini-cart'],
[
'product' => 'Single Product',
'cart' => 'Cart',
'checkout' => 'Checkout',
'mini-cart' => 'Mini Cart',
],
];
yield [
['cart', 'checkout'],
[
'cart' => 'Cart',
'checkout' => 'Checkout',
],
];
yield [
[],
[],
];
}
}

View file

@ -5,6 +5,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Requests_Utility_CaseInsensitiveDictionary; use Requests_Utility_CaseInsensitiveDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
use WooCommerce\PayPalCommerce\Helper\StubRedirectionException;
use WooCommerce\PayPalCommerce\ModularTestCase; use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use Mockery; use Mockery;
@ -50,7 +52,8 @@ class SettingsListenerTest extends ModularTestCase
$signup_link_cache, $signup_link_cache,
$signup_link_ids, $signup_link_ids,
$pui_status_cache, $pui_status_cache,
$dcc_status_cache $dcc_status_cache,
new RedirectorStub()
); );
$_GET['section'] = PayPalGateway::ID; $_GET['section'] = PayPalGateway::ID;
@ -85,6 +88,8 @@ class SettingsListenerTest extends ModularTestCase
$dcc_status_cache->shouldReceive('has') $dcc_status_cache->shouldReceive('has')
->andReturn(false); ->andReturn(false);
$this->expectException(StubRedirectionException::class);
$testee->listen(); $testee->listen();
} }
} }

View file

@ -45,6 +45,10 @@ class PurchaseUnitTest extends TestCase
$this->puFactory = $this->container->get( 'api.factory.purchase-unit' ); $this->puFactory = $this->container->get( 'api.factory.purchase-unit' );
assert($this->puFactory instanceof PurchaseUnitFactory); assert($this->puFactory instanceof PurchaseUnitFactory);
add_filter('woocommerce_get_base_location', function () {
return 'AQ';
});
} }
public function tearDown(): void public function tearDown(): void
@ -195,6 +199,7 @@ class PurchaseUnitTest extends TestCase
$product->set_regular_price((string) $data['price']); $product->set_regular_price((string) $data['price']);
$product->set_tax_status('taxable'); $product->set_tax_status('taxable');
$product->set_tax_class(''); $product->set_tax_class('');
$product->set_virtual(true);
$product->save(); $product->save();

View file

@ -43,6 +43,8 @@ class ValidationTest extends TestCase
'terms-field'=>'1', 'terms-field'=>'1',
'terms'=>'on', 'terms'=>'on',
]); ]);
self::assertTrue(true); // no assertions warnings
} }
public function testInvalid() public function testInvalid()