mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge branch 'refs/heads/trunk' into PCP-4156-implement-3ds-for-google-pay
This commit is contained in:
commit
d66a7521f5
35 changed files with 665 additions and 135 deletions
|
@ -94,6 +94,45 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group =
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the number of times a filter has been applied during the current request.
|
||||||
|
*
|
||||||
|
* @since 6.1.0
|
||||||
|
*
|
||||||
|
* @global int[] $wp_filters Stores the number of times each filter was triggered.
|
||||||
|
*
|
||||||
|
* @param string $hook_name The name of the filter hook.
|
||||||
|
* @return int The number of times the filter hook has been applied.
|
||||||
|
*/
|
||||||
|
function did_filter( $hook_name ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not a filter hook is currently being processed.
|
||||||
|
*
|
||||||
|
* The function current_filter() only returns the most recent filter being executed.
|
||||||
|
* did_filter() returns the number of times a filter has been applied during
|
||||||
|
* the current request.
|
||||||
|
*
|
||||||
|
* This function allows detection for any filter currently being executed
|
||||||
|
* (regardless of whether it's the most recent filter to fire, in the case of
|
||||||
|
* hooks called from hook callbacks) to be verified.
|
||||||
|
*
|
||||||
|
* @since 3.9.0
|
||||||
|
*
|
||||||
|
* @see current_filter()
|
||||||
|
* @see did_filter()
|
||||||
|
* @global string[] $wp_current_filter Current filter.
|
||||||
|
*
|
||||||
|
* @param string|null $hook_name Optional. Filter hook to check. Defaults to null,
|
||||||
|
* which checks if any filter is currently being run.
|
||||||
|
* @return bool Whether the filter is currently in the stack.
|
||||||
|
*/
|
||||||
|
function doing_filter( $hook_name = null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML API: WP_HTML_Tag_Processor class
|
* HTML API: WP_HTML_Tag_Processor class
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -91,9 +91,12 @@ return function ( string $root_dir ): iterable {
|
||||||
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
|
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
|
||||||
|
$preview_new_ux = '1' === getenv( 'PCP_SETTINGS_ENABLED' );
|
||||||
|
|
||||||
if ( apply_filters(
|
if ( apply_filters(
|
||||||
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
|
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
|
||||||
getenv( 'PCP_SETTINGS_ENABLED' ) === '1'
|
$show_new_ux || $preview_new_ux
|
||||||
) ) {
|
) ) {
|
||||||
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
|
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,16 @@ return array(
|
||||||
$container->get( 'settings.data.settings' ),
|
$container->get( 'settings.data.settings' ),
|
||||||
$subscription_map_helper->map()
|
$subscription_map_helper->map()
|
||||||
),
|
),
|
||||||
|
/**
|
||||||
|
* We need to pass the PaymentSettings model instance to use it in some helpers.
|
||||||
|
* Once the new settings module is permanently enabled,
|
||||||
|
* this model can be passed as a dependency to the appropriate helper classes.
|
||||||
|
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
|
||||||
|
*/
|
||||||
|
new SettingsMap(
|
||||||
|
$container->get( 'settings.data.payment' ),
|
||||||
|
array()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'compat.settings.settings_map_helper' => static function( ContainerInterface $container ) : SettingsMapHelper {
|
'compat.settings.settings_map_helper' => static function( ContainerInterface $container ) : SettingsMapHelper {
|
||||||
|
@ -180,7 +190,8 @@ return array(
|
||||||
$container->get( 'compat.settings.styling_map_helper' ),
|
$container->get( 'compat.settings.styling_map_helper' ),
|
||||||
$container->get( 'compat.settings.settings_tab_map_helper' ),
|
$container->get( 'compat.settings.settings_tab_map_helper' ),
|
||||||
$container->get( 'compat.settings.subscription_map_helper' ),
|
$container->get( 'compat.settings.subscription_map_helper' ),
|
||||||
$container->get( 'compat.settings.general_map_helper' )
|
$container->get( 'compat.settings.general_map_helper' ),
|
||||||
|
$container->get( 'wcgateway.settings.admin-settings-enabled' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'compat.settings.styling_map_helper' => static function() : StylingSettingsMapHelper {
|
'compat.settings.styling_map_helper' => static function() : StylingSettingsMapHelper {
|
||||||
|
|
|
@ -10,7 +10,9 @@ declare( strict_types = 1 );
|
||||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
|
||||||
|
|
||||||
|
@ -72,6 +74,13 @@ class SettingsMapHelper {
|
||||||
*/
|
*/
|
||||||
protected GeneralSettingsMapHelper $general_settings_map_helper;
|
protected GeneralSettingsMapHelper $general_settings_map_helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the new settings module is enabled.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected bool $new_settings_module_enabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -80,6 +89,7 @@ class SettingsMapHelper {
|
||||||
* @param SettingsTabMapHelper $settings_tab_map_helper A helper for mapping the old/new settings tab settings.
|
* @param SettingsTabMapHelper $settings_tab_map_helper A helper for mapping the old/new settings tab settings.
|
||||||
* @param SubscriptionSettingsMapHelper $subscription_map_helper A helper for mapping old and new subscription settings.
|
* @param SubscriptionSettingsMapHelper $subscription_map_helper A helper for mapping old and new subscription settings.
|
||||||
* @param GeneralSettingsMapHelper $general_settings_map_helper A helper for mapping old and new general settings.
|
* @param GeneralSettingsMapHelper $general_settings_map_helper A helper for mapping old and new general settings.
|
||||||
|
* @param bool $new_settings_module_enabled Whether the new settings module is enabled.
|
||||||
* @throws RuntimeException When an old key has multiple mappings.
|
* @throws RuntimeException When an old key has multiple mappings.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -87,7 +97,8 @@ class SettingsMapHelper {
|
||||||
StylingSettingsMapHelper $styling_settings_map_helper,
|
StylingSettingsMapHelper $styling_settings_map_helper,
|
||||||
SettingsTabMapHelper $settings_tab_map_helper,
|
SettingsTabMapHelper $settings_tab_map_helper,
|
||||||
SubscriptionSettingsMapHelper $subscription_map_helper,
|
SubscriptionSettingsMapHelper $subscription_map_helper,
|
||||||
GeneralSettingsMapHelper $general_settings_map_helper
|
GeneralSettingsMapHelper $general_settings_map_helper,
|
||||||
|
bool $new_settings_module_enabled
|
||||||
) {
|
) {
|
||||||
$this->validate_settings_map( $settings_map );
|
$this->validate_settings_map( $settings_map );
|
||||||
$this->settings_map = $settings_map;
|
$this->settings_map = $settings_map;
|
||||||
|
@ -95,6 +106,7 @@ class SettingsMapHelper {
|
||||||
$this->settings_tab_map_helper = $settings_tab_map_helper;
|
$this->settings_tab_map_helper = $settings_tab_map_helper;
|
||||||
$this->subscription_map_helper = $subscription_map_helper;
|
$this->subscription_map_helper = $subscription_map_helper;
|
||||||
$this->general_settings_map_helper = $general_settings_map_helper;
|
$this->general_settings_map_helper = $general_settings_map_helper;
|
||||||
|
$this->new_settings_module_enabled = $new_settings_module_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,6 +136,10 @@ class SettingsMapHelper {
|
||||||
* @return mixed|null The value of the mapped setting, or null if not found.
|
* @return mixed|null The value of the mapped setting, or null if not found.
|
||||||
*/
|
*/
|
||||||
public function mapped_value( string $old_key ) {
|
public function mapped_value( string $old_key ) {
|
||||||
|
if ( ! $this->new_settings_module_enabled ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ensure_map_initialized();
|
$this->ensure_map_initialized();
|
||||||
if ( ! isset( $this->key_to_model[ $old_key ] ) ) {
|
if ( ! isset( $this->key_to_model[ $old_key ] ) ) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -147,6 +163,10 @@ class SettingsMapHelper {
|
||||||
* @return bool True if the key exists in the new settings, false otherwise.
|
* @return bool True if the key exists in the new settings, false otherwise.
|
||||||
*/
|
*/
|
||||||
public function has_mapped_key( string $old_key ) : bool {
|
public function has_mapped_key( string $old_key ) : bool {
|
||||||
|
if ( ! $this->new_settings_module_enabled ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ensure_map_initialized();
|
$this->ensure_map_initialized();
|
||||||
|
|
||||||
return isset( $this->key_to_model[ $old_key ] );
|
return isset( $this->key_to_model[ $old_key ] );
|
||||||
|
@ -169,7 +189,11 @@ class SettingsMapHelper {
|
||||||
|
|
||||||
switch ( true ) {
|
switch ( true ) {
|
||||||
case $model instanceof StylingSettings:
|
case $model instanceof StylingSettings:
|
||||||
return $this->styling_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
return $this->styling_settings_map_helper->mapped_value(
|
||||||
|
$old_key,
|
||||||
|
$this->model_cache[ $model_id ],
|
||||||
|
$this->get_payment_settings_model()
|
||||||
|
);
|
||||||
|
|
||||||
case $model instanceof GeneralSettings:
|
case $model instanceof GeneralSettings:
|
||||||
return $this->general_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
return $this->general_settings_map_helper->mapped_value( $old_key, $this->model_cache[ $model_id ] );
|
||||||
|
@ -217,4 +241,23 @@ class SettingsMapHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the PaymentSettings model instance.
|
||||||
|
*
|
||||||
|
* Once the new settings module is permanently enabled,
|
||||||
|
* this model can be passed as a dependency to the appropriate helper classes.
|
||||||
|
* For now, we must pass it this way to avoid errors when the new settings module is disabled.
|
||||||
|
*
|
||||||
|
* @return AbstractDataModel|null
|
||||||
|
*/
|
||||||
|
protected function get_payment_settings_model() : ?AbstractDataModel {
|
||||||
|
foreach ( $this->settings_map as $settings_map_instance ) {
|
||||||
|
if ( $settings_map_instance->get_model() instanceof PaymentSettings ) {
|
||||||
|
return $settings_map_instance->get_model();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,11 @@ declare(strict_types=1);
|
||||||
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
namespace WooCommerce\PayPalCommerce\Compat\Settings;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||||
|
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\AbstractDataModel;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||||
use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO;
|
use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +27,7 @@ class StylingSettingsMapHelper {
|
||||||
|
|
||||||
use ContextTrait;
|
use ContextTrait;
|
||||||
|
|
||||||
protected const BUTTON_NAMES = array( 'googlepay', 'applepay', 'pay-later' );
|
protected const BUTTON_NAMES = array( GooglePayGateway::ID, ApplePayGateway::ID, 'pay-later' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps old setting keys to new setting style names.
|
* Maps old setting keys to new setting style names.
|
||||||
|
@ -64,12 +68,13 @@ class StylingSettingsMapHelper {
|
||||||
/**
|
/**
|
||||||
* 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 LocationStylingDTO[] $styling_models The list of location styling models.
|
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||||
|
* @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, array $styling_models ) {
|
public function mapped_value( string $old_key, array $styling_models, ?AbstractDataModel $payment_settings ) {
|
||||||
switch ( $old_key ) {
|
switch ( $old_key ) {
|
||||||
case 'smart_button_locations':
|
case 'smart_button_locations':
|
||||||
return $this->mapped_smart_button_locations_value( $styling_models );
|
return $this->mapped_smart_button_locations_value( $styling_models );
|
||||||
|
@ -81,16 +86,16 @@ class StylingSettingsMapHelper {
|
||||||
return $this->mapped_pay_later_button_locations_value( $styling_models );
|
return $this->mapped_pay_later_button_locations_value( $styling_models );
|
||||||
|
|
||||||
case 'disable_funding':
|
case 'disable_funding':
|
||||||
return $this->mapped_disabled_funding_value( $styling_models );
|
return $this->mapped_disabled_funding_value( $styling_models, $payment_settings );
|
||||||
|
|
||||||
case 'googlepay_button_enabled':
|
case 'googlepay_button_enabled':
|
||||||
return $this->mapped_button_enabled_value( $styling_models, 'googlepay' );
|
return $this->mapped_button_enabled_value( $styling_models, GooglePayGateway::ID, $payment_settings );
|
||||||
|
|
||||||
case 'applepay_button_enabled':
|
case 'applepay_button_enabled':
|
||||||
return $this->mapped_button_enabled_value( $styling_models, 'applepay' );
|
return $this->mapped_button_enabled_value( $styling_models, ApplePayGateway::ID, $payment_settings );
|
||||||
|
|
||||||
case 'pay_later_button_enabled':
|
case 'pay_later_button_enabled':
|
||||||
return $this->mapped_button_enabled_value( $styling_models, 'pay-later' );
|
return $this->mapped_button_enabled_value( $styling_models, 'pay-later', $payment_settings );
|
||||||
|
|
||||||
default:
|
default:
|
||||||
foreach ( $this->locations_map() as $old_location_name => $new_location_name ) {
|
foreach ( $this->locations_map() as $old_location_name => $new_location_name ) {
|
||||||
|
@ -224,52 +229,80 @@ class StylingSettingsMapHelper {
|
||||||
/**
|
/**
|
||||||
* Retrieves the mapped disabled funding value from the new settings.
|
* Retrieves the mapped disabled funding value from the new settings.
|
||||||
*
|
*
|
||||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||||
|
* @param AbstractDataModel|null $payment_settings The payment settings model.
|
||||||
* @return array|null The list of disabled funding, or null if none are disabled.
|
* @return array|null The list of disabled funding, or null if none are disabled.
|
||||||
*/
|
*/
|
||||||
protected function mapped_disabled_funding_value( array $styling_models ): ?array {
|
protected function mapped_disabled_funding_value( array $styling_models, ?AbstractDataModel $payment_settings ): ?array {
|
||||||
|
if ( is_null( $payment_settings ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$disabled_funding = array();
|
$disabled_funding = array();
|
||||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||||
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
||||||
|
assert( $payment_settings instanceof PaymentSettings );
|
||||||
|
|
||||||
foreach ( $styling_models as $model ) {
|
foreach ( $styling_models as $model ) {
|
||||||
if ( $model->location !== $current_context || in_array( 'venmo', $model->methods, true ) ) {
|
if ( $model->location === $current_context ) {
|
||||||
continue;
|
if ( ! in_array( 'venmo', $model->methods, true ) || ! $payment_settings->get_venmo_enabled() ) {
|
||||||
|
$disabled_funding[] = 'venmo';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$disabled_funding[] = 'venmo';
|
|
||||||
|
|
||||||
return $disabled_funding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return $disabled_funding;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the mapped enabled or disabled button value from the new settings.
|
* Retrieves the mapped enabled or disabled button value from the new settings.
|
||||||
*
|
*
|
||||||
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
* @param LocationStylingDTO[] $styling_models The list of location styling models.
|
||||||
* @param string $button_name The button name (see {@link self::BUTTON_NAMES}).
|
* @param string $button_name The button name (see {@link self::BUTTON_NAMES}).
|
||||||
|
* @param AbstractDataModel|null $payment_settings The payment settings model.
|
||||||
* @return int The enabled (1) or disabled (0) state.
|
* @return int The enabled (1) or disabled (0) state.
|
||||||
* @throws RuntimeException If an invalid button name is provided.
|
* @throws RuntimeException If an invalid button name is provided.
|
||||||
*/
|
*/
|
||||||
protected function mapped_button_enabled_value( array $styling_models, string $button_name ): ?int {
|
protected function mapped_button_enabled_value( array $styling_models, string $button_name, ?AbstractDataModel $payment_settings ): ?int {
|
||||||
|
if ( is_null( $payment_settings ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! in_array( $button_name, self::BUTTON_NAMES, true ) ) {
|
if ( ! in_array( $button_name, self::BUTTON_NAMES, true ) ) {
|
||||||
throw new RuntimeException( 'Wrong button name is provided.' );
|
throw new RuntimeException( 'Wrong button name is provided.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
$locations_to_context_map = $this->current_context_to_new_button_location_map();
|
||||||
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
$current_context = $locations_to_context_map[ $this->context() ] ?? '';
|
||||||
|
assert( $payment_settings instanceof PaymentSettings );
|
||||||
|
|
||||||
foreach ( $styling_models as $model ) {
|
foreach ( $styling_models as $model ) {
|
||||||
if ( ! $model->enabled
|
if ( $model->enabled && $model->location === $current_context ) {
|
||||||
|| $model->location !== $current_context
|
if ( in_array( $button_name, $model->methods, true ) && $payment_settings->is_method_enabled( $button_name ) ) {
|
||||||
|| ! in_array( $button_name, $model->methods, true )
|
return 1;
|
||||||
) {
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
if ( $current_context === 'classic_checkout' ) {
|
||||||
|
/**
|
||||||
|
* Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout)
|
||||||
|
* In case if the button is disabled from the styling settings but the gateway itself is enabled.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_checkout_button_render',
|
||||||
|
static function (): void {
|
||||||
|
?>
|
||||||
|
<style data-hide-gateway='<?php echo esc_attr( GooglePayGateway::ID ); ?>'>
|
||||||
|
.wc_payment_method.payment_method_ppcp-googlepay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -81,22 +81,23 @@ const GooglePayComponent = ( { isEditing, buttonAttributes } ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const features = [ 'products' ];
|
const features = [ 'products' ];
|
||||||
|
if ( buttonConfig?.is_enabled ) {
|
||||||
registerExpressPaymentMethod( {
|
registerExpressPaymentMethod( {
|
||||||
name: buttonData.id,
|
name: buttonData.id,
|
||||||
title: `PayPal - ${ buttonData.title }`,
|
title: `PayPal - ${ buttonData.title }`,
|
||||||
description: __(
|
description: __(
|
||||||
'Eligible users will see the PayPal button.',
|
'Eligible users will see the PayPal button.',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
gatewayId: 'ppcp-gateway',
|
gatewayId: 'ppcp-gateway',
|
||||||
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
||||||
content: <GooglePayComponent isEditing={ false } />,
|
content: <GooglePayComponent isEditing={ false } />,
|
||||||
edit: <GooglePayComponent isEditing={ true } />,
|
edit: <GooglePayComponent isEditing={ true } />,
|
||||||
ariaLabel: buttonData.title,
|
ariaLabel: buttonData.title,
|
||||||
canMakePayment: () => buttonData.enabled,
|
canMakePayment: () => buttonData.enabled,
|
||||||
supports: {
|
supports: {
|
||||||
features,
|
features,
|
||||||
style: [ 'height', 'borderRadius' ],
|
style: [ 'height', 'borderRadius' ],
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
}
|
||||||
|
|
120
modules/ppcp-settings/docs/authentication-flows.md
Normal file
120
modules/ppcp-settings/docs/authentication-flows.md
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# Authentication Flows
|
||||||
|
|
||||||
|
The settings UI offers two distinct authentication methods:
|
||||||
|
|
||||||
|
- OAuth
|
||||||
|
- Direct API
|
||||||
|
|
||||||
|
## OAuth
|
||||||
|
|
||||||
|
This is the usual authentication UI for most users. It opens a "PayPal popup" with a login mask.
|
||||||
|
The authentication flow consists of **three steps**:
|
||||||
|
|
||||||
|
- Generate a referral URL with a special token
|
||||||
|
- Translate a one-time OAuth secret into permanent API credentials
|
||||||
|
- Complete authentication by confirming the token from step 1
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
1. Available on the first onboarding page (for sandbox login), or on the last page of the onboarding wizard.
|
||||||
|
2. Authentication is initiated by clicking a "Connect" button which opens a popup with a PayPal login mask
|
||||||
|
- Sometimes the login opens in a new tab, mainly on Mac when using the browser in full-screen mode
|
||||||
|
3. After completing the login, the final page shows a "Return to your store" button; clicking that button closes the popup/tab and completes the authentication process
|
||||||
|
|
||||||
|
**More details on what happens:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
autonumber
|
||||||
|
participant R as React API
|
||||||
|
participant S as PHP Server
|
||||||
|
|
||||||
|
R->>S: Request partner referral URL
|
||||||
|
Note over S: Generate and store a one-time token
|
||||||
|
create participant W as WooCommerce API
|
||||||
|
S->>W: Request referral URL
|
||||||
|
destroy W
|
||||||
|
W->>S: Generate the full partner referral URL
|
||||||
|
S->>R: Return referral URL
|
||||||
|
create participant P as PayPal Popup
|
||||||
|
R->>P: Open PayPal popup, which was generated by WooCommerce APi
|
||||||
|
Note over P: Complete login inside Popup
|
||||||
|
P->>R: Call JS function with OAuth ID and shared secret
|
||||||
|
R->>S: Send OAuth data to REST endpoint
|
||||||
|
create participant PP as PayPal API
|
||||||
|
S->>PP: Request permanent credentials
|
||||||
|
PP->>S: Translate one-time secret to permanent credentials
|
||||||
|
destroy P
|
||||||
|
P->>R: Redirect browser tab with unique token
|
||||||
|
Note over R: App unmounts during redirect
|
||||||
|
|
||||||
|
Note over S: During page load
|
||||||
|
Note over S: Verify token and finalize authentication
|
||||||
|
S->>PP: Request merchant details
|
||||||
|
destroy PP
|
||||||
|
PP->>S: Return merchant details (e.g. country)
|
||||||
|
Note over S: Render the settings page with React app
|
||||||
|
S->>R: Boot react app in "settings mode"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Authentication starts _before_ the "Connect" button is rendered, as we generate a one-time partner referral URL
|
||||||
|
- See `ConnectionUrlGenerator::generate()`
|
||||||
|
- This referral URL configures PayPal: Which items render inside the Popup? What is the "return
|
||||||
|
URL" for the final step? Is it a sandbox or live login?
|
||||||
|
2. _...The merchant completes the login or account creation flow inside the popup..._
|
||||||
|
3. During page-load of the final confirmation page inside the popup: PayPal directly calls a JS function on the WooCommerce settings page, i.e. the popup communicates with the open WooCommerce tab. This JS function sends an oauth ID and shared secret (OTP) to a REST endpoint
|
||||||
|
- See `AuthenticatoinRestEndpoint::connect_oauth()`
|
||||||
|
- See `AuthenticationManager::authenticate_via_oauth()` → translates the one-time shared secret
|
||||||
|
into a permanent client secret
|
||||||
|
- At this stage, the authentication is _incomplete_, as some details are only provided by the
|
||||||
|
final step
|
||||||
|
4. When clicking the "Return to store" button, the popup closes and the WooCommerce settings page "reloads"; it's actually a _redirect_ which is initiated by PayPal and receives a unique token (which was generated by the `ConnectionUrlGenerator`) that is required to complete authentication.
|
||||||
|
- See `ConnectionListener::process()`
|
||||||
|
- See `AuthenticationManager::finish_oauth_authentication()`
|
||||||
|
- This listener runs on every wp-admin page load and bails if the required token is not present
|
||||||
|
5. After the final page reload, the React app directly enters "Settings mode"
|
||||||
|
|
||||||
|
## Direct API
|
||||||
|
|
||||||
|
This method is only available for business accounts, as it requires the merchant to create a PayPal REST app that's linked to their account.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Setup the PayPal REST app</strong></summary>
|
||||||
|
|
||||||
|
1. Visit https://developer.paypal.com/
|
||||||
|
2. In section "Apps & Credentials" click "Create App"
|
||||||
|
3. After the app is ready, it displays the `Client ID` and `Secret Key` values
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
1. Available on the first onboarding screen, via the "See advanced options" form at the bottom of the page
|
||||||
|
2. Activate the "Manual Connection" toggle; then enter the `Client ID` and `Secret Key` and hit Enter
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant R as React
|
||||||
|
participant S as Server
|
||||||
|
participant P as PayPal API
|
||||||
|
|
||||||
|
R->>S: Send credentials to REST endpoint
|
||||||
|
S->>P: Authenticate via Direct API
|
||||||
|
P->>S: Return authentication result
|
||||||
|
S->>P: Request merchant details
|
||||||
|
P->>S: Return merchant details (e.g. country)
|
||||||
|
|
||||||
|
Note over S: Process authentication result
|
||||||
|
S->>R: Return authentication status
|
||||||
|
Note over R: Update UI to authenticated state<br/>(no page reload)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Client ID and Secret are sent to a REST endpoint of the plugin. The authentication happens on server-side.
|
||||||
|
- See `AuthenticatoinRestEndpoint::connect_direct()`
|
||||||
|
- See `AuthenticationManager::authenticate_via_direct_api()`
|
||||||
|
2. After authentication is completed, the merchant account is prepared on server side and a confirmation is returned to the React app.
|
||||||
|
- See `AuthenticationManager::update_connection_details()` → condition `is_merchant_connected()`
|
||||||
|
3. The React app directly switches to the "Settings mode" without a page reload.
|
|
@ -32,7 +32,6 @@ const ButtonOrPlaceholder = ( {
|
||||||
|
|
||||||
if ( href ) {
|
if ( href ) {
|
||||||
buttonProps.href = href;
|
buttonProps.href = href;
|
||||||
buttonProps.target = 'PPFrame';
|
|
||||||
buttonProps[ 'data-paypal-button' ] = 'true';
|
buttonProps[ 'data-paypal-button' ] = 'true';
|
||||||
buttonProps[ 'data-paypal-onboard-button' ] = 'true';
|
buttonProps[ 'data-paypal-onboard-button' ] = 'true';
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ import AdvancedOptionsForm from '../Components/AdvancedOptionsForm';
|
||||||
|
|
||||||
const StepWelcome = ( { setStep, currentStep } ) => {
|
const StepWelcome = ( { setStep, currentStep } ) => {
|
||||||
const { storeCountry } = CommonHooks.useWooSettings();
|
const { storeCountry } = CommonHooks.useWooSettings();
|
||||||
const { canUseCardPayments } = OnboardingHooks.useFlags();
|
const { canUseCardPayments, canUseFastlane, canUsePayLater } =
|
||||||
|
OnboardingHooks.useFlags();
|
||||||
const nonAcdcIcons = [ 'paypal', 'visa', 'mastercard', 'amex', 'discover' ];
|
const nonAcdcIcons = [ 'paypal', 'visa', 'mastercard', 'amex', 'discover' ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -54,8 +55,8 @@ const StepWelcome = ( { setStep, currentStep } ) => {
|
||||||
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
<Separator className="ppcp-r-page-welcome-mode-separator" />
|
||||||
<WelcomeDocs
|
<WelcomeDocs
|
||||||
useAcdc={ canUseCardPayments }
|
useAcdc={ canUseCardPayments }
|
||||||
isFastlane={ true }
|
isFastlane={ canUseFastlane }
|
||||||
isPayLater={ true }
|
isPayLater={ canUsePayLater }
|
||||||
storeCountry={ storeCountry }
|
storeCountry={ storeCountry }
|
||||||
/>
|
/>
|
||||||
<Separator text={ __( 'or', 'woocommerce-paypal-payments' ) } />
|
<Separator text={ __( 'or', 'woocommerce-paypal-payments' ) } />
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { __, sprintf } from '@wordpress/i18n';
|
||||||
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
|
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
|
||||||
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
|
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
|
||||||
import { SettingsHooks } from '../../../../../../data';
|
import { SettingsHooks } from '../../../../../../data';
|
||||||
|
import { useMerchantInfo } from '../../../../../../data/common/hooks';
|
||||||
|
|
||||||
const SavePaymentMethods = () => {
|
const SavePaymentMethods = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -12,6 +13,8 @@ const SavePaymentMethods = () => {
|
||||||
setSaveCardDetails,
|
setSaveCardDetails,
|
||||||
} = SettingsHooks.useSettings();
|
} = SettingsHooks.useSettings();
|
||||||
|
|
||||||
|
const { features } = useMerchantInfo();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsBlock
|
<SettingsBlock
|
||||||
title={ __(
|
title={ __(
|
||||||
|
@ -38,8 +41,13 @@ const SavePaymentMethods = () => {
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
|
||||||
) }
|
) }
|
||||||
value={ savePaypalAndVenmo }
|
value={
|
||||||
|
features.save_paypal_and_venmo.enabled
|
||||||
|
? savePaypalAndVenmo
|
||||||
|
: false
|
||||||
|
}
|
||||||
onChange={ setSavePaypalAndVenmo }
|
onChange={ setSavePaypalAndVenmo }
|
||||||
|
disabled={ ! features.save_paypal_and_venmo.enabled }
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ControlToggleButton
|
<ControlToggleButton
|
||||||
|
|
|
@ -24,6 +24,8 @@ const defaultTransient = Object.freeze( {
|
||||||
canUseCardPayments: false,
|
canUseCardPayments: false,
|
||||||
canUseSubscriptions: false,
|
canUseSubscriptions: false,
|
||||||
shouldSkipPaymentMethods: false,
|
shouldSkipPaymentMethods: false,
|
||||||
|
canUseFastlane: false,
|
||||||
|
canUsePayLater: false,
|
||||||
} ),
|
} ),
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig;
|
use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\InternalRestService;
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'settings.url' => static function ( ContainerInterface $container ) : string {
|
'settings.url' => static function ( ContainerInterface $container ) : string {
|
||||||
|
@ -69,13 +70,17 @@ return array(
|
||||||
$can_use_subscriptions = $container->has( 'wc-subscriptions.helper' ) && $container->get( 'wc-subscriptions.helper' )
|
$can_use_subscriptions = $container->has( 'wc-subscriptions.helper' ) && $container->get( 'wc-subscriptions.helper' )
|
||||||
->plugin_is_active();
|
->plugin_is_active();
|
||||||
$should_skip_payment_methods = class_exists( '\WC_Payments' );
|
$should_skip_payment_methods = class_exists( '\WC_Payments' );
|
||||||
|
$can_use_fastlane = $container->get( 'axo.eligible' );
|
||||||
|
$can_use_pay_later = $container->get( 'button.helper.messages-apply' );
|
||||||
|
|
||||||
return new OnboardingProfile(
|
return new OnboardingProfile(
|
||||||
$can_use_casual_selling,
|
$can_use_casual_selling,
|
||||||
$can_use_vaulting,
|
$can_use_vaulting,
|
||||||
$can_use_card_payments,
|
$can_use_card_payments,
|
||||||
$can_use_subscriptions,
|
$can_use_subscriptions,
|
||||||
$should_skip_payment_methods
|
$should_skip_payment_methods,
|
||||||
|
$can_use_fastlane,
|
||||||
|
$can_use_pay_later->for_country()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.data.general' => static function ( ContainerInterface $container ) : GeneralSettings {
|
'settings.data.general' => static function ( ContainerInterface $container ) : GeneralSettings {
|
||||||
|
@ -168,7 +173,10 @@ return array(
|
||||||
return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) );
|
return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) );
|
||||||
},
|
},
|
||||||
'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint {
|
'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint {
|
||||||
return new CommonRestEndpoint( $container->get( 'settings.data.general' ) );
|
return new CommonRestEndpoint(
|
||||||
|
$container->get( 'settings.data.general' ),
|
||||||
|
$container->get( 'api.endpoint.partners' )
|
||||||
|
);
|
||||||
},
|
},
|
||||||
'settings.rest.payment' => static function ( ContainerInterface $container ) : PaymentRestEndpoint {
|
'settings.rest.payment' => static function ( ContainerInterface $container ) : PaymentRestEndpoint {
|
||||||
return new PaymentRestEndpoint(
|
return new PaymentRestEndpoint(
|
||||||
|
@ -309,7 +317,12 @@ return array(
|
||||||
$container->get( 'api.env.endpoint.login-seller' ),
|
$container->get( 'api.env.endpoint.login-seller' ),
|
||||||
$container->get( 'api.repository.partner-referrals-data' ),
|
$container->get( 'api.repository.partner-referrals-data' ),
|
||||||
$container->get( 'settings.connection-state' ),
|
$container->get( 'settings.connection-state' ),
|
||||||
$container->get( 'api.endpoint.partners' ),
|
$container->get( 'settings.service.rest-service' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'settings.service.rest-service' => static function ( ContainerInterface $container ) : InternalRestService {
|
||||||
|
return new InternalRestService(
|
||||||
$container->get( 'woocommerce.logger.woocommerce' )
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,21 +70,21 @@ class MerchantConnectionDTO {
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param bool $is_sandbox Whether this connection is a sandbox account.
|
* @param bool $is_sandbox Whether this connection is a sandbox account.
|
||||||
* @param string $client_id API client ID.
|
* @param string $client_id API client ID.
|
||||||
* @param string $client_secret API client secret.
|
* @param string $client_secret API client secret.
|
||||||
* @param string $merchant_id PayPal's 13-character merchant ID.
|
* @param string $merchant_id PayPal's 13-character merchant ID.
|
||||||
* @param string $merchant_email Email address of the merchant account.
|
* @param string $merchant_email Email address of the merchant account.
|
||||||
* @param string $merchant_country Merchant's country.
|
* @param string $merchant_country Merchant's country.
|
||||||
* @param string $seller_type Whether the merchant is a business or personal account.
|
* @param string $seller_type Whether the merchant is a business or personal account.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
bool $is_sandbox,
|
bool $is_sandbox,
|
||||||
string $client_id,
|
string $client_id,
|
||||||
string $client_secret,
|
string $client_secret,
|
||||||
string $merchant_id,
|
string $merchant_id,
|
||||||
string $merchant_email,
|
string $merchant_email = '',
|
||||||
string $merchant_country,
|
string $merchant_country = '',
|
||||||
string $seller_type = SellerTypeEnum::UNKNOWN
|
string $seller_type = SellerTypeEnum::UNKNOWN
|
||||||
) {
|
) {
|
||||||
$this->is_sandbox = $is_sandbox;
|
$this->is_sandbox = $is_sandbox;
|
||||||
|
|
|
@ -250,6 +250,11 @@ class GeneralSettings extends AbstractDataModel {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_merchant_country() : string {
|
public function get_merchant_country() : string {
|
||||||
|
// When we don't know the merchant's real country, we assume it's the Woo store-country.
|
||||||
|
if ( empty( $this->data['merchant_country'] ) ) {
|
||||||
|
return $this->woo_settings['country'];
|
||||||
|
}
|
||||||
|
|
||||||
return $this->data['merchant_country'];
|
return $this->data['merchant_country'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ class OnboardingProfile extends AbstractDataModel {
|
||||||
* @param bool $can_use_card_payments Whether credit card payments are possible.
|
* @param bool $can_use_card_payments Whether credit card payments are possible.
|
||||||
* @param bool $can_use_subscriptions Whether WC Subscriptions plugin is active.
|
* @param bool $can_use_subscriptions Whether WC Subscriptions plugin is active.
|
||||||
* @param bool $should_skip_payment_methods Whether it should skip payment methods screen.
|
* @param bool $should_skip_payment_methods Whether it should skip payment methods screen.
|
||||||
|
* @param bool $can_use_fastlane Whether it can use Fastlane or not.
|
||||||
|
* @param bool $can_use_pay_later Whether it can use Pay Later or not.
|
||||||
*
|
*
|
||||||
* @throws RuntimeException If the OPTION_KEY is not defined in the child class.
|
* @throws RuntimeException If the OPTION_KEY is not defined in the child class.
|
||||||
*/
|
*/
|
||||||
|
@ -52,7 +54,9 @@ class OnboardingProfile extends AbstractDataModel {
|
||||||
bool $can_use_vaulting = false,
|
bool $can_use_vaulting = false,
|
||||||
bool $can_use_card_payments = false,
|
bool $can_use_card_payments = false,
|
||||||
bool $can_use_subscriptions = false,
|
bool $can_use_subscriptions = false,
|
||||||
bool $should_skip_payment_methods = false
|
bool $should_skip_payment_methods = false,
|
||||||
|
bool $can_use_fastlane = false,
|
||||||
|
bool $can_use_pay_later = false
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
|
@ -61,6 +65,8 @@ class OnboardingProfile extends AbstractDataModel {
|
||||||
$this->flags['can_use_card_payments'] = $can_use_card_payments;
|
$this->flags['can_use_card_payments'] = $can_use_card_payments;
|
||||||
$this->flags['can_use_subscriptions'] = $can_use_subscriptions;
|
$this->flags['can_use_subscriptions'] = $can_use_subscriptions;
|
||||||
$this->flags['should_skip_payment_methods'] = $should_skip_payment_methods;
|
$this->flags['should_skip_payment_methods'] = $should_skip_payment_methods;
|
||||||
|
$this->flags['can_use_fastlane'] = $can_use_fastlane;
|
||||||
|
$this->flags['can_use_pay_later'] = $can_use_pay_later;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -105,6 +105,12 @@ class PaymentSettings extends AbstractDataModel {
|
||||||
return $this->get_paylater_enabled();
|
return $this->get_paylater_enabled();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if (
|
||||||
|
! did_filter( 'woocommerce_payment_gateways' )
|
||||||
|
|| doing_filter( 'woocommerce_payment_gateways' )
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
$gateway = $this->get_gateway( $method_id );
|
$gateway = $this->get_gateway( $method_id );
|
||||||
|
|
||||||
if ( $gateway ) {
|
if ( $gateway ) {
|
||||||
|
|
|
@ -87,7 +87,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/direct',
|
'/' . $this->rest_base . '/direct',
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
@ -125,7 +125,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/oauth',
|
'/' . $this->rest_base . '/oauth',
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
@ -155,7 +155,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
|
||||||
* POST /wp-json/wc/v3/wc_paypal/authenticate/disconnect
|
* POST /wp-json/wc/v3/wc_paypal/authenticate/disconnect
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/disconnect',
|
'/' . $this->rest_base . '/disconnect',
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -13,6 +13,8 @@ use WP_REST_Server;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\InternalRestService;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST controller for "common" settings, which are used and modified by
|
* REST controller for "common" settings, which are used and modified by
|
||||||
|
@ -22,6 +24,11 @@ use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||||
* internal data model.
|
* internal data model.
|
||||||
*/
|
*/
|
||||||
class CommonRestEndpoint extends RestEndpoint {
|
class CommonRestEndpoint extends RestEndpoint {
|
||||||
|
/**
|
||||||
|
* Full REST path to the merchant-details endpoint, relative to the namespace.
|
||||||
|
*/
|
||||||
|
protected const SELLER_ACCOUNT_PATH = 'common/seller-account';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base path for this REST controller.
|
* The base path for this REST controller.
|
||||||
*
|
*
|
||||||
|
@ -36,6 +43,13 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
*/
|
*/
|
||||||
protected GeneralSettings $settings;
|
protected GeneralSettings $settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Partners-Endpoint instance to request seller details from PayPal's API.
|
||||||
|
*
|
||||||
|
* @var PartnersEndpoint
|
||||||
|
*/
|
||||||
|
protected PartnersEndpoint $partners_endpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field mapping for request to profile transformation.
|
* Field mapping for request to profile transformation.
|
||||||
*
|
*
|
||||||
|
@ -104,10 +118,27 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param GeneralSettings $settings The settings instance.
|
* @param GeneralSettings $settings The settings instance.
|
||||||
|
* @param PartnersEndpoint $partners_endpoint Partners-API to get merchant details from PayPal.
|
||||||
*/
|
*/
|
||||||
public function __construct( GeneralSettings $settings ) {
|
public function __construct( GeneralSettings $settings, PartnersEndpoint $partners_endpoint ) {
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
|
$this->partners_endpoint = $partners_endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the "Get Seller Account Details" REST route.
|
||||||
|
* This is an internal route which is consumed by the plugin itself during onboarding.
|
||||||
|
*
|
||||||
|
* @param bool $full_route Whether to return the full endpoint path or just the route name.
|
||||||
|
* @return string The full path to the REST endpoint.
|
||||||
|
*/
|
||||||
|
public static function seller_account_route( bool $full_route = false ) : string {
|
||||||
|
if ( $full_route ) {
|
||||||
|
return '/' . static::NAMESPACE . '/' . self::SELLER_ACCOUNT_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SELLER_ACCOUNT_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,7 +149,7 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
* GET /wp-json/wc/v3/wc_paypal/common
|
* GET /wp-json/wc/v3/wc_paypal/common
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -134,7 +165,7 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
@ -147,7 +178,7 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
* GET /wp-json/wc/v3/wc_paypal/common/merchant
|
* GET /wp-json/wc/v3/wc_paypal/common/merchant
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
"/$this->rest_base/merchant",
|
"/$this->rest_base/merchant",
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -155,6 +186,19 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /wp-json/wc/v3/wc_paypal/common/seller-account
|
||||||
|
*/
|
||||||
|
register_rest_route(
|
||||||
|
static::NAMESPACE,
|
||||||
|
self::seller_account_route(),
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'get_seller_account_info' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,6 +249,19 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
return $this->return_success( $js_data, $extra_data );
|
return $this->return_success( $js_data, $extra_data );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests details from the PayPal API.
|
||||||
|
*
|
||||||
|
* Used during onboarding to enrich the merchant details in the DB.
|
||||||
|
*
|
||||||
|
* @return WP_REST_Response Seller details, provided by PayPal's API.
|
||||||
|
*/
|
||||||
|
public function get_seller_account_info() : WP_REST_Response {
|
||||||
|
$seller_status = $this->partners_endpoint->seller_status();
|
||||||
|
|
||||||
|
return $this->return_success( array( 'country' => $seller_status->country() ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends the "merchant" attribute to the extra_data collection, which
|
* Appends the "merchant" attribute to the extra_data collection, which
|
||||||
* contains details about the merchant's PayPal account, like the merchant ID.
|
* contains details about the merchant's PayPal account, like the merchant ID.
|
||||||
|
|
|
@ -62,7 +62,7 @@ class CompleteOnClickEndpoint extends RestEndpoint {
|
||||||
*/
|
*/
|
||||||
public function register_routes(): void {
|
public function register_routes(): void {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -64,7 +64,7 @@ class FeaturesRestEndpoint extends RestEndpoint {
|
||||||
public function register_routes(): void {
|
public function register_routes(): void {
|
||||||
// GET /features - Get features list.
|
// GET /features - Get features list.
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -60,7 +60,7 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -83,6 +83,12 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
||||||
'should_skip_payment_methods' => array(
|
'should_skip_payment_methods' => array(
|
||||||
'js_name' => 'shouldSkipPaymentMethods',
|
'js_name' => 'shouldSkipPaymentMethods',
|
||||||
),
|
),
|
||||||
|
'can_use_fastlane' => array(
|
||||||
|
'js_name' => 'canUseFastlane',
|
||||||
|
),
|
||||||
|
'can_use_pay_later' => array(
|
||||||
|
'js_name' => 'canUsePayLater',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,7 +110,7 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
||||||
* GET /wp-json/wc/v3/wc_paypal/onboarding
|
* GET /wp-json/wc/v3/wc_paypal/onboarding
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -120,7 +126,7 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -63,7 +63,7 @@ class PayLaterMessagingEndpoint extends RestEndpoint {
|
||||||
* GET wc/v3/wc_paypal/pay_later_messaging
|
* GET wc/v3/wc_paypal/pay_later_messaging
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -76,7 +76,7 @@ class PayLaterMessagingEndpoint extends RestEndpoint {
|
||||||
* POST wc/v3/wc_paypal/pay_later_messaging
|
* POST wc/v3/wc_paypal/pay_later_messaging
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -111,7 +111,7 @@ class PaymentRestEndpoint extends RestEndpoint {
|
||||||
* GET wc/v3/wc_paypal/payment
|
* GET wc/v3/wc_paypal/payment
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -131,7 +131,7 @@ class PaymentRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -87,7 +87,7 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
|
||||||
* POST /wp-json/wc/v3/wc_paypal/refresh-features
|
* POST /wp-json/wc/v3/wc_paypal/refresh-features
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -54,7 +54,7 @@ class ResetDismissedTodosEndpoint extends RestEndpoint {
|
||||||
* POST wc/v3/wc_paypal/reset-dismissed-todos
|
* POST wc/v3/wc_paypal/reset-dismissed-todos
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -18,10 +18,8 @@ use WP_REST_Response;
|
||||||
abstract class RestEndpoint extends WC_REST_Controller {
|
abstract class RestEndpoint extends WC_REST_Controller {
|
||||||
/**
|
/**
|
||||||
* Endpoint namespace.
|
* Endpoint namespace.
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
protected $namespace = 'wc/v3/wc_paypal';
|
protected const NAMESPACE = 'wc/v3/wc_paypal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify access.
|
* Verify access.
|
||||||
|
|
|
@ -109,7 +109,7 @@ class SettingsRestEndpoint extends RestEndpoint {
|
||||||
* POST wc/v3/wc_paypal/settings
|
* POST wc/v3/wc_paypal/settings
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -107,7 +107,7 @@ class StylingRestEndpoint extends RestEndpoint {
|
||||||
* GET wc/v3/wc_paypal/styling
|
* GET wc/v3/wc_paypal/styling
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
@ -123,7 +123,7 @@ class StylingRestEndpoint extends RestEndpoint {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -88,7 +88,7 @@ class TodosRestEndpoint extends RestEndpoint {
|
||||||
public function register_routes(): void {
|
public function register_routes(): void {
|
||||||
// GET/POST /todos - Get todos list and update dismissed todos.
|
// GET/POST /todos - Get todos list and update dismissed todos.
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
@ -106,7 +106,7 @@ class TodosRestEndpoint extends RestEndpoint {
|
||||||
|
|
||||||
// POST /todos/reset - Reset dismissed todos.
|
// POST /todos/reset - Reset dismissed todos.
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/reset',
|
'/' . $this->rest_base . '/reset',
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
@ -117,7 +117,7 @@ class TodosRestEndpoint extends RestEndpoint {
|
||||||
|
|
||||||
// POST /todos/complete - Mark todo as completed on click.
|
// POST /todos/complete - Mark todo as completed on click.
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/complete',
|
'/' . $this->rest_base . '/complete',
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
|
|
@ -79,7 +79,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
||||||
* POST /wp-json/wc/v3/wc_paypal/webhooks
|
* POST /wp-json/wc/v3/wc_paypal/webhooks
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
@ -100,7 +100,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
||||||
* POST /wp-json/wc/v3/wc_paypal/webhooks/simulate
|
* POST /wp-json/wc/v3/wc_paypal/webhooks/simulate
|
||||||
*/
|
*/
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
static::NAMESPACE,
|
||||||
'/' . $this->rest_base . '/simulate',
|
'/' . $this->rest_base . '/simulate',
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -27,6 +27,7 @@ use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that manages the connection to PayPal.
|
* Class that manages the connection to PayPal.
|
||||||
|
@ -75,11 +76,11 @@ class AuthenticationManager {
|
||||||
private ConnectionState $connection_state;
|
private ConnectionState $connection_state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partners endpoint.
|
* Internal REST service, to consume own REST handlers in a separate request.
|
||||||
*
|
*
|
||||||
* @var PartnersEndpoint
|
* @var InternalRestService
|
||||||
*/
|
*/
|
||||||
private PartnersEndpoint $partners_endpoint;
|
private InternalRestService $rest_service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -89,7 +90,7 @@ class AuthenticationManager {
|
||||||
* @param EnvironmentConfig $login_endpoint API handler to fetch merchant credentials.
|
* @param EnvironmentConfig $login_endpoint API handler to fetch merchant credentials.
|
||||||
* @param PartnerReferralsData $referrals_data Partner referrals data.
|
* @param PartnerReferralsData $referrals_data Partner referrals data.
|
||||||
* @param ConnectionState $connection_state Connection state manager.
|
* @param ConnectionState $connection_state Connection state manager.
|
||||||
* @param PartnersEndpoint $partners_endpoint Partners endpoint.
|
* @param InternalRestService $rest_service Allows calling internal REST endpoints.
|
||||||
* @param ?LoggerInterface $logger Logging instance.
|
* @param ?LoggerInterface $logger Logging instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -98,16 +99,16 @@ class AuthenticationManager {
|
||||||
EnvironmentConfig $login_endpoint,
|
EnvironmentConfig $login_endpoint,
|
||||||
PartnerReferralsData $referrals_data,
|
PartnerReferralsData $referrals_data,
|
||||||
ConnectionState $connection_state,
|
ConnectionState $connection_state,
|
||||||
PartnersEndpoint $partners_endpoint,
|
InternalRestService $rest_service,
|
||||||
?LoggerInterface $logger = null
|
?LoggerInterface $logger = null
|
||||||
) {
|
) {
|
||||||
$this->common_settings = $common_settings;
|
$this->common_settings = $common_settings;
|
||||||
$this->connection_host = $connection_host;
|
$this->connection_host = $connection_host;
|
||||||
$this->login_endpoint = $login_endpoint;
|
$this->login_endpoint = $login_endpoint;
|
||||||
$this->referrals_data = $referrals_data;
|
$this->referrals_data = $referrals_data;
|
||||||
$this->connection_state = $connection_state;
|
$this->connection_state = $connection_state;
|
||||||
$this->partners_endpoint = $partners_endpoint;
|
$this->rest_service = $rest_service;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,6 +189,7 @@ class AuthenticationManager {
|
||||||
* PayPal account using a client ID and secret.
|
* PayPal account using a client ID and secret.
|
||||||
*
|
*
|
||||||
* Part of the "Direct Connection" (Manual Connection) flow.
|
* Part of the "Direct Connection" (Manual Connection) flow.
|
||||||
|
* This connection type is only available to business merchants.
|
||||||
*
|
*
|
||||||
* @param bool $use_sandbox Whether to use the sandbox mode.
|
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||||
* @param string $client_id The client ID.
|
* @param string $client_id The client ID.
|
||||||
|
@ -208,19 +210,13 @@ class AuthenticationManager {
|
||||||
|
|
||||||
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
|
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
|
||||||
|
|
||||||
try {
|
|
||||||
$seller_status = $this->partners_endpoint->seller_status();
|
|
||||||
} catch ( PayPalApiException $exception ) {
|
|
||||||
$seller_status = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$connection = new MerchantConnectionDTO(
|
$connection = new MerchantConnectionDTO(
|
||||||
$use_sandbox,
|
$use_sandbox,
|
||||||
$client_id,
|
$client_id,
|
||||||
$client_secret,
|
$client_secret,
|
||||||
$payee['merchant_id'],
|
$payee['merchant_id'],
|
||||||
$payee['email_address'],
|
$payee['email_address'],
|
||||||
! is_null( $seller_status ) ? $seller_status->country() : '',
|
'',
|
||||||
SellerTypeEnum::BUSINESS
|
SellerTypeEnum::BUSINESS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -286,17 +282,10 @@ class AuthenticationManager {
|
||||||
*/
|
*/
|
||||||
$connection = $this->common_settings->get_merchant_data();
|
$connection = $this->common_settings->get_merchant_data();
|
||||||
|
|
||||||
try {
|
$connection->is_sandbox = $use_sandbox;
|
||||||
$seller_status = $this->partners_endpoint->seller_status();
|
$connection->client_id = $credentials['client_id'];
|
||||||
} catch ( PayPalApiException $exception ) {
|
$connection->client_secret = $credentials['client_secret'];
|
||||||
$seller_status = null;
|
$connection->merchant_id = $credentials['merchant_id'];
|
||||||
}
|
|
||||||
|
|
||||||
$connection->is_sandbox = $use_sandbox;
|
|
||||||
$connection->client_id = $credentials['client_id'];
|
|
||||||
$connection->client_secret = $credentials['client_secret'];
|
|
||||||
$connection->merchant_id = $credentials['merchant_id'];
|
|
||||||
$connection->merchant_country = ! is_null( $seller_status ) ? $seller_status->country() : '';
|
|
||||||
|
|
||||||
$this->update_connection_details( $connection );
|
$this->update_connection_details( $connection );
|
||||||
}
|
}
|
||||||
|
@ -332,13 +321,6 @@ class AuthenticationManager {
|
||||||
$connection->seller_type = $seller_type;
|
$connection->seller_type = $seller_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
$seller_status = $this->partners_endpoint->seller_status();
|
|
||||||
} catch ( PayPalApiException $exception ) {
|
|
||||||
$seller_status = null;
|
|
||||||
}
|
|
||||||
$connection->merchant_country = ! is_null( $seller_status ) ? $seller_status->country() : '';
|
|
||||||
|
|
||||||
$this->update_connection_details( $connection );
|
$this->update_connection_details( $connection );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,6 +431,38 @@ class AuthenticationManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches additional details about the connected merchant from PayPal
|
||||||
|
* and stores them in the DB.
|
||||||
|
*
|
||||||
|
* This process only works after persisting basic connection details.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function enrich_merchant_details() : void {
|
||||||
|
if ( ! $this->common_settings->is_merchant_connected() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$endpoint = CommonRestEndpoint::seller_account_route( true );
|
||||||
|
$details = $this->rest_service->get_response( $endpoint );
|
||||||
|
} catch ( Throwable $exception ) {
|
||||||
|
$this->logger->warning( 'Could not determine merchant country: ' . $exception->getMessage() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request the merchant details via a PayPal API request.
|
||||||
|
$connection = $this->common_settings->get_merchant_data();
|
||||||
|
|
||||||
|
// Enrich the connection details with additional details.
|
||||||
|
$connection->merchant_country = $details['country'];
|
||||||
|
|
||||||
|
// Persist the changes.
|
||||||
|
$this->common_settings->set_merchant_data( $connection );
|
||||||
|
$this->common_settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the provided details in the data model.
|
* Stores the provided details in the data model.
|
||||||
*
|
*
|
||||||
|
@ -470,6 +484,9 @@ class AuthenticationManager {
|
||||||
// Update the connection status and set the environment flags.
|
// Update the connection status and set the environment flags.
|
||||||
$this->connection_state->connect( $connection->is_sandbox );
|
$this->connection_state->connect( $connection->is_sandbox );
|
||||||
|
|
||||||
|
// At this point, we can use the PayPal API to get more details about the seller.
|
||||||
|
$this->enrich_merchant_details();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to flush caches before authenticating the merchant, to
|
* Request to flush caches before authenticating the merchant, to
|
||||||
* ensure the new merchant does not use stale data from previous
|
* ensure the new merchant does not use stale data from previous
|
||||||
|
|
135
modules/ppcp-settings/src/Service/InternalRestService.php
Normal file
135
modules/ppcp-settings/src/Service/InternalRestService.php
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Service that allows calling internal REST endpoints from server-side.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WP_Http_Cookie;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume internal REST endpoints from server-side.
|
||||||
|
*
|
||||||
|
* This service makes a real HTTP request to the endpoint, forwarding the
|
||||||
|
* authentication cookies of the current request to maintain the user session
|
||||||
|
* while invoking a completely isolated and freshly initialized server request.
|
||||||
|
*/
|
||||||
|
class InternalRestService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance.
|
||||||
|
*
|
||||||
|
* In this case, the logger is quite important for debugging, because the main
|
||||||
|
* functionality of this class cannot be step-debugged using Xdebug: While
|
||||||
|
* a Xdebug session is active, the remote call to the current server is also
|
||||||
|
* blocked and will end in a timeout.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param LoggerInterface $logger Logger instance.
|
||||||
|
*/
|
||||||
|
public function __construct( LoggerInterface $logger ) {
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a REST call to the defined local REST endpoint.
|
||||||
|
*
|
||||||
|
* @param string $endpoint The endpoint for which the token is generated.
|
||||||
|
* @return mixed The REST response.
|
||||||
|
*/
|
||||||
|
public function get_response( string $endpoint ) {
|
||||||
|
$rest_url = rest_url( $endpoint );
|
||||||
|
$rest_nonce = wp_create_nonce( 'wp_rest' );
|
||||||
|
$auth_cookies = $this->build_authentication_cookie();
|
||||||
|
|
||||||
|
$this->logger->info( "Calling internal REST endpoint: $rest_url" );
|
||||||
|
|
||||||
|
$response = wp_remote_request(
|
||||||
|
$rest_url,
|
||||||
|
array(
|
||||||
|
'method' => 'GET',
|
||||||
|
'headers' => array(
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'X-WP-Nonce' => $rest_nonce,
|
||||||
|
),
|
||||||
|
'cookies' => $auth_cookies,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
$this->logger->error( 'Internal REST error', array( 'response' => $response ) );
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$json = json_decode( $body, true, 512, JSON_THROW_ON_ERROR );
|
||||||
|
} catch ( Throwable $exception ) {
|
||||||
|
$this->logger->error(
|
||||||
|
'Internal REST error: Invalid JSON response',
|
||||||
|
array(
|
||||||
|
'error' => $exception->getMessage(),
|
||||||
|
'response_body' => $body,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $json || empty( $json['success'] ) ) {
|
||||||
|
$this->logger->error( 'Internal REST error: Invalid response', array( 'json' => $json ) );
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info( 'Internal REST success', array( 'data' => $json['data'] ) );
|
||||||
|
|
||||||
|
return $json['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the cookie collection with relevant WordPress authentication
|
||||||
|
* cookies, which allows us to extend the current user's session to the
|
||||||
|
* called REST endpoint.
|
||||||
|
*
|
||||||
|
* @return array A list of cookies that are required to authenticate the user.
|
||||||
|
*/
|
||||||
|
private function build_authentication_cookie() : array {
|
||||||
|
$cookies = array();
|
||||||
|
|
||||||
|
// Cookie names are defined in constants and can be changed by site owners.
|
||||||
|
$wp_cookie_constants = array( 'AUTH_COOKIE', 'SECURE_AUTH_COOKIE', 'LOGGED_IN_COOKIE' );
|
||||||
|
|
||||||
|
foreach ( $wp_cookie_constants as $cookie_const ) {
|
||||||
|
$cookie_name = (string) constant( $cookie_const );
|
||||||
|
|
||||||
|
if ( ! isset( $_COOKIE[ $cookie_name ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookies[] = new WP_Http_Cookie(
|
||||||
|
array(
|
||||||
|
'name' => $cookie_name,
|
||||||
|
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||||
|
'value' => wp_unslash( $_COOKIE[ $cookie_name ] ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cookies;
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,9 +53,19 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
* Returns whether the old settings UI should be loaded.
|
* Returns whether the old settings UI should be loaded.
|
||||||
*/
|
*/
|
||||||
public static function should_use_the_old_ui() : bool {
|
public static function should_use_the_old_ui() : bool {
|
||||||
|
// New merchants should never see the legacy UI.
|
||||||
|
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
|
||||||
|
|
||||||
|
if ( $show_new_ux ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing merchants can opt-in to see the new UI.
|
||||||
|
$opt_out_choice = 'yes' === get_option( SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI );
|
||||||
|
|
||||||
return apply_filters(
|
return apply_filters(
|
||||||
'woocommerce_paypal_payments_should_use_the_old_ui',
|
'woocommerce_paypal_payments_should_use_the_old_ui',
|
||||||
get_option( SwitchSettingsUiEndpoint::OPTION_NAME_SHOULD_USE_OLD_UI ) === 'yes'
|
$opt_out_choice
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,4 +229,21 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
|
||||||
return class_exists( 'woocommerce' );
|
return class_exists( 'woocommerce' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_gateway_migrate',
|
||||||
|
/**
|
||||||
|
* Set new merchant flag on plugin install.
|
||||||
|
*
|
||||||
|
* When installing the plugin for the first time, we direct the user to
|
||||||
|
* the new UI without a data migration, and fully hide the legacy UI.
|
||||||
|
*
|
||||||
|
* @param string|false $version String with previous installed plugin version.
|
||||||
|
* Boolean false on first installation on a new site.
|
||||||
|
*/
|
||||||
|
static function ( $version ) {
|
||||||
|
if ( ! $version ) {
|
||||||
|
update_option( 'woocommerce-ppcp-is-new-merchant', '1' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
} )();
|
} )();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue