mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-04 08:47:23 +08:00
Merge branch 'trunk'
# Conflicts: # modules/ppcp-button/src/Helper/ThreeDSecure.php
This commit is contained in:
commit
ebcf91461f
54 changed files with 1197 additions and 448 deletions
|
@ -1,5 +1,27 @@
|
||||||
*** Changelog ***
|
*** 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 =
|
= 3.0.1 - 2025-03-26 =
|
||||||
* Enhancement - Include Fastlane meta on homepage #3151
|
* Enhancement - Include Fastlane meta on homepage #3151
|
||||||
* Enhancement - Include Branded-only plugin configuration for certain installation paths
|
* Enhancement - Include Branded-only plugin configuration for certain installation paths
|
||||||
|
|
|
@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ApiModule
|
* Class ApiModule
|
||||||
|
@ -113,23 +114,20 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
'woocommerce_paypal_payments_flush_api_cache',
|
'woocommerce_paypal_payments_flush_api_cache',
|
||||||
static function () use ( $c ) {
|
static function () use ( $c ) {
|
||||||
$caches = array(
|
$caches = array(
|
||||||
'api.paypal-bearer-cache' => array(
|
'api.paypal-bearer-cache',
|
||||||
PayPalBearer::CACHE_KEY,
|
'api.client-credentials-cache',
|
||||||
),
|
'settings.service.signup-link-cache',
|
||||||
'api.client-credentials-cache' => array(
|
|
||||||
SdkClientToken::CACHE_KEY,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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 );
|
$cache = $c->get( $cache_id );
|
||||||
assert( $cache instanceof Cache );
|
assert( $cache instanceof Cache );
|
||||||
|
|
||||||
foreach ( $keys as $key ) {
|
$cache->flush();
|
||||||
if ( $cache->has( $key ) ) {
|
|
||||||
$cache->delete( $key );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
|
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare( strict_types=1 );
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
|
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
|
||||||
|
|
||||||
|
@ -48,8 +48,9 @@ class Cache {
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function has( string $key ): bool {
|
public function has( string $key ) : bool {
|
||||||
$value = $this->get( $key );
|
$value = $this->get( $key );
|
||||||
|
|
||||||
return false !== $value;
|
return false !== $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,20 +59,47 @@ class Cache {
|
||||||
*
|
*
|
||||||
* @param string $key The key.
|
* @param string $key The key.
|
||||||
*/
|
*/
|
||||||
public function delete( string $key ): void {
|
public function delete( string $key ) : void {
|
||||||
delete_transient( $this->prefix . $key );
|
delete_transient( $this->prefix . $key );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caches a value.
|
* Caches a value.
|
||||||
*
|
*
|
||||||
* @param string $key The key under which the value should be cached.
|
* @param string $key The key under which the value should be cached.
|
||||||
* @param mixed $value The value to cache.
|
* @param mixed $value The value to cache.
|
||||||
* @param int $expiration Time until expiration in seconds.
|
* @param int $expiration Time until expiration in seconds.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @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 );
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ $fast-transition-duration: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Express Payment Block
|
// 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,
|
transition: opacity $transition-duration ease-in,
|
||||||
scale $transition-duration ease-in,
|
scale $transition-duration ease-in,
|
||||||
display $transition-duration ease-in;
|
display $transition-duration ease-in;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { STORE_NAME } from '../stores/axoStore';
|
||||||
*/
|
*/
|
||||||
export const setupAuthenticationClassToggle = () => {
|
export const setupAuthenticationClassToggle = () => {
|
||||||
const targetSelector =
|
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 authClass = 'wc-block-axo-is-authenticated';
|
||||||
|
|
||||||
const updateAuthenticationClass = () => {
|
const updateAuthenticationClass = () => {
|
||||||
|
|
|
@ -181,8 +181,6 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
add_action(
|
add_action(
|
||||||
'wp_loaded',
|
'wp_loaded',
|
||||||
function () use ( $c ) {
|
function () use ( $c ) {
|
||||||
$module = $this;
|
|
||||||
|
|
||||||
$this->session_handler = $c->get( 'session.handler' );
|
$this->session_handler = $c->get( 'session.handler' );
|
||||||
|
|
||||||
$settings = $c->get( 'wcgateway.settings' );
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
@ -208,12 +206,12 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
// Enqueue frontend scripts.
|
// Enqueue frontend scripts.
|
||||||
add_action(
|
add_action(
|
||||||
'wp_enqueue_scripts',
|
'wp_enqueue_scripts',
|
||||||
static function () use ( $c, $manager, $module ) {
|
function () use ( $c, $manager ) {
|
||||||
|
|
||||||
$smart_button = $c->get( 'button.smart-button' );
|
$smart_button = $c->get( 'button.smart-button' );
|
||||||
assert( $smart_button instanceof SmartButtonInterface );
|
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();
|
$manager->enqueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,8 +220,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
// Render submit button.
|
// Render submit button.
|
||||||
add_action(
|
add_action(
|
||||||
$manager->checkout_button_renderer_hook(),
|
$manager->checkout_button_renderer_hook(),
|
||||||
static function () use ( $c, $manager, $module ) {
|
function () use ( $c, $manager ) {
|
||||||
if ( $module->should_render_fastlane( $c ) ) {
|
if ( $this->should_render_fastlane( $c ) ) {
|
||||||
$manager->render_checkout_button();
|
$manager->render_checkout_button();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,14 +276,14 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
|
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_localized_script_data',
|
'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' );
|
$api = $c->get( 'api.sdk-client-token' );
|
||||||
assert( $api instanceof SdkClientToken );
|
assert( $api instanceof SdkClientToken );
|
||||||
|
|
||||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||||
assert( $logger instanceof LoggerInterface );
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +421,7 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function should_render_fastlane( ContainerInterface $c ): bool {
|
private function should_render_fastlane( ContainerInterface $c ): bool {
|
||||||
|
|
||||||
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
|
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
|
||||||
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
|
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
|
||||||
|
|
||||||
|
|
|
@ -466,6 +466,10 @@ class SmartButton implements SmartButtonInterface {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function render_message_wrapper_registrar(): 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() ) {
|
if ( ! $this->settings_status->is_pay_later_messaging_enabled() || ! $this->settings_status->has_pay_later_messaging_locations() ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory
|
||||||
class ThreeDSecure {
|
class ThreeDSecure {
|
||||||
|
|
||||||
public const NO_DECISION = 0;
|
public const NO_DECISION = 0;
|
||||||
public const PROCEED = 1;
|
public const PROCEED = 1;
|
||||||
public const REJECT = 2;
|
public const REJECT = 2;
|
||||||
public const RETRY = 3;
|
public const RETRY = 3;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ declare(strict_types=1);
|
||||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +22,15 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||||
*/
|
*/
|
||||||
class PaymentMethodSettingsMapHelper {
|
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.
|
* Maps old setting keys to new payment method settings names.
|
||||||
*
|
*
|
||||||
|
@ -27,26 +38,39 @@ class PaymentMethodSettingsMapHelper {
|
||||||
*/
|
*/
|
||||||
public function map(): array {
|
public function map(): array {
|
||||||
return array(
|
return array(
|
||||||
'dcc_enabled' => CreditCardGateway::ID,
|
'dcc_enabled' => CreditCardGateway::ID,
|
||||||
'axo_enabled' => AxoGateway::ID,
|
'axo_enabled' => AxoGateway::ID,
|
||||||
|
'3d_secure_contingency' => 'three_d_secure',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the value of a mapped key from the new settings.
|
* 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).
|
* @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 ) {
|
default:
|
||||||
return null;
|
$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 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -214,7 +214,7 @@ class SettingsMapHelper {
|
||||||
: $this->settings_tab_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
: $this->settings_tab_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
||||||
|
|
||||||
case $model instanceof PaymentSettings:
|
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:
|
default:
|
||||||
return $this->model_cache[ $model_id ][ $new_key ] ?? null;
|
return $this->model_cache[ $model_id ][ $new_key ] ?? null;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
document.addEventListener( 'DOMContentLoaded', () => {
|
document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
|
|
||||||
const disableFields = ( productId ) => {
|
const disableFields = ( productId ) => {
|
||||||
const variations = document.querySelector( '.woocommerce_variations' );
|
const variations = document.querySelector( '.woocommerce_variations' );
|
||||||
if ( variations ) {
|
if ( variations ) {
|
||||||
const children = variations.children;
|
const children = variations.children;
|
||||||
for ( let i = 0; i < children.length; i++ ) {
|
for ( let i = 0; i < children.length; i++ ) {
|
||||||
|
@ -70,156 +68,232 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
soldIndividually.setAttribute( 'disabled', 'disabled' );
|
soldIndividually.setAttribute( 'disabled', 'disabled' );
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkSubscriptionPeriodsInterval = (period, period_interval, price, linkBtn) => {
|
const checkSubscriptionPeriodsInterval = (
|
||||||
if (
|
period,
|
||||||
( period === 'year' && parseInt( period_interval ) > 1 ) ||
|
period_interval,
|
||||||
( period === 'month' && parseInt( period_interval ) > 12 ) ||
|
price,
|
||||||
( period === 'week' && parseInt( period_interval ) > 52 ) ||
|
linkBtn
|
||||||
( period === 'day' && parseInt( period_interval ) > 356 ) ||
|
) => {
|
||||||
( ! price || parseInt( price ) <= 0 )
|
if ( ! linkBtn ) {
|
||||||
) {
|
return;
|
||||||
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' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
if (
|
||||||
linkBtn.disabled = false;
|
( period === 'year' && parseInt( period_interval ) > 1 ) ||
|
||||||
linkBtn.removeAttribute('title');
|
( 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 = () => {
|
const setupProducts = () => {
|
||||||
jQuery( '.wc_input_subscription_period' ).on( 'change', (e) => {
|
jQuery( '.wc_input_subscription_period' ).on( 'change', ( e ) => {
|
||||||
const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]');
|
const linkBtn =
|
||||||
const period_interval = e.target.parentElement.querySelector('select.wc_input_subscription_period_interval')?.value;
|
e.target.parentElement.parentElement.parentElement.parentElement.querySelector(
|
||||||
const period = e.target.value;
|
'input[name="_ppcp_enable_subscription_product"]'
|
||||||
const price = e.target.parentElement.querySelector('input.wc_input_subscription_price')?.value;
|
);
|
||||||
|
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) => {
|
jQuery( '.wc_input_subscription_period_interval' ).on(
|
||||||
const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]');
|
'change',
|
||||||
const period_interval = e.target.value;
|
( e ) => {
|
||||||
const period = e.target.parentElement.querySelector('select.wc_input_subscription_period')?.value;
|
const linkBtn =
|
||||||
const price = e.target.parentElement.querySelector('input.wc_input_subscription_price')?.value;
|
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) => {
|
jQuery( '.wc_input_subscription_price' ).on( 'change', ( e ) => {
|
||||||
const linkBtn = e.target.parentElement.parentElement.parentElement.parentElement.querySelector('input[name="_ppcp_enable_subscription_product"]');
|
const linkBtn =
|
||||||
const period_interval = e.target.parentElement.querySelector('select.wc_input_subscription_period_interval')?.value;
|
e.target.parentElement.parentElement.parentElement.parentElement.querySelector(
|
||||||
const period = e.target.parentElement.querySelector('select.wc_input_subscription_period')?.value;
|
'input[name="_ppcp_enable_subscription_product"]'
|
||||||
const price = e.target.value;
|
);
|
||||||
|
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 variationProductIds = [
|
||||||
const variationsInput = document.querySelectorAll( '.variable_post_id' );
|
PayPalCommerceGatewayPayPalSubscriptionProducts.product_id,
|
||||||
for ( let i = 0; i < variationsInput.length; i++ ) {
|
];
|
||||||
variationProductIds.push( variationsInput[ i ].value );
|
const variationsInput =
|
||||||
}
|
document.querySelectorAll( '.variable_post_id' );
|
||||||
|
for ( let i = 0; i < variationsInput.length; i++ ) {
|
||||||
|
variationProductIds.push( variationsInput[ i ].value );
|
||||||
|
}
|
||||||
|
|
||||||
variationProductIds?.forEach(
|
variationProductIds?.forEach( ( productId ) => {
|
||||||
( productId ) => {
|
const linkBtn = document.getElementById(
|
||||||
const linkBtn = document.getElementById(
|
`ppcp_enable_subscription_product-${ productId }`
|
||||||
`ppcp_enable_subscription_product-${ productId }`
|
);
|
||||||
);
|
if ( linkBtn ) {
|
||||||
if ( linkBtn.checked && linkBtn.value === 'yes' ) {
|
if ( linkBtn.checked && linkBtn.value === 'yes' ) {
|
||||||
disableFields( productId );
|
disableFields( productId );
|
||||||
}
|
}
|
||||||
linkBtn?.addEventListener( 'click', ( event ) => {
|
linkBtn.addEventListener( 'click', ( event ) => {
|
||||||
const unlinkBtnP = document.getElementById(
|
const unlinkBtnP = document.getElementById(
|
||||||
`ppcp-enable-subscription-${ productId }`
|
`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 }`
|
|
||||||
);
|
);
|
||||||
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',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
body: JSON.stringify( {
|
body: JSON.stringify( {
|
||||||
nonce: PayPalCommerceGatewayPayPalSubscriptionProducts.ajax.deactivate_plan.nonce,
|
nonce: PayPalCommerceGatewayPayPalSubscriptionProducts
|
||||||
|
.ajax.deactivate_plan.nonce,
|
||||||
plan_id: linkBtn.dataset.subsPlan,
|
plan_id: linkBtn.dataset.subsPlan,
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
} ),
|
} ),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then( function ( res ) {
|
||||||
|
return res.json();
|
||||||
} )
|
} )
|
||||||
.then( function ( res ) {
|
.then( function ( data ) {
|
||||||
return res.json();
|
if ( ! data.success ) {
|
||||||
} )
|
unlinkBtn.disabled = false;
|
||||||
.then( function ( data ) {
|
spinner.style.display = 'none';
|
||||||
if ( ! data.success ) {
|
console.error( data );
|
||||||
unlinkBtn.disabled = false;
|
throw Error( data.data.message );
|
||||||
spinner.style.display = 'none';
|
}
|
||||||
console.error( data );
|
|
||||||
throw Error( data.data.message );
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableSubscription = document.getElementById(
|
const enableSubscription = document.getElementById(
|
||||||
'ppcp-enable-subscription-' + data.data.product_id
|
'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 );
|
enable_subscription_product.disabled = true;
|
||||||
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 =
|
const planUnlinked = document.getElementById(
|
||||||
document.getElementById(
|
'pcpp-plan-unlinked-' + data.data.product_id
|
||||||
'ppcp_enable_subscription_product-' + data.data.product_id
|
);
|
||||||
);
|
planUnlinked.style.display = 'block';
|
||||||
enable_subscription_product.disabled = true;
|
|
||||||
|
|
||||||
const planUnlinked =
|
setTimeout( () => {
|
||||||
document.getElementById( 'pcpp-plan-unlinked-' + data.data.product_id );
|
location.reload();
|
||||||
planUnlinked.style.display = 'block';
|
}, 1000 );
|
||||||
|
} );
|
||||||
setTimeout( () => {
|
} );
|
||||||
location.reload();
|
} );
|
||||||
}, 1000 );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setupProducts();
|
setupProducts();
|
||||||
|
|
|
@ -581,6 +581,10 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'product_id' => $product->get_id(),
|
'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' ),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.ppcp-r-app.loading {
|
.ppcp-r-app.loading {
|
||||||
height: 400px;
|
|
||||||
width: 400px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.ppcp-r-spinner-overlay {
|
.ppcp-r-spinner-overlay {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -70,6 +70,15 @@ button.components-button, a.components-button {
|
||||||
|
|
||||||
--button-disabled-color: #{$color-gray-100};
|
--button-disabled-color: #{$color-gray-100};
|
||||||
--button-disabled-background: #{$color-gray-500};
|
--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 {
|
&.is-secondary {
|
||||||
|
@ -86,7 +95,7 @@ button.components-button, a.components-button {
|
||||||
--button-color: #{$color-blueberry};
|
--button-color: #{$color-blueberry};
|
||||||
--button-hover-color: #{$color-gradient-dark};
|
--button-hover-color: #{$color-gradient-dark};
|
||||||
|
|
||||||
&:focus:not(:disabled) {
|
&:focus-visible:not(:disabled) {
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -95,6 +104,16 @@ button.components-button, a.components-button {
|
||||||
&.small-button {
|
&.small-button {
|
||||||
@include 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 {
|
.ppcp--is-loading {
|
||||||
|
|
|
@ -111,12 +111,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom styles.
|
// Custom styles.
|
||||||
.components-form-toggle.is-checked > .components-form-toggle__track {
|
|
||||||
background-color: $color-blueberry;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ppcp-r-vertical-text-control {
|
.ppcp-r-vertical-text-control {
|
||||||
.components-base-control__field {
|
.components-base-control__field {
|
||||||
display: flex;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,11 @@ $margin_bottom: 48px;
|
||||||
|
|
||||||
.components-tab-panel__tabs-item {
|
.components-tab-panel__tabs-item {
|
||||||
height: var(--subnavigation-height);
|
height: var(--subnavigation-height);
|
||||||
|
|
||||||
|
&:focus-visible:not(:disabled),
|
||||||
|
&[data-focus-visible="true"]:focus:not(:disabled) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ $width_gap: 24px;
|
||||||
.ppcp-r-settings-card__title {
|
.ppcp-r-settings-card__title {
|
||||||
@include font(13, 24, 600);
|
@include font(13, 24, 600);
|
||||||
color: var(--color-text-main);
|
color: var(--color-text-main);
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 12px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,16 @@ $width_gap: 24px;
|
||||||
@include font(13, 20, 400);
|
@include font(13, 20, 400);
|
||||||
color: var(--color-text-teriary);
|
color: var(--color-text-teriary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+ .ppcp-r-settings-card {
|
+ .ppcp-r-settings-card {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.ppcp-r-spinner-overlay {
|
.ppcp-r-spinner-overlay {
|
||||||
background: var(--spinner-overlay-color);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -13,8 +12,6 @@
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: var(--spinner-size);
|
|
||||||
height: var(--spinner-size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ppcp--spinner-message {
|
.ppcp--spinner-message {
|
||||||
|
@ -29,7 +26,6 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: var(--spinner-overlay-width);
|
width: var(--spinner-overlay-width);
|
||||||
height: var(--spinner-overlay-height);
|
height: var(--spinner-overlay-height);
|
||||||
box-shadow: var(--spinner-overlay-box-shadow);
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
|
@ -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};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
|
|
||||||
&__dismiss {
|
&__dismiss {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 2px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
.ppcp-r-paylater-configurator {
|
.ppcp-r-paylater-configurator {
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 1px solid var(--color-separators);
|
|
||||||
border-radius: var(--container-border-radius);
|
border-radius: var(--container-border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: "PayPalPro", sans-serif;
|
font-family: "PayPalPro", sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
width: 1200px;
|
||||||
|
|
||||||
|
// Reset box-sizing for the preview container.
|
||||||
|
.etu8a6w3 * {
|
||||||
|
box-sizing: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.css-1snxoyf.eolpigi0 {
|
.css-1snxoyf.eolpigi0 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.css-1f9aeda {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.css-1adsww8 {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#configurator-eligibleContainer.css-4nclxm.e1vy3g880 {
|
#configurator-eligibleContainer.css-4nclxm.e1vy3g880 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 16px 0px 16px 16px;
|
|
||||||
|
|
||||||
#configurator-controlPanelContainer.css-5urmrq.e1vy3g880 {
|
#configurator-controlPanelContainer.css-5urmrq.e1vy3g880 {
|
||||||
width: 374px;
|
width: 374px;
|
||||||
|
@ -43,7 +55,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.css-8vwtr6-state {
|
.css-8vwtr6-state {
|
||||||
height: 1.4rem;
|
height: 1.5rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,17 +66,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subheader, #configurator-controlPanelSubHeader {
|
&__subheader, #configurator-controlPanelSubHeader {
|
||||||
color: var(--color-text-description);
|
color: var(--color-text-teriary);
|
||||||
margin: 0 0 18px 0;
|
margin: 0 0 18px 0;
|
||||||
|
@include font(13, 20, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header, #configurator-controlPanelHeader, #configurator-previewSectionSubHeaderText.css-14ujlqd-text_body, .css-16jt5za-text_body {
|
&__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);
|
color: var(--color-text-title);
|
||||||
margin-bottom: 6px;
|
|
||||||
font-family: "PayPalPro", sans-serif;
|
font-family: "PayPalPro", sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-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 {
|
.css-1yo2lxy-text_body_strong {
|
||||||
color: var(--color-text-description);
|
color: var(--color-text-description);
|
||||||
|
@ -73,8 +95,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.css-rok10q, .css-dfgbdq-text_body_strong {
|
.css-rok10q, .css-dfgbdq-text_body_strong {
|
||||||
margin-top: 0;
|
margin: 0 0 12px 0;
|
||||||
margin-bottom: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__publish-button {
|
&__publish-button {
|
||||||
|
@ -109,9 +132,9 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.css-4nclxm.e1vy3g880, {
|
.css-4nclxm.e1vy3g880 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 48px 8px;
|
padding: 0 0 48px 0;
|
||||||
|
|
||||||
.css-11hsg2u.e1vy3g880 {
|
.css-11hsg2u.e1vy3g880 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -119,7 +142,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.css-n4cwz8 {
|
.css-n4cwz8 {
|
||||||
margin-top: 20px;
|
margin-top: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.css-1ce6bcu-container {
|
.css-1ce6bcu-container {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Icon } from '@wordpress/components';
|
import { Icon } from '@wordpress/components';
|
||||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { useToggleState } from '../../hooks/useToggleState';
|
import { useToggleState } from '../../hooks/useToggleState';
|
||||||
import {
|
import {
|
||||||
Content,
|
Content,
|
||||||
|
@ -22,33 +21,44 @@ const Accordion = ( {
|
||||||
className = '',
|
className = '',
|
||||||
} ) => {
|
} ) => {
|
||||||
const { isOpen, toggleOpen } = useToggleState( id, initiallyOpen );
|
const { isOpen, toggleOpen } = useToggleState( id, initiallyOpen );
|
||||||
const wrapperClasses = classNames( 'ppcp-r-accordion', className, {
|
const contentId = id
|
||||||
'ppcp--is-open': isOpen,
|
? `${ id }-content`
|
||||||
} );
|
: `accordion-${ title.replace( /\s+/g, '-' ).toLowerCase() }-content`;
|
||||||
const contentClass = classNames( 'ppcp--accordion-content', {
|
|
||||||
'ppcp--is-open': isOpen,
|
|
||||||
} );
|
|
||||||
|
|
||||||
const icon = isOpen ? chevronUp : chevronDown;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ wrapperClasses } { ...( id && { id } ) }>
|
<div
|
||||||
|
className={ classNames( 'ppcp-r-accordion', className, {
|
||||||
|
'ppcp--is-open': isOpen,
|
||||||
|
} ) }
|
||||||
|
id={ id || undefined }
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="ppcp--toggler"
|
className="ppcp--toggler"
|
||||||
onClick={ toggleOpen }
|
onClick={ toggleOpen }
|
||||||
|
aria-expanded={ isOpen }
|
||||||
|
aria-controls={ contentId }
|
||||||
>
|
>
|
||||||
<Header>
|
<Header>
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<Title noCaps={ noCaps }>{ title }</Title>
|
<Title noCaps={ noCaps }>{ title }</Title>
|
||||||
<Action>
|
<Action>
|
||||||
<Icon icon={ icon } />
|
<Icon icon={ isOpen ? chevronUp : chevronDown } />
|
||||||
</Action>
|
</Action>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<Description>{ description }</Description>
|
{ description && (
|
||||||
|
<Description>{ description }</Description>
|
||||||
|
) }
|
||||||
</Header>
|
</Header>
|
||||||
</button>
|
</button>
|
||||||
<div className={ contentClass }>
|
<div
|
||||||
|
className={ classNames( 'ppcp--accordion-content', {
|
||||||
|
'ppcp--is-open': isOpen,
|
||||||
|
} ) }
|
||||||
|
id={ contentId }
|
||||||
|
aria-hidden={ ! isOpen }
|
||||||
|
inert={ isOpen ? undefined : '' }
|
||||||
|
>
|
||||||
<Content asCard={ false }>{ children }</Content>
|
<Content asCard={ false }>{ children }</Content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,58 +1,40 @@
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
|
|
||||||
import { Header, Title, Action, Description } from '../Elements';
|
import { Header, Title, Action, Description } from '../Elements';
|
||||||
import SettingsBlock from '../SettingsBlock';
|
import SettingsBlock from '../SettingsBlock';
|
||||||
import TitleBadge from '../TitleBadge';
|
import TitleBadge from '../TitleBadge';
|
||||||
|
import { CommonHooks } from '../../../data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a feature settings block with title, description, and action buttons.
|
||||||
|
*
|
||||||
|
* @param {Object} props Component properties
|
||||||
|
* @param {string} props.title The feature title
|
||||||
|
* @param {string} props.description HTML description of the feature
|
||||||
|
* @return {JSX.Element} The rendered component
|
||||||
|
*/
|
||||||
const FeatureSettingsBlock = ( { title, description, ...props } ) => {
|
const FeatureSettingsBlock = ( { title, description, ...props } ) => {
|
||||||
const printNotes = () => {
|
const { actionProps } = props;
|
||||||
const notes = props.actionProps?.notes;
|
const { isSandbox } = CommonHooks.useMerchant();
|
||||||
if ( ! notes || ( Array.isArray( notes ) && notes.length === 0 ) ) {
|
|
||||||
return null;
|
/**
|
||||||
|
* Gets the appropriate URL for a button based on environment
|
||||||
|
* Always prioritizes urls object over url when it exists
|
||||||
|
*
|
||||||
|
* @param {Object} buttonData The button configuration object
|
||||||
|
* @param {string} [buttonData.url] Single URL for the button
|
||||||
|
* @param {Object} [buttonData.urls] Environment-specific URLs
|
||||||
|
* @param {string} [buttonData.urls.sandbox] URL for sandbox environment
|
||||||
|
* @param {string} [buttonData.urls.live] URL for live environment
|
||||||
|
* @return {string|undefined} The appropriate URL to use for the button
|
||||||
|
*/
|
||||||
|
const getButtonUrl = ( buttonData ) => {
|
||||||
|
const { url, urls } = buttonData;
|
||||||
|
|
||||||
|
if ( urls ) {
|
||||||
|
return isSandbox ? urls.sandbox : urls.live;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return url;
|
||||||
<span className="ppcp--item-notes">
|
|
||||||
{ notes.map( ( note, index ) => (
|
|
||||||
<span key={ index }>{ note }</span>
|
|
||||||
) ) }
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FeatureButton = ( {
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
text,
|
|
||||||
isBusy,
|
|
||||||
url,
|
|
||||||
urls,
|
|
||||||
onClick,
|
|
||||||
} ) => {
|
|
||||||
const buttonProps = {
|
|
||||||
className,
|
|
||||||
isBusy,
|
|
||||||
variant,
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( url || urls ) {
|
|
||||||
buttonProps.href = urls ? urls.live : url;
|
|
||||||
buttonProps.target = '_blank';
|
|
||||||
}
|
|
||||||
if ( ! buttonProps.href ) {
|
|
||||||
buttonProps.onClick = onClick;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Button { ...buttonProps }>{ text }</Button>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDescription = () => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="ppcp-r-feature-item__description ppcp-r-settings-block__feature__description"
|
|
||||||
dangerouslySetInnerHTML={ { __html: description } }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -60,38 +42,52 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
|
||||||
<Header>
|
<Header>
|
||||||
<Title>
|
<Title>
|
||||||
{ title }
|
{ title }
|
||||||
{ props.actionProps?.enabled && (
|
{ actionProps?.enabled && (
|
||||||
<TitleBadge { ...props.actionProps?.badge } />
|
<TitleBadge { ...actionProps?.badge } />
|
||||||
) }
|
) }
|
||||||
</Title>
|
</Title>
|
||||||
<Description className="ppcp-r-settings-block__feature__description">
|
<Description className="ppcp-r-settings-block__feature__description">
|
||||||
{ renderDescription() }
|
<span
|
||||||
{ printNotes() }
|
className="ppcp-r-feature-item__description"
|
||||||
|
dangerouslySetInnerHTML={ { __html: description } }
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ actionProps?.notes?.length > 0 && (
|
||||||
|
<span className="ppcp--item-notes">
|
||||||
|
{ actionProps.notes.map( ( note, index ) => (
|
||||||
|
<span key={ index }>{ note }</span>
|
||||||
|
) ) }
|
||||||
|
</span>
|
||||||
|
) }
|
||||||
</Description>
|
</Description>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<Action>
|
<Action>
|
||||||
<div className="ppcp--action-buttons">
|
<div className="ppcp--action-buttons">
|
||||||
{ props.actionProps?.buttons.map(
|
{ actionProps?.buttons.map( ( buttonData ) => {
|
||||||
( {
|
const {
|
||||||
class: className,
|
class: className,
|
||||||
type,
|
type,
|
||||||
text,
|
text,
|
||||||
url,
|
|
||||||
urls,
|
|
||||||
onClick,
|
onClick,
|
||||||
} ) => (
|
} = buttonData;
|
||||||
<FeatureButton
|
|
||||||
|
const buttonUrl = getButtonUrl( buttonData );
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
key={ text }
|
key={ text }
|
||||||
className={ className }
|
className={ className }
|
||||||
variant={ type }
|
variant={ type }
|
||||||
text={ text }
|
isBusy={ actionProps.isBusy }
|
||||||
isBusy={ props.actionProps.isBusy }
|
href={ buttonUrl }
|
||||||
url={ url }
|
target={ buttonUrl ? '_blank' : undefined }
|
||||||
urls={ urls }
|
onClick={ ! buttonUrl ? onClick : undefined }
|
||||||
onClick={ onClick }
|
>
|
||||||
/>
|
{ text }
|
||||||
)
|
</Button>
|
||||||
) }
|
);
|
||||||
|
} ) }
|
||||||
</div>
|
</div>
|
||||||
</Action>
|
</Action>
|
||||||
</SettingsBlock>
|
</SettingsBlock>
|
||||||
|
|
|
@ -31,10 +31,15 @@ const PaymentMethodItemBlock = ( {
|
||||||
id={ paymentMethod.id }
|
id={ paymentMethod.id }
|
||||||
className={ methodItemClasses }
|
className={ methodItemClasses }
|
||||||
separatorAndGap={ false }
|
separatorAndGap={ false }
|
||||||
|
aria-disabled={ isDisabled ? 'true' : 'false' }
|
||||||
>
|
>
|
||||||
{ isDisabled && (
|
{ isDisabled && (
|
||||||
<div className="ppcp--method-disabled-overlay">
|
<div
|
||||||
<p className="ppcp--method-disabled-message">
|
className="ppcp--method-disabled-overlay"
|
||||||
|
role="alert"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
<p className="ppcp--method-disabled-message" tabIndex="0">
|
||||||
{ disabledMessage }
|
{ disabledMessage }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,6 +65,8 @@ const PaymentMethodItemBlock = ( {
|
||||||
__nextHasNoMarginBottom
|
__nextHasNoMarginBottom
|
||||||
checked={ isSelected }
|
checked={ isSelected }
|
||||||
onChange={ onSelect }
|
onChange={ onSelect }
|
||||||
|
disabled={ isDisabled }
|
||||||
|
aria-label={ `Enable ${ paymentMethod.itemTitle }` }
|
||||||
/>
|
/>
|
||||||
{ hasWarning && ! isDisabled && isSelected && (
|
{ hasWarning && ! isDisabled && isSelected && (
|
||||||
<WarningMessages
|
<WarningMessages
|
||||||
|
@ -70,7 +77,9 @@ const PaymentMethodItemBlock = ( {
|
||||||
{ paymentMethod?.fields && onTriggerModal && (
|
{ paymentMethod?.fields && onTriggerModal && (
|
||||||
<Button
|
<Button
|
||||||
className="ppcp--method-settings"
|
className="ppcp--method-settings"
|
||||||
|
disabled={ isDisabled }
|
||||||
onClick={ onTriggerModal }
|
onClick={ onTriggerModal }
|
||||||
|
aria-label={ `Configure ${ paymentMethod.itemTitle } settings` }
|
||||||
>
|
>
|
||||||
<Icon icon={ cog } />
|
<Icon icon={ cog } />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -2,6 +2,18 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { Content } from './Elements';
|
import { Content } from './Elements';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a settings card.
|
||||||
|
*
|
||||||
|
* @param {Object} props Component properties
|
||||||
|
* @param {string} [props.id] Unique identifier for the card
|
||||||
|
* @param {string} [props.className] Additional CSS classes
|
||||||
|
* @param {string} props.title Card title
|
||||||
|
* @param {*} props.description Card description content
|
||||||
|
* @param {*} props.children Card content
|
||||||
|
* @param {boolean} [props.contentContainer=true] Whether to wrap content in a container
|
||||||
|
* @return {JSX.Element} The settings card component
|
||||||
|
*/
|
||||||
const SettingsCard = ( {
|
const SettingsCard = ( {
|
||||||
id,
|
id,
|
||||||
className,
|
className,
|
||||||
|
@ -16,14 +28,20 @@ const SettingsCard = ( {
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const titleId = id ? `${ id }-title` : undefined;
|
||||||
|
const descriptionId = id ? `${ id }-description` : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...cardProps }>
|
<div { ...cardProps } role="region" aria-labelledby={ titleId }>
|
||||||
<div className="ppcp-r-settings-card__header">
|
<div className="ppcp-r-settings-card__header">
|
||||||
<div className="ppcp-r-settings-card__content-inner">
|
<div className="ppcp-r-settings-card__content-inner">
|
||||||
<span className="ppcp-r-settings-card__title">
|
<h2 id={ titleId } className="ppcp-r-settings-card__title">
|
||||||
{ title }
|
{ title }
|
||||||
</span>
|
</h2>
|
||||||
<div className="ppcp-r-settings-card__description">
|
<div
|
||||||
|
id={ descriptionId }
|
||||||
|
className="ppcp-r-settings-card__description"
|
||||||
|
>
|
||||||
{ description }
|
{ description }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,20 +2,24 @@ import { __ } from '@wordpress/i18n';
|
||||||
import { Spinner } from '@wordpress/components';
|
import { Spinner } from '@wordpress/components';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const SpinnerOverlay = ( { asModal = false, message = null } ) => {
|
/**
|
||||||
|
* Renders a loading spinner.
|
||||||
|
*
|
||||||
|
* @param {Object} props Component properties.
|
||||||
|
* @param {boolean} [props.asModal=false] Whether to display the spinner as a modal overlay.
|
||||||
|
* @param {string} [props.ariaLabel] Accessible label for screen readers.
|
||||||
|
* @return {JSX.Element} The spinner overlay component.
|
||||||
|
*/
|
||||||
|
const SpinnerOverlay = ( {
|
||||||
|
asModal = false,
|
||||||
|
ariaLabel = __( 'Loading…', 'woocommerce-paypal-payments' ),
|
||||||
|
} ) => {
|
||||||
const className = classnames( 'ppcp-r-spinner-overlay', {
|
const className = classnames( 'ppcp-r-spinner-overlay', {
|
||||||
'ppcp--is-modal': asModal,
|
'ppcp--is-modal': asModal,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if ( null === message ) {
|
|
||||||
message = __( 'Loading…', 'woocommerce-paypal-payments' );
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className } role="status" aria-label={ ariaLabel }>
|
||||||
{ message && (
|
|
||||||
<span className="ppcp--spinner-message">{ message }</span>
|
|
||||||
) }
|
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,8 +30,16 @@ const TabBar = ( { tabs, activePanel, setActivePanel } ) => {
|
||||||
initialTabName={ activePanel }
|
initialTabName={ activePanel }
|
||||||
onSelect={ updateActivePanel }
|
onSelect={ updateActivePanel }
|
||||||
tabs={ tabs }
|
tabs={ tabs }
|
||||||
|
orientation="horizontal"
|
||||||
|
selectOnMove={ false }
|
||||||
>
|
>
|
||||||
{ () => '' }
|
{ ( tab ) => (
|
||||||
|
<div
|
||||||
|
className={ `ppcp-r-tabpanel-content ppcp-r-tabpanel-${ tab.name }` }
|
||||||
|
>
|
||||||
|
{ tab.render ? tab.render() : '' }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import PaymentFlow from '../Components/PaymentFlow';
|
||||||
const StepPaymentMethods = () => {
|
const StepPaymentMethods = () => {
|
||||||
const { optionalMethods, setOptionalMethods } =
|
const { optionalMethods, setOptionalMethods } =
|
||||||
OnboardingHooks.useOptionalPaymentMethods();
|
OnboardingHooks.useOptionalPaymentMethods();
|
||||||
|
const { ownBrandOnly } = CommonHooks.useWooSettings();
|
||||||
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
||||||
|
|
||||||
const optionalMethodTitle = useMemo( () => {
|
const optionalMethodTitle = useMemo( () => {
|
||||||
|
@ -31,7 +32,10 @@ const StepPaymentMethods = () => {
|
||||||
description: <OptionalMethodDescription />,
|
description: <OptionalMethodDescription />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: __(
|
title: ownBrandOnly ? __(
|
||||||
|
'No thanks, I prefer to use a different provider for local payment methods',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) : __(
|
||||||
'No thanks, I prefer to use a different provider for processing credit cards, digital wallets, and local payment methods',
|
'No thanks, I prefer to use a different provider for processing credit cards, digital wallets, and local payment methods',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
|
@ -41,7 +45,9 @@ const StepPaymentMethods = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-page-optional-payment-methods">
|
<div className="ppcp-r-page-optional-payment-methods">
|
||||||
<OnboardingHeader title={ <PaymentStepTitle /> } />
|
<OnboardingHeader
|
||||||
|
title={ <PaymentStepTitle isBrandedOnly={ ownBrandOnly } /> }
|
||||||
|
/>
|
||||||
<div className="ppcp-r-inner-container">
|
<div className="ppcp-r-inner-container">
|
||||||
<OptionSelector
|
<OptionSelector
|
||||||
multiSelect={ false }
|
multiSelect={ false }
|
||||||
|
@ -58,7 +64,13 @@ const StepPaymentMethods = () => {
|
||||||
|
|
||||||
export default StepPaymentMethods;
|
export default StepPaymentMethods;
|
||||||
|
|
||||||
const PaymentStepTitle = () => {
|
const PaymentStepTitle = ( ownBrandOnly ) => {
|
||||||
|
if ( ownBrandOnly.isBrandedOnly ) {
|
||||||
|
return __(
|
||||||
|
'Add Expanded Checkout for more ways to pay',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
}
|
||||||
return __( 'Add Credit and Debit Cards', 'woocommerce-paypal-payments' );
|
return __( 'Add Credit and Debit Cards', 'woocommerce-paypal-payments' );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,16 @@ const StepWelcome = ( { setStep, currentStep } ) => {
|
||||||
ownBrandOnly
|
ownBrandOnly
|
||||||
);
|
);
|
||||||
|
|
||||||
const onboardingHeaderDescription = canUseCardPayments
|
const onboardingHeaderDescription =
|
||||||
? __(
|
canUseCardPayments && ! ownBrandOnly
|
||||||
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
|
? __(
|
||||||
'woocommerce-paypal-payments'
|
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
|
||||||
)
|
'woocommerce-paypal-payments'
|
||||||
: __(
|
)
|
||||||
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, and more.',
|
: __(
|
||||||
'woocommerce-paypal-payments'
|
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, and more.',
|
||||||
);
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-page-welcome">
|
<div className="ppcp-r-page-welcome">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
import { CommonHooks, OnboardingHooks } from '../../../../data';
|
||||||
import StepWelcome from './StepWelcome';
|
import StepWelcome from './StepWelcome';
|
||||||
import StepBusiness from './StepBusiness';
|
import StepBusiness from './StepBusiness';
|
||||||
import StepProducts from './StepProducts';
|
import StepProducts from './StepProducts';
|
||||||
|
@ -56,11 +57,17 @@ const filterSteps = ( steps, conditions ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSteps = ( flags ) => {
|
export const getSteps = ( flags ) => {
|
||||||
|
const { ownBrandOnly } = CommonHooks.useWooSettings();
|
||||||
|
const { isCasualSeller } = OnboardingHooks.useBusiness();
|
||||||
|
|
||||||
const steps = filterSteps( ALL_STEPS, [
|
const steps = filterSteps( ALL_STEPS, [
|
||||||
// Casual selling: Unlock the "Personal Account" choice.
|
// Casual selling: Unlock the "Personal Account" choice.
|
||||||
( step ) => flags.canUseCasualSelling || step.id !== 'business',
|
( step ) => flags.canUseCasualSelling || step.id !== 'business',
|
||||||
// Skip payment methods screen.
|
// Skip payment methods screen.
|
||||||
( step ) => ! flags.shouldSkipPaymentMethods || step.id !== 'methods',
|
( step ) =>
|
||||||
|
step.id !== 'methods' ||
|
||||||
|
( ! flags.shouldSkipPaymentMethods &&
|
||||||
|
! ( ownBrandOnly && isCasualSeller ) ),
|
||||||
] );
|
] );
|
||||||
|
|
||||||
const totalStepsCount = steps.length;
|
const totalStepsCount = steps.length;
|
||||||
|
|
|
@ -10,6 +10,15 @@ const OnboardingScreen = () => {
|
||||||
const Steps = getSteps( flags );
|
const Steps = getSteps( flags );
|
||||||
const currentStep = getCurrentStep( step, Steps );
|
const currentStep = getCurrentStep( step, Steps );
|
||||||
|
|
||||||
|
if ( ! currentStep?.StepComponent ) {
|
||||||
|
console.error( 'Invalid Onboarding State', {
|
||||||
|
step,
|
||||||
|
flags,
|
||||||
|
Steps,
|
||||||
|
currentStep,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
const handleNext = () => setStep( currentStep.nextStep );
|
const handleNext = () => setStep( currentStep.nextStep );
|
||||||
const handlePrev = () => setStep( currentStep.prevStep );
|
const handlePrev = () => setStep( currentStep.prevStep );
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { speak } from '@wordpress/a11y';
|
||||||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||||
|
|
||||||
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
||||||
|
@ -21,8 +22,17 @@ const SettingsNavigation = ( {
|
||||||
setActivePanel = () => {},
|
setActivePanel = () => {},
|
||||||
} ) => {
|
} ) => {
|
||||||
const { persistAll } = useStoreManager();
|
const { persistAll } = useStoreManager();
|
||||||
|
|
||||||
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
||||||
|
const [ isSaving, setIsSaving ] = useState( false );
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setIsSaving( true );
|
||||||
|
speak(
|
||||||
|
__( 'Saving settings…', 'woocommerce-paypal-payments' ),
|
||||||
|
'assertive'
|
||||||
|
);
|
||||||
|
persistAll();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopNavigation
|
<TopNavigation
|
||||||
|
@ -38,10 +48,19 @@ const SettingsNavigation = ( {
|
||||||
>
|
>
|
||||||
{ canSave && (
|
{ canSave && (
|
||||||
<>
|
<>
|
||||||
<Button variant="primary" onClick={ persistAll }>
|
<Button
|
||||||
{ __( 'Save', 'woocommerce-paypal-payments' ) }
|
variant="primary"
|
||||||
|
onClick={ handleSave }
|
||||||
|
aria-busy={ isSaving }
|
||||||
|
>
|
||||||
|
{ isSaving
|
||||||
|
? __( 'Saving…', 'woocommerce-paypal-payments' )
|
||||||
|
: __( 'Save', 'woocommerce-paypal-payments' ) }
|
||||||
</Button>
|
</Button>
|
||||||
<SaveStateMessage />
|
<SaveStateMessage
|
||||||
|
setIsSaving={ setIsSaving }
|
||||||
|
isSaving={ isSaving }
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) }
|
) }
|
||||||
</TopNavigation>
|
</TopNavigation>
|
||||||
|
@ -50,24 +69,26 @@ const SettingsNavigation = ( {
|
||||||
|
|
||||||
export default SettingsNavigation;
|
export default SettingsNavigation;
|
||||||
|
|
||||||
const SaveStateMessage = () => {
|
const SaveStateMessage = ( { setIsSaving, isSaving } ) => {
|
||||||
const [ isSaving, setIsSaving ] = useState( false );
|
|
||||||
const [ isVisible, setIsVisible ] = useState( false );
|
const [ isVisible, setIsVisible ] = useState( false );
|
||||||
const [ isAnimating, setIsAnimating ] = useState( false );
|
const [ isAnimating, setIsAnimating ] = useState( false );
|
||||||
const { onStarted, onFinished } = CommonHooks.useActivityObserver();
|
const { onStarted, onFinished } = CommonHooks.useActivityObserver();
|
||||||
const timerRef = useRef( null );
|
const timerRef = useRef( null );
|
||||||
|
|
||||||
const handleActivityStart = useCallback( ( started ) => {
|
const handleActivityStart = useCallback(
|
||||||
if ( started.startsWith( 'persist' ) ) {
|
( started ) => {
|
||||||
setIsSaving( true );
|
if ( started.startsWith( 'persist' ) ) {
|
||||||
setIsVisible( false );
|
setIsSaving( true );
|
||||||
setIsAnimating( false );
|
setIsVisible( false );
|
||||||
|
setIsAnimating( false );
|
||||||
|
|
||||||
if ( timerRef.current ) {
|
if ( timerRef.current ) {
|
||||||
clearTimeout( timerRef.current );
|
clearTimeout( timerRef.current );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [] );
|
[ setIsSaving ]
|
||||||
|
);
|
||||||
|
|
||||||
const handleActivityDone = useCallback(
|
const handleActivityDone = useCallback(
|
||||||
( done, remaining ) => {
|
( done, remaining ) => {
|
||||||
|
@ -76,6 +97,14 @@ const SaveStateMessage = () => {
|
||||||
setIsVisible( true );
|
setIsVisible( true );
|
||||||
setTimeout( () => setIsAnimating( true ), 50 );
|
setTimeout( () => setIsAnimating( true ), 50 );
|
||||||
|
|
||||||
|
speak(
|
||||||
|
__(
|
||||||
|
'Settings saved successfully.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
'assertive'
|
||||||
|
);
|
||||||
|
|
||||||
timerRef.current = setTimeout( () => {
|
timerRef.current = setTimeout( () => {
|
||||||
setIsAnimating( false );
|
setIsAnimating( false );
|
||||||
setTimeout(
|
setTimeout(
|
||||||
|
@ -85,7 +114,7 @@ const SaveStateMessage = () => {
|
||||||
}, SAVE_CONFIRMATION_DURATION );
|
}, SAVE_CONFIRMATION_DURATION );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ isSaving ]
|
[ isSaving, setIsSaving ]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
|
@ -102,7 +131,7 @@ const SaveStateMessage = () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={ className }>
|
<span className={ className } role="status" aria-live="polite">
|
||||||
<span className="ppcp--inner-text">
|
<span className="ppcp--inner-text">
|
||||||
{ __( 'Completed', 'woocommerce-paypal-payments' ) }
|
{ __( 'Completed', 'woocommerce-paypal-payments' ) }
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -43,7 +43,10 @@ const Features = () => {
|
||||||
'Features refreshed successfully.',
|
'Features refreshed successfully.',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
{ icon: NOTIFICATION_SUCCESS }
|
{
|
||||||
|
icon: NOTIFICATION_SUCCESS,
|
||||||
|
speak: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -58,7 +61,10 @@ const Features = () => {
|
||||||
error.message ||
|
error.message ||
|
||||||
__( 'Unknown error', 'woocommerce-paypal-payments' )
|
__( 'Unknown error', 'woocommerce-paypal-payments' )
|
||||||
),
|
),
|
||||||
{ icon: NOTIFICATION_ERROR }
|
{
|
||||||
|
icon: NOTIFICATION_ERROR,
|
||||||
|
speak: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsRefreshing( false );
|
setIsRefreshing( false );
|
||||||
|
@ -76,6 +82,8 @@ const Features = () => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
contentContainer={ false }
|
contentContainer={ false }
|
||||||
|
aria-live="polite"
|
||||||
|
aria-busy={ isRefreshing }
|
||||||
>
|
>
|
||||||
<ContentWrapper>
|
<ContentWrapper>
|
||||||
{ features.map( ( { id, enabled, ...feature } ) => (
|
{ features.map( ( { id, enabled, ...feature } ) => (
|
||||||
|
|
|
@ -33,7 +33,10 @@ const Todos = () => {
|
||||||
'Dismissed items restored successfully.',
|
'Dismissed items restored successfully.',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
{ icon: NOTIFICATION_SUCCESS }
|
{
|
||||||
|
icon: NOTIFICATION_SUCCESS,
|
||||||
|
speak: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsResetting( false );
|
setIsResetting( false );
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
import Todos from '../Components/Overview/Todos/Todos';
|
import Todos from '../Components/Overview/Todos/Todos';
|
||||||
import Features from '../Components/Overview/Features/Features';
|
import Features from '../Components/Overview/Features/Features';
|
||||||
import Help from '../Components/Overview/Help/Help';
|
import Help from '../Components/Overview/Help/Help';
|
||||||
|
@ -14,11 +15,26 @@ const TabOverview = () => {
|
||||||
usePaymentGatewaySync();
|
usePaymentGatewaySync();
|
||||||
|
|
||||||
if ( ! areTodosReady || ! merchantIsReady || ! featuresIsReady ) {
|
if ( ! areTodosReady || ! merchantIsReady || ! featuresIsReady ) {
|
||||||
return <SpinnerOverlay asModal={ true } />;
|
return (
|
||||||
|
<SpinnerOverlay
|
||||||
|
asModal={ true }
|
||||||
|
ariaLabel={ __(
|
||||||
|
'Loading PayPal settings',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-tab-overview">
|
<div
|
||||||
|
className="ppcp-r-tab-overview"
|
||||||
|
role="region"
|
||||||
|
aria-label={ __(
|
||||||
|
'PayPal Overview',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
>
|
||||||
<Todos />
|
<Todos />
|
||||||
<Features />
|
<Features />
|
||||||
<Help />
|
<Help />
|
||||||
|
|
|
@ -162,10 +162,15 @@ export const useNavigationState = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDetermineProducts = () => {
|
export const useDetermineProducts = ( ownBrandOnly ) => {
|
||||||
return useSelect( ( select ) => {
|
return useSelect(
|
||||||
return select( STORE_NAME ).determineProductsAndCaps();
|
( select ) => {
|
||||||
}, [] );
|
return select( STORE_NAME ).determineProductsAndCaps(
|
||||||
|
ownBrandOnly
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[ ownBrandOnly ]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFlags = () => {
|
export const useFlags = () => {
|
||||||
|
|
|
@ -33,10 +33,11 @@ export const flags = ( state ) => {
|
||||||
* This selector does not return state-values, but uses the state to derive the products-array
|
* This selector does not return state-values, but uses the state to derive the products-array
|
||||||
* that should be returned.
|
* that should be returned.
|
||||||
*
|
*
|
||||||
* @param {{}} state
|
* @param {{}} state
|
||||||
|
* @param {boolean} ownBrandOnly
|
||||||
* @return {{products:string[], options:{}}} The ISU products, based on choices made in the onboarding wizard.
|
* @return {{products:string[], options:{}}} The ISU products, based on choices made in the onboarding wizard.
|
||||||
*/
|
*/
|
||||||
export const determineProductsAndCaps = ( state ) => {
|
export const determineProductsAndCaps = ( state, ownBrandOnly ) => {
|
||||||
/**
|
/**
|
||||||
* An array of product-names that are used to build an onboarding URL via the
|
* An array of product-names that are used to build an onboarding URL via the
|
||||||
* PartnerReferrals API. To avoid confusion with the "products" property from the
|
* PartnerReferrals API. To avoid confusion with the "products" property from the
|
||||||
|
@ -58,8 +59,12 @@ export const determineProductsAndCaps = ( state ) => {
|
||||||
const { isCasualSeller, areOptionalPaymentMethodsEnabled, products } =
|
const { isCasualSeller, areOptionalPaymentMethodsEnabled, products } =
|
||||||
persistentData( state );
|
persistentData( state );
|
||||||
const { canUseVaulting, canUseCardPayments } = flags( state );
|
const { canUseVaulting, canUseCardPayments } = flags( state );
|
||||||
|
const isBrandedCasualSeller = isCasualSeller && ownBrandOnly;
|
||||||
|
|
||||||
const cardPaymentsEligibleAndSelected =
|
const cardPaymentsEligibleAndSelected =
|
||||||
canUseCardPayments && areOptionalPaymentMethodsEnabled;
|
canUseCardPayments &&
|
||||||
|
areOptionalPaymentMethodsEnabled &&
|
||||||
|
! isBrandedCasualSeller;
|
||||||
|
|
||||||
if ( ! cardPaymentsEligibleAndSelected ) {
|
if ( ! cardPaymentsEligibleAndSelected ) {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,7 +31,9 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
const { onboardingUrl } = isSandbox
|
const { onboardingUrl } = isSandbox
|
||||||
? CommonHooks.useSandbox()
|
? CommonHooks.useSandbox()
|
||||||
: CommonHooks.useProduction();
|
: CommonHooks.useProduction();
|
||||||
const { products, options } = OnboardingHooks.useDetermineProducts();
|
const { ownBrandOnly } = CommonHooks.useWooSettings();
|
||||||
|
const { products, options } =
|
||||||
|
OnboardingHooks.useDetermineProducts( ownBrandOnly );
|
||||||
const { startActivity } = CommonHooks.useBusyState();
|
const { startActivity } = CommonHooks.useBusyState();
|
||||||
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
||||||
const [ onboardingUrlState, setOnboardingUrl ] = useState( '' );
|
const [ onboardingUrlState, setOnboardingUrl ] = useState( '' );
|
||||||
|
|
|
@ -51,6 +51,7 @@ use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\PathRepository
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\FeaturesEligibilityService;
|
use WooCommerce\PayPalCommerce\Settings\Service\FeaturesEligibilityService;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\LoadingScreenService;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
|
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\TodosSortingAndFilteringService;
|
use WooCommerce\PayPalCommerce\Settings\Service\TodosSortingAndFilteringService;
|
||||||
|
@ -452,15 +453,26 @@ return array(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.service.merchant_capabilities' => static function ( ContainerInterface $container ) : array {
|
'settings.service.merchant_capabilities' => static function ( ContainerInterface $container ) : array {
|
||||||
|
/**
|
||||||
|
* Use the REST API filter to collect eligibility flags.
|
||||||
|
*
|
||||||
|
* TODO: We should switch to using the new `*.eligibility.check` services, which return a callback instead of a boolean.
|
||||||
|
* Problem with booleans is, that they are evaluated during DI service creation (plugin_loaded), and some relevant filters are not registered at that point.
|
||||||
|
* Overthink the capability system, it's difficult to reuse across the plugin.
|
||||||
|
*/
|
||||||
$features = apply_filters(
|
$features = apply_filters(
|
||||||
'woocommerce_paypal_payments_rest_common_merchant_features',
|
'woocommerce_paypal_payments_rest_common_merchant_features',
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: This condition included in the `*.eligibility.check` services; it can be removed when we switch to those services.
|
||||||
|
$general_settings = $container->get( 'settings.data.general' );
|
||||||
|
assert( $general_settings instanceof GeneralSettings );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
|
'apple_pay' => ( $features['apple_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
|
||||||
'google_pay' => $features['google_pay']['enabled'] ?? false,
|
'google_pay' => ( $features['google_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
|
||||||
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
|
'acdc' => ( $features['advanced_credit_and_debit_cards']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
|
||||||
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
|
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
|
||||||
'apm' => $features['alternative_payment_methods']['enabled'] ?? false,
|
'apm' => $features['alternative_payment_methods']['enabled'] ?? false,
|
||||||
'paylater' => $features['pay_later_messaging']['enabled'] ?? false,
|
'paylater' => $features['pay_later_messaging']['enabled'] ?? false,
|
||||||
|
@ -474,6 +486,8 @@ return array(
|
||||||
|
|
||||||
$button_locations = $container->get( 'settings.service.button_locations' );
|
$button_locations = $container->get( 'settings.service.button_locations' );
|
||||||
$gateways = $container->get( 'settings.service.gateways_status' );
|
$gateways = $container->get( 'settings.service.gateways_status' );
|
||||||
|
|
||||||
|
// TODO: This "merchant_capabilities" service is only used here. Could it be merged to make the code cleaner and less segmented?
|
||||||
$capabilities = $container->get( 'settings.service.merchant_capabilities' );
|
$capabilities = $container->get( 'settings.service.merchant_capabilities' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -514,7 +528,7 @@ return array(
|
||||||
! $button_locations['cart_enabled'], // Add PayPal buttons to cart.
|
! $button_locations['cart_enabled'], // Add PayPal buttons to cart.
|
||||||
! $button_locations['block_checkout_enabled'], // Add PayPal buttons to block checkout.
|
! $button_locations['block_checkout_enabled'], // Add PayPal buttons to block checkout.
|
||||||
! $button_locations['product_enabled'], // Add PayPal buttons to product.
|
! $button_locations['product_enabled'], // Add PayPal buttons to product.
|
||||||
$capabilities['apple_pay'], // Register Domain for Apple Pay.
|
$container->get( 'applepay.eligible' ) && $capabilities['apple_pay'], // Register Domain for Apple Pay.
|
||||||
$capabilities['acdc'] && ! ( $capabilities['apple_pay'] && $capabilities['google_pay'] ), // Add digital wallets to your account.
|
$capabilities['acdc'] && ! ( $capabilities['apple_pay'] && $capabilities['google_pay'] ), // Add digital wallets to your account.
|
||||||
$container->get( 'applepay.eligible' ) && $capabilities['acdc'] && ! $capabilities['apple_pay'], // Add Apple Pay to your account.
|
$container->get( 'applepay.eligible' ) && $capabilities['acdc'] && ! $capabilities['apple_pay'], // Add Apple Pay to your account.
|
||||||
$container->get( 'googlepay.eligible' ) && $capabilities['acdc'] && ! $capabilities['google_pay'], // Add Google Pay to your account.
|
$container->get( 'googlepay.eligible' ) && $capabilities['acdc'] && ! $capabilities['google_pay'], // Add Google Pay to your account.
|
||||||
|
@ -591,6 +605,9 @@ return array(
|
||||||
'settings.service.gateway-redirect' => static function (): GatewayRedirectService {
|
'settings.service.gateway-redirect' => static function (): GatewayRedirectService {
|
||||||
return new GatewayRedirectService();
|
return new GatewayRedirectService();
|
||||||
},
|
},
|
||||||
|
'settings.services.loading-screen-service' => static function ( ContainerInterface $container ) : LoadingScreenService {
|
||||||
|
return new LoadingScreenService();
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Returns a list of all payment gateway IDs created by this plugin.
|
* Returns a list of all payment gateway IDs created by this plugin.
|
||||||
*
|
*
|
||||||
|
|
|
@ -40,6 +40,13 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
*/
|
*/
|
||||||
protected array $woo_settings = array();
|
protected array $woo_settings = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contexts in which the installation path can be reset.
|
||||||
|
*/
|
||||||
|
private const ALLOWED_RESET_REASONS = array(
|
||||||
|
'plugin_uninstall',
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -82,7 +89,7 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
'seller_type' => 'unknown',
|
'seller_type' => 'unknown',
|
||||||
|
|
||||||
// Branded experience installation path.
|
// Branded experience installation path.
|
||||||
'installation_path' => '',
|
'wc_installation_path' => '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +286,7 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
*/
|
*/
|
||||||
public function set_installation_path( string $installation_path ) : void {
|
public function set_installation_path( string $installation_path ) : void {
|
||||||
// The installation path can be set only once.
|
// The installation path can be set only once.
|
||||||
if ( InstallationPathEnum::is_valid( $this->data['installation_path'] ?? '' ) ) {
|
if ( InstallationPathEnum::is_valid( $this->data['wc_installation_path'] ?? '' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +295,7 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->data['installation_path'] = $installation_path;
|
$this->data['wc_installation_path'] = $installation_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,7 +304,23 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_installation_path() : string {
|
public function get_installation_path() : string {
|
||||||
return $this->data['installation_path'] ?? InstallationPathEnum::DIRECT;
|
return $this->data['wc_installation_path'] ?? InstallationPathEnum::DIRECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the installation path to empty string. This method should only be called
|
||||||
|
* during specific circumstances like plugin uninstallation.
|
||||||
|
*
|
||||||
|
* @param string $reason The reason for resetting the path, must be an allowed value.
|
||||||
|
* @return bool Whether the reset was successful.
|
||||||
|
*/
|
||||||
|
public function reset_installation_path( string $reason ) : bool {
|
||||||
|
if ( ! in_array( $reason, self::ALLOWED_RESET_REASONS, true ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data['wc_installation_path'] = '';
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -56,7 +56,7 @@ class StylingSettings extends AbstractDataModel {
|
||||||
'cart' => new LocationStylingDTO( 'cart' ),
|
'cart' => new LocationStylingDTO( 'cart' ),
|
||||||
'classic_checkout' => new LocationStylingDTO( 'classic_checkout' ),
|
'classic_checkout' => new LocationStylingDTO( 'classic_checkout' ),
|
||||||
'express_checkout' => new LocationStylingDTO( 'express_checkout' ),
|
'express_checkout' => new LocationStylingDTO( 'express_checkout' ),
|
||||||
'mini_cart' => new LocationStylingDTO( 'mini_cart' ),
|
'mini_cart' => new LocationStylingDTO( 'mini_cart', false ),
|
||||||
'product' => new LocationStylingDTO( 'product' ),
|
'product' => new LocationStylingDTO( 'product' ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,9 +67,18 @@ class ConnectionListener {
|
||||||
/**
|
/**
|
||||||
* ID of the current user, set by the process() method.
|
* ID of the current user, set by the process() method.
|
||||||
*
|
*
|
||||||
|
* Default value is 0 (guest), until the real ID is provided to process().
|
||||||
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private int $user_id;
|
private int $user_id = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request details (usually the GET data) which were provided.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $request_data = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the instance.
|
* Prepare the instance.
|
||||||
|
@ -92,9 +101,6 @@ class ConnectionListener {
|
||||||
$this->authentication_manager = $authentication_manager;
|
$this->authentication_manager = $authentication_manager;
|
||||||
$this->redirector = $redirector;
|
$this->redirector = $redirector;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
|
||||||
// Initialize as "guest", the real ID is provided via process().
|
|
||||||
$this->user_id = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,18 +112,43 @@ class ConnectionListener {
|
||||||
* @throws RuntimeException If the merchant ID does not match the ID previously set via OAuth.
|
* @throws RuntimeException If the merchant ID does not match the ID previously set via OAuth.
|
||||||
*/
|
*/
|
||||||
public function process( int $user_id, array $request ) : void {
|
public function process( int $user_id, array $request ) : void {
|
||||||
$this->user_id = $user_id;
|
$this->user_id = $user_id;
|
||||||
|
$this->request_data = $request;
|
||||||
|
|
||||||
if ( ! $this->is_valid_request( $request ) ) {
|
if ( ! $this->is_valid_request() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->get_token_from_request();
|
||||||
|
|
||||||
|
$this->process_oauth_token( $token );
|
||||||
|
|
||||||
|
$this->redirect_after_authentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the OAuth token from the request.
|
||||||
|
*
|
||||||
|
* @param string $token The OAuth token extracted from the request.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function process_oauth_token( string $token ) : void {
|
||||||
|
// The request contains OAuth details: To avoid abuse we'll slow down the processing.
|
||||||
|
sleep( 2 );
|
||||||
|
|
||||||
|
if ( ! $token ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->was_token_processed( $token ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = $this->get_token_from_request( $request );
|
|
||||||
if ( ! $this->url_manager->validate_token_and_delete( $token, $this->user_id ) ) {
|
if ( ! $this->url_manager->validate_token_and_delete( $token, $this->user_id ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->extract_data( $request );
|
$data = $this->extract_data();
|
||||||
if ( ! $data ) {
|
if ( ! $data ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -126,22 +157,19 @@ class ConnectionListener {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->authentication_manager->finish_oauth_authentication( $data );
|
$this->authentication_manager->finish_oauth_authentication( $data );
|
||||||
|
$this->mark_token_as_processed( $token );
|
||||||
} catch ( \Exception $e ) {
|
} catch ( \Exception $e ) {
|
||||||
$this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
|
$this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->redirect_after_authentication();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine, if the request details contain connection data that should be
|
* Determine, if the request details contain connection data that should be
|
||||||
* extracted and stored.
|
* extracted and stored.
|
||||||
*
|
*
|
||||||
* @param array $request Request details to verify.
|
|
||||||
*
|
|
||||||
* @return bool True, if the request contains valid connection details.
|
* @return bool True, if the request contains valid connection details.
|
||||||
*/
|
*/
|
||||||
private function is_valid_request( array $request ) : bool {
|
private function is_valid_request() : bool {
|
||||||
if ( $this->user_id < 1 || ! $this->settings_page_id ) {
|
if ( $this->user_id < 1 || ! $this->settings_page_id ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -157,7 +185,7 @@ class ConnectionListener {
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( $required_params as $param ) {
|
foreach ( $required_params as $param ) {
|
||||||
if ( empty( $request[ $param ] ) ) {
|
if ( empty( $this->request_data[ $param ] ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,19 +194,43 @@ class ConnectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the merchant details (ID & email) from the request details.
|
* Checks if the provided authentication token is new or has been used before.
|
||||||
*
|
*
|
||||||
* @param array $request The full request details.
|
* This check catches an issue where we receive the same authentication token twice,
|
||||||
|
* which does not impact the login flow but creates noise in the logs.
|
||||||
|
*
|
||||||
|
* @param string $token The authentication token to check.
|
||||||
|
* @return bool True if the token was already processed.
|
||||||
|
*/
|
||||||
|
private function was_token_processed( string $token ) : bool {
|
||||||
|
$prev_token = get_transient( 'ppcp_previous_auth_token' );
|
||||||
|
|
||||||
|
return $prev_token && $prev_token === $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the processed authentication token so we can prevent double-processing
|
||||||
|
* of already verified token.
|
||||||
|
*
|
||||||
|
* @param string $token The processed authentication token.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function mark_token_as_processed( string $token ) : void {
|
||||||
|
set_transient( 'ppcp_previous_auth_token', $token, 60 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the merchant details (ID & email) from the request details.
|
||||||
*
|
*
|
||||||
* @return array Structured array with 'is_sandbox', 'merchant_id', and 'merchant_email' keys,
|
* @return array Structured array with 'is_sandbox', 'merchant_id', and 'merchant_email' keys,
|
||||||
* or an empty array on failure.
|
* or an empty array on failure.
|
||||||
*/
|
*/
|
||||||
private function extract_data( array $request ) : array {
|
private function extract_data() : array {
|
||||||
$this->logger->info( 'Extracting connection data from request...' );
|
$this->logger->info( 'Extracting connection data from request...' );
|
||||||
|
|
||||||
$merchant_id = $this->get_merchant_id_from_request( $request );
|
$merchant_id = $this->get_merchant_id_from_request( $this->request_data );
|
||||||
$merchant_email = $this->get_merchant_email_from_request( $request );
|
$merchant_email = $this->get_merchant_email_from_request( $this->request_data );
|
||||||
$seller_type = $this->get_seller_type_from_request( $request );
|
$seller_type = $this->get_seller_type_from_request( $this->request_data );
|
||||||
|
|
||||||
if ( ! $merchant_id || ! $merchant_email ) {
|
if ( ! $merchant_id || ! $merchant_email ) {
|
||||||
return array();
|
return array();
|
||||||
|
@ -200,17 +252,16 @@ class ConnectionListener {
|
||||||
$redirect_url = $this->get_onboarding_redirect_url();
|
$redirect_url = $this->get_onboarding_redirect_url();
|
||||||
|
|
||||||
$this->redirector->redirect( $redirect_url );
|
$this->redirector->redirect( $redirect_url );
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sanitized connection token from the incoming request.
|
* Returns the sanitized connection token from the incoming request.
|
||||||
*
|
*
|
||||||
* @param array $request Full request details.
|
|
||||||
*
|
|
||||||
* @return string The sanitized token, or an empty string.
|
* @return string The sanitized token, or an empty string.
|
||||||
*/
|
*/
|
||||||
private function get_token_from_request( array $request ) : string {
|
private function get_token_from_request() : string {
|
||||||
return $this->sanitize_string( $request['ppcpToken'] ?? '' );
|
return $this->sanitize_string( $this->request_data['ppcpToken'] ?? '' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -198,8 +198,6 @@ class AuthenticationManager {
|
||||||
* @throws RuntimeException When failed to retrieve payee.
|
* @throws RuntimeException When failed to retrieve payee.
|
||||||
*/
|
*/
|
||||||
public function authenticate_via_direct_api( bool $use_sandbox, string $client_id, string $client_secret ) : void {
|
public function authenticate_via_direct_api( bool $use_sandbox, string $client_id, string $client_secret ) : void {
|
||||||
$this->disconnect();
|
|
||||||
|
|
||||||
$this->logger->info(
|
$this->logger->info(
|
||||||
'Attempting manual connection to PayPal...',
|
'Attempting manual connection to PayPal...',
|
||||||
array(
|
array(
|
||||||
|
@ -261,8 +259,6 @@ class AuthenticationManager {
|
||||||
* @throws RuntimeException When failed to retrieve payee.
|
* @throws RuntimeException When failed to retrieve payee.
|
||||||
*/
|
*/
|
||||||
public function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
|
public function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
|
||||||
$this->disconnect();
|
|
||||||
|
|
||||||
$this->logger->info(
|
$this->logger->info(
|
||||||
'Attempting OAuth login to PayPal...',
|
'Attempting OAuth login to PayPal...',
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -85,14 +85,9 @@ class GatewayRedirectService {
|
||||||
|
|
||||||
// Get current URL parameters.
|
// Get current URL parameters.
|
||||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
$page = isset( $_GET['page'] ) ? wc_clean( wp_unslash( $_GET['page'] ) ) : '';
|
||||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
$tab = isset( $_GET['tab'] ) ? wc_clean( wp_unslash( $_GET['tab'] ) ) : '';
|
||||||
// The sanitize_get_param method handles unslashing and sanitization internally.
|
$section = isset( $_GET['section'] ) ? wc_clean( wp_unslash( $_GET['section'] ) ) : '';
|
||||||
$page = isset( $_GET['page'] ) ? $this->sanitize_get_param( $_GET['page'] ) : '';
|
|
||||||
$tab = isset( $_GET['tab'] ) ? $this->sanitize_get_param( $_GET['tab'] ) : '';
|
|
||||||
$section = isset( $_GET['section'] ) ? $this->sanitize_get_param( $_GET['section'] ) : '';
|
|
||||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
|
||||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
||||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||||
|
|
||||||
// Check if we're on a WooCommerce settings page and checkout tab.
|
// Check if we're on a WooCommerce settings page and checkout tab.
|
||||||
|
@ -113,17 +108,4 @@ class GatewayRedirectService {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes a GET parameter that could be string or array.
|
|
||||||
*
|
|
||||||
* @param mixed $param The parameter to sanitize.
|
|
||||||
* @return string The sanitized parameter.
|
|
||||||
*/
|
|
||||||
private function sanitize_get_param( $param ): string {
|
|
||||||
if ( is_array( $param ) ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return sanitize_text_field( wp_unslash( $param ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ class InternalRestService {
|
||||||
$rest_nonce = wp_create_nonce( 'wp_rest' );
|
$rest_nonce = wp_create_nonce( 'wp_rest' );
|
||||||
$auth_cookies = $this->build_authentication_cookie();
|
$auth_cookies = $this->build_authentication_cookie();
|
||||||
|
|
||||||
$this->logger->info( "Calling internal REST endpoint: $rest_url" );
|
$this->logger->info( "Calling internal REST endpoint [$rest_url]" );
|
||||||
|
|
||||||
$response = wp_remote_request(
|
$response = wp_remote_request(
|
||||||
$rest_url,
|
$rest_url,
|
||||||
|
@ -69,6 +69,7 @@ class InternalRestService {
|
||||||
'cookies' => $auth_cookies,
|
'cookies' => $auth_cookies,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
$this->logger->debug( "Finished internal REST call [$rest_url]" );
|
||||||
|
|
||||||
if ( is_wp_error( $response ) ) {
|
if ( is_wp_error( $response ) ) {
|
||||||
// Error: The wp_remote_request() call failed (timeout or similar).
|
// Error: The wp_remote_request() call failed (timeout or similar).
|
||||||
|
|
73
modules/ppcp-settings/src/Service/LoadingScreenService.php
Normal file
73
modules/ppcp-settings/src/Service/LoadingScreenService.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Provides loading screen handling logic for PayPal settings page.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoadingScreenService class. Handles the display of loading screen for the PayPal settings page.
|
||||||
|
*/
|
||||||
|
class LoadingScreenService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hooks.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register(): void {
|
||||||
|
if ( ! is_admin() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'admin_head',
|
||||||
|
array( $this, 'add_settings_loading_screen' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add CSS to permanently hide specific WooCommerce elements on the PayPal settings page.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function add_settings_loading_screen(): void {
|
||||||
|
// Only run on the specific WooCommerce PayPal settings page.
|
||||||
|
if ( ! $this->is_ppcp_settings_page() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<style>
|
||||||
|
/* Permanently hide these WooCommerce elements. */
|
||||||
|
.woocommerce form#mainform > *:not(#ppcp-settings-container),
|
||||||
|
#woocommerce-embedded-root {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wpcontent #wpbody {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we're on the PayPal checkout settings page.
|
||||||
|
*
|
||||||
|
* @return bool True if we're on the PayPal settings page
|
||||||
|
*/
|
||||||
|
private function is_ppcp_settings_page(): bool {
|
||||||
|
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||||
|
$page = wc_clean( wp_unslash( $_GET['page'] ?? '' ) );
|
||||||
|
$tab = wc_clean( wp_unslash( $_GET['tab'] ?? '' ) );
|
||||||
|
$section = wc_clean( wp_unslash( $_GET['section'] ?? '' ) );
|
||||||
|
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||||
|
|
||||||
|
return $page === 'wc-settings' && $tab === 'checkout' && $section === 'ppcp-gateway';
|
||||||
|
}
|
||||||
|
}
|
|
@ -234,6 +234,9 @@ class SettingsDataManager {
|
||||||
$methods_apm = $this->methods_definition->group_apms();
|
$methods_apm = $this->methods_definition->group_apms();
|
||||||
$all_methods = array_merge( $methods_paypal, $methods_cards, $methods_apm );
|
$all_methods = array_merge( $methods_paypal, $methods_cards, $methods_apm );
|
||||||
|
|
||||||
|
// Enable the Fastlane watermark by default.
|
||||||
|
$this->payment_methods->set_fastlane_display_watermark( true );
|
||||||
|
|
||||||
foreach ( $all_methods as $method ) {
|
foreach ( $all_methods as $method ) {
|
||||||
$this->payment_methods->toggle_method_state( $method['id'], false );
|
$this->payment_methods->toggle_method_state( $method['id'], false );
|
||||||
}
|
}
|
||||||
|
@ -330,7 +333,7 @@ class SettingsDataManager {
|
||||||
'cart' => new LocationStylingDTO( 'cart', true, $methods_full ),
|
'cart' => new LocationStylingDTO( 'cart', true, $methods_full ),
|
||||||
'classic_checkout' => new LocationStylingDTO( 'classic_checkout', true, $methods_full ),
|
'classic_checkout' => new LocationStylingDTO( 'classic_checkout', true, $methods_full ),
|
||||||
'express_checkout' => new LocationStylingDTO( 'express_checkout', true, $methods_full ),
|
'express_checkout' => new LocationStylingDTO( 'express_checkout', true, $methods_full ),
|
||||||
'mini_cart' => new LocationStylingDTO( 'mini_cart', true, $methods_full ),
|
'mini_cart' => new LocationStylingDTO( 'mini_cart', false, $methods_full ),
|
||||||
'product' => new LocationStylingDTO( 'product', true, $methods_own ),
|
'product' => new LocationStylingDTO( 'product', true, $methods_own ),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ declare( strict_types = 1 );
|
||||||
namespace WooCommerce\PayPalCommerce\Settings;
|
namespace WooCommerce\PayPalCommerce\Settings;
|
||||||
|
|
||||||
use WC_Payment_Gateway;
|
use WC_Payment_Gateway;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
||||||
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
|
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
|
||||||
|
@ -25,11 +26,13 @@ use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\P24Gateway;
|
||||||
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\TrustlyGateway;
|
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\TrustlyGateway;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
|
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\PathRepository;
|
use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\PathRepository;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\LoadingScreenService;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||||
|
@ -123,7 +126,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_enqueue_script( 'ppcp-switch-settings-ui', '', array( 'wp-i18n' ), $script_asset_file['version'] );
|
wp_enqueue_script( 'ppcp-switch-settings-ui', '', array( 'wp-i18n' ), $script_asset_file['version'], false );
|
||||||
wp_set_script_translations(
|
wp_set_script_translations(
|
||||||
'ppcp-switch-settings-ui',
|
'ppcp-switch-settings-ui',
|
||||||
'woocommerce-paypal-payments',
|
'woocommerce-paypal-payments',
|
||||||
|
@ -174,6 +177,11 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Suppress WooCommerce Settings UI elements via CSS to improve the loading experience.
|
||||||
|
$loading_screen_service = $container->get( 'settings.services.loading-screen-service' );
|
||||||
|
assert( $loading_screen_service instanceof LoadingScreenService );
|
||||||
|
$loading_screen_service->register();
|
||||||
|
|
||||||
$this->apply_branded_only_limitations( $container );
|
$this->apply_branded_only_limitations( $container );
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
|
@ -205,7 +213,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
wp_enqueue_script( 'ppcp-admin-settings', '', array( 'wp-i18n' ), $script_asset_file['version'] );
|
wp_enqueue_script( 'ppcp-admin-settings', '', array( 'wp-i18n' ), $script_asset_file['version'], false );
|
||||||
wp_set_script_translations(
|
wp_set_script_translations(
|
||||||
'ppcp-admin-settings',
|
'ppcp-admin-settings',
|
||||||
'woocommerce-paypal-payments',
|
'woocommerce-paypal-payments',
|
||||||
|
@ -281,10 +289,12 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_gateway_admin_options_wrapper',
|
'woocommerce_paypal_payments_gateway_admin_options_wrapper',
|
||||||
function () : void {
|
function () use ( $container ) : void {
|
||||||
global $hide_save_button;
|
global $hide_save_button;
|
||||||
$hide_save_button = true;
|
$hide_save_button = true;
|
||||||
|
|
||||||
|
$this->initialize_branded_only( $container );
|
||||||
|
|
||||||
$this->render_header();
|
$this->render_header();
|
||||||
$this->render_content();
|
$this->render_content();
|
||||||
}
|
}
|
||||||
|
@ -329,6 +339,10 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_merchant_disconnected',
|
'woocommerce_paypal_payments_merchant_disconnected',
|
||||||
static function () use ( $container ) : void {
|
static function () use ( $container ) : void {
|
||||||
|
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
assert( $logger instanceof LoggerInterface );
|
||||||
|
$logger->info( 'Merchant disconnected, reset onboarding' );
|
||||||
|
|
||||||
// Reset onboarding profile.
|
// Reset onboarding profile.
|
||||||
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
||||||
assert( $onboarding_profile instanceof OnboardingProfile );
|
assert( $onboarding_profile instanceof OnboardingProfile );
|
||||||
|
@ -350,6 +364,10 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_authenticated_merchant',
|
'woocommerce_paypal_payments_authenticated_merchant',
|
||||||
static function () use ( $container ) : void {
|
static function () use ( $container ) : void {
|
||||||
|
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
assert( $logger instanceof LoggerInterface );
|
||||||
|
$logger->info( 'Merchant connected, complete onboarding and set defaults.' );
|
||||||
|
|
||||||
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
||||||
assert( $onboarding_profile instanceof OnboardingProfile );
|
assert( $onboarding_profile instanceof OnboardingProfile );
|
||||||
|
|
||||||
|
@ -403,18 +421,18 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
unset( $payment_methods[ CardButtonGateway::ID ] );
|
unset( $payment_methods[ CardButtonGateway::ID ] );
|
||||||
} else {
|
} else {
|
||||||
// For non-ACDC regions unset ACDC, local APMs and set BCDC.
|
// For non-ACDC regions unset ACDC, local APMs and set BCDC.
|
||||||
unset( $payment_methods[ CreditCardGateway::ID ] );
|
unset( $payment_methods[ CreditCardGateway::ID ] );
|
||||||
unset( $payment_methods['pay-later'] );
|
unset( $payment_methods['pay-later'] );
|
||||||
unset( $payment_methods[ BancontactGateway::ID ] );
|
unset( $payment_methods[ BancontactGateway::ID ] );
|
||||||
unset( $payment_methods[ BlikGateway::ID ] );
|
unset( $payment_methods[ BlikGateway::ID ] );
|
||||||
unset( $payment_methods[ EPSGateway::ID ] );
|
unset( $payment_methods[ EPSGateway::ID ] );
|
||||||
unset( $payment_methods[ IDealGateway::ID ] );
|
unset( $payment_methods[ IDealGateway::ID ] );
|
||||||
unset( $payment_methods[ MyBankGateway::ID ] );
|
unset( $payment_methods[ MyBankGateway::ID ] );
|
||||||
unset( $payment_methods[ P24Gateway::ID ] );
|
unset( $payment_methods[ P24Gateway::ID ] );
|
||||||
unset( $payment_methods[ TrustlyGateway::ID ] );
|
unset( $payment_methods[ TrustlyGateway::ID ] );
|
||||||
unset( $payment_methods[ MultibancoGateway::ID ] );
|
unset( $payment_methods[ MultibancoGateway::ID ] );
|
||||||
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
|
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
|
||||||
unset( $payment_methods[ OXXO::ID ] );
|
unset( $payment_methods[ OXXO::ID ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unset Venmo when store location is not United States.
|
// Unset Venmo when store location is not United States.
|
||||||
|
@ -481,34 +499,42 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
$methods[] = $applepay_gateway;
|
$methods[] = $applepay_gateway;
|
||||||
$methods[] = $axo_gateway;
|
$methods[] = $axo_gateway;
|
||||||
|
|
||||||
$is_payments_page = $container->get( 'wcgateway.is-wc-payments-page' );
|
|
||||||
$all_gateway_ids = $container->get( 'settings.config.all-gateway-ids' );
|
|
||||||
|
|
||||||
if ( $is_payments_page ) {
|
|
||||||
$methods = array_filter(
|
|
||||||
$methods,
|
|
||||||
function ( $method ) use ( $all_gateway_ids ): bool {
|
|
||||||
if ( ! is_object( $method )
|
|
||||||
|| $method->id === PayPalGateway::ID
|
|
||||||
|| ! in_array( $method->id, $all_gateway_ids, true )
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $this->is_gateway_enabled( $method->id ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $methods;
|
return $methods;
|
||||||
},
|
},
|
||||||
99
|
99
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the available payment gateways in the WooCommerce admin settings.
|
||||||
|
*
|
||||||
|
* Ensures that only enabled PayPal payment gateways are displayed.
|
||||||
|
*
|
||||||
|
* @hook woocommerce_admin_field_payment_gateways
|
||||||
|
* @priority 5 Allows modifying the registered gateways before they are displayed.
|
||||||
|
*/
|
||||||
|
add_action(
|
||||||
|
'woocommerce_admin_field_payment_gateways',
|
||||||
|
function () use ( $container ) : void {
|
||||||
|
$all_gateway_ids = $container->get( 'settings.config.all-gateway-ids' );
|
||||||
|
$payment_gateways = WC()->payment_gateways->payment_gateways;
|
||||||
|
|
||||||
|
foreach ( $payment_gateways as $index => $payment_gateway ) {
|
||||||
|
$payment_gateway_id = $payment_gateway->id;
|
||||||
|
|
||||||
|
if (
|
||||||
|
! in_array( $payment_gateway_id, $all_gateway_ids, true )
|
||||||
|
|| $payment_gateway_id === PayPalGateway::ID
|
||||||
|
|| $this->is_gateway_enabled( $payment_gateway_id )
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( WC()->payment_gateways->payment_gateways[ $index ] );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
// Remove the Fastlane gateway if the customer is logged in, ensuring that we don't interfere with the Fastlane gateway status in the settings UI.
|
// Remove the Fastlane gateway if the customer is logged in, ensuring that we don't interfere with the Fastlane gateway status in the settings UI.
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_available_payment_gateways',
|
'woocommerce_available_payment_gateways',
|
||||||
|
@ -611,7 +637,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
// Enable Fastlane after onboarding if the store is compatible.
|
// Enable Fastlane after onboarding if the store is compatible.
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_toggle_payment_gateways',
|
'woocommerce_paypal_payments_toggle_payment_gateways',
|
||||||
function( PaymentSettings $payment_methods, ConfigurationFlagsDTO $flags ) use ( $container ) {
|
function ( PaymentSettings $payment_methods, ConfigurationFlagsDTO $flags ) use ( $container ) {
|
||||||
if ( $flags->is_business_seller && $flags->use_card_payments ) {
|
if ( $flags->is_business_seller && $flags->use_card_payments ) {
|
||||||
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
|
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
|
||||||
assert( $compatibility_checker instanceof CompatibilityChecker );
|
assert( $compatibility_checker instanceof CompatibilityChecker );
|
||||||
|
@ -628,7 +654,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
// Toggle payment gateways after onboarding based on flags.
|
// Toggle payment gateways after onboarding based on flags.
|
||||||
add_action(
|
add_action(
|
||||||
'woocommerce_paypal_payments_sync_gateways',
|
'woocommerce_paypal_payments_sync_gateways',
|
||||||
static function() use ( $container ) {
|
static function () use ( $container ) {
|
||||||
$settings_data_manager = $container->get( 'settings.service.data-manager' );
|
$settings_data_manager = $container->get( 'settings.service.data-manager' );
|
||||||
assert( $settings_data_manager instanceof SettingsDataManager );
|
assert( $settings_data_manager instanceof SettingsDataManager );
|
||||||
$settings_data_manager->sync_gateway_settings();
|
$settings_data_manager->sync_gateway_settings();
|
||||||
|
@ -640,6 +666,17 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
assert( $gateway_redirect_service instanceof GatewayRedirectService );
|
assert( $gateway_redirect_service instanceof GatewayRedirectService );
|
||||||
$gateway_redirect_service->register();
|
$gateway_redirect_service->register();
|
||||||
|
|
||||||
|
// Do not render Pay Later messaging if the "Save PayPal and Venmo" setting is enabled.
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_paypal_payments_should_render_pay_later_messaging',
|
||||||
|
static function() use ( $container ): bool {
|
||||||
|
$settings_model = $container->get( 'settings.data.settings' );
|
||||||
|
assert( $settings_model instanceof SettingsModel );
|
||||||
|
|
||||||
|
return ! $settings_model->get_save_paypal_and_venmo();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,6 +704,36 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
add_filter( 'woocommerce_paypal_payments_is_eligible_for_card_fields', '__return_false' );
|
add_filter( 'woocommerce_paypal_payments_is_eligible_for_card_fields', '__return_false' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the branded-only flags if they are not set.
|
||||||
|
*
|
||||||
|
* This method can be called multiple times:
|
||||||
|
* The flags are only initialized once but does not change afterward.
|
||||||
|
*
|
||||||
|
* Also, this check has no impact on performance for two reasons:
|
||||||
|
* 1. The GeneralSettings class is already initialized and will short-circuit
|
||||||
|
* the check if the settings are already initialized.
|
||||||
|
* 2. The settings UI is a React app, this method only runs when the React app
|
||||||
|
* is injected to the DOM, and not while the UI is used.
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container The DI container provider.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function initialize_branded_only( ContainerInterface $container ) : void {
|
||||||
|
$path_repository = $container->get( 'settings.service.branded-experience.path-repository' );
|
||||||
|
assert( $path_repository instanceof PathRepository );
|
||||||
|
|
||||||
|
$partner_attribution = $container->get( 'api.helper.partner-attribution' );
|
||||||
|
assert( $partner_attribution instanceof PartnerAttribution );
|
||||||
|
|
||||||
|
$general_settings = $container->get( 'settings.data.general' );
|
||||||
|
assert( $general_settings instanceof GeneralSettings );
|
||||||
|
|
||||||
|
$path_repository->persist();
|
||||||
|
|
||||||
|
$partner_attribution->initialize_bn_code( $general_settings->get_installation_path() );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outputs the settings page header (title and back-link).
|
* Outputs the settings page header (title and back-link).
|
||||||
*
|
*
|
||||||
|
@ -693,7 +760,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
* @param string $gateway_name The gateway name.
|
* @param string $gateway_name The gateway name.
|
||||||
* @return bool True if the payment gateway with the given name is enabled, otherwise false.
|
* @return bool True if the payment gateway with the given name is enabled, otherwise false.
|
||||||
*/
|
*/
|
||||||
protected function is_gateway_enabled( string $gateway_name ): bool {
|
protected function is_gateway_enabled( string $gateway_name ) : bool {
|
||||||
$gateway_settings = get_option( "woocommerce_{$gateway_name}_settings", array() );
|
$gateway_settings = get_option( "woocommerce_{$gateway_name}_settings", array() );
|
||||||
$gateway_enabled = $gateway_settings['enabled'] ?? false;
|
$gateway_enabled = $gateway_settings['enabled'] ?? false;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,11 @@ class SubscriptionHelper {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$cart = WC()->cart;
|
$cart = WC()->cart;
|
||||||
if ( ! $cart || $cart->is_empty() ) {
|
/**
|
||||||
|
* Don't use `$cart->is_empty()` for checking for an empty cart.
|
||||||
|
* This is maybe called so early that it can corrupt it because it loads it than from session
|
||||||
|
*/
|
||||||
|
if ( ! $cart || empty( $cart->cart_contents ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
* @package WooCommerce\WooCommerce\Logging\Logger
|
* @package WooCommerce\WooCommerce\Logging\Logger
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\WooCommerce\Logging\Logger;
|
namespace WooCommerce\WooCommerce\Logging\Logger;
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ use Psr\Log\LoggerTrait;
|
||||||
*/
|
*/
|
||||||
class WooCommerceLogger implements LoggerInterface {
|
class WooCommerceLogger implements LoggerInterface {
|
||||||
|
|
||||||
|
|
||||||
use LoggerTrait;
|
use LoggerTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,23 +34,48 @@ class WooCommerceLogger implements LoggerInterface {
|
||||||
*
|
*
|
||||||
* @var string The source.
|
* @var string The source.
|
||||||
*/
|
*/
|
||||||
private $source;
|
private string $source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details that are output before the first real log message, to help
|
||||||
|
* identify the request.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $request_info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A random prefix which is visible in every log message, to better
|
||||||
|
* understand which messages belong to the same request.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $prefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WooCommerceLogger constructor.
|
* WooCommerceLogger constructor.
|
||||||
*
|
*
|
||||||
* @param \WC_Logger_Interface $wc_logger The WooCommerce logger.
|
* @param \WC_Logger_Interface $wc_logger The WooCommerce logger.
|
||||||
* @param string $source The source.
|
* @param string $source The source.
|
||||||
*/
|
*/
|
||||||
public function __construct( \WC_Logger_Interface $wc_logger, string $source ) {
|
public function __construct( \WC_Logger_Interface $wc_logger, string $source ) {
|
||||||
$this->wc_logger = $wc_logger;
|
$this->wc_logger = $wc_logger;
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
|
$this->prefix = sprintf( '#%s - ', wp_rand( 1000, 9999 ) );
|
||||||
|
|
||||||
|
// phpcs:disable -- Intentionally not sanitized, for logging purposes.
|
||||||
|
$method = wp_unslash( $_SERVER['REQUEST_METHOD'] ?? 'CLI' );
|
||||||
|
$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '-' );
|
||||||
|
// phpcs:enable
|
||||||
|
|
||||||
|
$request_path = wp_parse_url( $request_uri, PHP_URL_PATH );
|
||||||
|
$this->request_info = "$method $request_path";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message.
|
* Logs a message.
|
||||||
*
|
*
|
||||||
* @param mixed $level The logging level.
|
* @param mixed $level The logging level.
|
||||||
* @param string $message The message.
|
* @param string $message The message.
|
||||||
* @param array $context The context.
|
* @param array $context The context.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +83,16 @@ class WooCommerceLogger implements LoggerInterface {
|
||||||
if ( ! isset( $context['source'] ) ) {
|
if ( ! isset( $context['source'] ) ) {
|
||||||
$context['source'] = $this->source;
|
$context['source'] = $this->source;
|
||||||
}
|
}
|
||||||
$this->wc_logger->log( $level, $message, $context );
|
|
||||||
|
if ( $this->request_info ) {
|
||||||
|
$this->wc_logger->log(
|
||||||
|
'debug',
|
||||||
|
"{$this->prefix}[New Request] $this->request_info",
|
||||||
|
array( 'source' => $context['source'] )
|
||||||
|
);
|
||||||
|
$this->request_info = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wc_logger->log( $level, "{$this->prefix}$message", $context );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "woocommerce-paypal-payments",
|
"name": "woocommerce-paypal-payments",
|
||||||
"version": "3.0.1",
|
"version": "3.0.4",
|
||||||
"description": "WooCommerce PayPal Payments",
|
"description": "WooCommerce PayPal Payments",
|
||||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0",
|
||||||
|
|
26
readme.txt
26
readme.txt
|
@ -2,9 +2,9 @@
|
||||||
Contributors: paypal, woocommerce, automattic, syde
|
Contributors: paypal, woocommerce, automattic, syde
|
||||||
Tags: woocommerce, paypal, payments, ecommerce, credit card
|
Tags: woocommerce, paypal, payments, ecommerce, credit card
|
||||||
Requires at least: 6.5
|
Requires at least: 6.5
|
||||||
Tested up to: 6.7
|
Tested up to: 6.8
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Stable tag: 3.0.1
|
Stable tag: 3.0.4
|
||||||
License: GPLv2
|
License: GPLv2
|
||||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
|
||||||
|
@ -156,6 +156,28 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
|
||||||
|
|
||||||
== Changelog ==
|
== 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 =
|
= 3.0.1 - 2025-03-26 =
|
||||||
* Enhancement - Include Fastlane meta on homepage #3151
|
* Enhancement - Include Fastlane meta on homepage #3151
|
||||||
* Enhancement - Include Branded-only plugin configuration for certain installation paths
|
* Enhancement - Include Branded-only plugin configuration for certain installation paths
|
||||||
|
|
|
@ -5,28 +5,30 @@
|
||||||
* @package WooCommerce\PayPalCommerce
|
* @package WooCommerce\PayPalCommerce
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce;
|
namespace WooCommerce\PayPalCommerce;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\Uninstall\ClearDatabaseInterface;
|
use WooCommerce\PayPalCommerce\Uninstall\ClearDatabaseInterface;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||||
|
|
||||||
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
||||||
die( 'Direct access not allowed.' );
|
die( 'Direct access not allowed.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$root_dir = __DIR__;
|
$root_dir = __DIR__;
|
||||||
$main_plugin_file = "{$root_dir}/woocommerce-paypal-payments.php";
|
$main_plugin_file = "{$root_dir}/woocommerce-paypal-payments.php";
|
||||||
|
|
||||||
if ( !file_exists( $main_plugin_file ) ) {
|
if ( ! file_exists( $main_plugin_file ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
require $main_plugin_file;
|
require $main_plugin_file;
|
||||||
|
|
||||||
( static function (string $root_dir): void {
|
( static function ( string $root_dir ) : void {
|
||||||
|
|
||||||
$autoload_filepath = "{$root_dir}/vendor/autoload.php";
|
$autoload_filepath = "{$root_dir}/vendor/autoload.php";
|
||||||
if ( file_exists( $autoload_filepath ) && ! class_exists( '\WooCommerce\PayPalCommerce\PluginModule' ) ) {
|
if ( file_exists( $autoload_filepath ) && ! class_exists( '\WooCommerce\PayPalCommerce\PluginModule' ) ) {
|
||||||
|
@ -39,9 +41,12 @@ require $main_plugin_file;
|
||||||
$app_container = $bootstrap( $root_dir );
|
$app_container = $bootstrap( $root_dir );
|
||||||
assert( $app_container instanceof ContainerInterface );
|
assert( $app_container instanceof ContainerInterface );
|
||||||
|
|
||||||
|
clear_plugin_branding( $app_container );
|
||||||
|
|
||||||
$settings = $app_container->get( 'wcgateway.settings' );
|
$settings = $app_container->get( 'wcgateway.settings' );
|
||||||
assert( $settings instanceof Settings );
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
// TODO: This is a flag only present in the #legacy-ui. Should we change this to a filter, or remove the DB reset code?
|
||||||
$should_clear_db = $settings->has( 'uninstall_clear_db_on_uninstall' ) && $settings->get( 'uninstall_clear_db_on_uninstall' );
|
$should_clear_db = $settings->has( 'uninstall_clear_db_on_uninstall' ) && $settings->get( 'uninstall_clear_db_on_uninstall' );
|
||||||
if ( ! $should_clear_db ) {
|
if ( ! $should_clear_db ) {
|
||||||
return;
|
return;
|
||||||
|
@ -74,4 +79,33 @@ require $main_plugin_file;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} )($root_dir);
|
} )( $root_dir );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears plugin branding by resetting the installation path flag.
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container The plugin's DI container.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function clear_plugin_branding( ContainerInterface $container ) : void {
|
||||||
|
/*
|
||||||
|
* This flag is set by WooCommerce when the plugin is installed via their
|
||||||
|
* Settings page. We remove it here, as uninstalling the plugin should
|
||||||
|
* open up the possibility of installing it from a different source in
|
||||||
|
* "white label" mode.
|
||||||
|
*/
|
||||||
|
delete_option( 'woocommerce_paypal_branded' );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$general_settings = $container->get( 'settings.data.general' );
|
||||||
|
assert( $general_settings instanceof GeneralSettings );
|
||||||
|
|
||||||
|
if ( $general_settings->reset_installation_path( 'plugin_uninstall' ) ) {
|
||||||
|
$general_settings->save();
|
||||||
|
}
|
||||||
|
} catch ( NotFoundExceptionInterface $e ) {
|
||||||
|
// The container does not exist or did not return a GeneralSettings instance.
|
||||||
|
// In any case: A failure can be ignored, as it means we cannot reset anything.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: WooCommerce PayPal Payments
|
* Plugin Name: WooCommerce PayPal Payments
|
||||||
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
|
||||||
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
|
||||||
* Version: 3.0.1
|
* Version: 3.0.4
|
||||||
* Author: PayPal
|
* Author: PayPal
|
||||||
* Author URI: https://paypal.com/
|
* Author URI: https://paypal.com/
|
||||||
* License: GPL-2.0
|
* License: GPL-2.0
|
||||||
|
@ -27,7 +27,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
|
||||||
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
define( 'PAYPAL_URL', 'https://www.paypal.com' );
|
||||||
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
|
||||||
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
|
||||||
define( 'PAYPAL_INTEGRATION_DATE', '2025-03-25' );
|
define( 'PAYPAL_INTEGRATION_DATE', '2025-04-23' );
|
||||||
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
||||||
|
|
||||||
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue