mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Merge remote-tracking branch 'origin/trunk' into PCP-3930-Make-the-webhook-resubscribe/simulate-logic-usable-in-React-application
This commit is contained in:
commit
0c8de4900c
26 changed files with 441 additions and 171 deletions
3
.github/workflows/php.yml
vendored
3
.github/workflows/php.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3']
|
php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
|
||||||
|
|
||||||
name: PHP ${{ matrix.php-versions }}
|
name: PHP ${{ matrix.php-versions }}
|
||||||
steps:
|
steps:
|
||||||
|
@ -30,6 +30,7 @@ jobs:
|
||||||
run: vendor/bin/phpunit
|
run: vendor/bin/phpunit
|
||||||
|
|
||||||
- name: Psalm
|
- name: Psalm
|
||||||
|
if: ${{ matrix.php-versions == '7.4' }}
|
||||||
run: ./vendor/bin/psalm --show-info=false --threads=8 --diff
|
run: ./vendor/bin/psalm --show-info=false --threads=8 --diff
|
||||||
|
|
||||||
- name: Run PHPCS
|
- name: Run PHPCS
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
*** Changelog ***
|
*** Changelog ***
|
||||||
|
|
||||||
= 2.9.5 - xxxx-xx-xx =
|
= 2.9.5 - 2024-12-10 =
|
||||||
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
||||||
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
||||||
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "woocommerce/woocommerce-paypal-payments",
|
"name": "woocommerce/woocommerce-paypal-payments",
|
||||||
"type": "wordpress-plugin",
|
"type": "wordpress-plugin",
|
||||||
"description": "PayPal Commerce Platform for WooCommerce",
|
"description": "PayPal Commerce Platform for WooCommerce",
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0-or-later",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4 | ^8.0",
|
"php": "^7.4 | ^8.0",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
|
6
composer.lock
generated
6
composer.lock
generated
|
@ -5541,8 +5541,8 @@
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": {
|
"stability-flags": {
|
||||||
"php-stubs/wordpress-stubs": 0,
|
"php-stubs/woocommerce-stubs": 0,
|
||||||
"php-stubs/woocommerce-stubs": 0
|
"php-stubs/wordpress-stubs": 0
|
||||||
},
|
},
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
|
@ -5550,7 +5550,7 @@
|
||||||
"php": "^7.4 | ^8.0",
|
"php": "^7.4 | ^8.0",
|
||||||
"ext-json": "*"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.4"
|
"php": "7.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,66 +5,153 @@
|
||||||
* @package WooCommerce\PayPalCommerce\Compat
|
* @package WooCommerce\PayPalCommerce\Compat
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Compat;
|
namespace WooCommerce\PayPalCommerce\Compat;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper for mapping the new/old settings.
|
* A helper class to manage the transition between legacy and new settings.
|
||||||
|
*
|
||||||
|
* This utility provides mapping from old setting keys to new ones and retrieves
|
||||||
|
* their corresponding values from the appropriate models. The class uses lazy
|
||||||
|
* loading and caching to optimize performance during runtime.
|
||||||
*/
|
*/
|
||||||
class SettingsMapHelper {
|
class SettingsMapHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of mapped settings.
|
* A list of settings maps containing mapping definitions.
|
||||||
*
|
*
|
||||||
* @var SettingsMap[]
|
* @var SettingsMap[]
|
||||||
*/
|
*/
|
||||||
protected array $settings_map;
|
protected array $settings_map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexed map for faster lookups, initialized lazily.
|
||||||
|
*
|
||||||
|
* @var array|null Associative array where old keys map to metadata.
|
||||||
|
*/
|
||||||
|
protected ?array $key_to_model = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for results of `to_array()` calls on models.
|
||||||
|
*
|
||||||
|
* @var array Associative array where keys are model IDs.
|
||||||
|
*/
|
||||||
|
protected array $model_cache = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param SettingsMap[] $settings_map A list of mapped settings.
|
* @param SettingsMap[] $settings_map A list of settings maps containing key definitions.
|
||||||
|
* @throws RuntimeException When an old key has multiple mappings.
|
||||||
*/
|
*/
|
||||||
public function __construct( array $settings_map ) {
|
public function __construct( array $settings_map ) {
|
||||||
|
$this->validate_settings_map( $settings_map );
|
||||||
$this->settings_map = $settings_map;
|
$this->settings_map = $settings_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the mapped value from the new settings.
|
* Validates the settings map for duplicate keys.
|
||||||
*
|
*
|
||||||
* @param string $key The key.
|
* @param SettingsMap[] $settings_map The settings map to validate.
|
||||||
* @return ?mixed the mapped value or Null if it doesn't exist.
|
* @throws RuntimeException When an old key has multiple mappings.
|
||||||
*/
|
*/
|
||||||
public function mapped_value( string $key ) {
|
protected function validate_settings_map( array $settings_map ) : void {
|
||||||
if ( ! $this->has_mapped_key( $key ) ) {
|
$seen_keys = array();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ( $this->settings_map as $settings_map ) {
|
foreach ( $settings_map as $settings_map_instance ) {
|
||||||
$mapped_key = array_search( $key, $settings_map->get_map(), true );
|
foreach ( $settings_map_instance->get_map() as $old_key => $new_key ) {
|
||||||
$new_settings = $settings_map->get_model()->to_array();
|
if ( isset( $seen_keys[ $old_key ] ) ) {
|
||||||
if ( ! empty( $new_settings[ $mapped_key ] ) ) {
|
throw new RuntimeException( "Duplicate mapping for legacy key '$old_key'." );
|
||||||
return $new_settings[ $mapped_key ];
|
}
|
||||||
|
$seen_keys[ $old_key ] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given key exists in the new settings.
|
* Retrieves the value of a mapped key from the new settings.
|
||||||
*
|
*
|
||||||
* @param string $key The key.
|
* @param string $old_key The key from the legacy settings.
|
||||||
* @return bool true if the given key exists in the new settings, otherwise false.
|
*
|
||||||
|
* @return mixed|null The value of the mapped setting, or null if not found.
|
||||||
*/
|
*/
|
||||||
public function has_mapped_key( string $key ) : bool {
|
public function mapped_value( string $old_key ) {
|
||||||
foreach ( $this->settings_map as $settings_map ) {
|
$this->ensure_map_initialized();
|
||||||
if ( in_array( $key, $settings_map->get_map(), true ) ) {
|
|
||||||
return true;
|
if ( ! isset( $this->key_to_model[ $old_key ] ) ) {
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
$mapping = $this->key_to_model[ $old_key ];
|
||||||
|
$model_id = spl_object_id( $mapping['model'] );
|
||||||
|
|
||||||
|
return $this->get_cached_model_value( $model_id, $mapping['new_key'], $mapping['model'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given legacy key exists in the new settings.
|
||||||
|
*
|
||||||
|
* @param string $old_key The key from the legacy settings.
|
||||||
|
*
|
||||||
|
* @return bool True if the key exists in the new settings, false otherwise.
|
||||||
|
*/
|
||||||
|
public function has_mapped_key( string $old_key ) : bool {
|
||||||
|
$this->ensure_map_initialized();
|
||||||
|
|
||||||
|
return isset( $this->key_to_model[ $old_key ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a cached model value or caches it if not already cached.
|
||||||
|
*
|
||||||
|
* @param int $model_id The unique identifier for the model object.
|
||||||
|
* @param string $new_key The key in the new settings structure.
|
||||||
|
* @param object $model The model object.
|
||||||
|
*
|
||||||
|
* @return mixed|null The value of the key in the model, or null if not found.
|
||||||
|
*/
|
||||||
|
protected function get_cached_model_value( int $model_id, string $new_key, object $model ) {
|
||||||
|
if ( ! isset( $this->model_cache[ $model_id ] ) ) {
|
||||||
|
$this->model_cache[ $model_id ] = $model->to_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->model_cache[ $model_id ][ $new_key ] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the map of old-to-new settings is initialized.
|
||||||
|
*
|
||||||
|
* This method initializes the `key_to_model` array lazily to improve performance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function ensure_map_initialized() : void {
|
||||||
|
if ( $this->key_to_model === null ) {
|
||||||
|
$this->initialize_key_map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the indexed map of old-to-new settings keys.
|
||||||
|
*
|
||||||
|
* This method processes the provided settings maps and indexes the legacy
|
||||||
|
* keys to their corresponding metadata for efficient lookup.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function initialize_key_map() : void {
|
||||||
|
$this->key_to_model = array();
|
||||||
|
|
||||||
|
foreach ( $this->settings_map as $settings_map_instance ) {
|
||||||
|
foreach ( $settings_map_instance->get_map() as $old_key => $new_key ) {
|
||||||
|
$this->key_to_model[ $old_key ] = array(
|
||||||
|
'new_key' => $new_key,
|
||||||
|
'model' => $settings_map_instance->get_model(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,14 @@
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
*:not(a){
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
|
|
|
@ -69,7 +69,6 @@ const AcdcOptionalPaymentMethods = ( {
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
) }
|
) }
|
||||||
imageBadge={ [
|
imageBadge={ [
|
||||||
'icon-button-sepa.svg',
|
|
||||||
'icon-button-ideal.svg',
|
'icon-button-ideal.svg',
|
||||||
'icon-button-blik.svg',
|
'icon-button-blik.svg',
|
||||||
'icon-button-bancontact.svg',
|
'icon-button-bancontact.svg',
|
||||||
|
|
|
@ -11,7 +11,6 @@ const PaymentMethodIcons = ( props ) => {
|
||||||
<PaymentMethodIcon type="discover" icons={ props.icons } />
|
<PaymentMethodIcon type="discover" icons={ props.icons } />
|
||||||
<PaymentMethodIcon type="apple-pay" icons={ props.icons } />
|
<PaymentMethodIcon type="apple-pay" icons={ props.icons } />
|
||||||
<PaymentMethodIcon type="google-pay" icons={ props.icons } />
|
<PaymentMethodIcon type="google-pay" icons={ props.icons } />
|
||||||
<PaymentMethodIcon type="sepa" icons={ props.icons } />
|
|
||||||
<PaymentMethodIcon type="ideal" icons={ props.icons } />
|
<PaymentMethodIcon type="ideal" icons={ props.icons } />
|
||||||
<PaymentMethodIcon type="bancontact" icons={ props.icons } />
|
<PaymentMethodIcon type="bancontact" icons={ props.icons } />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,7 +66,7 @@ const AcdcFlow = ( {
|
||||||
description={ sprintf(
|
description={ sprintf(
|
||||||
// translators: %s: Link to PayPal business fees guide
|
// translators: %s: Link to PayPal business fees guide
|
||||||
__(
|
__(
|
||||||
'Offer installment payment options and get paid upfront - at no extra cost to you. <a target="_blank" href="%s">Learn more</a>',
|
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
'https://www.paypal.com/us/business/paypal-business-fees'
|
'https://www.paypal.com/us/business/paypal-business-fees'
|
||||||
|
@ -256,7 +256,7 @@ const AcdcFlow = ( {
|
||||||
description={ sprintf(
|
description={ sprintf(
|
||||||
// translators: %s: Link to PayPal REST application guide
|
// translators: %s: Link to PayPal REST application guide
|
||||||
__(
|
__(
|
||||||
'Offer installment payment options and get paid upfront - at no extra cost to you. <a target="_blank" href="%s">Learn more</a>',
|
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||||
|
|
|
@ -60,7 +60,7 @@ const BcdcFlow = ( { isPayLater, storeCountry, storeCurrency } ) => {
|
||||||
description={ sprintf(
|
description={ sprintf(
|
||||||
// translators: %s: Link to PayPal REST application guide
|
// translators: %s: Link to PayPal REST application guide
|
||||||
__(
|
__(
|
||||||
'Offer installment payment options and get paid upfront - at no extra cost to you. <a target="_blank" href="%s">Learn more</a>',
|
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||||
|
@ -158,7 +158,7 @@ const BcdcFlow = ( { isPayLater, storeCountry, storeCurrency } ) => {
|
||||||
description={ sprintf(
|
description={ sprintf(
|
||||||
// translators: %s: Link to PayPal REST application guide
|
// translators: %s: Link to PayPal REST application guide
|
||||||
__(
|
__(
|
||||||
'Offer installment payment options and get paid upfront - at no extra cost to you. <a target="_blank" href="%s">Learn more</a>',
|
'Offer installment payment options and get paid upfront. <a target="_blank" href="%s">Learn more</a>',
|
||||||
'woocommerce-paypal-payments'
|
'woocommerce-paypal-payments'
|
||||||
),
|
),
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import AcdcFlow from './AcdcFlow';
|
import AcdcFlow from './AcdcFlow';
|
||||||
import BcdcFlow from './BcdcFlow';
|
import BcdcFlow from './BcdcFlow';
|
||||||
import { Button } from '@wordpress/components';
|
import { countryPriceInfo } from '../../../utils/countryPriceInfo';
|
||||||
|
import { pricesBasedDescription } from './pricesBasedDescription';
|
||||||
|
|
||||||
const WelcomeDocs = ( {
|
const WelcomeDocs = ( {
|
||||||
useAcdc,
|
useAcdc,
|
||||||
|
@ -10,15 +11,6 @@ const WelcomeDocs = ( {
|
||||||
storeCountry,
|
storeCountry,
|
||||||
storeCurrency,
|
storeCurrency,
|
||||||
} ) => {
|
} ) => {
|
||||||
const pricesBasedDescription = sprintf(
|
|
||||||
// translators: %s: Link to PayPal REST application guide
|
|
||||||
__(
|
|
||||||
'<sup>1</sup>Prices based on domestic transactions as of October 25th, 2024. <a target="_blank" href="%s">Click here</a> for full pricing details.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-welcome-docs">
|
<div className="ppcp-r-welcome-docs">
|
||||||
<h2 className="ppcp-r-welcome-docs__title">
|
<h2 className="ppcp-r-welcome-docs__title">
|
||||||
|
@ -41,10 +33,14 @@ const WelcomeDocs = ( {
|
||||||
storeCurrency={ storeCurrency }
|
storeCurrency={ storeCurrency }
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
<p
|
{ storeCountry in countryPriceInfo && (
|
||||||
className="ppcp-r-optional-payment-methods__description"
|
<p
|
||||||
dangerouslySetInnerHTML={ { __html: pricesBasedDescription } }
|
className="ppcp-r-optional-payment-methods__description"
|
||||||
></p>
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: pricesBasedDescription,
|
||||||
|
} }
|
||||||
|
></p>
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
export const pricesBasedDescription = sprintf(
|
||||||
|
// translators: %s: Link to PayPal REST application guide
|
||||||
|
__(
|
||||||
|
'<sup>1</sup>Prices based on domestic transactions as of October 25th, 2024. <a target="_blank" href="%s">Click here</a> for full pricing details.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
||||||
|
);
|
|
@ -3,27 +3,12 @@ import { OnboardingHooks } from '../../../data';
|
||||||
|
|
||||||
import { getSteps, getCurrentStep } from './availableSteps';
|
import { getSteps, getCurrentStep } from './availableSteps';
|
||||||
import Navigation from './Components/Navigation';
|
import Navigation from './Components/Navigation';
|
||||||
import { useEffect } from '@wordpress/element';
|
|
||||||
|
|
||||||
const Onboarding = () => {
|
const Onboarding = () => {
|
||||||
const { step, setStep, flags } = OnboardingHooks.useSteps();
|
const { step, setStep, flags } = OnboardingHooks.useSteps();
|
||||||
const Steps = getSteps( flags );
|
const Steps = getSteps( flags );
|
||||||
const currentStep = getCurrentStep( step, Steps );
|
const currentStep = getCurrentStep( step, Steps );
|
||||||
|
|
||||||
// Disable the "Changes you made might not be saved" browser warning.
|
|
||||||
useEffect( () => {
|
|
||||||
const suppressBeforeUnload = ( event ) => {
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener( 'beforeunload', suppressBeforeUnload );
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener( 'beforeunload', suppressBeforeUnload );
|
|
||||||
};
|
|
||||||
}, [] );
|
|
||||||
|
|
||||||
const handleNext = () => setStep( currentStep.nextStep );
|
const handleNext = () => setStep( currentStep.nextStep );
|
||||||
const handlePrev = () => setStep( currentStep.prevStep );
|
const handlePrev = () => setStep( currentStep.prevStep );
|
||||||
const handleExit = () => {
|
const handleExit = () => {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
import OnboardingHeader from '../../ReusableComponents/OnboardingHeader';
|
||||||
import SelectBoxWrapper from '../../ReusableComponents/SelectBoxWrapper';
|
import SelectBoxWrapper from '../../ReusableComponents/SelectBoxWrapper';
|
||||||
import SelectBox from '../../ReusableComponents/SelectBox';
|
import SelectBox from '../../ReusableComponents/SelectBox';
|
||||||
import { CommonHooks, OnboardingHooks } from '../../../data';
|
import { CommonHooks, OnboardingHooks } from '../../../data';
|
||||||
import OptionalPaymentMethods from '../../ReusableComponents/OptionalPaymentMethods/OptionalPaymentMethods';
|
import OptionalPaymentMethods from '../../ReusableComponents/OptionalPaymentMethods/OptionalPaymentMethods';
|
||||||
|
import { pricesBasedDescription } from '../../ReusableComponents/WelcomeDocs/pricesBasedDescription';
|
||||||
|
import { countryPriceInfo } from '../../../utils/countryPriceInfo';
|
||||||
|
|
||||||
const OPM_RADIO_GROUP_NAME = 'optional-payment-methods';
|
const OPM_RADIO_GROUP_NAME = 'optional-payment-methods';
|
||||||
|
|
||||||
|
@ -16,15 +18,6 @@ const StepPaymentMethods = ( {} ) => {
|
||||||
|
|
||||||
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
|
||||||
|
|
||||||
const pricesBasedDescription = sprintf(
|
|
||||||
// translators: %s: Link to PayPal REST application guide
|
|
||||||
__(
|
|
||||||
'<sup>1</sup>Prices based on domestic transactions as of October 25th, 2024. <a target="_blank" href="%s">Click here</a> for full pricing details.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ppcp-r-page-optional-payment-methods">
|
<div className="ppcp-r-page-optional-payment-methods">
|
||||||
<OnboardingHeader
|
<OnboardingHeader
|
||||||
|
@ -67,12 +60,14 @@ const StepPaymentMethods = ( {} ) => {
|
||||||
type="radio"
|
type="radio"
|
||||||
></SelectBox>
|
></SelectBox>
|
||||||
</SelectBoxWrapper>
|
</SelectBoxWrapper>
|
||||||
<p
|
{ storeCountry in countryPriceInfo && (
|
||||||
className="ppcp-r-optional-payment-methods__description"
|
<p
|
||||||
dangerouslySetInnerHTML={ {
|
className="ppcp-r-optional-payment-methods__description"
|
||||||
__html: pricesBasedDescription,
|
dangerouslySetInnerHTML={ {
|
||||||
} }
|
__html: pricesBasedDescription,
|
||||||
></p>
|
} }
|
||||||
|
></p>
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useMemo } from '@wordpress/element';
|
import { useEffect, useMemo } from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
@ -11,6 +11,20 @@ import SettingsScreen from './SettingsScreen';
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const onboardingProgress = OnboardingHooks.useSteps();
|
const onboardingProgress = OnboardingHooks.useSteps();
|
||||||
|
|
||||||
|
// Disable the "Changes you made might not be saved" browser warning.
|
||||||
|
useEffect( () => {
|
||||||
|
const suppressBeforeUnload = ( event ) => {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener( 'beforeunload', suppressBeforeUnload );
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener( 'beforeunload', suppressBeforeUnload );
|
||||||
|
};
|
||||||
|
}, [] );
|
||||||
|
|
||||||
const wrapperClass = classNames( 'ppcp-r-app', {
|
const wrapperClass = classNames( 'ppcp-r-app', {
|
||||||
loading: ! onboardingProgress.isReady,
|
loading: ! onboardingProgress.isReady,
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'settings.url' => static function ( ContainerInterface $container ) : string {
|
'settings.url' => static function ( ContainerInterface $container ) : string {
|
||||||
|
@ -138,11 +139,24 @@ return array(
|
||||||
|
|
||||||
return in_array( $country, $eligible_countries, true );
|
return in_array( $country, $eligible_countries, true );
|
||||||
},
|
},
|
||||||
|
'settings.handler.connection-listener' => static function ( ContainerInterface $container ) : ConnectionListener {
|
||||||
|
$page_id = $container->has( 'wcgateway.current-ppcp-settings-page-id' ) ? $container->get( 'wcgateway.current-ppcp-settings-page-id' ) : '';
|
||||||
|
|
||||||
|
return new ConnectionListener(
|
||||||
|
$page_id,
|
||||||
|
$container->get( 'settings.data.common' ),
|
||||||
|
$container->get( 'settings.service.onboarding-url-manager' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
);
|
||||||
|
},
|
||||||
'settings.service.signup-link-cache' => static function ( ContainerInterface $container ) : Cache {
|
'settings.service.signup-link-cache' => static function ( ContainerInterface $container ) : Cache {
|
||||||
return new Cache( 'ppcp-paypal-signup-link' );
|
return new Cache( 'ppcp-paypal-signup-link' );
|
||||||
},
|
},
|
||||||
'settings.service.onboarding-url-manager' => static function ( ContainerInterface $container ) : OnboardingUrlManager {
|
'settings.service.onboarding-url-manager' => static function ( ContainerInterface $container ) : OnboardingUrlManager {
|
||||||
return new OnboardingUrlManager();
|
return new OnboardingUrlManager(
|
||||||
|
$container->get( 'settings.service.signup-link-cache' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
);
|
||||||
},
|
},
|
||||||
'settings.service.connection-url-generators' => static function ( ContainerInterface $container ) : array {
|
'settings.service.connection-url-generators' => static function ( ContainerInterface $container ) : array {
|
||||||
// Define available environments.
|
// Define available environments.
|
||||||
|
@ -162,7 +176,6 @@ return array(
|
||||||
$generators[ $environment ] = new ConnectionUrlGenerator(
|
$generators[ $environment ] = new ConnectionUrlGenerator(
|
||||||
$config['partner_referrals'],
|
$config['partner_referrals'],
|
||||||
$container->get( 'api.repository.partner-referrals-data' ),
|
$container->get( 'api.repository.partner-referrals-data' ),
|
||||||
$container->get( 'settings.service.signup-link-cache' ),
|
|
||||||
$environment,
|
$environment,
|
||||||
$container->get( 'settings.service.onboarding-url-manager' ),
|
$container->get( 'settings.service.onboarding-url-manager' ),
|
||||||
$container->get( 'woocommerce.logger.woocommerce' )
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
|
|
@ -206,12 +206,6 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
$this->merchant_info_map
|
$this->merchant_info_map
|
||||||
);
|
);
|
||||||
|
|
||||||
// TEMP for demonstration.
|
|
||||||
$extra_data['merchant']['isConnected'] = true;
|
|
||||||
$extra_data['merchant']['isSandbox'] = true;
|
|
||||||
$extra_data['merchant']['id'] = '1234567890';
|
|
||||||
$extra_data['merchant']['email'] = 'example@example.com';
|
|
||||||
|
|
||||||
return $extra_data;
|
return $extra_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,18 @@ namespace WooCommerce\PayPalCommerce\Settings\Handler;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||||
|
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class ConnectionHandler {
|
/**
|
||||||
|
* Provides a listener that handles merchant-connection requests.
|
||||||
|
*
|
||||||
|
* Those connection requests are made after the merchant logs into their PayPal
|
||||||
|
* account (inside the login popup). At the last step, they see a "Return to
|
||||||
|
* Store" button.
|
||||||
|
* Clicking that button triggers the merchant-connection request.
|
||||||
|
*/
|
||||||
|
class ConnectionListener {
|
||||||
/**
|
/**
|
||||||
* ID of the current settings page; empty if not on a PayPal settings page.
|
* ID of the current settings page; empty if not on a PayPal settings page.
|
||||||
*
|
*
|
||||||
|
@ -35,6 +45,13 @@ class ConnectionHandler {
|
||||||
*/
|
*/
|
||||||
private OnboardingUrlManager $url_manager;
|
private OnboardingUrlManager $url_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance, mainly used for debugging purposes.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the current user, set by the process() method.
|
* ID of the current user, set by the process() method.
|
||||||
*
|
*
|
||||||
|
@ -48,11 +65,13 @@ class ConnectionHandler {
|
||||||
* @param string $settings_page_id Current plugin settings page ID.
|
* @param string $settings_page_id Current plugin settings page ID.
|
||||||
* @param CommonSettings $settings Access to saved connection details.
|
* @param CommonSettings $settings Access to saved connection details.
|
||||||
* @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
|
* @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
|
||||||
|
* @param ?LoggerInterface $logger The logger, for debugging purposes.
|
||||||
*/
|
*/
|
||||||
public function __construct( string $settings_page_id, CommonSettings $settings, OnboardingUrlManager $url_manager ) {
|
public function __construct( string $settings_page_id, CommonSettings $settings, OnboardingUrlManager $url_manager, LoggerInterface $logger = null ) {
|
||||||
$this->settings_page_id = $settings_page_id;
|
$this->settings_page_id = $settings_page_id;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->url_manager = $url_manager;
|
$this->url_manager = $url_manager;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
|
||||||
// Initialize as "guest", the real ID is provided via process().
|
// Initialize as "guest", the real ID is provided via process().
|
||||||
$this->user_id = 0;
|
$this->user_id = 0;
|
||||||
|
@ -71,9 +90,20 @@ class ConnectionHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$token = $this->get_token_from_request( $request );
|
||||||
|
if ( ! $this->url_manager->validate_token_and_delete( $token, $this->user_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$data = $this->extract_data( $request );
|
$data = $this->extract_data( $request );
|
||||||
|
if ( ! $data ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info( 'Found merchant data in request', $data );
|
||||||
|
|
||||||
$this->store_data(
|
$this->store_data(
|
||||||
$data['use_sandbox'],
|
$data['is_sandbox'],
|
||||||
$data['merchant_id'],
|
$data['merchant_id'],
|
||||||
$data['merchant_email']
|
$data['merchant_email']
|
||||||
);
|
);
|
||||||
|
@ -96,32 +126,38 @@ class ConnectionHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requirement 3: The params are present and not empty - 'merchantIdInPayPal' - 'merchantId' - 'ppcpToken'
|
$required_params = array(
|
||||||
|
'merchantIdInPayPal',
|
||||||
|
'merchantId',
|
||||||
|
'ppcpToken',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $required_params as $param ) {
|
||||||
|
if ( empty( $request[ $param ] ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks, if the connection token is valid.
|
* Extract the merchant details (ID & email) from the request details.
|
||||||
*
|
*
|
||||||
* If the token is valid, it is *instantly invalidated* by this check: It's
|
* @param array $request The full request details.
|
||||||
* not possible to verify the same token twice.
|
|
||||||
*
|
*
|
||||||
* @param string $token The token to verify.
|
* @return array Structured array with 'is_sandbox', 'merchant_id', and 'merchant_email' keys,
|
||||||
*
|
* or an empty array on failure.
|
||||||
* @return bool True, if the token is valid.
|
|
||||||
*/
|
*/
|
||||||
protected function is_token_valid( string $token ) : bool {
|
|
||||||
|
|
||||||
// $valid = OnboardingUrl::validate_token_and_delete( $this->cache, $token, $user_id )
|
|
||||||
// OR OnboardingUrl::validate_previous_token( $this->cache, $token, $user_id )
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function extract_data( array $request ) : array {
|
protected function extract_data( array $request ) : array {
|
||||||
// $merchant_id: $request['merchantIdInPayPal'] (!), sanitize: sanitize_text_field( wp_unslash() )
|
$this->logger->info( 'Extracting connection data from request...' );
|
||||||
// $merchant_email: $request['merchantId'] (!), sanitize: $this->sanitize_merchant_email()
|
|
||||||
|
$merchant_id = $this->get_merchant_id_from_request( $request );
|
||||||
|
$merchant_email = $this->get_merchant_email_from_request( $request );
|
||||||
|
|
||||||
|
if ( ! $merchant_id || ! $merchant_email ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'is_sandbox' => $this->settings->get_sandbox(),
|
'is_sandbox' => $this->settings->get_sandbox(),
|
||||||
|
@ -130,26 +166,76 @@ class ConnectionHandler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the merchant details to the database.
|
||||||
|
*
|
||||||
|
* @param bool $is_sandbox Whether the details are for a sandbox account.
|
||||||
|
* @param string $merchant_id The anonymized merchant ID.
|
||||||
|
* @param string $merchant_email The merchant's email.
|
||||||
|
*/
|
||||||
|
protected function store_data( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
|
||||||
|
$this->logger->info( "Save merchant details to the DB: $merchant_email ($merchant_id)" );
|
||||||
|
|
||||||
|
$this->settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
|
||||||
|
$this->settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sanitized connection token from the incoming request.
|
||||||
|
*
|
||||||
|
* @param array $request Full request details.
|
||||||
|
*
|
||||||
|
* @return string The sanitized token, or an empty string.
|
||||||
|
*/
|
||||||
|
protected function get_token_from_request( array $request ) : string {
|
||||||
|
return $this->sanitize_string( $request['ppcpToken'] ?? '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sanitized merchant ID from the incoming request.
|
||||||
|
*
|
||||||
|
* @param array $request Full request details.
|
||||||
|
*
|
||||||
|
* @return string The sanitized merchant ID, or an empty string.
|
||||||
|
*/
|
||||||
|
protected function get_merchant_id_from_request( array $request ) : string {
|
||||||
|
return $this->sanitize_string( $request['merchantIdInPayPal'] ?? '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sanitized merchant email from the incoming request.
|
||||||
|
*
|
||||||
|
* Note that the email is provided via the argument "merchantId", which
|
||||||
|
* looks incorrect at first, but PayPal uses the email address as merchant
|
||||||
|
* IDm and offers a more anonymous ID via the "merchantIdInPayPal" argument.
|
||||||
|
*
|
||||||
|
* @param array $request Full request details.
|
||||||
|
*
|
||||||
|
* @return string The sanitized merchant email, or an empty string.
|
||||||
|
*/
|
||||||
|
protected function get_merchant_email_from_request( array $request ) : string {
|
||||||
|
return $this->sanitize_merchant_email( $request['merchantId'] ?? '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes a request-argument for processing.
|
||||||
|
*
|
||||||
|
* @param string $value Value from the request argument.
|
||||||
|
*
|
||||||
|
* @return string Sanitized value.
|
||||||
|
*/
|
||||||
|
protected function sanitize_string( string $value ) : string {
|
||||||
|
return trim( sanitize_text_field( wp_unslash( $value ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes the merchant's email address for processing.
|
* Sanitizes the merchant's email address for processing.
|
||||||
*
|
*
|
||||||
* @param string $email The plain email.
|
* @param string $email The plain email.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string Sanitized email address.
|
||||||
*/
|
*/
|
||||||
private function sanitize_merchant_email( string $email ) : string {
|
protected function sanitize_merchant_email( string $email ) : string {
|
||||||
return sanitize_text_field( str_replace( ' ', '+', $email ) );
|
return sanitize_text_field( str_replace( ' ', '+', $email ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist the merchant details to the database.
|
|
||||||
*
|
|
||||||
* @param bool $is_sandbox
|
|
||||||
* @param string $merchant_id
|
|
||||||
* @param string $merchant_email
|
|
||||||
*/
|
|
||||||
protected function store_data( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
|
|
||||||
$this->settings->set_merchant_data( $is_sandbox, $merchant_id, $merchant_email );
|
|
||||||
$this->settings->save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,6 @@ class ConnectionUrlGenerator {
|
||||||
*/
|
*/
|
||||||
protected PartnerReferralsData $referrals_data;
|
protected PartnerReferralsData $referrals_data;
|
||||||
|
|
||||||
/**
|
|
||||||
* The cache
|
|
||||||
*
|
|
||||||
* @var Cache
|
|
||||||
*/
|
|
||||||
protected Cache $cache;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages access to OnboardingUrl instances
|
* Manages access to OnboardingUrl instances
|
||||||
*
|
*
|
||||||
|
@ -63,7 +56,7 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* @var LoggerInterface
|
* @var LoggerInterface
|
||||||
*/
|
*/
|
||||||
private $logger;
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the ConnectionUrlGenerator class.
|
* Constructor for the ConnectionUrlGenerator class.
|
||||||
|
@ -72,8 +65,6 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* @param PartnerReferrals $partner_referrals PartnerReferrals for URL generation.
|
* @param PartnerReferrals $partner_referrals PartnerReferrals for URL generation.
|
||||||
* @param PartnerReferralsData $referrals_data Default partner referrals data.
|
* @param PartnerReferralsData $referrals_data Default partner referrals data.
|
||||||
* @param Cache $cache The cache object used for storing and
|
|
||||||
* retrieving data.
|
|
||||||
* @param string $environment Environment that is used to generate the URL.
|
* @param string $environment Environment that is used to generate the URL.
|
||||||
* ['production'|'sandbox'].
|
* ['production'|'sandbox'].
|
||||||
* @param OnboardingUrlManager $url_manager Manages access to OnboardingUrl instances.
|
* @param OnboardingUrlManager $url_manager Manages access to OnboardingUrl instances.
|
||||||
|
@ -82,14 +73,12 @@ class ConnectionUrlGenerator {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
PartnerReferrals $partner_referrals,
|
PartnerReferrals $partner_referrals,
|
||||||
PartnerReferralsData $referrals_data,
|
PartnerReferralsData $referrals_data,
|
||||||
Cache $cache,
|
|
||||||
string $environment,
|
string $environment,
|
||||||
OnboardingUrlManager $url_manager,
|
OnboardingUrlManager $url_manager,
|
||||||
?LoggerInterface $logger = null
|
?LoggerInterface $logger = null
|
||||||
) {
|
) {
|
||||||
$this->partner_referrals = $partner_referrals;
|
$this->partner_referrals = $partner_referrals;
|
||||||
$this->referrals_data = $referrals_data;
|
$this->referrals_data = $referrals_data;
|
||||||
$this->cache = $cache;
|
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->url_manager = $url_manager;
|
$this->url_manager = $url_manager;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
@ -119,7 +108,7 @@ class ConnectionUrlGenerator {
|
||||||
public function generate( array $products = array() ) : string {
|
public function generate( array $products = array() ) : string {
|
||||||
$cache_key = $this->cache_key( $products );
|
$cache_key = $this->cache_key( $products );
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
$onboarding_url = $this->url_manager->get( $this->cache, $cache_key, $user_id );
|
$onboarding_url = $this->url_manager->get( $cache_key, $user_id );
|
||||||
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
|
$cached_url = $this->try_get_from_cache( $onboarding_url, $cache_key );
|
||||||
|
|
||||||
if ( $cached_url ) {
|
if ( $cached_url ) {
|
||||||
|
|
|
@ -9,7 +9,9 @@ declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||||
|
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||||
|
|
||||||
// TODO: Replace the OnboardingUrl with a new implementation for this module.
|
// TODO: Replace the OnboardingUrl with a new implementation for this module.
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
||||||
|
@ -25,16 +27,75 @@ use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
||||||
* without having to re-write all token-related details just yet.
|
* without having to re-write all token-related details just yet.
|
||||||
*/
|
*/
|
||||||
class OnboardingUrlManager {
|
class OnboardingUrlManager {
|
||||||
|
/**
|
||||||
|
* Cache instance for onboarding token.
|
||||||
|
*
|
||||||
|
* @var Cache
|
||||||
|
*/
|
||||||
|
private Cache $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance, mainly used for debugging purposes.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Cache $cache Cache instance for onboarding token.
|
||||||
|
* @param ?LoggerInterface $logger The logger, for debugging purposes.
|
||||||
|
*/
|
||||||
|
public function __construct( Cache $cache, LoggerInterface $logger = null ) {
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new Onboarding Url instance.
|
* Returns a new Onboarding Url instance.
|
||||||
*
|
*
|
||||||
* @param Cache $cache The cache object to store the URL.
|
|
||||||
* @param string $cache_key_prefix The prefix for the cache entry.
|
* @param string $cache_key_prefix The prefix for the cache entry.
|
||||||
* @param int $user_id User ID to associate the link with.
|
* @param int $user_id User ID to associate the link with.
|
||||||
*
|
*
|
||||||
* @return OnboardingUrl
|
* @return OnboardingUrl
|
||||||
*/
|
*/
|
||||||
public function get( Cache $cache, string $cache_key_prefix, int $user_id ) : OnboardingUrl {
|
public function get( string $cache_key_prefix, int $user_id ) : OnboardingUrl {
|
||||||
return new OnboardingUrl( $cache, $cache_key_prefix, $user_id );
|
return new OnboardingUrl( $this->cache, $cache_key_prefix, $user_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the authentication token; if it's valid, the token is instantly
|
||||||
|
* invalidated (deleted), so it cannot be validated again.
|
||||||
|
*
|
||||||
|
* @param string $token The token to validate.
|
||||||
|
* @param int $user_id User ID who generated the token.
|
||||||
|
*
|
||||||
|
* @return bool True, if the token is valid. False otherwise.
|
||||||
|
*/
|
||||||
|
public function validate_token_and_delete( string $token, int $user_id ) : bool {
|
||||||
|
if ( $user_id < 1 || strlen( $token ) < 10 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log_token = ( (string) substr( $token, 0, 2 ) ) . '...' . ( (string) substr( $token, - 6 ) );
|
||||||
|
$this->logger->debug( 'Validating onboarding ppcpToken: ' . $log_token );
|
||||||
|
|
||||||
|
if ( OnboardingUrl::validate_token_and_delete( $this->cache, $token, $user_id ) ) {
|
||||||
|
$this->logger->info( 'Validated onboarding ppcpToken: ' . $log_token );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( OnboardingUrl::validate_previous_token( $this->cache, $token, $user_id ) ) {
|
||||||
|
// TODO: Do we need this here? Previous logic was to reload the page without doing anything in this case.
|
||||||
|
$this->logger->info( 'Validated previous token, silently redirecting: ' . $log_token );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->error( 'Failed to validate onboarding ppcpToken: ' . $log_token );
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Settings;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||||
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;
|
||||||
|
@ -85,7 +86,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$endpoint = $container->get( 'settings.switch-ui.endpoint' );
|
$endpoint = $container->get( 'settings.switch-ui.endpoint' ) ? $container->get( 'settings.switch-ui.endpoint' ) : null;
|
||||||
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
|
@ -189,6 +190,17 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'admin_init',
|
||||||
|
static function () use ( $container ) : void {
|
||||||
|
$connection_handler = $container->get( 'settings.handler.connection-listener' );
|
||||||
|
assert( $connection_handler instanceof ConnectionListener );
|
||||||
|
|
||||||
|
// @phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no nonce; sanitation done by the handler
|
||||||
|
$connection_handler->process( get_current_user_id(), $_GET );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* @package WooCommerce\PayPalCommerce\WcGateway\Settings
|
* @package WooCommerce\PayPalCommerce\WcGateway\Settings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
|
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
|
||||||
|
|
||||||
|
@ -18,44 +18,46 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
*/
|
*/
|
||||||
class Settings implements ContainerInterface {
|
class Settings implements ContainerInterface {
|
||||||
|
|
||||||
const KEY = 'woocommerce-ppcp-settings';
|
const KEY = 'woocommerce-ppcp-settings';
|
||||||
|
|
||||||
const CONNECTION_TAB_ID = 'ppcp-connection';
|
const CONNECTION_TAB_ID = 'ppcp-connection';
|
||||||
const PAY_LATER_TAB_ID = 'ppcp-pay-later';
|
|
||||||
|
const PAY_LATER_TAB_ID = 'ppcp-pay-later';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The settings.
|
* The settings.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $settings = array();
|
private array $settings = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of selected default button locations.
|
* The list of selected default button locations.
|
||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $default_button_locations;
|
protected array $default_button_locations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of selected default pay later button locations.
|
* The list of selected default pay later button locations.
|
||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $default_pay_later_button_locations;
|
protected array $default_pay_later_button_locations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of selected default pay later messaging locations.
|
* The list of selected default pay later messaging locations.
|
||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $default_pay_later_messaging_locations;
|
protected array $default_pay_later_messaging_locations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default ACDC gateway title.
|
* The default ACDC gateway title.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $default_dcc_gateway_title;
|
protected string $default_dcc_gateway_title;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper for mapping the new/old settings.
|
* A helper for mapping the new/old settings.
|
||||||
|
@ -67,11 +69,17 @@ class Settings implements ContainerInterface {
|
||||||
/**
|
/**
|
||||||
* Settings constructor.
|
* Settings constructor.
|
||||||
*
|
*
|
||||||
* @param string[] $default_button_locations The list of selected default button locations.
|
* @param string[] $default_button_locations The list of selected default
|
||||||
* @param string $default_dcc_gateway_title The default ACDC gateway title.
|
* button locations.
|
||||||
* @param string[] $default_pay_later_button_locations The list of selected default pay later button locations.
|
* @param string $default_dcc_gateway_title The default ACDC gateway
|
||||||
* @param string[] $default_pay_later_messaging_locations The list of selected default pay later messaging locations.
|
* title.
|
||||||
* @param SettingsMapHelper $settings_map_helper A helper for mapping the new/old settings.
|
* @param string[] $default_pay_later_button_locations The list of selected default
|
||||||
|
* pay later button locations.
|
||||||
|
* @param string[] $default_pay_later_messaging_locations The list of selected default
|
||||||
|
* pay later messaging
|
||||||
|
* locations.
|
||||||
|
* @param SettingsMapHelper $settings_map_helper A helper for mapping the
|
||||||
|
* new/old settings.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
array $default_button_locations,
|
array $default_button_locations,
|
||||||
|
@ -90,10 +98,11 @@ class Settings implements ContainerInterface {
|
||||||
/**
|
/**
|
||||||
* Returns the value for an id.
|
* Returns the value for an id.
|
||||||
*
|
*
|
||||||
* @param string $id The value identificator.
|
* @throws NotFoundException When nothing was found.
|
||||||
|
*
|
||||||
|
* @param string $id The value identifier.
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws NotFoundException When nothing was found.
|
|
||||||
*/
|
*/
|
||||||
public function get( $id ) {
|
public function get( $id ) {
|
||||||
if ( ! $this->has( $id ) ) {
|
if ( ! $this->has( $id ) ) {
|
||||||
|
@ -106,23 +115,24 @@ class Settings implements ContainerInterface {
|
||||||
/**
|
/**
|
||||||
* Whether a value exists.
|
* Whether a value exists.
|
||||||
*
|
*
|
||||||
* @param string $id The value identificator.
|
* @param string $id The value identifier.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function has( $id ) {
|
public function has( string $id ) {
|
||||||
if ( $this->settings_map_helper->has_mapped_key( $id ) ) {
|
if ( $this->settings_map_helper->has_mapped_key( $id ) ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->load();
|
$this->load();
|
||||||
|
|
||||||
return array_key_exists( $id, $this->settings );
|
return array_key_exists( $id, $this->settings );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a value.
|
* Sets a value.
|
||||||
*
|
*
|
||||||
* @param string $id The value identificator.
|
* @param string $id The value identifier.
|
||||||
* @param mixed $value The value.
|
* @param mixed $value The value.
|
||||||
*/
|
*/
|
||||||
public function set( $id, $value ) {
|
public function set( $id, $value ) {
|
||||||
|
@ -142,7 +152,7 @@ class Settings implements ContainerInterface {
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function load(): bool {
|
private function load() : bool {
|
||||||
if ( $this->settings ) {
|
if ( $this->settings ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -175,6 +185,7 @@ class Settings implements ContainerInterface {
|
||||||
}
|
}
|
||||||
$this->settings[ $key ] = $value;
|
$this->settings[ $key ] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,14 @@ class TaskRegistrar implements TaskRegistrarInterface {
|
||||||
*
|
*
|
||||||
* @throws RuntimeException If problem registering.
|
* @throws RuntimeException If problem registering.
|
||||||
*/
|
*/
|
||||||
public function register( array $tasks ): void {
|
public function register( string $list_id, array $tasks ): void {
|
||||||
|
$task_lists = TaskLists::get_lists();
|
||||||
|
if ( ! isset( $task_lists[ $list_id ] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ( $tasks as $task ) {
|
foreach ( $tasks as $task ) {
|
||||||
$added_task = TaskLists::add_task( 'extended', $task );
|
$added_task = TaskLists::add_task( $list_id, $task );
|
||||||
if ( $added_task instanceof WP_Error ) {
|
if ( $added_task instanceof WP_Error ) {
|
||||||
throw new RuntimeException( $added_task->get_error_message() );
|
throw new RuntimeException( $added_task->get_error_message() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,12 @@ use RuntimeException;
|
||||||
interface TaskRegistrarInterface {
|
interface TaskRegistrarInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the tasks inside "Things to do next" WC section.
|
* Registers the tasks inside the section with given list ID.
|
||||||
*
|
*
|
||||||
|
* @param string $list_id The list ID.
|
||||||
* @param Task[] $tasks The list of tasks.
|
* @param Task[] $tasks The list of tasks.
|
||||||
* @return void
|
* @return void
|
||||||
* @throws RuntimeException If problem registering.
|
* @throws RuntimeException If problem registering.
|
||||||
*/
|
*/
|
||||||
public function register( array $tasks ): void;
|
public function register( string $list_id, array $tasks ): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,7 +653,10 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
||||||
$listener = $container->get( 'wcgateway.settings.listener' );
|
$listener = $container->get( 'wcgateway.settings.listener' );
|
||||||
assert( $listener instanceof SettingsListener );
|
assert( $listener instanceof SettingsListener );
|
||||||
|
|
||||||
$listener->listen_for_merchant_id();
|
$use_new_ui = $container->get( 'wcgateway.settings.admin-settings-enabled' );
|
||||||
|
if ( ! $use_new_ui ) {
|
||||||
|
$listener->listen_for_merchant_id();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$listener->listen_for_vaulting_enabled();
|
$listener->listen_for_vaulting_enabled();
|
||||||
|
@ -880,10 +883,11 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
||||||
if ( empty( $simple_redirect_tasks ) ) {
|
if ( empty( $simple_redirect_tasks ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' );
|
$task_registrar = $container->get( 'wcgateway.settings.wc-tasks.task-registrar' );
|
||||||
assert( $task_registrar instanceof TaskRegistrarInterface );
|
assert( $task_registrar instanceof TaskRegistrarInterface );
|
||||||
|
|
||||||
$task_registrar->register( $simple_redirect_tasks );
|
$task_registrar->register( 'extended', $simple_redirect_tasks );
|
||||||
} catch ( Exception $exception ) {
|
} catch ( Exception $exception ) {
|
||||||
$logger->error( "Failed to create a task in the 'Things to do next' section of WC. " . $exception->getMessage() );
|
$logger->error( "Failed to create a task in the 'Things to do next' section of WC. " . $exception->getMessage() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
|
||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
= 2.9.5 - xxxx-xx-xx =
|
= 2.9.5 - 2024-12-10 =
|
||||||
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
Fix - Early translation loading triggers `Function _load_textdomain_just_in_time was called incorrectly.` notice #2816
|
||||||
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
Fix - ACDC card fields not loading and payment not successful when Classic Checkout Smart Button Location disabled #2852
|
||||||
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
Fix - ACDC gateway does not appear for guests when is Fastlane enabled and a subscription product is in the cart #2745
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue