diff --git a/bootstrap.php b/bootstrap.php index 27c8f36a3..7b8f7271d 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -16,10 +16,13 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return function ( string $root_dir, - ContainerInterface ...$additional_containers + array $additional_containers = array(), + array $additional_modules = array() ): ContainerInterface { $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. diff --git a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php index 252c1a294..d8cc06dca 100644 --- a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php @@ -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 + ); + } } /** diff --git a/modules/ppcp-api-client/src/Factory/ItemFactory.php b/modules/ppcp-api-client/src/Factory/ItemFactory.php index c71e7ddfa..0c5e9b542 100644 --- a/modules/ppcp-api-client/src/Factory/ItemFactory.php +++ b/modules/ppcp-api-client/src/Factory/ItemFactory.php @@ -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 ) ?: ''; + } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index 6c0a03283..a36c1d408 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -69,8 +69,13 @@ 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) { - return priceEl.innerText; + // 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; }, diff --git a/modules/ppcp-button/yarn.lock b/modules/ppcp-button/yarn.lock index 5d7c6fab8..2807fdfb1 100644 --- a/modules/ppcp-button/yarn.lock +++ b/modules/ppcp-button/yarn.lock @@ -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" diff --git a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php index 523909d77..c61d036e3 100644 --- a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php +++ b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php @@ -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 ); diff --git a/modules/ppcp-compat/yarn.lock b/modules/ppcp-compat/yarn.lock index c790c4b52..1377741f8 100644 --- a/modules/ppcp-compat/yarn.lock +++ b/modules/ppcp-compat/yarn.lock @@ -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" diff --git a/modules/ppcp-onboarding/src/OnboardingRESTController.php b/modules/ppcp-onboarding/src/OnboardingRESTController.php index 4eac5ba72..283cf64be 100644 --- a/modules/ppcp-onboarding/src/OnboardingRESTController.php +++ b/modules/ppcp-onboarding/src/OnboardingRESTController.php @@ -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(); diff --git a/modules/ppcp-onboarding/yarn.lock b/modules/ppcp-onboarding/yarn.lock index 84a6766b7..74b5c80ed 100644 --- a/modules/ppcp-onboarding/yarn.lock +++ b/modules/ppcp-onboarding/yarn.lock @@ -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" diff --git a/modules/ppcp-order-tracking/yarn.lock b/modules/ppcp-order-tracking/yarn.lock index 54ef909ff..1bb98cbe5 100644 --- a/modules/ppcp-order-tracking/yarn.lock +++ b/modules/ppcp-order-tracking/yarn.lock @@ -1805,9 +1805,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" diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index 3e380b069..abf84b83b 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -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 ); diff --git a/modules/ppcp-uninstall/yarn.lock b/modules/ppcp-uninstall/yarn.lock index 54ef909ff..1bb98cbe5 100644 --- a/modules/ppcp-uninstall/yarn.lock +++ b/modules/ppcp-uninstall/yarn.lock @@ -1805,9 +1805,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" diff --git a/modules/ppcp-vaulting/yarn.lock b/modules/ppcp-vaulting/yarn.lock index c25ba6dd3..99bd79caa 100644 --- a/modules/ppcp-vaulting/yarn.lock +++ b/modules/ppcp-vaulting/yarn.lock @@ -1776,9 +1776,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" diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index acdbc763e..7e3ff7eca 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -336,7 +336,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 { @@ -644,7 +645,7 @@ return array( >', '' ), - 'options' => $container->get( 'wcgateway.all-funding-sources' ), + 'options' => $container->get( 'wcgateway.settings.funding-sources' ), 'screens' => array( State::STATE_START, State::STATE_ONBOARDED, @@ -881,6 +882,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', + ) + ) ); }, @@ -952,9 +964,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(); }, @@ -1256,7 +1270,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 @@ -1289,7 +1303,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 ) @@ -1340,24 +1354,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( diff --git a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php index e91d87574..a465db721 100644 --- a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php +++ b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php @@ -22,13 +22,32 @@ class FundingSourceRenderer { */ protected $settings; + /** + * Map funding source ID -> human-readable name. + * + * @var array + */ + 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 ContainerInterface $settings The settings. + * @param array $funding_sources Map funding source ID -> human-readable name. */ - public function __construct( ContainerInterface $settings ) { - $this->settings = $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' ); diff --git a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php index 7655ef8ad..02f5e7082 100644 --- a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php +++ b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php @@ -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' ) ) ); } /** diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 980b0db80..88c9bee98 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -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,20 +112,28 @@ class SettingsListener { */ protected $dcc_status_cache; + /** + * The HTTP redirector. + * + * @var RedirectorInterface + */ + protected $redirector; + /** * SettingsListener constructor. * - * @param Settings $settings The settings. - * @param array $setting_fields The setting fields. - * @param WebhookRegistrar $webhook_registrar The Webhook Registrar. - * @param Cache $cache The Cache. - * @param State $state The state. - * @param Bearer $bearer The bearer. - * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. - * @param Cache $signup_link_cache The signup link cache. - * @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 Settings $settings The settings. + * @param array $setting_fields The setting fields. + * @param WebhookRegistrar $webhook_registrar The Webhook Registrar. + * @param Cache $cache The Cache. + * @param State $state The state. + * @param Bearer $bearer The bearer. + * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page. + * @param Cache $signup_link_cache The signup link cache. + * @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,6 +161,7 @@ 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; } /** @@ -198,12 +209,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 +233,7 @@ class SettingsListener { $this->settings->set( 'vault_enabled', false ); $this->settings->persist(); - add_action( - 'admin_notices', - function () use ( $exception ) { - printf( - '

%1$s

%2$s

', - 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 plugin documentation for more information about the setup.', 'woocommerce-paypal-payments' ) ) - ); - } - ); + throw $exception; } /** @@ -322,16 +325,6 @@ class SettingsListener { } $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 ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } @@ -345,7 +338,7 @@ class SettingsListener { } $redirect_url = false; - if ( self::CREDENTIALS_ADDED === $credentials_change_status ) { + if ( self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) { $redirect_url = $this->get_onboarding_redirect_url(); } @@ -354,8 +347,7 @@ class SettingsListener { } if ( $redirect_url ) { - wp_safe_redirect( $redirect_url, 302 ); - exit; + $this->redirector->redirect( $redirect_url ); } // phpcs:enable WordPress.Security.NonceVerification.Missing @@ -532,6 +524,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() ) { @@ -549,16 +543,7 @@ class SettingsListener { $this->settings->set( 'tracking_enabled', false ); $this->settings->persist(); - add_action( - 'admin_notices', - function () use ( $exception ) { - printf( - '

%1$s

%2$s

', - 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 plugin documentation for more information about the setup.', 'woocommerce-paypal-payments' ) ) - ); - } - ); + throw $exception; } } } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 289eb178c..92105781c 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway; use Psr\Log\LoggerInterface; use Throwable; +use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WC_Order; @@ -179,7 +180,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(); } @@ -453,14 +454,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(); - $listener->listen_for_vaulting_enabled(); - $listener->listen_for_tracking_enabled(); + + try { + $listener->listen_for_vaulting_enabled(); + $listener->listen_for_tracking_enabled(); + } catch ( RuntimeException $exception ) { + add_action( + 'admin_notices', + function () use ( $exception ) { + printf( + '

%1$s

%2$s

', + 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 plugin documentation for more information about the setup.', + 'woocommerce-paypal-payments' + ) + ) + ); + } + ); + } } ); diff --git a/modules/ppcp-webhooks/resources/js/status-page.js b/modules/ppcp-webhooks/resources/js/status-page.js index f477ec403..8f5334f51 100644 --- a/modules/ppcp-webhooks/resources/js/status-page.js +++ b/modules/ppcp-webhooks/resources/js/status-page.js @@ -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); + }); + } } ); diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php index d7ed8013c..9e330e812 100644 --- a/modules/ppcp-webhooks/services.php +++ b/modules/ppcp-webhooks/services.php @@ -167,7 +167,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' ) ); }, @@ -198,8 +199,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'; diff --git a/modules/ppcp-webhooks/src/Endpoint/ResubscribeEndpoint.php b/modules/ppcp-webhooks/src/Endpoint/ResubscribeEndpoint.php index b504cc38a..9fe20a1ed 100644 --- a/modules/ppcp-webhooks/src/Endpoint/ResubscribeEndpoint.php +++ b/modules/ppcp-webhooks/src/Endpoint/ResubscribeEndpoint.php @@ -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; diff --git a/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php b/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php index 9f8bbcf7a..4a7631572 100644 --- a/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php +++ b/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php @@ -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,18 +102,18 @@ class IncomingWebhookEndpoint { bool $verify_request, WebhookEventFactory $webhook_event_factory, WebhookSimulation $simulation, - WebhookInfoStorage $last_webhook_storage, + WebhookEventStorage $last_webhook_event_storage, RequestHandler ...$handlers ) { - $this->webhook_endpoint = $webhook_endpoint; - $this->webhook = $webhook; - $this->handlers = $handlers; - $this->logger = $logger; - $this->verify_request = $verify_request; - $this->webhook_event_factory = $webhook_event_factory; - $this->last_webhook_storage = $last_webhook_storage; - $this->simulation = $simulation; + $this->webhook_endpoint = $webhook_endpoint; + $this->webhook = $webhook; + $this->handlers = $handlers; + $this->logger = $logger; + $this->verify_request = $verify_request; + $this->webhook_event_factory = $webhook_event_factory; + $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.' ); diff --git a/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php b/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php index 28a9ddd84..475c2a85e 100644 --- a/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php +++ b/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php @@ -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 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->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(), ); } diff --git a/modules/ppcp-webhooks/src/WebhookInfoStorage.php b/modules/ppcp-webhooks/src/WebhookEventStorage.php similarity index 95% rename from modules/ppcp-webhooks/src/WebhookInfoStorage.php rename to modules/ppcp-webhooks/src/WebhookEventStorage.php index 9fb9865f6..2c2370c0e 100644 --- a/modules/ppcp-webhooks/src/WebhookInfoStorage.php +++ b/modules/ppcp-webhooks/src/WebhookEventStorage.php @@ -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. diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 6f8669a8b..e9cc66909 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -152,7 +152,6 @@ class WebhookModule implements ModuleInterface { add_action( 'init', function () use ( $registrar ) { - $registrar->unregister(); $registrar->register(); } ); diff --git a/modules/ppcp-webhooks/src/WebhookRegistrar.php b/modules/ppcp-webhooks/src/WebhookRegistrar.php index c16aa9acd..8f8919ec2 100644 --- a/modules/ppcp-webhooks/src/WebhookRegistrar.php +++ b/modules/ppcp-webhooks/src/WebhookRegistrar.php @@ -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,22 +64,22 @@ 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->logger = $logger; + $this->webhook_factory = $webhook_factory; + $this->endpoint = $endpoint; + $this->rest_endpoint = $rest_endpoint; + $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->logger->info( 'Webhooks deleted.' ); - } - return $success; + delete_option( self::KEY ); + $this->last_webhook_event_storage->clear(); + $this->logger->info( 'Webhooks deleted.' ); } } diff --git a/modules/ppcp-webhooks/yarn.lock b/modules/ppcp-webhooks/yarn.lock index c25ba6dd3..99bd79caa 100644 --- a/modules/ppcp-webhooks/yarn.lock +++ b/modules/ppcp-webhooks/yarn.lock @@ -1776,9 +1776,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" diff --git a/src/Http/RedirectorInterface.php b/src/Http/RedirectorInterface.php new file mode 100644 index 000000000..83403e496 --- /dev/null +++ b/src/Http/RedirectorInterface.php @@ -0,0 +1,22 @@ +getVersion(); }, + + 'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface { + return new WpRedirector(); + }, ); diff --git a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php index 640dffcdf..3f72dab07 100644 --- a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php @@ -43,9 +43,8 @@ class ItemFactoryTest extends TestCase ->expects('get_cart_contents') ->andReturn($items); - expect('wp_strip_all_tags') - ->with('description') - ->andReturn('description'); + expect('wp_strip_all_tags')->andReturnFirstArg(); + expect('strip_shortcodes')->andReturnFirstArg(); $woocommerce = Mockery::mock(\WooCommerce::class); $session = Mockery::mock(\WC_Session::class); @@ -99,9 +98,8 @@ class ItemFactoryTest extends TestCase ->expects('get_cart_contents') ->andReturn($items); - expect('wp_strip_all_tags') - ->with('description') - ->andReturn('description'); + expect('wp_strip_all_tags')->andReturnFirstArg(); + expect('strip_shortcodes')->andReturnFirstArg(); $woocommerce = Mockery::mock(\WooCommerce::class); $session = Mockery::mock(\WC_Session::class); @@ -130,9 +128,9 @@ class ItemFactoryTest extends TestCase $product ->expects('is_virtual') ->andReturn(false); - expect('wp_strip_all_tags') - ->with('description') - ->andReturn('description'); + + expect('wp_strip_all_tags')->andReturnFirstArg(); + expect('strip_shortcodes')->andReturnFirstArg(); $item = Mockery::mock(\WC_Order_Item_Product::class); $item @@ -190,9 +188,8 @@ class ItemFactoryTest extends TestCase ->expects('is_virtual') ->andReturn(true); - expect('wp_strip_all_tags') - ->with('description') - ->andReturn('description'); + expect('wp_strip_all_tags')->andReturnFirstArg(); + expect('strip_shortcodes')->andReturnFirstArg(); $item = Mockery::mock(\WC_Order_Item_Product::class); $item @@ -245,9 +242,8 @@ class ItemFactoryTest extends TestCase ->expects('is_virtual') ->andReturn(true); - expect('wp_strip_all_tags') - ->with($description) - ->andReturn(mb_substr( $description, 0, 127 )); + expect('wp_strip_all_tags')->andReturnFirstArg(); + expect('strip_shortcodes')->andReturnFirstArg(); $item = Mockery::mock(\WC_Order_Item_Product::class); $item diff --git a/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php index 39d38bfb0..8d8a7dabc 100644 --- a/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php @@ -5,6 +5,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Mockery; +use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\Button\Exception\ValidationException; use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; @@ -13,6 +14,8 @@ use function Brain\Monkey\Functions\expect; class ValidateCheckoutEndpointTest extends TestCase { + use MockeryPHPUnitIntegration; + private $requestData; private $formValidator; private $logger; diff --git a/tests/PHPUnit/Helper/RedirectorStub.php b/tests/PHPUnit/Helper/RedirectorStub.php new file mode 100644 index 000000000..9e79c6fe5 --- /dev/null +++ b/tests/PHPUnit/Helper/RedirectorStub.php @@ -0,0 +1,14 @@ +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() { + } +} diff --git a/tests/PHPUnit/Helper/StubRedirectionException.php b/tests/PHPUnit/Helper/StubRedirectionException.php new file mode 100644 index 000000000..e1f1e5aeb --- /dev/null +++ b/tests/PHPUnit/Helper/StubRedirectionException.php @@ -0,0 +1,10 @@ + 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; $bootstrap = require ("$rootDir/bootstrap.php"); - $appContainer = $bootstrap($rootDir, $overridingContainer); + $appContainer = $bootstrap($rootDir, [], [$module]); return $appContainer; } diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index b62f013d7..26107abd0 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -59,7 +59,10 @@ class WcGatewayTest extends TestCase $this->environment = Mockery::mock(Environment::class); $this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::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->onboardingState->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED); @@ -271,6 +274,8 @@ class WcGatewayTest extends TestCase return [ [null, 'PayPal', 'Pay via PayPal.'], ['venmo', 'Venmo', 'Pay via Venmo.'], + ['paylater', 'Pay Later', 'Pay via Pay Later.'], + ['blik', 'BLIK (via PayPal)', 'Pay via BLIK.'], ['qwerty', 'PayPal', 'Pay via PayPal.'], ]; } diff --git a/tests/PHPUnit/WcGateway/Settings/LocationsTest.php b/tests/PHPUnit/WcGateway/Settings/LocationsTest.php new file mode 100644 index 000000000..45cb63ffa --- /dev/null +++ b/tests/PHPUnit/WcGateway/Settings/LocationsTest.php @@ -0,0 +1,60 @@ +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 [ + [], + [], + ]; + } +} diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php index bad2dda33..d1397a421 100644 --- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php +++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php @@ -5,6 +5,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings; use Requests_Utility_CaseInsensitiveDictionary; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\Helper\RedirectorStub; +use WooCommerce\PayPalCommerce\Helper\StubRedirectionException; use WooCommerce\PayPalCommerce\ModularTestCase; use WooCommerce\PayPalCommerce\Onboarding\State; use Mockery; @@ -50,7 +52,8 @@ class SettingsListenerTest extends ModularTestCase $signup_link_cache, $signup_link_ids, $pui_status_cache, - $dcc_status_cache + $dcc_status_cache, + new RedirectorStub() ); $_GET['section'] = PayPalGateway::ID; @@ -85,6 +88,8 @@ class SettingsListenerTest extends ModularTestCase $dcc_status_cache->shouldReceive('has') ->andReturn(false); + $this->expectException(StubRedirectionException::class); + $testee->listen(); } } diff --git a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php index 5871dc100..23edee911 100644 --- a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php +++ b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php @@ -45,6 +45,10 @@ class PurchaseUnitTest extends TestCase $this->puFactory = $this->container->get( 'api.factory.purchase-unit' ); assert($this->puFactory instanceof PurchaseUnitFactory); + + add_filter('woocommerce_get_base_location', function () { + return 'AQ'; + }); } public function tearDown(): void @@ -195,6 +199,7 @@ class PurchaseUnitTest extends TestCase $product->set_regular_price((string) $data['price']); $product->set_tax_status('taxable'); $product->set_tax_class(''); + $product->set_virtual(true); $product->save(); diff --git a/tests/e2e/PHPUnit/Validation/ValidationTest.php b/tests/e2e/PHPUnit/Validation/ValidationTest.php index cf6d87f36..4c012e5c1 100644 --- a/tests/e2e/PHPUnit/Validation/ValidationTest.php +++ b/tests/e2e/PHPUnit/Validation/ValidationTest.php @@ -43,6 +43,8 @@ class ValidationTest extends TestCase 'terms-field'=>'1', 'terms'=>'on', ]); + + self::assertTrue(true); // no assertions warnings } public function testInvalid()