diff --git a/changelog.txt b/changelog.txt index f89b8dbf0..408a70333 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,27 @@ *** Changelog *** += 3.0.4 - xxxx-xx-xx = +* Fix - Onboarding screen blank when WooPayments plugin is active #3312 + += 3.0.3 - 2025-04-08 = +* Fix - BN code was set before the installation path was initialized #3309 +* Fix - Things to do next referenced Apple Pay while in branded-only mode #3308 +* Fix - Disabled payment methods were not hidden in reactified WooCommerce Payments settings tab #3290 + += 3.0.2 - 2025-04-03 = +* Enhancement - Check the branded-only flag when settings-UI is loaded the first time #3278 +* Enhancement - Implement a Cache-Flush API #3276 +* Enhancement - Disable the mini-cart location by default #3284 +* Enhancement - Remove branded-only flag when uninstalling PayPal Payments #3295 +* Fix - Welcome screen lists "all major credit/debit cards, Apple Pay, Google Pay," in branded-only mode #3281 +* Fix - Correct heading in onboarding step 4 in branded-only mode #3282 +* Fix - Hide the payment methods screen for personal user in branded-only mode #3286 +* Fix - Enabling Save PayPal does not disable Pay Later messaging #3288 +* Fix - Settings UI: Fix Feature button links #3285 +* Fix - Create mapping for the 3d_secure_contingency setting #3262 +* Fix - Enable Fastlane Watermark by default in new settings UI #3296 +* Fix - Payment method screen is referencing credit cards, digital wallets in branded-only mode #3297 + = 3.0.1 - 2025-03-26 = * Enhancement - Include Fastlane meta on homepage #3151 * Enhancement - Include Branded-only plugin configuration for certain installation paths diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 12cdd12a6..a1559f12c 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use Psr\Log\LoggerInterface; /** * Class ApiModule @@ -113,23 +114,20 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { 'woocommerce_paypal_payments_flush_api_cache', static function () use ( $c ) { $caches = array( - 'api.paypal-bearer-cache' => array( - PayPalBearer::CACHE_KEY, - ), - 'api.client-credentials-cache' => array( - SdkClientToken::CACHE_KEY, - ), + 'api.paypal-bearer-cache', + 'api.client-credentials-cache', + 'settings.service.signup-link-cache', ); - foreach ( $caches as $cache_id => $keys ) { + $logger = $c->get( 'woocommerce.logger.woocommerce' ); + assert( $logger instanceof LoggerInterface ); + $logger->info( 'Flushing API caches...' ); + + foreach ( $caches as $cache_id ) { $cache = $c->get( $cache_id ); assert( $cache instanceof Cache ); - foreach ( $keys as $key ) { - if ( $cache->has( $key ) ) { - $cache->delete( $key ); - } - } + $cache->flush(); } } ); diff --git a/modules/ppcp-api-client/src/Helper/Cache.php b/modules/ppcp-api-client/src/Helper/Cache.php index 3b831ea00..e77f78825 100644 --- a/modules/ppcp-api-client/src/Helper/Cache.php +++ b/modules/ppcp-api-client/src/Helper/Cache.php @@ -5,7 +5,7 @@ * @package WooCommerce\PayPalCommerce\ApiClient\Helper */ -declare( strict_types=1 ); +declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\ApiClient\Helper; @@ -48,8 +48,9 @@ class Cache { * * @return bool */ - public function has( string $key ): bool { + public function has( string $key ) : bool { $value = $this->get( $key ); + return false !== $value; } @@ -58,20 +59,47 @@ class Cache { * * @param string $key The key. */ - public function delete( string $key ): void { + public function delete( string $key ) : void { delete_transient( $this->prefix . $key ); } /** * Caches a value. * - * @param string $key The key under which the value should be cached. - * @param mixed $value The value to cache. + * @param string $key The key under which the value should be cached. + * @param mixed $value The value to cache. * @param int $expiration Time until expiration in seconds. * * @return bool */ - public function set( string $key, $value, int $expiration = 0 ): bool { + public function set( string $key, $value, int $expiration = 0 ) : bool { return (bool) set_transient( $this->prefix . $key, $value, $expiration ); } + + /** + * Flushes all items of the current "cache group", i.e., items that use the defined prefix. + * + * @return void + */ + public function flush() : void { + global $wpdb; + + // Get a list of all transients with the relevant "group prefix" from the DB. + $transients = $wpdb->get_col( + $wpdb->prepare( + "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s", + $wpdb->esc_like( '_transient_' . $this->prefix ) . '%' + ) + ); + + /** + * Delete each cache item individually to ensure WP can fire all relevant + * actions, perform checks and other cleanup tasks and ensures eventually + * object cache systems, like Redis, are kept in-sync with the DB. + */ + foreach ( $transients as $transient ) { + $key = str_replace( '_transient_' . $this->prefix, '', $transient ); + $this->delete( $key ); + } + } } diff --git a/modules/ppcp-axo-block/resources/css/gateway.scss b/modules/ppcp-axo-block/resources/css/gateway.scss index 4611bfa35..605dbf4ca 100644 --- a/modules/ppcp-axo-block/resources/css/gateway.scss +++ b/modules/ppcp-axo-block/resources/css/gateway.scss @@ -101,7 +101,7 @@ $fast-transition-duration: 0.5s; } // 3. Express Payment Block -.wp-block-woocommerce-checkout-express-payment-block { +.wc-block-components-express-payment--checkout, .wp-block-woocommerce-checkout-express-payment-block { transition: opacity $transition-duration ease-in, scale $transition-duration ease-in, display $transition-duration ease-in; diff --git a/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js b/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js index 7b25cec31..713b548ba 100644 --- a/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js +++ b/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js @@ -10,7 +10,7 @@ import { STORE_NAME } from '../stores/axoStore'; */ export const setupAuthenticationClassToggle = () => { const targetSelector = - '.wp-block-woocommerce-checkout-express-payment-block'; + '.wc-block-components-express-payment--checkout, .wp-block-woocommerce-checkout-express-payment-block'; const authClass = 'wc-block-axo-is-authenticated'; const updateAuthenticationClass = () => { diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index f097882b9..1b9b6f59a 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -181,8 +181,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_action( 'wp_loaded', function () use ( $c ) { - $module = $this; - $this->session_handler = $c->get( 'session.handler' ); $settings = $c->get( 'wcgateway.settings' ); @@ -208,12 +206,12 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { // Enqueue frontend scripts. add_action( 'wp_enqueue_scripts', - static function () use ( $c, $manager, $module ) { + function () use ( $c, $manager ) { $smart_button = $c->get( 'button.smart-button' ); assert( $smart_button instanceof SmartButtonInterface ); - if ( $module->should_render_fastlane( $c ) && $smart_button->should_load_ppcp_script() ) { + if ( $this->should_render_fastlane( $c ) && $smart_button->should_load_ppcp_script() ) { $manager->enqueue(); } } @@ -222,8 +220,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { // Render submit button. add_action( $manager->checkout_button_renderer_hook(), - static function () use ( $c, $manager, $module ) { - if ( $module->should_render_fastlane( $c ) ) { + function () use ( $c, $manager ) { + if ( $this->should_render_fastlane( $c ) ) { $manager->render_checkout_button(); } } @@ -278,14 +276,14 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_filter( 'woocommerce_paypal_payments_localized_script_data', - function( array $localized_script_data ) use ( $c, $module ) { + function( array $localized_script_data ) use ( $c ) { $api = $c->get( 'api.sdk-client-token' ); assert( $api instanceof SdkClientToken ); $logger = $c->get( 'woocommerce.logger.woocommerce' ); assert( $logger instanceof LoggerInterface ); - return $module->add_sdk_client_token_to_script_data( $api, $logger, $localized_script_data ); + return $this->add_sdk_client_token_to_script_data( $api, $logger, $localized_script_data ); } ); @@ -349,6 +347,26 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); + // Remove Fastlane on the Pay for Order page. + add_filter( + 'woocommerce_available_payment_gateways', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $methods ) { + if ( ! is_array( $methods ) || ! is_wc_endpoint_url( 'order-pay' ) ) { + return $methods; + } + + // Remove Fastlane if present. + unset( $methods[ AxoGateway::ID ] ); + + return $methods; + } + ); + return true; } @@ -403,6 +421,7 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { * @return bool */ private function should_render_fastlane( ContainerInterface $c ): bool { + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); assert( $dcc_configuration instanceof CardPaymentsConfiguration ); diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 5cd26fcbe..6ba97bed1 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -466,6 +466,10 @@ class SmartButton implements SmartButtonInterface { * @return bool */ private function render_message_wrapper_registrar(): bool { + if ( ! apply_filters( 'woocommerce_paypal_payments_should_render_pay_later_messaging', true ) ) { + return false; + } + if ( ! $this->settings_status->is_pay_later_messaging_enabled() || ! $this->settings_status->has_pay_later_messaging_locations() ) { return false; } diff --git a/modules/ppcp-button/src/Helper/ThreeDSecure.php b/modules/ppcp-button/src/Helper/ThreeDSecure.php index dee633fe2..1c694dc06 100644 --- a/modules/ppcp-button/src/Helper/ThreeDSecure.php +++ b/modules/ppcp-button/src/Helper/ThreeDSecure.php @@ -20,7 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory class ThreeDSecure { public const NO_DECISION = 0; - public const PROCEED = 1; + public const PROCEED = 1; public const REJECT = 2; public const RETRY = 3; diff --git a/modules/ppcp-compat/src/Settings/PaymentMethodSettingsMapHelper.php b/modules/ppcp-compat/src/Settings/PaymentMethodSettingsMapHelper.php index 1faf46955..d1efea41f 100644 --- a/modules/ppcp-compat/src/Settings/PaymentMethodSettingsMapHelper.php +++ b/modules/ppcp-compat/src/Settings/PaymentMethodSettingsMapHelper.php @@ -10,6 +10,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat\Settings; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; +use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel; +use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; /** @@ -20,6 +22,15 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; */ class PaymentMethodSettingsMapHelper { + /** + * A map of new to old 3d secure values. + */ + protected const THREE_D_SECURE_VALUES_MAP = array( + 'no-3d-secure' => 'NO_3D_SECURE', + 'only-required-3d-secure' => 'SCA_WHEN_REQUIRED', + 'always-3d-secure' => 'SCA_ALWAYS', + ); + /** * Maps old setting keys to new payment method settings names. * @@ -27,26 +38,39 @@ class PaymentMethodSettingsMapHelper { */ public function map(): array { return array( - 'dcc_enabled' => CreditCardGateway::ID, - 'axo_enabled' => AxoGateway::ID, + 'dcc_enabled' => CreditCardGateway::ID, + 'axo_enabled' => AxoGateway::ID, + '3d_secure_contingency' => 'three_d_secure', ); } /** * Retrieves the value of a mapped key from the new settings. * - * @param string $old_key The key from the legacy settings. + * @param string $old_key The key from the legacy settings. + * @param AbstractDataModel|null $payment_settings The payment settings model. * @return mixed The value of the mapped setting, (null if not found). */ - public function mapped_value( string $old_key ): ?bool { + public function mapped_value( string $old_key, ?AbstractDataModel $payment_settings ) { + switch ( $old_key ) { + case '3d_secure_contingency': + if ( is_null( $payment_settings ) ) { + return null; + } - $payment_method = $this->map()[ $old_key ] ?? false; + assert( $payment_settings instanceof PaymentSettings ); + $selected_three_d_secure = $payment_settings->get_three_d_secure(); + return self::THREE_D_SECURE_VALUES_MAP[ $selected_three_d_secure ] ?? null; - if ( ! $payment_method ) { - return null; + default: + $payment_method = $this->map()[ $old_key ] ?? false; + + if ( ! $payment_method ) { + return null; + } + + return $this->is_gateway_enabled( $payment_method ); } - - return $this->is_gateway_enabled( $payment_method ); } /** diff --git a/modules/ppcp-compat/src/Settings/SettingsMapHelper.php b/modules/ppcp-compat/src/Settings/SettingsMapHelper.php index 39a285041..263ac45cb 100644 --- a/modules/ppcp-compat/src/Settings/SettingsMapHelper.php +++ b/modules/ppcp-compat/src/Settings/SettingsMapHelper.php @@ -214,7 +214,7 @@ class SettingsMapHelper { : $this->settings_tab_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] ); case $model instanceof PaymentSettings: - return $this->payment_method_settings_map_helper->mapped_value( $old_key ); + return $this->payment_method_settings_map_helper->mapped_value( $old_key, $this->get_payment_settings_model() ); default: return $this->model_cache[ $model_id ][ $new_key ] ?? null; diff --git a/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js b/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js index f2015194e..876cbf796 100644 --- a/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js +++ b/modules/ppcp-paypal-subscriptions/resources/js/paypal-subscription.js @@ -1,8 +1,6 @@ -import { __ } from '@wordpress/i18n'; document.addEventListener( 'DOMContentLoaded', () => { - const disableFields = ( productId ) => { - const variations = document.querySelector( '.woocommerce_variations' ); + const variations = document.querySelector( '.woocommerce_variations' ); if ( variations ) { const children = variations.children; for ( let i = 0; i < children.length; i++ ) { @@ -70,156 +68,232 @@ document.addEventListener( 'DOMContentLoaded', () => { soldIndividually.setAttribute( 'disabled', 'disabled' ); }; - const checkSubscriptionPeriodsInterval = (period, period_interval, price, linkBtn) => { - if ( - ( period === 'year' && parseInt( period_interval ) > 1 ) || - ( period === 'month' && parseInt( period_interval ) > 12 ) || - ( period === 'week' && parseInt( period_interval ) > 52 ) || - ( period === 'day' && parseInt( period_interval ) > 356 ) || - ( ! price || parseInt( price ) <= 0 ) - ) { - linkBtn.disabled = true; - linkBtn.checked = false; - if (! price || parseInt( price ) <= 0 ) { - linkBtn.setAttribute('title', __( 'Prices must be above zero for PayPal Subscriptions!', 'woocommerce-paypal-subscriptions' ) ); - } else { - linkBtn.setAttribute('title', __( 'Not allowed period interval combination for PayPal Subscriptions!', 'woocommerce-paypal-subscriptions' ) ); - } + const checkSubscriptionPeriodsInterval = ( + period, + period_interval, + price, + linkBtn + ) => { + if ( ! linkBtn ) { + return; + } - } else { - linkBtn.disabled = false; - linkBtn.removeAttribute('title'); - } - } + if ( + ( period === 'year' && parseInt( period_interval ) > 1 ) || + ( period === 'month' && parseInt( period_interval ) > 12 ) || + ( period === 'week' && parseInt( period_interval ) > 52 ) || + ( period === 'day' && parseInt( period_interval ) > 356 ) || + ! price || + parseInt( price ) <= 0 + ) { + linkBtn.disabled = true; + linkBtn.checked = false; + if ( ! price || parseInt( price ) <= 0 ) { + linkBtn.setAttribute( + 'title', + PayPalCommerceGatewayPayPalSubscriptionProducts.i18n + .prices_must_be_above_zero + ); + } else { + linkBtn.setAttribute( + 'title', + PayPalCommerceGatewayPayPalSubscriptionProducts.i18n + .not_allowed_period_interval + ); + } + } else { + linkBtn.disabled = false; + linkBtn.removeAttribute( 'title' ); + } + }; const setupProducts = () => { - jQuery( '.wc_input_subscription_period' ).on( 'change', (e) => { - const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]'); - const period_interval = e.target.parentElement.querySelector('select.wc_input_subscription_period_interval')?.value; - const period = e.target.value; - const price = e.target.parentElement.querySelector('input.wc_input_subscription_price')?.value; + jQuery( '.wc_input_subscription_period' ).on( 'change', ( e ) => { + const linkBtn = + e.target.parentElement.parentElement.parentElement.parentElement.querySelector( + 'input[name="_ppcp_enable_subscription_product"]' + ); + if ( linkBtn ) { + const period_interval = e.target.parentElement.querySelector( + 'select.wc_input_subscription_period_interval' + )?.value; + const period = e.target.value; + const price = e.target.parentElement.querySelector( + 'input.wc_input_subscription_price' + )?.value; - checkSubscriptionPeriodsInterval(period, period_interval, price, linkBtn); - }); + checkSubscriptionPeriodsInterval( + period, + period_interval, + price, + linkBtn + ); + } + } ); - jQuery( '.wc_input_subscription_period_interval' ).on( 'change', (e) => { - const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]'); - const period_interval = e.target.value; - const period = e.target.parentElement.querySelector('select.wc_input_subscription_period')?.value; - const price = e.target.parentElement.querySelector('input.wc_input_subscription_price')?.value; + jQuery( '.wc_input_subscription_period_interval' ).on( + 'change', + ( e ) => { + const linkBtn = + e.target.parentElement.parentElement.parentElement.parentElement.querySelector( + 'input[name="_ppcp_enable_subscription_product"]' + ); + if ( linkBtn ) { + const period_interval = e.target.value; + const period = e.target.parentElement.querySelector( + 'select.wc_input_subscription_period' + )?.value; + const price = e.target.parentElement.querySelector( + 'input.wc_input_subscription_price' + )?.value; - checkSubscriptionPeriodsInterval(period, period_interval, price, linkBtn); - }); + checkSubscriptionPeriodsInterval( + period, + period_interval, + price, + linkBtn + ); + } + } + ); - jQuery( '.wc_input_subscription_price' ).on( 'change', (e) => { - const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]'); - const period_interval = e.target.parentElement.querySelector('select.wc_input_subscription_period_interval')?.value; - const period = e.target.parentElement.querySelector('select.wc_input_subscription_period')?.value; - const price = e.target.value; + jQuery( '.wc_input_subscription_price' ).on( 'change', ( e ) => { + const linkBtn = + e.target.parentElement.parentElement.parentElement.parentElement.querySelector( + 'input[name="_ppcp_enable_subscription_product"]' + ); + if ( linkBtn ) { + const period_interval = e.target.parentElement.querySelector( + 'select.wc_input_subscription_period_interval' + )?.value; + const period = e.target.parentElement.querySelector( + 'select.wc_input_subscription_period' + )?.value; + const price = e.target.value; - checkSubscriptionPeriodsInterval(period, period_interval, price, linkBtn); - }); + checkSubscriptionPeriodsInterval( + period, + period_interval, + price, + linkBtn + ); + } + } ); - jQuery( '.wc_input_subscription_price' ).trigger( 'change' ); + jQuery( '.wc_input_subscription_price' ).trigger( 'change' ); - let variationProductIds = [ PayPalCommerceGatewayPayPalSubscriptionProducts.product_id ]; - const variationsInput = document.querySelectorAll( '.variable_post_id' ); - for ( let i = 0; i < variationsInput.length; i++ ) { - variationProductIds.push( variationsInput[ i ].value ); - } + const variationProductIds = [ + PayPalCommerceGatewayPayPalSubscriptionProducts.product_id, + ]; + const variationsInput = + document.querySelectorAll( '.variable_post_id' ); + for ( let i = 0; i < variationsInput.length; i++ ) { + variationProductIds.push( variationsInput[ i ].value ); + } - variationProductIds?.forEach( - ( productId ) => { - const linkBtn = document.getElementById( - `ppcp_enable_subscription_product-${ productId }` - ); + variationProductIds?.forEach( ( productId ) => { + const linkBtn = document.getElementById( + `ppcp_enable_subscription_product-${ productId }` + ); + if ( linkBtn ) { if ( linkBtn.checked && linkBtn.value === 'yes' ) { disableFields( productId ); } - linkBtn?.addEventListener( 'click', ( event ) => { - const unlinkBtnP = document.getElementById( - `ppcp-enable-subscription-${ productId }` - ); - const titleP = document.getElementById( - `ppcp_subscription_plan_name_p-${ productId }` - ); - if (event.target.checked === true) { - if ( unlinkBtnP ) { - unlinkBtnP.style.display = 'none'; - } - if ( titleP ) { - titleP.style.display = 'block'; - } - } else { - if ( unlinkBtnP ) { - unlinkBtnP.style.display = 'block'; - } - if ( titleP ) { - titleP.style.display = 'none'; - } - } - }); - - const unlinkBtn = document.getElementById( - `ppcp-unlink-sub-plan-${ productId }` - ); - unlinkBtn?.addEventListener( 'click', ( event ) => { - event.preventDefault(); - unlinkBtn.disabled = true; - const spinner = document.getElementById( - `spinner-unlink-plan-${ productId }` + linkBtn.addEventListener( 'click', ( event ) => { + const unlinkBtnP = document.getElementById( + `ppcp-enable-subscription-${ productId }` ); - spinner.style.display = 'inline-block'; + const titleP = document.getElementById( + `ppcp_subscription_plan_name_p-${ productId }` + ); + if ( event.target.checked === true ) { + if ( unlinkBtnP ) { + unlinkBtnP.style.display = 'none'; + } + if ( titleP ) { + titleP.style.display = 'block'; + } + } else { + if ( unlinkBtnP ) { + unlinkBtnP.style.display = 'block'; + } + if ( titleP ) { + titleP.style.display = 'none'; + } + } + } ); + } - fetch( PayPalCommerceGatewayPayPalSubscriptionProducts.ajax.deactivate_plan.endpoint, { + const unlinkBtn = document.getElementById( + `ppcp-unlink-sub-plan-${ productId }` + ); + unlinkBtn?.addEventListener( 'click', ( event ) => { + event.preventDefault(); + unlinkBtn.disabled = true; + const spinner = document.getElementById( + `spinner-unlink-plan-${ productId }` + ); + spinner.style.display = 'inline-block'; + + fetch( + PayPalCommerceGatewayPayPalSubscriptionProducts.ajax + .deactivate_plan.endpoint, + { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'same-origin', body: JSON.stringify( { - nonce: PayPalCommerceGatewayPayPalSubscriptionProducts.ajax.deactivate_plan.nonce, + nonce: PayPalCommerceGatewayPayPalSubscriptionProducts + .ajax.deactivate_plan.nonce, plan_id: linkBtn.dataset.subsPlan, product_id: productId, } ), + } + ) + .then( function ( res ) { + return res.json(); } ) - .then( function ( res ) { - return res.json(); - } ) - .then( function ( data ) { - if ( ! data.success ) { - unlinkBtn.disabled = false; - spinner.style.display = 'none'; - console.error( data ); - throw Error( data.data.message ); - } + .then( function ( data ) { + if ( ! data.success ) { + unlinkBtn.disabled = false; + spinner.style.display = 'none'; + console.error( data ); + throw Error( data.data.message ); + } - const enableSubscription = document.getElementById( - 'ppcp-enable-subscription-' + data.data.product_id + const enableSubscription = document.getElementById( + 'ppcp-enable-subscription-' + data.data.product_id + ); + const product = document.getElementById( + 'pcpp-product-' + data.data.product_id + ); + const plan = document.getElementById( + 'pcpp-plan-' + data.data.product_id + ); + enableSubscription.style.display = 'none'; + product.style.display = 'none'; + plan.style.display = 'none'; + + const enable_subscription_product = + document.getElementById( + 'ppcp_enable_subscription_product-' + + data.data.product_id ); - const product = document.getElementById( 'pcpp-product-' + data.data.product_id ); - const plan = document.getElementById( 'pcpp-plan-' + data.data.product_id ); - enableSubscription.style.display = 'none'; - product.style.display = 'none'; - plan.style.display = 'none'; + enable_subscription_product.disabled = true; - const enable_subscription_product = - document.getElementById( - 'ppcp_enable_subscription_product-' + data.data.product_id - ); - enable_subscription_product.disabled = true; + const planUnlinked = document.getElementById( + 'pcpp-plan-unlinked-' + data.data.product_id + ); + planUnlinked.style.display = 'block'; - const planUnlinked = - document.getElementById( 'pcpp-plan-unlinked-' + data.data.product_id ); - planUnlinked.style.display = 'block'; - - setTimeout( () => { - location.reload(); - }, 1000 ); - } ); - } ); - } - ); + setTimeout( () => { + location.reload(); + }, 1000 ); + } ); + } ); + } ); }; setupProducts(); diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index d35905ab7..b2c2d264f 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -581,6 +581,10 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu ), ), 'product_id' => $product->get_id(), + 'i18n' => array( + 'prices_must_be_above_zero' => __( 'Prices must be above zero for PayPal Subscriptions!', 'woocommerce-paypal-payments' ), + 'not_allowed_period_interval' => __( 'Not allowed period interval combination for PayPal Subscriptions!', 'woocommerce-paypal-payments' ), + ), ) ); } diff --git a/modules/ppcp-settings/resources/css/components/_app.scss b/modules/ppcp-settings/resources/css/components/_app.scss index 7e69cbada..6f5d8dc9a 100644 --- a/modules/ppcp-settings/resources/css/components/_app.scss +++ b/modules/ppcp-settings/resources/css/components/_app.scss @@ -3,13 +3,6 @@ */ .ppcp-r-app.loading { - height: 400px; - width: 400px; - position: absolute; - left: 50%; - transform: translate(-50%, 0); - text-align: center; - .ppcp-r-spinner-overlay { display: flex; flex-direction: column; diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss index 8116068a9..884493ada 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_button.scss @@ -70,6 +70,15 @@ button.components-button, a.components-button { --button-disabled-color: #{$color-gray-100}; --button-disabled-background: #{$color-gray-500}; + + &:hover:not(:disabled) { + background: #{$color-blue}; + } + + + &:not(.components-tab-panel__tabs-item):focus-visible:not(:disabled) { + outline: 2px solid #{$color-gray-500}; + } } &.is-secondary { @@ -86,7 +95,7 @@ button.components-button, a.components-button { --button-color: #{$color-blueberry}; --button-hover-color: #{$color-gradient-dark}; - &:focus:not(:disabled) { + &:focus-visible:not(:disabled) { border: none; box-shadow: none; } @@ -95,6 +104,16 @@ button.components-button, a.components-button { &.small-button { @include small-button; } + + &:focus:not(:disabled) { + outline: none; + } + + &:focus-visible:not(:disabled), + &:not(.components-tab-panel__tabs-item):focus-visible:not(:disabled) + &[data-focus-visible="true"] { + outline: 2px solid #{$color-blueberry}; + } } .ppcp--is-loading { diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss index 195367dfb..7dba93d17 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_fields.scss @@ -111,12 +111,7 @@ margin: 0; } } - // Custom styles. - .components-form-toggle.is-checked > .components-form-toggle__track { - background-color: $color-blueberry; - } - .ppcp-r-vertical-text-control { .components-base-control__field { display: flex; @@ -126,3 +121,74 @@ } } } + +.ppcp-r-app, .ppcp-r-modal__container { + // Form toggle styling. + .components-form-toggle { + &.is-checked { + > .components-form-toggle__track { + background-color: $color-blueberry; + } + .components-form-toggle__track { + border-color: $color-blueberry; + } + } + .components-form-toggle__input { + &:focus { + + .components-form-toggle__track { + box-shadow: none; + } + } + &:focus-visible + .components-form-toggle__track { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) #fff, + 0 0 0 calc(var(--wp-admin-border-width-focus)*2) $color-blueberry; + } + } + } + + // Form inputs. + .components-text-control__input { + &:focus, + &[type="color"]:focus, + &[type="date"]:focus, + &[type="datetime-local"]:focus, + &[type="datetime"]:focus, + &[type="email"]:focus, + &[type="month"]:focus, + &[type="number"]:focus, + &[type="password"]:focus, + &[type="tel"]:focus, + &[type="text"]:focus, + &[type="time"]:focus, + &[type="url"]:focus, + &[type="week"]:focus { + border-color: $color-blueberry; + } + } + + // Radio inputs. + .components-radio-control__input[type="radio"] { + &:checked { + background-color: $color-blueberry; + border-color: $color-blueberry; + } + &:focus { + box-shadow: none; + } + &:focus-visible { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) #fff, + 0 0 0 calc(var(--wp-admin-border-width-focus)*2) $color-blueberry; + } + } + + // Checkbox inputs. + .components-checkbox-control__input[type="checkbox"] { + &:focus { + box-shadow: none; + } + &:focus-visible { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) #fff, + 0 0 0 calc(var(--wp-admin-border-width-focus)*2) $color-blueberry; + } + } + } \ No newline at end of file diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_navigation.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_navigation.scss index b3188b461..50c798438 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_navigation.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_navigation.scss @@ -103,6 +103,11 @@ $margin_bottom: 48px; .components-tab-panel__tabs-item { height: var(--subnavigation-height); + + &:focus-visible:not(:disabled), + &[data-focus-visible="true"]:focus:not(:disabled) { + outline: none; + } } } diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-card.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-card.scss index 59d897132..f4b02442a 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_settings-card.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_settings-card.scss @@ -60,7 +60,7 @@ $width_gap: 24px; .ppcp-r-settings-card__title { @include font(13, 24, 600); color: var(--color-text-main); - margin: 0 0 4px 0; + margin: 0 0 12px 0; display: block; } @@ -68,6 +68,16 @@ $width_gap: 24px; @include font(13, 20, 400); color: var(--color-text-teriary); margin: 0; + + + p { + margin: 0 0 12px 0; + } + + button { + padding: 0; + margin: 0; + } } + .ppcp-r-settings-card { diff --git a/modules/ppcp-settings/resources/css/components/reusable-components/_spinner-overlay.scss b/modules/ppcp-settings/resources/css/components/reusable-components/_spinner-overlay.scss index 8a34ff17c..79d2f2c05 100644 --- a/modules/ppcp-settings/resources/css/components/reusable-components/_spinner-overlay.scss +++ b/modules/ppcp-settings/resources/css/components/reusable-components/_spinner-overlay.scss @@ -1,5 +1,4 @@ .ppcp-r-spinner-overlay { - background: var(--spinner-overlay-color); position: absolute; width: 100%; height: 100%; @@ -13,8 +12,6 @@ left: 50%; transform: translate(-50%, -50%); margin: 0; - width: var(--spinner-size); - height: var(--spinner-size); } .ppcp--spinner-message { @@ -29,7 +26,6 @@ position: fixed; width: var(--spinner-overlay-width); height: var(--spinner-overlay-height); - box-shadow: var(--spinner-overlay-box-shadow); left: 50%; top: 50%; transform: translate(-50%, -50%); diff --git a/modules/ppcp-settings/resources/css/components/screens/_modals.scss b/modules/ppcp-settings/resources/css/components/screens/_modals.scss index 5dbdf7652..0b628556e 100644 --- a/modules/ppcp-settings/resources/css/components/screens/_modals.scss +++ b/modules/ppcp-settings/resources/css/components/screens/_modals.scss @@ -17,3 +17,18 @@ } } } + +.ppcp-r-modal { + button.components-button, + a.components-button { + &:focus:not(:disabled) { + outline: none; + } + + &:focus-visible:not(:disabled), + &:not(.components-tab-panel__tabs-item):focus-visible:not(:disabled) + &[data-focus-visible="true"] { + outline: 2px solid #{$color-blueberry}; + } + } +} diff --git a/modules/ppcp-settings/resources/css/components/screens/_settings.scss b/modules/ppcp-settings/resources/css/components/screens/_settings.scss index 63952ed1f..232dbabd8 100644 --- a/modules/ppcp-settings/resources/css/components/screens/_settings.scss +++ b/modules/ppcp-settings/resources/css/components/screens/_settings.scss @@ -103,7 +103,7 @@ &__dismiss { position: absolute; - right: 0; + right: 2px; top: 50%; transform: translateY(-50%); background-color: transparent; diff --git a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss index e37fce07a..999820bdf 100644 --- a/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss +++ b/modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss @@ -1,19 +1,31 @@ .ppcp-r-paylater-configurator { display: flex; - border: 1px solid var(--color-separators); border-radius: var(--container-border-radius); overflow: hidden; font-family: "PayPalPro", sans-serif; -webkit-font-smoothing: antialiased; + width: 1200px; + + // Reset box-sizing for the preview container. + .etu8a6w3 * { + box-sizing: unset; + } .css-1snxoyf.eolpigi0 { margin: 0; } + .css-1f9aeda { + width: 100%; + } + + .css-1adsww8 { + padding: 0; + } + #configurator-eligibleContainer.css-4nclxm.e1vy3g880 { width: 100%; max-width: 100%; - padding: 16px 0px 16px 16px; #configurator-controlPanelContainer.css-5urmrq.e1vy3g880 { width: 374px; @@ -43,7 +55,7 @@ } .css-8vwtr6-state { - height: 1.4rem; + height: 1.5rem; width: 3rem; } } @@ -54,17 +66,27 @@ } &__subheader, #configurator-controlPanelSubHeader { - color: var(--color-text-description); + color: var(--color-text-teriary); margin: 0 0 18px 0; + @include font(13, 20, 400); } &__header, #configurator-controlPanelHeader, #configurator-previewSectionSubHeaderText.css-14ujlqd-text_body, .css-16jt5za-text_body { - @include font(16, 20, 600); + @include font(13, 20, 600); color: var(--color-text-title); - margin-bottom: 6px; font-family: "PayPalPro", sans-serif; -webkit-font-smoothing: antialiased; } + &__header, + #configurator-controlPanelHeader { + margin-bottom: 12px; + } + + + #configurator-previewSectionSubHeaderText.css-14ujlqd-text_body, + .css-16jt5za-text_body { + color: var(--color-text-teriary); + } .css-1yo2lxy-text_body_strong { color: var(--color-text-description); @@ -73,8 +95,9 @@ } .css-rok10q, .css-dfgbdq-text_body_strong { - margin-top: 0; - margin-bottom: 0; + margin: 0 0 12px 0; + padding: 0; + width: 100%; } &__publish-button { @@ -109,9 +132,9 @@ display: none; } - .css-4nclxm.e1vy3g880, { + .css-4nclxm.e1vy3g880 { width: 100%; - padding: 48px 8px; + padding: 0 0 48px 0; .css-11hsg2u.e1vy3g880 { width: 100%; @@ -119,7 +142,7 @@ } .css-n4cwz8 { - margin-top: 20px; + margin-top: 48px; } .css-1ce6bcu-container { diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js index ca73ab531..228dafc5f 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/AccordionSection.js @@ -1,7 +1,6 @@ import { Icon } from '@wordpress/components'; import { chevronDown, chevronUp } from '@wordpress/icons'; import classNames from 'classnames'; - import { useToggleState } from '../../hooks/useToggleState'; import { Content, @@ -22,33 +21,44 @@ const Accordion = ( { className = '', } ) => { const { isOpen, toggleOpen } = useToggleState( id, initiallyOpen ); - const wrapperClasses = classNames( 'ppcp-r-accordion', className, { - 'ppcp--is-open': isOpen, - } ); - const contentClass = classNames( 'ppcp--accordion-content', { - 'ppcp--is-open': isOpen, - } ); - - const icon = isOpen ? chevronUp : chevronDown; + const contentId = id + ? `${ id }-content` + : `accordion-${ title.replace( /\s+/g, '-' ).toLowerCase() }-content`; return ( -
+
{ disabledMessage }