mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Merge pull request #2945 from woocommerce/PCP-4020-review-and-fix-the-sandbox-login
Review and fix the login logic - Frontend changes (4020)
This commit is contained in:
commit
689463e243
46 changed files with 1750 additions and 1023 deletions
|
@ -79,6 +79,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'api.host' => function( ContainerInterface $container ) : string {
|
'api.host' => function( ContainerInterface $container ) : string {
|
||||||
|
@ -879,4 +880,54 @@ return array(
|
||||||
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
|
'api.partner_merchant_id-sandbox' => static function( ContainerInterface $container ) : string {
|
||||||
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
|
return CONNECT_WOO_SANDBOX_MERCHANT_ID;
|
||||||
},
|
},
|
||||||
|
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||||
|
return new LoginSeller(
|
||||||
|
$container->get( 'api.paypal-host-production' ),
|
||||||
|
$container->get( 'api.partner_merchant_id-production' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
|
||||||
|
return new LoginSeller(
|
||||||
|
$container->get( 'api.paypal-host-sandbox' ),
|
||||||
|
$container->get( 'api.partner_merchant_id-sandbox' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'api.env.paypal-host' => static function ( ContainerInterface $container ) : EnvironmentConfig {
|
||||||
|
/**
|
||||||
|
* Environment specific API host names.
|
||||||
|
*
|
||||||
|
* @type EnvironmentConfig<string>
|
||||||
|
*/
|
||||||
|
return EnvironmentConfig::create(
|
||||||
|
'string',
|
||||||
|
$container->get( 'api.paypal-host-production' ),
|
||||||
|
$container->get( 'api.paypal-host-sandbox' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'api.env.endpoint.login-seller' => static function ( ContainerInterface $container ) : EnvironmentConfig {
|
||||||
|
/**
|
||||||
|
* Environment specific LoginSeller API instances.
|
||||||
|
*
|
||||||
|
* @type EnvironmentConfig<LoginSeller>
|
||||||
|
*/
|
||||||
|
return EnvironmentConfig::create(
|
||||||
|
LoginSeller::class,
|
||||||
|
$container->get( 'api.endpoint.login-seller-production' ),
|
||||||
|
$container->get( 'api.endpoint.login-seller-sandbox' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'api.env.endpoint.partner-referrals' => static function ( ContainerInterface $container ) : EnvironmentConfig {
|
||||||
|
/**
|
||||||
|
* Environment specific PartnerReferrals API instances.
|
||||||
|
*
|
||||||
|
* @type EnvironmentConfig<PartnerReferrals>
|
||||||
|
*/
|
||||||
|
return EnvironmentConfig::create(
|
||||||
|
PartnerReferrals::class,
|
||||||
|
$container->get( 'api.endpoint.partner-referrals-production' ),
|
||||||
|
$container->get( 'api.endpoint.partner-referrals-sandbox' )
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -184,21 +184,17 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
|
||||||
|
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_rest_common_merchant_data',
|
'woocommerce_paypal_payments_rest_common_merchant_data',
|
||||||
function( array $merchant_data ) use ( $c ): array {
|
function( array $features ) use ( $c ): array {
|
||||||
if ( ! isset( $merchant_data['features'] ) ) {
|
|
||||||
$merchant_data['features'] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$product_status = $c->get( 'applepay.apple-product-status' );
|
$product_status = $c->get( 'applepay.apple-product-status' );
|
||||||
assert( $product_status instanceof AppleProductStatus );
|
assert( $product_status instanceof AppleProductStatus );
|
||||||
|
|
||||||
$apple_pay_enabled = $product_status->is_active();
|
$apple_pay_enabled = $product_status->is_active();
|
||||||
|
|
||||||
$merchant_data['features']['apple_pay'] = array(
|
$features['apple_pay'] = array(
|
||||||
'enabled' => $apple_pay_enabled,
|
'enabled' => $apple_pay_enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $merchant_data;
|
return $features;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -234,21 +234,17 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
||||||
|
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_rest_common_merchant_data',
|
'woocommerce_paypal_payments_rest_common_merchant_data',
|
||||||
function ( array $merchant_data ) use ( $c ): array {
|
function ( array $features ) use ( $c ): array {
|
||||||
if ( ! isset( $merchant_data['features'] ) ) {
|
|
||||||
$merchant_data['features'] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$product_status = $c->get( 'googlepay.helpers.apm-product-status' );
|
$product_status = $c->get( 'googlepay.helpers.apm-product-status' );
|
||||||
assert( $product_status instanceof ApmProductStatus );
|
assert( $product_status instanceof ApmProductStatus );
|
||||||
|
|
||||||
$google_pay_enabled = $product_status->is_active();
|
$google_pay_enabled = $product_status->is_active();
|
||||||
|
|
||||||
$merchant_data['features']['google_pay'] = array(
|
$features['google_pay'] = array(
|
||||||
'enabled' => $google_pay_enabled,
|
'enabled' => $google_pay_enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $merchant_data;
|
return $features;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,12 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
|
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
|
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
|
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
|
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
|
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
|
'api.sandbox-host' => static function ( ContainerInterface $container ): string {
|
||||||
|
@ -144,26 +142,6 @@ return array(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
'api.endpoint.login-seller-production' => static function ( ContainerInterface $container ) : LoginSeller {
|
|
||||||
|
|
||||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
|
||||||
return new LoginSeller(
|
|
||||||
$container->get( 'api.paypal-host-production' ),
|
|
||||||
$container->get( 'api.partner_merchant_id-production' ),
|
|
||||||
$logger
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
'api.endpoint.login-seller-sandbox' => static function ( ContainerInterface $container ) : LoginSeller {
|
|
||||||
|
|
||||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
|
||||||
return new LoginSeller(
|
|
||||||
$container->get( 'api.paypal-host-sandbox' ),
|
|
||||||
$container->get( 'api.partner_merchant_id-sandbox' ),
|
|
||||||
$logger
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint {
|
'onboarding.endpoint.login-seller' => static function ( ContainerInterface $container ) : LoginSellerEndpoint {
|
||||||
|
|
||||||
$request_data = $container->get( 'button.request-data' );
|
$request_data = $container->get( 'button.request-data' );
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@wordpress/data": "^10.10.0",
|
"@wordpress/data": "^10.10.0",
|
||||||
"@wordpress/data-controls": "^4.10.0",
|
"@wordpress/data-controls": "^4.10.0",
|
||||||
|
"@wordpress/icons": "^10.14.0",
|
||||||
"@wordpress/scripts": "^30.3.0",
|
"@wordpress/scripts": "^30.3.0",
|
||||||
"classnames": "^2.5.1"
|
"classnames": "^2.5.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,7 @@ const BusyContext = createContext( false );
|
||||||
* @param {boolean} props.busySpinner - Allows disabling the spinner in busy-state.
|
* @param {boolean} props.busySpinner - Allows disabling the spinner in busy-state.
|
||||||
* @param {string} props.className - Additional class names for the wrapper.
|
* @param {string} props.className - Additional class names for the wrapper.
|
||||||
* @param {Function} props.onBusy - Callback to process child props when busy.
|
* @param {Function} props.onBusy - Callback to process child props when busy.
|
||||||
|
* @param {boolean} props.isBusy - Optional. Additional condition to determine if the component is busy.
|
||||||
*/
|
*/
|
||||||
const BusyStateWrapper = ( {
|
const BusyStateWrapper = ( {
|
||||||
children,
|
children,
|
||||||
|
@ -31,11 +32,12 @@ const BusyStateWrapper = ( {
|
||||||
busySpinner = true,
|
busySpinner = true,
|
||||||
className = '',
|
className = '',
|
||||||
onBusy = () => ( { disabled: true } ),
|
onBusy = () => ( { disabled: true } ),
|
||||||
|
isBusy = false,
|
||||||
} ) => {
|
} ) => {
|
||||||
const { isBusy } = CommonHooks.useBusyState();
|
const { isBusy: globalIsBusy } = CommonHooks.useBusyState();
|
||||||
const hasBusyParent = useContext( BusyContext );
|
const hasBusyParent = useContext( BusyContext );
|
||||||
|
|
||||||
const isBusyComponent = isBusy && enabled;
|
const isBusyComponent = ( isBusy || globalIsBusy ) && enabled;
|
||||||
const showSpinner = busySpinner && isBusyComponent && ! hasBusyParent;
|
const showSpinner = busySpinner && isBusyComponent && ! hasBusyParent;
|
||||||
|
|
||||||
const wrapperClassName = classNames( 'ppcp-r-busy-wrapper', className, {
|
const wrapperClassName = classNames( 'ppcp-r-busy-wrapper', className, {
|
||||||
|
|
|
@ -1,208 +1,13 @@
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
|
||||||
import { Button, TextControl } from '@wordpress/components';
|
|
||||||
import {
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useCallback,
|
|
||||||
} from '@wordpress/element';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
|
|
||||||
import Separator from '../../../ReusableComponents/Separator';
|
import Separator from '../../../ReusableComponents/Separator';
|
||||||
import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
|
import SandboxConnectionForm from './SandboxConnectionForm';
|
||||||
import { CommonHooks } from '../../../../data';
|
import ManualConnectionForm from './ManualConnectionForm';
|
||||||
import {
|
|
||||||
useSandboxConnection,
|
|
||||||
useManualConnection,
|
|
||||||
} from '../../../../hooks/useHandleConnections';
|
|
||||||
|
|
||||||
import ConnectionButton from './ConnectionButton';
|
|
||||||
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
|
||||||
|
|
||||||
const FORM_ERRORS = {
|
|
||||||
noClientId: __(
|
|
||||||
'Please enter your Client ID',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
noClientSecret: __(
|
|
||||||
'Please enter your Secret Key',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
invalidClientId: __(
|
|
||||||
'Please enter a valid Client ID',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdvancedOptionsForm = () => {
|
const AdvancedOptionsForm = () => {
|
||||||
const [ clientValid, setClientValid ] = useState( false );
|
|
||||||
const [ secretValid, setSecretValid ] = useState( false );
|
|
||||||
|
|
||||||
const { isBusy } = CommonHooks.useBusyState();
|
|
||||||
const { isSandboxMode, setSandboxMode } = useSandboxConnection();
|
|
||||||
const {
|
|
||||||
handleConnectViaIdAndSecret,
|
|
||||||
isManualConnectionMode,
|
|
||||||
setManualConnectionMode,
|
|
||||||
clientId,
|
|
||||||
setClientId,
|
|
||||||
clientSecret,
|
|
||||||
setClientSecret,
|
|
||||||
} = useManualConnection();
|
|
||||||
|
|
||||||
const refClientId = useRef( null );
|
|
||||||
const refClientSecret = useRef( null );
|
|
||||||
|
|
||||||
const validateManualConnectionForm = useCallback( () => {
|
|
||||||
const checks = [
|
|
||||||
{
|
|
||||||
ref: refClientId,
|
|
||||||
valid: () => clientId,
|
|
||||||
errorMessage: FORM_ERRORS.noClientId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: refClientId,
|
|
||||||
valid: () => clientValid,
|
|
||||||
errorMessage: FORM_ERRORS.invalidClientId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: refClientSecret,
|
|
||||||
valid: () => clientSecret && secretValid,
|
|
||||||
errorMessage: FORM_ERRORS.noClientSecret,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for ( const { ref, valid, errorMessage } of checks ) {
|
|
||||||
if ( valid() ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref?.current?.focus();
|
|
||||||
throw new Error( errorMessage );
|
|
||||||
}
|
|
||||||
}, [ clientId, clientSecret, clientValid, secretValid ] );
|
|
||||||
|
|
||||||
const handleManualConnect = useCallback(
|
|
||||||
() =>
|
|
||||||
handleConnectViaIdAndSecret( {
|
|
||||||
validation: validateManualConnectionForm,
|
|
||||||
} ),
|
|
||||||
[ handleConnectViaIdAndSecret, validateManualConnectionForm ]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect( () => {
|
|
||||||
setClientValid( ! clientId || /^A[\w-]{79}$/.test( clientId ) );
|
|
||||||
setSecretValid( clientSecret && clientSecret.length > 0 );
|
|
||||||
}, [ clientId, clientSecret ] );
|
|
||||||
|
|
||||||
const clientIdLabel = useMemo(
|
|
||||||
() =>
|
|
||||||
isSandboxMode
|
|
||||||
? __( 'Sandbox Client ID', 'woocommerce-paypal-payments' )
|
|
||||||
: __( 'Live Client ID', 'woocommerce-paypal-payments' ),
|
|
||||||
[ isSandboxMode ]
|
|
||||||
);
|
|
||||||
|
|
||||||
const secretKeyLabel = useMemo(
|
|
||||||
() =>
|
|
||||||
isSandboxMode
|
|
||||||
? __( 'Sandbox Secret Key', 'woocommerce-paypal-payments' )
|
|
||||||
: __( 'Live Secret Key', 'woocommerce-paypal-payments' ),
|
|
||||||
[ isSandboxMode ]
|
|
||||||
);
|
|
||||||
|
|
||||||
const advancedUsersDescription = sprintf(
|
|
||||||
// translators: %s: Link to PayPal REST application guide
|
|
||||||
__(
|
|
||||||
'For advanced users: Connect a custom PayPal REST app for full control over your integration. For more information on creating a PayPal REST application, <a target="_blank" href="%s">click here</a>.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input'
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BusyStateWrapper>
|
<SandboxConnectionForm />
|
||||||
<SettingsToggleBlock
|
|
||||||
label={ __(
|
|
||||||
'Enable Sandbox Mode',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
description={ __(
|
|
||||||
'Activate Sandbox mode to safely test PayPal with sample data. Once your store is ready to go live, you can easily switch to your production account.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
isToggled={ !! isSandboxMode }
|
|
||||||
setToggled={ setSandboxMode }
|
|
||||||
>
|
|
||||||
<ConnectionButton
|
|
||||||
title={ __(
|
|
||||||
'Connect Account',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
showIcon={ false }
|
|
||||||
variant="secondary"
|
|
||||||
className="small-button"
|
|
||||||
isSandbox={
|
|
||||||
true /* This button always connects to sandbox */
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SettingsToggleBlock>
|
|
||||||
</BusyStateWrapper>
|
|
||||||
<Separator withLine={ false } />
|
<Separator withLine={ false } />
|
||||||
<BusyStateWrapper
|
<ManualConnectionForm />
|
||||||
onBusy={ ( props ) => ( {
|
|
||||||
disabled: true,
|
|
||||||
label: props.label + ' ...',
|
|
||||||
} ) }
|
|
||||||
>
|
|
||||||
<SettingsToggleBlock
|
|
||||||
label={ __(
|
|
||||||
'Manually Connect',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
description={ advancedUsersDescription }
|
|
||||||
isToggled={ !! isManualConnectionMode }
|
|
||||||
setToggled={ setManualConnectionMode }
|
|
||||||
>
|
|
||||||
<DataStoreControl
|
|
||||||
control={ TextControl }
|
|
||||||
ref={ refClientId }
|
|
||||||
label={ clientIdLabel }
|
|
||||||
value={ clientId }
|
|
||||||
onChange={ setClientId }
|
|
||||||
className={ classNames( {
|
|
||||||
'has-error': ! clientValid,
|
|
||||||
} ) }
|
|
||||||
/>
|
|
||||||
{ clientValid || (
|
|
||||||
<p className="client-id-error">
|
|
||||||
{ FORM_ERRORS.invalidClientId }
|
|
||||||
</p>
|
|
||||||
) }
|
|
||||||
<DataStoreControl
|
|
||||||
control={ TextControl }
|
|
||||||
ref={ refClientSecret }
|
|
||||||
label={ secretKeyLabel }
|
|
||||||
value={ clientSecret }
|
|
||||||
onChange={ setClientSecret }
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
className="small-button"
|
|
||||||
onClick={ handleManualConnect }
|
|
||||||
>
|
|
||||||
{ __(
|
|
||||||
'Connect Account',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
) }
|
|
||||||
</Button>
|
|
||||||
</SettingsToggleBlock>
|
|
||||||
</BusyStateWrapper>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,45 @@
|
||||||
import { Button } from '@wordpress/components';
|
import { Button } from '@wordpress/components';
|
||||||
|
import { useEffect } from '@wordpress/element';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { CommonHooks } from '../../../../data';
|
|
||||||
import { openSignup } from '../../../ReusableComponents/Icons';
|
import { openSignup } from '../../../ReusableComponents/Icons';
|
||||||
import {
|
import { useHandleOnboardingButton } from '../../../../hooks/useHandleConnections';
|
||||||
useProductionConnection,
|
|
||||||
useSandboxConnection,
|
|
||||||
} from '../../../../hooks/useHandleConnections';
|
|
||||||
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button component that outputs a placeholder button when no onboardingUrl is present yet - the
|
||||||
|
* placeholder button looks identical to the working button, but has no href, target, or
|
||||||
|
* custom connection attributes.
|
||||||
|
*
|
||||||
|
* @param {Object} props
|
||||||
|
* @param {string} props.className
|
||||||
|
* @param {string} props.variant
|
||||||
|
* @param {boolean} props.showIcon
|
||||||
|
* @param {?string} props.href
|
||||||
|
* @param {Element} props.children
|
||||||
|
*/
|
||||||
|
const ButtonOrPlaceholder = ( {
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
showIcon,
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
} ) => {
|
||||||
|
const buttonProps = {
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
icon: showIcon ? openSignup : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( href ) {
|
||||||
|
buttonProps.href = href;
|
||||||
|
buttonProps.target = 'PPFrame';
|
||||||
|
buttonProps[ 'data-paypal-button' ] = 'true';
|
||||||
|
buttonProps[ 'data-paypal-onboard-button' ] = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Button { ...buttonProps }>{ children }</Button>;
|
||||||
|
};
|
||||||
|
|
||||||
const ConnectionButton = ( {
|
const ConnectionButton = ( {
|
||||||
title,
|
title,
|
||||||
isSandbox = false,
|
isSandbox = false,
|
||||||
|
@ -17,31 +47,45 @@ const ConnectionButton = ( {
|
||||||
showIcon = true,
|
showIcon = true,
|
||||||
className = '',
|
className = '',
|
||||||
} ) => {
|
} ) => {
|
||||||
const { handleSandboxConnect } = useSandboxConnection();
|
const {
|
||||||
const { handleProductionConnect } = useProductionConnection();
|
onboardingUrl,
|
||||||
|
scriptLoaded,
|
||||||
|
setCompleteHandler,
|
||||||
|
removeCompleteHandler,
|
||||||
|
} = useHandleOnboardingButton( isSandbox );
|
||||||
const buttonClassName = classNames( 'ppcp-r-connection-button', className, {
|
const buttonClassName = classNames( 'ppcp-r-connection-button', className, {
|
||||||
'sandbox-mode': isSandbox,
|
'sandbox-mode': isSandbox,
|
||||||
'live-mode': ! isSandbox,
|
'live-mode': ! isSandbox,
|
||||||
} );
|
} );
|
||||||
|
const environment = isSandbox ? 'sandbox' : 'production';
|
||||||
|
|
||||||
const handleConnectClick = async () => {
|
useEffect( () => {
|
||||||
if ( isSandbox ) {
|
if ( scriptLoaded && onboardingUrl ) {
|
||||||
await handleSandboxConnect();
|
window.PAYPAL.apps.Signup.render();
|
||||||
} else {
|
setCompleteHandler( environment );
|
||||||
await handleProductionConnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeCompleteHandler();
|
||||||
};
|
};
|
||||||
|
}, [
|
||||||
|
scriptLoaded,
|
||||||
|
onboardingUrl,
|
||||||
|
environment,
|
||||||
|
setCompleteHandler,
|
||||||
|
removeCompleteHandler,
|
||||||
|
] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BusyStateWrapper>
|
<BusyStateWrapper isBusy={ ! onboardingUrl }>
|
||||||
<Button
|
<ButtonOrPlaceholder
|
||||||
className={ buttonClassName }
|
className={ buttonClassName }
|
||||||
variant={ variant }
|
variant={ variant }
|
||||||
icon={ showIcon ? openSignup : null }
|
showIcon={ showIcon }
|
||||||
onClick={ handleConnectClick }
|
href={ onboardingUrl }
|
||||||
>
|
>
|
||||||
<span className="button-title">{ title }</span>
|
<span className="button-title">{ title }</span>
|
||||||
</Button>
|
</ButtonOrPlaceholder>
|
||||||
</BusyStateWrapper>
|
</BusyStateWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from '@wordpress/element';
|
||||||
|
import { Button, TextControl } from '@wordpress/components';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
|
||||||
|
import DataStoreControl from '../../../ReusableComponents/DataStoreControl';
|
||||||
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
import {
|
||||||
|
useDirectAuthentication,
|
||||||
|
useSandboxConnection,
|
||||||
|
} from '../../../../hooks/useHandleConnections';
|
||||||
|
import { OnboardingHooks } from '../../../../data';
|
||||||
|
|
||||||
|
const FORM_ERRORS = {
|
||||||
|
noClientId: __(
|
||||||
|
'Please enter your Client ID',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
noClientSecret: __(
|
||||||
|
'Please enter your Secret Key',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
invalidClientId: __(
|
||||||
|
'Please enter a valid Client ID',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManualConnectionForm = () => {
|
||||||
|
const [ clientValid, setClientValid ] = useState( false );
|
||||||
|
const [ secretValid, setSecretValid ] = useState( false );
|
||||||
|
const { isSandboxMode } = useSandboxConnection();
|
||||||
|
const {
|
||||||
|
manualClientId,
|
||||||
|
setManualClientId,
|
||||||
|
manualClientSecret,
|
||||||
|
setManualClientSecret,
|
||||||
|
} = OnboardingHooks.useManualConnectionForm();
|
||||||
|
const {
|
||||||
|
handleDirectAuthentication,
|
||||||
|
isManualConnectionMode,
|
||||||
|
setManualConnectionMode,
|
||||||
|
} = useDirectAuthentication();
|
||||||
|
const refClientId = useRef( null );
|
||||||
|
const refClientSecret = useRef( null );
|
||||||
|
|
||||||
|
// Form data validation and sanitation.
|
||||||
|
const getManualConnectionDetails = useCallback( () => {
|
||||||
|
const checks = [
|
||||||
|
{
|
||||||
|
ref: refClientId,
|
||||||
|
valid: () => manualClientId,
|
||||||
|
errorMessage: FORM_ERRORS.noClientId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: refClientId,
|
||||||
|
valid: () => clientValid,
|
||||||
|
errorMessage: FORM_ERRORS.invalidClientId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: refClientSecret,
|
||||||
|
valid: () => manualClientSecret && secretValid,
|
||||||
|
errorMessage: FORM_ERRORS.noClientSecret,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for ( const { ref, valid, errorMessage } of checks ) {
|
||||||
|
if ( valid() ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref?.current?.focus();
|
||||||
|
throw new Error( errorMessage );
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId: manualClientId,
|
||||||
|
clientSecret: manualClientSecret,
|
||||||
|
isSandbox: isSandboxMode,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
manualClientId,
|
||||||
|
manualClientSecret,
|
||||||
|
isSandboxMode,
|
||||||
|
clientValid,
|
||||||
|
secretValid,
|
||||||
|
] );
|
||||||
|
|
||||||
|
// On-the-fly form validation.
|
||||||
|
useEffect( () => {
|
||||||
|
setClientValid(
|
||||||
|
! manualClientId || /^A[\w-]{79}$/.test( manualClientId )
|
||||||
|
);
|
||||||
|
setSecretValid( manualClientSecret && manualClientSecret.length > 0 );
|
||||||
|
}, [ manualClientId, manualClientSecret ] );
|
||||||
|
|
||||||
|
// Environment-specific field labels.
|
||||||
|
const clientIdLabel = useMemo(
|
||||||
|
() =>
|
||||||
|
isSandboxMode
|
||||||
|
? __( 'Sandbox Client ID', 'woocommerce-paypal-payments' )
|
||||||
|
: __( 'Live Client ID', 'woocommerce-paypal-payments' ),
|
||||||
|
[ isSandboxMode ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretKeyLabel = useMemo(
|
||||||
|
() =>
|
||||||
|
isSandboxMode
|
||||||
|
? __( 'Sandbox Secret Key', 'woocommerce-paypal-payments' )
|
||||||
|
: __( 'Live Secret Key', 'woocommerce-paypal-payments' ),
|
||||||
|
[ isSandboxMode ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Translations with placeholders.
|
||||||
|
const advancedUsersDescription = sprintf(
|
||||||
|
// translators: %s: Link to PayPal REST application guide
|
||||||
|
__(
|
||||||
|
'For advanced users: Connect a custom PayPal REST app for full control over your integration. For more information on creating a PayPal REST application, <a target="_blank" href="%s">click here</a>.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Button click handler.
|
||||||
|
const handleManualConnect = useCallback(
|
||||||
|
() => handleDirectAuthentication( getManualConnectionDetails ),
|
||||||
|
[ handleDirectAuthentication, getManualConnectionDetails ]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BusyStateWrapper
|
||||||
|
onBusy={ ( props ) => ( {
|
||||||
|
disabled: true,
|
||||||
|
label: props.label + ' ...',
|
||||||
|
} ) }
|
||||||
|
>
|
||||||
|
<SettingsToggleBlock
|
||||||
|
label={ __(
|
||||||
|
'Manually Connect',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
description={ advancedUsersDescription }
|
||||||
|
isToggled={ !! isManualConnectionMode }
|
||||||
|
setToggled={ setManualConnectionMode }
|
||||||
|
>
|
||||||
|
<DataStoreControl
|
||||||
|
control={ TextControl }
|
||||||
|
ref={ refClientId }
|
||||||
|
label={ clientIdLabel }
|
||||||
|
value={ manualClientId }
|
||||||
|
onChange={ setManualClientId }
|
||||||
|
className={ classNames( {
|
||||||
|
'has-error': ! clientValid,
|
||||||
|
} ) }
|
||||||
|
/>
|
||||||
|
{ clientValid || (
|
||||||
|
<p className="client-id-error">
|
||||||
|
{ FORM_ERRORS.invalidClientId }
|
||||||
|
</p>
|
||||||
|
) }
|
||||||
|
<DataStoreControl
|
||||||
|
control={ TextControl }
|
||||||
|
ref={ refClientSecret }
|
||||||
|
label={ secretKeyLabel }
|
||||||
|
value={ manualClientSecret }
|
||||||
|
onChange={ setManualClientSecret }
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="small-button"
|
||||||
|
onClick={ handleManualConnect }
|
||||||
|
>
|
||||||
|
{ __( 'Connect Account', 'woocommerce-paypal-payments' ) }
|
||||||
|
</Button>
|
||||||
|
</SettingsToggleBlock>
|
||||||
|
</BusyStateWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManualConnectionForm;
|
|
@ -1,7 +1,6 @@
|
||||||
import { Button, Icon } from '@wordpress/components';
|
import { Button, Icon } from '@wordpress/components';
|
||||||
import { chevronLeft } from '@wordpress/icons';
|
import { chevronLeft } from '@wordpress/icons';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { OnboardingHooks } from '../../../../data';
|
import { OnboardingHooks } from '../../../../data';
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||||
|
import SettingsToggleBlock from '../../../ReusableComponents/SettingsToggleBlock';
|
||||||
|
import { useSandboxConnection } from '../../../../hooks/useHandleConnections';
|
||||||
|
import ConnectionButton from './ConnectionButton';
|
||||||
|
|
||||||
|
const SandboxConnectionForm = () => {
|
||||||
|
const { isSandboxMode, setSandboxMode } = useSandboxConnection();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BusyStateWrapper>
|
||||||
|
<SettingsToggleBlock
|
||||||
|
label={ __(
|
||||||
|
'Enable Sandbox Mode',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
description={ __(
|
||||||
|
'Activate Sandbox mode to safely test PayPal with sample data. Once your store is ready to go live, you can easily switch to your production account.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
isToggled={ !! isSandboxMode }
|
||||||
|
setToggled={ setSandboxMode }
|
||||||
|
>
|
||||||
|
<ConnectionButton
|
||||||
|
title={ __(
|
||||||
|
'Connect Account',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
) }
|
||||||
|
showIcon={ false }
|
||||||
|
variant="secondary"
|
||||||
|
className="small-button"
|
||||||
|
isSandbox={
|
||||||
|
true /* This button always connects to sandbox */
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingsToggleBlock>
|
||||||
|
</BusyStateWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SandboxConnectionForm;
|
|
@ -17,7 +17,7 @@ const TabOverview = () => {
|
||||||
const [ todosData, setTodosData ] = useState( todosDataDefault );
|
const [ todosData, setTodosData ] = useState( todosDataDefault );
|
||||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||||
|
|
||||||
const { merchant } = useMerchantInfo();
|
const { merchantFeatures } = useMerchantInfo();
|
||||||
const { refreshFeatureStatuses, setActiveModal } =
|
const { refreshFeatureStatuses, setActiveModal } =
|
||||||
useDispatch( STORE_NAME );
|
useDispatch( STORE_NAME );
|
||||||
|
|
||||||
|
@ -30,13 +30,13 @@ const TabOverview = () => {
|
||||||
// Map merchant features status to our config
|
// Map merchant features status to our config
|
||||||
const features = useMemo( () => {
|
const features = useMemo( () => {
|
||||||
return featuresData.map( ( feature ) => {
|
return featuresData.map( ( feature ) => {
|
||||||
const merchantFeature = merchant?.features?.[ feature.id ];
|
const merchantFeature = merchantFeatures?.[ feature.id ];
|
||||||
return {
|
return {
|
||||||
...feature,
|
...feature,
|
||||||
enabled: merchantFeature?.enabled ?? false,
|
enabled: merchantFeature?.enabled ?? false,
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
}, [ featuresData, merchant?.features ] );
|
}, [ featuresData, merchantFeatures ] );
|
||||||
|
|
||||||
const refreshHandler = async () => {
|
const refreshHandler = async () => {
|
||||||
setIsRefreshing( true );
|
setIsRefreshing( true );
|
||||||
|
|
|
@ -19,11 +19,11 @@ export default {
|
||||||
|
|
||||||
// Controls - always start with "DO_".
|
// Controls - always start with "DO_".
|
||||||
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
|
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
|
||||||
DO_MANUAL_CONNECTION: 'COMMON:DO_MANUAL_CONNECTION',
|
DO_DIRECT_API_AUTHENTICATION: 'COMMON:DO_DIRECT_API_AUTHENTICATION',
|
||||||
DO_SANDBOX_LOGIN: 'COMMON:DO_SANDBOX_LOGIN',
|
DO_OAUTH_AUTHENTICATION: 'COMMON:DO_OAUTH_AUTHENTICATION',
|
||||||
DO_PRODUCTION_LOGIN: 'COMMON:DO_PRODUCTION_LOGIN',
|
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
|
||||||
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
|
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
|
||||||
DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES',
|
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
|
||||||
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
|
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
|
||||||
DO_START_WEBHOOK_SIMULATION: 'COMMON:DO_START_WEBHOOK_SIMULATION',
|
DO_START_WEBHOOK_SIMULATION: 'COMMON:DO_START_WEBHOOK_SIMULATION',
|
||||||
DO_CHECK_WEBHOOK_SIMULATION_STATE:
|
DO_CHECK_WEBHOOK_SIMULATION_STATE:
|
||||||
|
|
|
@ -123,28 +123,6 @@ export const setManualConnectionMode = ( useManualConnection ) => ( {
|
||||||
payload: { useManualConnection },
|
payload: { useManualConnection },
|
||||||
} );
|
} );
|
||||||
|
|
||||||
/**
|
|
||||||
* Persistent. Changes the "client ID" value.
|
|
||||||
*
|
|
||||||
* @param {string} clientId
|
|
||||||
* @return {Action} The action.
|
|
||||||
*/
|
|
||||||
export const setClientId = ( clientId ) => ( {
|
|
||||||
type: ACTION_TYPES.SET_PERSISTENT,
|
|
||||||
payload: { clientId },
|
|
||||||
} );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persistent. Changes the "client secret" value.
|
|
||||||
*
|
|
||||||
* @param {string} clientSecret
|
|
||||||
* @return {Action} The action.
|
|
||||||
*/
|
|
||||||
export const setClientSecret = ( clientSecret ) => ( {
|
|
||||||
type: ACTION_TYPES.SET_PERSISTENT,
|
|
||||||
payload: { clientSecret },
|
|
||||||
} );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Side effect. Saves the persistent details to the WP database.
|
* Side effect. Saves the persistent details to the WP database.
|
||||||
*
|
*
|
||||||
|
@ -161,8 +139,12 @@ export const persist = function* () {
|
||||||
*
|
*
|
||||||
* @return {Action} The action.
|
* @return {Action} The action.
|
||||||
*/
|
*/
|
||||||
export const connectToSandbox = function* () {
|
export const sandboxOnboardingUrl = function* () {
|
||||||
return yield { type: ACTION_TYPES.DO_SANDBOX_LOGIN };
|
return yield {
|
||||||
|
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
|
||||||
|
useSandbox: true,
|
||||||
|
products: [ 'EXPRESS_CHECKOUT' ],
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,23 +153,61 @@ export const connectToSandbox = function* () {
|
||||||
* @param {string[]} products Which products/features to display in the ISU popup.
|
* @param {string[]} products Which products/features to display in the ISU popup.
|
||||||
* @return {Action} The action.
|
* @return {Action} The action.
|
||||||
*/
|
*/
|
||||||
export const connectToProduction = function* ( products = [] ) {
|
export const productionOnboardingUrl = function* ( products = [] ) {
|
||||||
return yield { type: ACTION_TYPES.DO_PRODUCTION_LOGIN, products };
|
return yield {
|
||||||
|
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
|
||||||
|
useSandbox: false,
|
||||||
|
products,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Side effect. Initiates a manual connection attempt using the provided client ID and secret.
|
* Side effect. Initiates a direct connection attempt using the provided client ID and secret.
|
||||||
*
|
*
|
||||||
|
* This action accepts parameters instead of fetching data from the Redux state because the
|
||||||
|
* values (ID and secret) are not managed by a central redux store, but might come from private
|
||||||
|
* component state.
|
||||||
|
*
|
||||||
|
* @param {string} clientId - AP client ID (always 80-characters, starting with "A").
|
||||||
|
* @param {string} clientSecret - API client secret.
|
||||||
|
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||||
* @return {Action} The action.
|
* @return {Action} The action.
|
||||||
*/
|
*/
|
||||||
export const connectViaIdAndSecret = function* () {
|
export const authenticateWithCredentials = function* (
|
||||||
const { clientId, clientSecret, useSandbox } =
|
|
||||||
yield select( STORE_NAME ).persistentData();
|
|
||||||
|
|
||||||
return yield {
|
|
||||||
type: ACTION_TYPES.DO_MANUAL_CONNECTION,
|
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
|
useSandbox
|
||||||
|
) {
|
||||||
|
return yield {
|
||||||
|
type: ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION,
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
useSandbox,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect. Completes the ISU login by authenticating the user via the one time sharedId and
|
||||||
|
* authCode provided by PayPal.
|
||||||
|
*
|
||||||
|
* This action accepts parameters instead of fetching data from the Redux state because all
|
||||||
|
* parameters are dynamically generated during the authentication process, and not managed by our
|
||||||
|
* Redux store.
|
||||||
|
*
|
||||||
|
* @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the API client ID.
|
||||||
|
* @param {string} authCode - OAuth authorization code provided during onboarding.
|
||||||
|
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const authenticateWithOAuth = function* (
|
||||||
|
sharedId,
|
||||||
|
authCode,
|
||||||
|
useSandbox
|
||||||
|
) {
|
||||||
|
return yield {
|
||||||
|
type: ACTION_TYPES.DO_OAUTH_AUTHENTICATION,
|
||||||
|
sharedId,
|
||||||
|
authCode,
|
||||||
useSandbox,
|
useSandbox,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,14 +35,25 @@ export const REST_HYDRATE_MERCHANT_PATH = '/wc/v3/wc_paypal/common/merchant';
|
||||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common';
|
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST path to perform the manual connection check, using client ID and secret,
|
* REST path to perform the manual connection authentication, using client ID and secret.
|
||||||
*
|
*
|
||||||
* Used by: Controls
|
* Used by: Controls
|
||||||
* See: ConnectManualRestEndpoint.php
|
* See: AuthenticateRestEndpoint.php
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual';
|
export const REST_DIRECT_AUTHENTICATION_PATH =
|
||||||
|
'/wc/v3/wc_paypal/authenticate/direct';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST path to perform the ISU authentication check, using shared ID and authCode.
|
||||||
|
*
|
||||||
|
* Used by: Controls
|
||||||
|
* See: AuthenticateRestEndpoint.php
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const REST_ISU_AUTHENTICATION_PATH = '/wc/v3/wc_paypal/authenticate/isu';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST path to generate an ISU URL for the PayPal-login.
|
* REST path to generate an ISU URL for the PayPal-login.
|
||||||
|
|
|
@ -10,11 +10,12 @@
|
||||||
import apiFetch from '@wordpress/api-fetch';
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
REST_PERSIST_PATH,
|
||||||
|
REST_DIRECT_AUTHENTICATION_PATH,
|
||||||
REST_CONNECTION_URL_PATH,
|
REST_CONNECTION_URL_PATH,
|
||||||
REST_HYDRATE_MERCHANT_PATH,
|
REST_HYDRATE_MERCHANT_PATH,
|
||||||
REST_MANUAL_CONNECTION_PATH,
|
|
||||||
REST_PERSIST_PATH,
|
|
||||||
REST_REFRESH_FEATURES_PATH,
|
REST_REFRESH_FEATURES_PATH,
|
||||||
|
REST_ISU_AUTHENTICATION_PATH,
|
||||||
REST_WEBHOOKS,
|
REST_WEBHOOKS,
|
||||||
REST_WEBHOOKS_SIMULATE,
|
REST_WEBHOOKS_SIMULATE,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
@ -33,33 +34,15 @@ export const controls = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async [ ACTION_TYPES.DO_SANDBOX_LOGIN ]() {
|
async [ ACTION_TYPES.DO_GENERATE_ONBOARDING_URL ]( {
|
||||||
try {
|
|
||||||
return apiFetch( {
|
|
||||||
path: REST_CONNECTION_URL_PATH,
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
environment: 'sandbox',
|
|
||||||
products: [ 'EXPRESS_CHECKOUT' ], // Sandbox always uses EXPRESS_CHECKOUT.
|
|
||||||
},
|
|
||||||
} );
|
|
||||||
} catch ( e ) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: e,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async [ ACTION_TYPES.DO_PRODUCTION_LOGIN ]( { products } ) {
|
|
||||||
try {
|
|
||||||
return apiFetch( {
|
|
||||||
path: REST_CONNECTION_URL_PATH,
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
environment: 'production',
|
|
||||||
products,
|
products,
|
||||||
},
|
useSandbox,
|
||||||
|
} ) {
|
||||||
|
try {
|
||||||
|
return apiFetch( {
|
||||||
|
path: REST_CONNECTION_URL_PATH,
|
||||||
|
method: 'POST',
|
||||||
|
data: { useSandbox, products },
|
||||||
} );
|
} );
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
return {
|
return {
|
||||||
|
@ -69,14 +52,14 @@ export const controls = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async [ ACTION_TYPES.DO_MANUAL_CONNECTION ]( {
|
async [ ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION ]( {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
useSandbox,
|
useSandbox,
|
||||||
} ) {
|
} ) {
|
||||||
try {
|
try {
|
||||||
return await apiFetch( {
|
return await apiFetch( {
|
||||||
path: REST_MANUAL_CONNECTION_PATH,
|
path: REST_DIRECT_AUTHENTICATION_PATH,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
clientId,
|
clientId,
|
||||||
|
@ -92,6 +75,29 @@ export const controls = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async [ ACTION_TYPES.DO_OAUTH_AUTHENTICATION ]( {
|
||||||
|
sharedId,
|
||||||
|
authCode,
|
||||||
|
useSandbox,
|
||||||
|
} ) {
|
||||||
|
try {
|
||||||
|
return await apiFetch( {
|
||||||
|
path: REST_ISU_AUTHENTICATION_PATH,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
sharedId,
|
||||||
|
authCode,
|
||||||
|
useSandbox,
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
} catch ( e ) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: e,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
|
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
|
||||||
try {
|
try {
|
||||||
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
|
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
|
||||||
|
|
|
@ -28,11 +28,10 @@ const useHooks = () => {
|
||||||
persist,
|
persist,
|
||||||
setSandboxMode,
|
setSandboxMode,
|
||||||
setManualConnectionMode,
|
setManualConnectionMode,
|
||||||
setClientId,
|
sandboxOnboardingUrl,
|
||||||
setClientSecret,
|
productionOnboardingUrl,
|
||||||
connectToSandbox,
|
authenticateWithCredentials,
|
||||||
connectToProduction,
|
authenticateWithOAuth,
|
||||||
connectViaIdAndSecret,
|
|
||||||
setActiveModal,
|
setActiveModal,
|
||||||
startWebhookSimulation,
|
startWebhookSimulation,
|
||||||
checkWebhookSimulationState,
|
checkWebhookSimulationState,
|
||||||
|
@ -43,19 +42,26 @@ const useHooks = () => {
|
||||||
const activeModal = useTransient( 'activeModal' );
|
const activeModal = useTransient( 'activeModal' );
|
||||||
|
|
||||||
// Persistent accessors.
|
// Persistent accessors.
|
||||||
const clientId = usePersistent( 'clientId' );
|
|
||||||
const clientSecret = usePersistent( 'clientSecret' );
|
|
||||||
const isSandboxMode = usePersistent( 'useSandbox' );
|
const isSandboxMode = usePersistent( 'useSandbox' );
|
||||||
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
||||||
const webhooks = usePersistent( 'webhooks' );
|
|
||||||
const merchant = useSelect(
|
const merchant = useSelect(
|
||||||
( select ) => select( STORE_NAME ).merchant(),
|
( select ) => select( STORE_NAME ).merchant(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Read-only properties.
|
||||||
const wooSettings = useSelect(
|
const wooSettings = useSelect(
|
||||||
( select ) => select( STORE_NAME ).wooSettings(),
|
( select ) => select( STORE_NAME ).wooSettings(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const features = useSelect(
|
||||||
|
( select ) => select( STORE_NAME ).features(),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const webhooks = useSelect(
|
||||||
|
( select ) => select( STORE_NAME ).webhooks(),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const savePersistent = async ( setter, value ) => {
|
const savePersistent = async ( setter, value ) => {
|
||||||
setter( value );
|
setter( value );
|
||||||
|
@ -74,19 +80,13 @@ const useHooks = () => {
|
||||||
setManualConnectionMode: ( state ) => {
|
setManualConnectionMode: ( state ) => {
|
||||||
return savePersistent( setManualConnectionMode, state );
|
return savePersistent( setManualConnectionMode, state );
|
||||||
},
|
},
|
||||||
clientId,
|
sandboxOnboardingUrl,
|
||||||
setClientId: ( value ) => {
|
productionOnboardingUrl,
|
||||||
return savePersistent( setClientId, value );
|
authenticateWithCredentials,
|
||||||
},
|
authenticateWithOAuth,
|
||||||
clientSecret,
|
|
||||||
setClientSecret: ( value ) => {
|
|
||||||
return savePersistent( setClientSecret, value );
|
|
||||||
},
|
|
||||||
connectToSandbox,
|
|
||||||
connectToProduction,
|
|
||||||
connectViaIdAndSecret,
|
|
||||||
merchant,
|
merchant,
|
||||||
wooSettings,
|
wooSettings,
|
||||||
|
features,
|
||||||
webhooks,
|
webhooks,
|
||||||
startWebhookSimulation,
|
startWebhookSimulation,
|
||||||
checkWebhookSimulationState,
|
checkWebhookSimulationState,
|
||||||
|
@ -94,36 +94,30 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSandbox = () => {
|
export const useSandbox = () => {
|
||||||
const { isSandboxMode, setSandboxMode, connectToSandbox } = useHooks();
|
const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks();
|
||||||
|
|
||||||
return { isSandboxMode, setSandboxMode, connectToSandbox };
|
return { isSandboxMode, setSandboxMode, sandboxOnboardingUrl };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProduction = () => {
|
export const useProduction = () => {
|
||||||
const { connectToProduction } = useHooks();
|
const { productionOnboardingUrl } = useHooks();
|
||||||
|
|
||||||
return { connectToProduction };
|
return { productionOnboardingUrl };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useManualConnection = () => {
|
export const useAuthentication = () => {
|
||||||
const {
|
const {
|
||||||
isManualConnectionMode,
|
isManualConnectionMode,
|
||||||
setManualConnectionMode,
|
setManualConnectionMode,
|
||||||
clientId,
|
authenticateWithCredentials,
|
||||||
setClientId,
|
authenticateWithOAuth,
|
||||||
clientSecret,
|
|
||||||
setClientSecret,
|
|
||||||
connectViaIdAndSecret,
|
|
||||||
} = useHooks();
|
} = useHooks();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isManualConnectionMode,
|
isManualConnectionMode,
|
||||||
setManualConnectionMode,
|
setManualConnectionMode,
|
||||||
clientId,
|
authenticateWithCredentials,
|
||||||
setClientId,
|
authenticateWithOAuth,
|
||||||
clientSecret,
|
|
||||||
setClientSecret,
|
|
||||||
connectViaIdAndSecret,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,7 +144,7 @@ export const useWebhooks = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const useMerchantInfo = () => {
|
export const useMerchantInfo = () => {
|
||||||
const { merchant } = useHooks();
|
const { merchant, features } = useHooks();
|
||||||
const { refreshMerchantData } = useDispatch( STORE_NAME );
|
const { refreshMerchantData } = useDispatch( STORE_NAME );
|
||||||
|
|
||||||
const verifyLoginStatus = useCallback( async () => {
|
const verifyLoginStatus = useCallback( async () => {
|
||||||
|
@ -166,6 +160,7 @@ export const useMerchantInfo = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
merchant, // Merchant details
|
merchant, // Merchant details
|
||||||
|
features, // Eligible merchant features
|
||||||
verifyLoginStatus, // Callback
|
verifyLoginStatus, // Callback
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,20 +23,36 @@ const defaultTransient = Object.freeze( {
|
||||||
isSandbox: false,
|
isSandbox: false,
|
||||||
id: '',
|
id: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
clientId: '',
|
||||||
|
clientSecret: '',
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
wooSettings: Object.freeze( {
|
wooSettings: Object.freeze( {
|
||||||
storeCountry: '',
|
storeCountry: '',
|
||||||
storeCurrency: '',
|
storeCurrency: '',
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
|
features: Object.freeze( {
|
||||||
|
save_paypal_and_venmo: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
advanced_credit_and_debit_cards: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
apple_pay: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
google_pay: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} ),
|
||||||
|
|
||||||
|
webhooks: Object.freeze( [] ),
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const defaultPersistent = Object.freeze( {
|
const defaultPersistent = Object.freeze( {
|
||||||
useSandbox: false,
|
useSandbox: false,
|
||||||
useManualConnection: false,
|
useManualConnection: false,
|
||||||
clientId: '',
|
|
||||||
clientSecret: '',
|
|
||||||
webhooks: [],
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Reducer logic.
|
// Reducer logic.
|
||||||
|
@ -84,13 +100,15 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
|
||||||
[ ACTION_TYPES.DO_REFRESH_MERCHANT ]: ( state ) => ( {
|
[ ACTION_TYPES.DO_REFRESH_MERCHANT ]: ( state ) => ( {
|
||||||
...state,
|
...state,
|
||||||
merchant: Object.freeze( { ...defaultTransient.merchant } ),
|
merchant: Object.freeze( { ...defaultTransient.merchant } ),
|
||||||
|
features: Object.freeze( { ...defaultTransient.features } ),
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
|
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
|
||||||
const newState = setPersistent( state, payload.data );
|
const newState = setPersistent( state, payload.data );
|
||||||
|
|
||||||
// Populate read-only properties.
|
// Populate read-only properties.
|
||||||
[ 'wooSettings', 'merchant' ].forEach( ( key ) => {
|
[ 'wooSettings', 'merchant', 'features', 'webhooks' ].forEach(
|
||||||
|
( key ) => {
|
||||||
if ( ! payload[ key ] ) {
|
if ( ! payload[ key ] ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +117,8 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
|
||||||
...newState[ key ],
|
...newState[ key ],
|
||||||
...payload[ key ],
|
...payload[ key ],
|
||||||
} );
|
} );
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,8 +16,14 @@ export const persistentData = ( state ) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transientData = ( state ) => {
|
export const transientData = ( state ) => {
|
||||||
const { data, merchant, wooSettings, ...transientState } =
|
const {
|
||||||
getState( state );
|
data,
|
||||||
|
merchant,
|
||||||
|
features,
|
||||||
|
wooSettings,
|
||||||
|
webhooks,
|
||||||
|
...transientState
|
||||||
|
} = getState( state );
|
||||||
return transientState || EMPTY_OBJ;
|
return transientState || EMPTY_OBJ;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,6 +36,10 @@ export const merchant = ( state ) => {
|
||||||
return getState( state ).merchant || EMPTY_OBJ;
|
return getState( state ).merchant || EMPTY_OBJ;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const features = ( state ) => {
|
||||||
|
return getState( state ).features || EMPTY_OBJ;
|
||||||
|
};
|
||||||
|
|
||||||
export const wooSettings = ( state ) => {
|
export const wooSettings = ( state ) => {
|
||||||
return getState( state ).wooSettings || EMPTY_OBJ;
|
return getState( state ).wooSettings || EMPTY_OBJ;
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,28 @@ export const setIsReady = ( isReady ) => ( {
|
||||||
payload: { isReady },
|
payload: { isReady },
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient. Sets the "manualClientId" value.
|
||||||
|
*
|
||||||
|
* @param {string} manualClientId
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const setManualClientId = ( manualClientId ) => ( {
|
||||||
|
type: ACTION_TYPES.SET_TRANSIENT,
|
||||||
|
payload: { manualClientId },
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient. Sets the "manualClientSecret" value.
|
||||||
|
*
|
||||||
|
* @param {string} manualClientSecret
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const setManualClientSecret = ( manualClientSecret ) => ( {
|
||||||
|
type: ACTION_TYPES.SET_TRANSIENT,
|
||||||
|
payload: { manualClientSecret },
|
||||||
|
} );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
|
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
|
||||||
*
|
*
|
||||||
|
|
|
@ -30,6 +30,8 @@ const useHooks = () => {
|
||||||
setStep,
|
setStep,
|
||||||
setCompleted,
|
setCompleted,
|
||||||
setIsCasualSeller,
|
setIsCasualSeller,
|
||||||
|
setManualClientId,
|
||||||
|
setManualClientSecret,
|
||||||
setAreOptionalPaymentMethodsEnabled,
|
setAreOptionalPaymentMethodsEnabled,
|
||||||
setProducts,
|
setProducts,
|
||||||
} = useDispatch( STORE_NAME );
|
} = useDispatch( STORE_NAME );
|
||||||
|
@ -43,6 +45,8 @@ const useHooks = () => {
|
||||||
|
|
||||||
// Transient accessors.
|
// Transient accessors.
|
||||||
const isReady = useTransient( 'isReady' );
|
const isReady = useTransient( 'isReady' );
|
||||||
|
const manualClientId = useTransient( 'manualClientId' );
|
||||||
|
const manualClientSecret = useTransient( 'manualClientSecret' );
|
||||||
|
|
||||||
// Persistent accessors.
|
// Persistent accessors.
|
||||||
const step = usePersistent( 'step' );
|
const step = usePersistent( 'step' );
|
||||||
|
@ -73,6 +77,14 @@ const useHooks = () => {
|
||||||
setIsCasualSeller: ( value ) => {
|
setIsCasualSeller: ( value ) => {
|
||||||
return savePersistent( setIsCasualSeller, value );
|
return savePersistent( setIsCasualSeller, value );
|
||||||
},
|
},
|
||||||
|
manualClientId,
|
||||||
|
setManualClientId: ( value ) => {
|
||||||
|
return savePersistent( setManualClientId, value );
|
||||||
|
},
|
||||||
|
manualClientSecret,
|
||||||
|
setManualClientSecret: ( value ) => {
|
||||||
|
return savePersistent( setManualClientSecret, value );
|
||||||
|
},
|
||||||
areOptionalPaymentMethodsEnabled,
|
areOptionalPaymentMethodsEnabled,
|
||||||
setAreOptionalPaymentMethodsEnabled: ( value ) => {
|
setAreOptionalPaymentMethodsEnabled: ( value ) => {
|
||||||
return savePersistent( setAreOptionalPaymentMethodsEnabled, value );
|
return savePersistent( setAreOptionalPaymentMethodsEnabled, value );
|
||||||
|
@ -88,6 +100,22 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useManualConnectionForm = () => {
|
||||||
|
const {
|
||||||
|
manualClientId,
|
||||||
|
setManualClientId,
|
||||||
|
manualClientSecret,
|
||||||
|
setManualClientSecret,
|
||||||
|
} = useHooks();
|
||||||
|
|
||||||
|
return {
|
||||||
|
manualClientId,
|
||||||
|
setManualClientId,
|
||||||
|
manualClientSecret,
|
||||||
|
setManualClientSecret,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useBusiness = () => {
|
export const useBusiness = () => {
|
||||||
const { isCasualSeller, setIsCasualSeller } = useHooks();
|
const { isCasualSeller, setIsCasualSeller } = useHooks();
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
const defaultTransient = Object.freeze( {
|
const defaultTransient = Object.freeze( {
|
||||||
isReady: false,
|
isReady: false,
|
||||||
|
manualClientId: '',
|
||||||
|
manualClientSecret: '',
|
||||||
|
|
||||||
// Read only values, provided by the server.
|
// Read only values, provided by the server.
|
||||||
flags: Object.freeze( {
|
flags: Object.freeze( {
|
||||||
|
|
|
@ -52,9 +52,6 @@ export const determineProducts = ( state ) => {
|
||||||
* The store uses the Express-checkout product.
|
* The store uses the Express-checkout product.
|
||||||
*/
|
*/
|
||||||
derivedProducts.push( 'EXPRESS_CHECKOUT' );
|
derivedProducts.push( 'EXPRESS_CHECKOUT' );
|
||||||
|
|
||||||
// TODO: Add the "BCDC" product/feature
|
|
||||||
// Requirement: "EXPRESS_CHECKOUT with BCDC"
|
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* Branch 3: Merchant is business, and can use CC payments.
|
* Branch 3: Merchant is business, and can use CC payments.
|
||||||
|
@ -64,8 +61,7 @@ export const determineProducts = ( state ) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( canUseVaulting ) {
|
if ( canUseVaulting ) {
|
||||||
// TODO: Add the "Vaulting" product/feature
|
derivedProducts.push( 'ADVANCED_VAULTING' );
|
||||||
// Requirement: "... with Vault"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return derivedProducts;
|
return derivedProducts;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
|
||||||
import { store as noticesStore } from '@wordpress/notices';
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
|
||||||
import { CommonHooks, OnboardingHooks } from '../data';
|
import { CommonHooks, OnboardingHooks } from '../data';
|
||||||
import { openPopup } from '../utils/window';
|
|
||||||
|
const PAYPAL_PARTNER_SDK_URL =
|
||||||
|
'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
|
||||||
|
|
||||||
const MESSAGES = {
|
const MESSAGES = {
|
||||||
CONNECTED: __( 'Connected to PayPal', 'woocommerce-paypal-payments' ),
|
CONNECTED: __( 'Connected to PayPal', 'woocommerce-paypal-payments' ),
|
||||||
|
@ -32,36 +35,138 @@ const MESSAGES = {
|
||||||
const ACTIVITIES = {
|
const ACTIVITIES = {
|
||||||
CONNECT_SANDBOX: 'ISU_LOGIN_SANDBOX',
|
CONNECT_SANDBOX: 'ISU_LOGIN_SANDBOX',
|
||||||
CONNECT_PRODUCTION: 'ISU_LOGIN_PRODUCTION',
|
CONNECT_PRODUCTION: 'ISU_LOGIN_PRODUCTION',
|
||||||
|
CONNECT_ISU: 'ISU_LOGIN',
|
||||||
CONNECT_MANUAL: 'MANUAL_LOGIN',
|
CONNECT_MANUAL: 'MANUAL_LOGIN',
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePopupWithCompletion = ( url, onError ) => {
|
export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
return new Promise( ( resolve ) => {
|
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
|
||||||
const popup = openPopup( url );
|
const { productionOnboardingUrl } = CommonHooks.useProduction();
|
||||||
|
const products = OnboardingHooks.useDetermineProducts();
|
||||||
|
const { withActivity } = CommonHooks.useBusyState();
|
||||||
|
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
||||||
|
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
|
||||||
|
const [ scriptLoaded, setScriptLoaded ] = useState( false );
|
||||||
|
const timerRef = useRef( null );
|
||||||
|
|
||||||
if ( ! popup ) {
|
useEffect( () => {
|
||||||
onError( MESSAGES.POPUP_BLOCKED );
|
const fetchOnboardingUrl = async () => {
|
||||||
resolve( false );
|
let res;
|
||||||
|
if ( isSandbox ) {
|
||||||
|
res = await sandboxOnboardingUrl();
|
||||||
|
} else {
|
||||||
|
res = await productionOnboardingUrl( products );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( res.success && res.data ) {
|
||||||
|
setOnboardingUrl( res.data );
|
||||||
|
} else {
|
||||||
|
console.error( 'Failed to fetch onboarding URL' );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchOnboardingUrl();
|
||||||
|
}, [ isSandbox, productionOnboardingUrl, products, sandboxOnboardingUrl ] );
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
/**
|
||||||
|
* The partner.js script initializes all onboarding buttons in the onload event.
|
||||||
|
* When no buttons are present, a JS error is displayed; i.e. we should load this script
|
||||||
|
* only when the button is ready (with a valid href and data-attributes).
|
||||||
|
*/
|
||||||
|
if ( ! onboardingUrl ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check popup state every 500ms
|
const script = document.createElement( 'script' );
|
||||||
const checkPopup = setInterval( () => {
|
script.id = 'partner-js';
|
||||||
if ( popup.closed ) {
|
script.src = PAYPAL_PARTNER_SDK_URL;
|
||||||
clearInterval( checkPopup );
|
script.onload = () => {
|
||||||
resolve( true );
|
setScriptLoaded( true );
|
||||||
}
|
};
|
||||||
}, 500 );
|
document.body.appendChild( script );
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval( checkPopup );
|
/**
|
||||||
|
* When the component is unmounted, remove the partner.js script, as well as the
|
||||||
|
* dynamic scripts it loaded (signup-js and rampConfig-js)
|
||||||
|
*
|
||||||
|
* This is important, as the onboarding button is only initialized during the onload
|
||||||
|
* event of those scripts; i.e. we need to load the scripts again, when the button is
|
||||||
|
* rendered again.
|
||||||
|
*/
|
||||||
|
const onboardingScripts = [
|
||||||
|
'partner-js',
|
||||||
|
'signup-js',
|
||||||
|
'rampConfig-js',
|
||||||
|
];
|
||||||
|
|
||||||
if ( popup && ! popup.closed ) {
|
onboardingScripts.forEach( ( id ) => {
|
||||||
popup.close();
|
const el = document.querySelector( `script[id="${ id }"]` );
|
||||||
|
|
||||||
|
if ( el?.parentNode ) {
|
||||||
|
el.parentNode.removeChild( el );
|
||||||
}
|
}
|
||||||
};
|
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
}, [ onboardingUrl ] );
|
||||||
|
|
||||||
|
const setCompleteHandler = useCallback(
|
||||||
|
( environment ) => {
|
||||||
|
const onComplete = async ( authCode, sharedId ) => {
|
||||||
|
/**
|
||||||
|
* Until now, the full page is blocked by PayPal's semi-transparent, black overlay.
|
||||||
|
* But at this point, the overlay is removed, while we process the sharedId and
|
||||||
|
* authCode via a REST call.
|
||||||
|
*
|
||||||
|
* Note: The REST response is irrelevant, since PayPal will most likely refresh this
|
||||||
|
* frame before the REST endpoint returns a value. Using "withActivity" is more of a
|
||||||
|
* visual cue to the user that something is still processing in the background.
|
||||||
|
*/
|
||||||
|
await withActivity(
|
||||||
|
ACTIVITIES.CONNECT_ISU,
|
||||||
|
'Validating the connection details',
|
||||||
|
async () => {
|
||||||
|
await authenticateWithOAuth(
|
||||||
|
sharedId,
|
||||||
|
authCode,
|
||||||
|
'sandbox' === environment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addHandler = () => {
|
||||||
|
const MiniBrowser = window.PAYPAL?.apps?.Signup?.MiniBrowser;
|
||||||
|
if ( ! MiniBrowser || MiniBrowser.onOnboardComplete ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MiniBrowser.onOnboardComplete = onComplete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure the onComplete handler is not removed by a PayPal init script.
|
||||||
|
timerRef.current = setInterval( addHandler, 250 );
|
||||||
|
},
|
||||||
|
[ authenticateWithOAuth, withActivity ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeCompleteHandler = useCallback( () => {
|
||||||
|
if ( timerRef.current ) {
|
||||||
|
clearInterval( timerRef.current );
|
||||||
|
timerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete window.PAYPAL?.apps?.Signup?.MiniBrowser?.onOnboardComplete;
|
||||||
|
}, [] );
|
||||||
|
|
||||||
|
return {
|
||||||
|
onboardingUrl,
|
||||||
|
scriptLoaded,
|
||||||
|
setCompleteHandler,
|
||||||
|
removeCompleteHandler,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const useConnectionBase = () => {
|
const useConnectionBase = () => {
|
||||||
const { setCompleted } = OnboardingHooks.useSteps();
|
const { setCompleted } = OnboardingHooks.useSteps();
|
||||||
|
@ -92,104 +197,55 @@ const useConnectionBase = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const useConnectionAttempt = ( connectFn, errorMessage ) => {
|
|
||||||
const { handleFailed, createErrorNotice, handleCompleted } =
|
|
||||||
useConnectionBase();
|
|
||||||
|
|
||||||
return async ( ...args ) => {
|
|
||||||
const res = await connectFn( ...args );
|
|
||||||
|
|
||||||
if ( ! res.success || ! res.data ) {
|
|
||||||
handleFailed( res, errorMessage );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const popupClosed = await handlePopupWithCompletion(
|
|
||||||
res.data,
|
|
||||||
createErrorNotice
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( popupClosed ) {
|
|
||||||
await handleCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
return popupClosed;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSandboxConnection = () => {
|
export const useSandboxConnection = () => {
|
||||||
const { connectToSandbox, isSandboxMode, setSandboxMode } =
|
const { isSandboxMode, setSandboxMode } = CommonHooks.useSandbox();
|
||||||
CommonHooks.useSandbox();
|
|
||||||
const { withActivity } = CommonHooks.useBusyState();
|
|
||||||
const connectionAttempt = useConnectionAttempt(
|
|
||||||
connectToSandbox,
|
|
||||||
MESSAGES.SANDBOX_ERROR
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSandboxConnect = async () => {
|
|
||||||
return withActivity(
|
|
||||||
ACTIVITIES.CONNECT_SANDBOX,
|
|
||||||
'Connecting to sandbox account',
|
|
||||||
connectionAttempt
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleSandboxConnect,
|
|
||||||
isSandboxMode,
|
isSandboxMode,
|
||||||
setSandboxMode,
|
setSandboxMode,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProductionConnection = () => {
|
export const useDirectAuthentication = () => {
|
||||||
const { connectToProduction } = CommonHooks.useProduction();
|
|
||||||
const { withActivity } = CommonHooks.useBusyState();
|
|
||||||
const products = OnboardingHooks.useDetermineProducts();
|
|
||||||
const connectionAttempt = useConnectionAttempt(
|
|
||||||
() => connectToProduction( products ),
|
|
||||||
MESSAGES.PRODUCTION_ERROR
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleProductionConnect = async () => {
|
|
||||||
return withActivity(
|
|
||||||
ACTIVITIES.CONNECT_PRODUCTION,
|
|
||||||
'Connecting to production account',
|
|
||||||
connectionAttempt
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return { handleProductionConnect };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useManualConnection = () => {
|
|
||||||
const { handleFailed, handleCompleted, createErrorNotice } =
|
const { handleFailed, handleCompleted, createErrorNotice } =
|
||||||
useConnectionBase();
|
useConnectionBase();
|
||||||
const { withActivity } = CommonHooks.useBusyState();
|
const { withActivity } = CommonHooks.useBusyState();
|
||||||
const {
|
const {
|
||||||
connectViaIdAndSecret,
|
authenticateWithCredentials,
|
||||||
isManualConnectionMode,
|
isManualConnectionMode,
|
||||||
setManualConnectionMode,
|
setManualConnectionMode,
|
||||||
clientId,
|
} = CommonHooks.useAuthentication();
|
||||||
setClientId,
|
|
||||||
clientSecret,
|
|
||||||
setClientSecret,
|
|
||||||
} = CommonHooks.useManualConnection();
|
|
||||||
|
|
||||||
const handleConnectViaIdAndSecret = async ( { validation } = {} ) => {
|
const handleDirectAuthentication = async ( connectionDetails ) => {
|
||||||
return withActivity(
|
return withActivity(
|
||||||
ACTIVITIES.CONNECT_MANUAL,
|
ACTIVITIES.CONNECT_MANUAL,
|
||||||
'Connecting manually via Client ID and Secret',
|
'Connecting manually via Client ID and Secret',
|
||||||
async () => {
|
async () => {
|
||||||
if ( 'function' === typeof validation ) {
|
let data;
|
||||||
|
|
||||||
|
if ( 'function' === typeof connectionDetails ) {
|
||||||
try {
|
try {
|
||||||
validation();
|
data = connectionDetails();
|
||||||
} catch ( exception ) {
|
} catch ( exception ) {
|
||||||
createErrorNotice( exception.message );
|
createErrorNotice( exception.message );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if ( 'object' === typeof connectionDetails ) {
|
||||||
|
data = connectionDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await connectViaIdAndSecret();
|
if ( ! data || ! data.clientId || ! data.clientSecret ) {
|
||||||
|
createErrorNotice(
|
||||||
|
'Invalid connection details (clientID or clientSecret missing)'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await authenticateWithCredentials(
|
||||||
|
data.clientId,
|
||||||
|
data.clientSecret,
|
||||||
|
!! data.isSandbox
|
||||||
|
);
|
||||||
|
|
||||||
if ( res.success ) {
|
if ( res.success ) {
|
||||||
await handleCompleted();
|
await handleCompleted();
|
||||||
|
@ -203,12 +259,8 @@ export const useManualConnection = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleConnectViaIdAndSecret,
|
handleDirectAuthentication,
|
||||||
isManualConnectionMode,
|
isManualConnectionMode,
|
||||||
setManualConnectionMode,
|
setManualConnectionMode,
|
||||||
clientId,
|
|
||||||
setClientId,
|
|
||||||
clientSecret,
|
|
||||||
setClientSecret,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
/**
|
|
||||||
* Opens the provided URL, preferably in a popup window.
|
|
||||||
*
|
|
||||||
* Popups are usually only supported on desktop devices, when the browser is not in fullscreen mode.
|
|
||||||
*
|
|
||||||
* @param {string} url
|
|
||||||
* @param {Object} options
|
|
||||||
* @param {string} options.name
|
|
||||||
* @param {number} options.width
|
|
||||||
* @param {number} options.height
|
|
||||||
* @param {boolean} options.resizeable
|
|
||||||
* @return {null|Window} Popup window instance, or null.
|
|
||||||
*/
|
|
||||||
export const openPopup = (
|
|
||||||
url,
|
|
||||||
{ name = '_blank', width = 450, height = 720, resizeable = false } = {}
|
|
||||||
) => {
|
|
||||||
width = Math.max( 100, Math.min( window.screen.width - 40, width ) );
|
|
||||||
height = Math.max( 100, Math.min( window.screen.height - 40, height ) );
|
|
||||||
|
|
||||||
const left = ( window.screen.width - width ) / 2;
|
|
||||||
const top = ( window.screen.height - height ) / 2;
|
|
||||||
|
|
||||||
const features = [
|
|
||||||
`width=${ width }`,
|
|
||||||
`height=${ height }`,
|
|
||||||
`left=${ left }`,
|
|
||||||
`top=${ top }`,
|
|
||||||
`resizable=${ resizeable ? 'yes' : 'no' }`,
|
|
||||||
`scrollbars=yes`,
|
|
||||||
`status=no`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const popup = window.open( url, name, features.join( ',' ) );
|
|
||||||
|
|
||||||
if ( popup && ! popup.closed ) {
|
|
||||||
popup.focus();
|
|
||||||
return popup;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -10,20 +10,21 @@ declare( strict_types = 1 );
|
||||||
namespace WooCommerce\PayPalCommerce\Settings;
|
namespace WooCommerce\PayPalCommerce\Settings;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\AuthenticationRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint;
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
|
||||||
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 {
|
||||||
|
@ -79,17 +80,14 @@ return array(
|
||||||
$container->get( 'woocommerce.logger.woocommerce' )
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint {
|
'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : AuthenticationRestEndpoint {
|
||||||
return new ConnectManualRestEndpoint(
|
return new AuthenticationRestEndpoint(
|
||||||
$container->get( 'api.paypal-host-production' ),
|
$container->get( 'settings.service.authentication_manager' ),
|
||||||
$container->get( 'api.paypal-host-sandbox' ),
|
|
||||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
|
||||||
$container->get( 'settings.data.general' )
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
|
'settings.rest.login_link' => static function ( ContainerInterface $container ) : LoginLinkRestEndpoint {
|
||||||
return new LoginLinkRestEndpoint(
|
return new LoginLinkRestEndpoint(
|
||||||
$container->get( 'settings.service.connection-url-generators' ),
|
$container->get( 'settings.service.connection-url-generator' ),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.rest.webhooks' => static function ( ContainerInterface $container ) : WebhookSettingsEndpoint {
|
'settings.rest.webhooks' => static function ( ContainerInterface $container ) : WebhookSettingsEndpoint {
|
||||||
|
@ -160,8 +158,8 @@ return array(
|
||||||
|
|
||||||
return new ConnectionListener(
|
return new ConnectionListener(
|
||||||
$page_id,
|
$page_id,
|
||||||
$container->get( 'settings.data.common' ),
|
|
||||||
$container->get( 'settings.service.onboarding-url-manager' ),
|
$container->get( 'settings.service.onboarding-url-manager' ),
|
||||||
|
$container->get( 'settings.service.authentication_manager' ),
|
||||||
$container->get( 'woocommerce.logger.woocommerce' )
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -174,33 +172,24 @@ return array(
|
||||||
$container->get( 'woocommerce.logger.woocommerce' )
|
$container->get( 'woocommerce.logger.woocommerce' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'settings.service.connection-url-generators' => static function ( ContainerInterface $container ) : array {
|
'settings.service.connection-url-generator' => static function ( ContainerInterface $container ) : ConnectionUrlGenerator {
|
||||||
// Define available environments.
|
return new ConnectionUrlGenerator(
|
||||||
$environments = array(
|
$container->get( 'api.env.endpoint.partner-referrals' ),
|
||||||
'production' => array(
|
|
||||||
'partner_referrals' => $container->get( 'api.endpoint.partner-referrals-production' ),
|
|
||||||
),
|
|
||||||
'sandbox' => array(
|
|
||||||
'partner_referrals' => $container->get( 'api.endpoint.partner-referrals-sandbox' ),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$generators = array();
|
|
||||||
|
|
||||||
// Instantiate URL generators for each environment.
|
|
||||||
foreach ( $environments as $environment => $config ) {
|
|
||||||
$generators[ $environment ] = new ConnectionUrlGenerator(
|
|
||||||
$config['partner_referrals'],
|
|
||||||
$container->get( 'api.repository.partner-referrals-data' ),
|
$container->get( 'api.repository.partner-referrals-data' ),
|
||||||
$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' )
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return $generators;
|
|
||||||
},
|
},
|
||||||
'settings.switch-ui.endpoint' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
|
'settings.service.authentication_manager' => static function ( ContainerInterface $container ) : AuthenticationManager {
|
||||||
|
return new AuthenticationManager(
|
||||||
|
$container->get( 'settings.data.common' ),
|
||||||
|
$container->get( 'api.env.paypal-host' ),
|
||||||
|
$container->get( 'api.env.endpoint.login-seller' ),
|
||||||
|
$container->get( 'api.repository.partner-referrals-data' ),
|
||||||
|
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
|
||||||
return new SwitchSettingsUiEndpoint(
|
return new SwitchSettingsUiEndpoint(
|
||||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||||
$container->get( 'button.request-data' ),
|
$container->get( 'button.request-data' ),
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* The settings UI switching Endpoint.
|
* The settings UI switching Ajax endpoint.
|
||||||
*
|
*
|
||||||
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare( strict_types=1 );
|
declare( strict_types=1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
namespace WooCommerce\PayPalCommerce\Settings\Ajax;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
75
modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php
Normal file
75
modules/ppcp-settings/src/DTO/MerchantConnectionDTO.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Data transfer object. Stores all connection credentials of the PayPal merchant connection.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\DTO;
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\DTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO that collects all details of a "merchant connection".
|
||||||
|
*
|
||||||
|
* Intentionally has no internal logic, sanitation or validation.
|
||||||
|
*/
|
||||||
|
class MerchantConnectionDTO {
|
||||||
|
/**
|
||||||
|
* Whether this connection is a sandbox account.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $is_sandbox = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API client ID.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $client_id = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API client secret.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $client_secret = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PayPal's 13-character merchant ID.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $merchant_id = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email address of the merchant account.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $merchant_email = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param bool $is_sandbox Whether this connection is a sandbox account.
|
||||||
|
* @param string $client_id API client ID.
|
||||||
|
* @param string $client_secret API client secret.
|
||||||
|
* @param string $merchant_id PayPal's 13-character merchant ID.
|
||||||
|
* @param string $merchant_email Email address of the merchant account.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
bool $is_sandbox,
|
||||||
|
string $client_id,
|
||||||
|
string $client_secret,
|
||||||
|
string $merchant_id,
|
||||||
|
string $merchant_email
|
||||||
|
) {
|
||||||
|
$this->is_sandbox = $is_sandbox;
|
||||||
|
$this->client_id = $client_id;
|
||||||
|
$this->client_secret = $client_secret;
|
||||||
|
$this->merchant_id = $merchant_id;
|
||||||
|
$this->merchant_email = $merchant_email;
|
||||||
|
}
|
||||||
|
}
|
|
@ -122,5 +122,4 @@ abstract class AbstractDataModel {
|
||||||
|
|
||||||
return $stripped_key ? "set_$stripped_key" : '';
|
return $stripped_key ? "set_$stripped_key" : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ declare( strict_types = 1 );
|
||||||
namespace WooCommerce\PayPalCommerce\Settings\Data;
|
namespace WooCommerce\PayPalCommerce\Settings\Data;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CommonSettings
|
* Class CommonSettings
|
||||||
|
@ -41,11 +42,16 @@ class CommonSettings extends AbstractDataModel {
|
||||||
*
|
*
|
||||||
* @param string $country WooCommerce store country.
|
* @param string $country WooCommerce store country.
|
||||||
* @param string $currency WooCommerce store currency.
|
* @param string $currency WooCommerce store currency.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException When forgetting to define the OPTION_KEY in this class.
|
||||||
*/
|
*/
|
||||||
public function __construct( string $country, string $currency ) {
|
public function __construct( string $country, string $currency ) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->woo_settings['country'] = $country;
|
$this->woo_settings['country'] = $country;
|
||||||
$this->woo_settings['currency'] = $currency;
|
$this->woo_settings['currency'] = $currency;
|
||||||
|
|
||||||
|
$this->data['merchant_connected'] = $this->is_merchant_connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,16 +61,16 @@ class CommonSettings extends AbstractDataModel {
|
||||||
*/
|
*/
|
||||||
protected function get_defaults() : array {
|
protected function get_defaults() : array {
|
||||||
return array(
|
return array(
|
||||||
'use_sandbox' => false,
|
'use_sandbox' => false, // UI state, not a connection detail.
|
||||||
'use_manual_connection' => false,
|
'use_manual_connection' => false, // UI state, not a connection detail.
|
||||||
'client_id' => '',
|
|
||||||
'client_secret' => '',
|
|
||||||
|
|
||||||
// Details about connected merchant account.
|
// Details about connected merchant account.
|
||||||
'merchant_connected' => false,
|
'merchant_connected' => false,
|
||||||
'sandbox_merchant' => false,
|
'sandbox_merchant' => false,
|
||||||
'merchant_id' => '',
|
'merchant_id' => '',
|
||||||
'merchant_email' => '',
|
'merchant_email' => '',
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,42 +112,6 @@ class CommonSettings extends AbstractDataModel {
|
||||||
$this->data['use_manual_connection'] = $use_manual_connection;
|
$this->data['use_manual_connection'] = $use_manual_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the client ID.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function get_client_id() : string {
|
|
||||||
return $this->data['client_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the client ID.
|
|
||||||
*
|
|
||||||
* @param string $client_id The client ID.
|
|
||||||
*/
|
|
||||||
public function set_client_id( string $client_id ) : void {
|
|
||||||
$this->data['client_id'] = sanitize_text_field( $client_id );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the client secret.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function get_client_secret() : string {
|
|
||||||
return $this->data['client_secret'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the client secret.
|
|
||||||
*
|
|
||||||
* @param string $client_secret The client secret.
|
|
||||||
*/
|
|
||||||
public function set_client_secret( string $client_secret ) : void {
|
|
||||||
$this->data['client_secret'] = sanitize_text_field( $client_secret );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of read-only customization flags.
|
* Returns the list of read-only customization flags.
|
||||||
*
|
*
|
||||||
|
@ -154,19 +124,48 @@ class CommonSettings extends AbstractDataModel {
|
||||||
/**
|
/**
|
||||||
* Setter to update details of the connected merchant account.
|
* Setter to update details of the connected merchant account.
|
||||||
*
|
*
|
||||||
* Those details cannot be changed individually.
|
* @param MerchantConnectionDTO $connection Connection details.
|
||||||
*
|
|
||||||
* @param bool $is_sandbox Whether the details are for a sandbox account.
|
|
||||||
* @param string $merchant_id The merchant ID.
|
|
||||||
* @param string $merchant_email The merchant's email.
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function set_merchant_data( bool $is_sandbox, string $merchant_id, string $merchant_email ) : void {
|
public function set_merchant_data( MerchantConnectionDTO $connection ) : void {
|
||||||
$this->data['sandbox_merchant'] = $is_sandbox;
|
$this->data['sandbox_merchant'] = $connection->is_sandbox;
|
||||||
$this->data['merchant_id'] = sanitize_text_field( $merchant_id );
|
$this->data['merchant_id'] = sanitize_text_field( $connection->merchant_id );
|
||||||
$this->data['merchant_email'] = sanitize_email( $merchant_email );
|
$this->data['merchant_email'] = sanitize_email( $connection->merchant_email );
|
||||||
$this->data['merchant_connected'] = true;
|
$this->data['client_id'] = sanitize_text_field( $connection->client_id );
|
||||||
|
$this->data['client_secret'] = sanitize_text_field( $connection->client_secret );
|
||||||
|
$this->data['merchant_connected'] = $this->is_merchant_connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full merchant connection DTO for the current connection.
|
||||||
|
*
|
||||||
|
* @return MerchantConnectionDTO All connection details.
|
||||||
|
*/
|
||||||
|
public function get_merchant_data() : MerchantConnectionDTO {
|
||||||
|
return new MerchantConnectionDTO(
|
||||||
|
$this->is_sandbox_merchant(),
|
||||||
|
$this->data['client_id'],
|
||||||
|
$this->data['client_secret'],
|
||||||
|
$this->data['merchant_id'],
|
||||||
|
$this->data['merchant_email']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all connection details to the initial, disconnected state.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function reset_merchant_data() : void {
|
||||||
|
$defaults = $this->get_defaults();
|
||||||
|
|
||||||
|
$this->data['sandbox_merchant'] = $defaults['sandbox_merchant'];
|
||||||
|
$this->data['merchant_id'] = $defaults['merchant_id'];
|
||||||
|
$this->data['merchant_email'] = $defaults['merchant_email'];
|
||||||
|
$this->data['client_id'] = $defaults['client_id'];
|
||||||
|
$this->data['client_secret'] = $defaults['client_secret'];
|
||||||
|
$this->data['merchant_connected'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,7 +183,10 @@ class CommonSettings extends AbstractDataModel {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function is_merchant_connected() : bool {
|
public function is_merchant_connected() : bool {
|
||||||
return $this->data['merchant_connected'] && $this->data['merchant_id'] && $this->data['merchant_email'];
|
return $this->data['merchant_email']
|
||||||
|
&& $this->data['merchant_id']
|
||||||
|
&& $this->data['client_id']
|
||||||
|
&& $this->data['client_secret'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST controller for authenticating a PayPal merchant.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use WP_REST_Request;
|
||||||
|
use WP_REST_Response;
|
||||||
|
use WP_REST_Server;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST controller for authenticating and connecting to a PayPal merchant account.
|
||||||
|
*
|
||||||
|
* This endpoint is responsible for verifying credentials and establishing
|
||||||
|
* a connection, regardless of whether they are provided via:
|
||||||
|
* 1. Direct login (clientId + secret)
|
||||||
|
* 2. UI-driven login (sharedId + authCode)
|
||||||
|
*
|
||||||
|
* It handles the actual authentication process after the login URL has been used.
|
||||||
|
*/
|
||||||
|
class AuthenticationRestEndpoint extends RestEndpoint {
|
||||||
|
/**
|
||||||
|
* The base path for this REST controller.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $rest_base = 'authenticate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication manager service.
|
||||||
|
*
|
||||||
|
* @var AuthenticationManager
|
||||||
|
*/
|
||||||
|
private AuthenticationManager $authentication_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the JSON response format (when connection was successful).
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $response_map = array(
|
||||||
|
'merchant_id' => array(
|
||||||
|
'js_name' => 'merchantId',
|
||||||
|
),
|
||||||
|
'merchant_email' => array(
|
||||||
|
'js_name' => 'email',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param AuthenticationManager $authentication_manager The authentication manager.
|
||||||
|
*/
|
||||||
|
public function __construct( AuthenticationManager $authentication_manager ) {
|
||||||
|
$this->authentication_manager = $authentication_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure REST API routes.
|
||||||
|
*/
|
||||||
|
public function register_routes() : void {
|
||||||
|
register_rest_route(
|
||||||
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base . '/direct',
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
'callback' => array( $this, 'connect_direct' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
'args' => array(
|
||||||
|
'clientId' => array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
'minLength' => 80,
|
||||||
|
'maxLength' => 80,
|
||||||
|
),
|
||||||
|
'clientSecret' => array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
),
|
||||||
|
'useSandbox' => array(
|
||||||
|
'required' => false,
|
||||||
|
'type' => 'boolean',
|
||||||
|
'default' => false,
|
||||||
|
'sanitize_callback' => array( $this, 'to_boolean' ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_rest_route(
|
||||||
|
$this->namespace,
|
||||||
|
'/' . $this->rest_base . '/isu',
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
'callback' => array( $this, 'connect_isu' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
'args' => array(
|
||||||
|
'sharedId' => array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
),
|
||||||
|
'authCode' => array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field',
|
||||||
|
),
|
||||||
|
'useSandbox' => array(
|
||||||
|
'default' => 0,
|
||||||
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => array( $this, 'to_boolean' ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct login: Retrieves merchantId and email using clientId and clientSecret.
|
||||||
|
*
|
||||||
|
* This is the "Manual Login" logic, when a merchant already knows their
|
||||||
|
* API credentials.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Full data about the request.
|
||||||
|
*/
|
||||||
|
public function connect_direct( WP_REST_Request $request ) : WP_REST_Response {
|
||||||
|
$client_id = $request->get_param( 'clientId' );
|
||||||
|
$client_secret = $request->get_param( 'clientSecret' );
|
||||||
|
$use_sandbox = $request->get_param( 'useSandbox' );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->authentication_manager->validate_id_and_secret( $client_id, $client_secret );
|
||||||
|
$this->authentication_manager->authenticate_via_direct_api( $use_sandbox, $client_id, $client_secret );
|
||||||
|
} catch ( Exception $exception ) {
|
||||||
|
return $this->return_error( $exception->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = $this->authentication_manager->get_account_details();
|
||||||
|
$response = $this->sanitize_for_javascript( $this->response_map, $account );
|
||||||
|
|
||||||
|
return $this->return_success( $response );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISU login: Retrieves clientId and clientSecret using a sharedId and authCode.
|
||||||
|
*
|
||||||
|
* This is the final step in the UI-driven login via the ISU popup, which
|
||||||
|
* is triggered by the LoginLinkRestEndpoint URL.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Full data about the request.
|
||||||
|
*/
|
||||||
|
public function connect_isu( WP_REST_Request $request ) : WP_REST_Response {
|
||||||
|
$shared_id = $request->get_param( 'sharedId' );
|
||||||
|
$auth_code = $request->get_param( 'authCode' );
|
||||||
|
$use_sandbox = $request->get_param( 'useSandbox' );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->authentication_manager->validate_id_and_auth_code( $shared_id, $auth_code );
|
||||||
|
$this->authentication_manager->authenticate_via_oauth( $use_sandbox, $shared_id, $auth_code );
|
||||||
|
} catch ( Exception $exception ) {
|
||||||
|
return $this->return_error( $exception->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = $this->authentication_manager->get_account_details();
|
||||||
|
$response = $this->sanitize_for_javascript( $this->response_map, $account );
|
||||||
|
|
||||||
|
return $this->return_success( $response );
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,14 +50,6 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
'js_name' => 'useManualConnection',
|
'js_name' => 'useManualConnection',
|
||||||
'sanitize' => 'to_boolean',
|
'sanitize' => 'to_boolean',
|
||||||
),
|
),
|
||||||
'client_id' => array(
|
|
||||||
'js_name' => 'clientId',
|
|
||||||
'sanitize' => 'sanitize_text_field',
|
|
||||||
),
|
|
||||||
'client_secret' => array(
|
|
||||||
'js_name' => 'clientSecret',
|
|
||||||
'sanitize' => 'sanitize_text_field',
|
|
||||||
),
|
|
||||||
'webhooks' => array(
|
'webhooks' => array(
|
||||||
'js_name' => 'webhooks',
|
'js_name' => 'webhooks',
|
||||||
),
|
),
|
||||||
|
@ -81,6 +73,12 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
'merchant_email' => array(
|
'merchant_email' => array(
|
||||||
'js_name' => 'email',
|
'js_name' => 'email',
|
||||||
),
|
),
|
||||||
|
'client_id' => array(
|
||||||
|
'js_name' => 'clientId',
|
||||||
|
),
|
||||||
|
'client_secret' => array(
|
||||||
|
'js_name' => 'clientSecret',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,36 +111,30 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
'callback' => array( $this, 'get_details' ),
|
'callback' => array( $this, 'get_details' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( $this, 'update_details' ),
|
'callback' => array( $this, 'update_details' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
"/$this->rest_base/merchant",
|
"/$this->rest_base/merchant",
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
'callback' => array( $this, 'get_merchant_details' ),
|
'callback' => array( $this, 'get_merchant_details' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -243,10 +235,12 @@ class CommonRestEndpoint extends RestEndpoint {
|
||||||
$this->merchant_info_map
|
$this->merchant_info_map
|
||||||
);
|
);
|
||||||
|
|
||||||
$extra_data['merchant'] = apply_filters(
|
if ( $this->settings->is_merchant_connected() ) {
|
||||||
|
$extra_data['features'] = apply_filters(
|
||||||
'woocommerce_paypal_payments_rest_common_merchant_data',
|
'woocommerce_paypal_payments_rest_common_merchant_data',
|
||||||
$extra_data['merchant'],
|
array(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If no real data is available yet, use mock data.
|
// If no real data is available yet, use mock data.
|
||||||
if ( empty( $extra_data['merchant'] ) || ( empty( $extra_data['merchant']['id'] ) && empty( $extra_data['merchant']['email'] ) ) ) {
|
if ( empty( $extra_data['merchant'] ) || ( empty( $extra_data['merchant']['id'] ) && empty( $extra_data['merchant']['email'] ) ) ) {
|
||||||
|
|
|
@ -1,238 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* REST controller for connection via manual credentials input.
|
|
||||||
*
|
|
||||||
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare( strict_types = 1 );
|
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use stdClass;
|
|
||||||
use RuntimeException;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use WP_REST_Request;
|
|
||||||
use WP_REST_Response;
|
|
||||||
use WP_REST_Server;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* REST controller for connection via manual credentials input.
|
|
||||||
*/
|
|
||||||
class ConnectManualRestEndpoint extends RestEndpoint {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The API host for the live mode.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private string $live_host;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The API host for the sandbox mode.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private string $sandbox_host;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logger.
|
|
||||||
*
|
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base path for this REST controller.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $rest_base = 'connect_manual';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings instance.
|
|
||||||
*
|
|
||||||
* @var GeneralSettings
|
|
||||||
*/
|
|
||||||
private $settings = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Field mapping for request.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private array $field_map = array(
|
|
||||||
'client_id' => array(
|
|
||||||
'js_name' => 'clientId',
|
|
||||||
'sanitize' => 'sanitize_text_field',
|
|
||||||
),
|
|
||||||
'client_secret' => array(
|
|
||||||
'js_name' => 'clientSecret',
|
|
||||||
'sanitize' => 'sanitize_text_field',
|
|
||||||
),
|
|
||||||
'use_sandbox' => array(
|
|
||||||
'js_name' => 'useSandbox',
|
|
||||||
'sanitize' => 'to_boolean',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConnectManualRestEndpoint constructor.
|
|
||||||
*
|
|
||||||
* @param string $live_host The API host for the live mode.
|
|
||||||
* @param string $sandbox_host The API host for the sandbox mode.
|
|
||||||
* @param LoggerInterface $logger The logger.
|
|
||||||
* @param GeneralSettings $settings Settings instance.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
string $live_host,
|
|
||||||
string $sandbox_host,
|
|
||||||
LoggerInterface $logger,
|
|
||||||
GeneralSettings $settings
|
|
||||||
) {
|
|
||||||
$this->live_host = $live_host;
|
|
||||||
$this->sandbox_host = $sandbox_host;
|
|
||||||
$this->logger = $logger;
|
|
||||||
$this->settings = $settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure REST API routes.
|
|
||||||
*/
|
|
||||||
public function register_routes() {
|
|
||||||
register_rest_route(
|
|
||||||
$this->namespace,
|
|
||||||
'/' . $this->rest_base,
|
|
||||||
array(
|
|
||||||
array(
|
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
|
||||||
'callback' => array( $this, 'connect_manual' ),
|
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves merchantId and email.
|
|
||||||
*
|
|
||||||
* @param WP_REST_Request $request Full data about the request.
|
|
||||||
*/
|
|
||||||
public function connect_manual( WP_REST_Request $request ) : WP_REST_Response {
|
|
||||||
$data = $this->sanitize_for_wordpress(
|
|
||||||
$request->get_params(),
|
|
||||||
$this->field_map
|
|
||||||
);
|
|
||||||
|
|
||||||
$client_id = $data['client_id'] ?? '';
|
|
||||||
$client_secret = $data['client_secret'] ?? '';
|
|
||||||
$use_sandbox = (bool) ( $data['use_sandbox'] ?? false );
|
|
||||||
|
|
||||||
if ( empty( $client_id ) || empty( $client_secret ) ) {
|
|
||||||
return $this->return_error( 'No client ID or secret provided.' );
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
|
|
||||||
} catch ( Exception $exception ) {
|
|
||||||
return $this->return_error( $exception->getMessage() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $use_sandbox ) {
|
|
||||||
$this->settings->set_is_sandbox( true );
|
|
||||||
$this->settings->set_sandbox_client_id( $client_id );
|
|
||||||
$this->settings->set_sandbox_client_secret( $client_secret );
|
|
||||||
$this->settings->set_sandbox_merchant_id( $payee->merchant_id );
|
|
||||||
$this->settings->set_sandbox_merchant_email( $payee->email_address );
|
|
||||||
} else {
|
|
||||||
$this->settings->set_is_sandbox( false );
|
|
||||||
$this->settings->set_live_client_id( $client_id );
|
|
||||||
$this->settings->set_live_client_secret( $client_secret );
|
|
||||||
$this->settings->set_live_merchant_id( $payee->merchant_id );
|
|
||||||
$this->settings->set_live_merchant_email( $payee->email_address );
|
|
||||||
}
|
|
||||||
$this->settings->save();
|
|
||||||
|
|
||||||
return $this->return_success(
|
|
||||||
array(
|
|
||||||
'merchantId' => $payee->merchant_id,
|
|
||||||
'email' => $payee->email_address,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the payee object with the merchant data
|
|
||||||
* by creating a minimal PayPal order.
|
|
||||||
*
|
|
||||||
* @throws Exception When failed to retrieve payee.
|
|
||||||
*
|
|
||||||
* phpcs:disable Squiz.Commenting
|
|
||||||
* phpcs:disable Generic.Commenting
|
|
||||||
*
|
|
||||||
* @param string $client_secret The client secret.
|
|
||||||
* @param bool $use_sandbox Whether to use the sandbox mode.
|
|
||||||
* @param string $client_id The client ID.
|
|
||||||
*
|
|
||||||
* @return stdClass The payee object.
|
|
||||||
*/
|
|
||||||
private function request_payee(
|
|
||||||
string $client_id,
|
|
||||||
string $client_secret,
|
|
||||||
bool $use_sandbox
|
|
||||||
) : stdClass {
|
|
||||||
|
|
||||||
$host = $use_sandbox ? $this->sandbox_host : $this->live_host;
|
|
||||||
|
|
||||||
$bearer = new PayPalBearer(
|
|
||||||
new InMemoryCache(),
|
|
||||||
$host,
|
|
||||||
$client_id,
|
|
||||||
$client_secret,
|
|
||||||
$this->logger,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
$orders = new Orders(
|
|
||||||
$host,
|
|
||||||
$bearer,
|
|
||||||
$this->logger
|
|
||||||
);
|
|
||||||
|
|
||||||
$request_body = array(
|
|
||||||
'intent' => 'CAPTURE',
|
|
||||||
'purchase_units' => array(
|
|
||||||
array(
|
|
||||||
'amount' => array(
|
|
||||||
'currency_code' => 'USD',
|
|
||||||
'value' => 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$response = $orders->create( $request_body );
|
|
||||||
$body = json_decode( $response['body'] );
|
|
||||||
|
|
||||||
$order_id = $body->id;
|
|
||||||
|
|
||||||
$order_response = $orders->order( $order_id );
|
|
||||||
$order_body = json_decode( $order_response['body'] );
|
|
||||||
|
|
||||||
$pu = $order_body->purchase_units[0];
|
|
||||||
$payee = $pu->payee;
|
|
||||||
if ( ! is_object( $payee ) ) {
|
|
||||||
throw new RuntimeException( 'Payee not found.' );
|
|
||||||
}
|
|
||||||
if ( ! isset( $payee->merchant_id ) || ! isset( $payee->email_address ) ) {
|
|
||||||
throw new RuntimeException( 'Payee info not found.' );
|
|
||||||
}
|
|
||||||
|
|
||||||
return $payee;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,14 @@ use WP_REST_Request;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST controller that generates merchant login URLs.
|
* REST controller that generates merchant login URLs for PayPal.
|
||||||
|
*
|
||||||
|
* This endpoint is responsible solely for generating a URL that initiates
|
||||||
|
* the PayPal login flow. It does not handle the authentication itself.
|
||||||
|
*
|
||||||
|
* The generated URL is typically used to redirect merchants to PayPal's login page.
|
||||||
|
* After successful login, the authentication process is completed via the
|
||||||
|
* AuthenticationRestEndpoint.
|
||||||
*/
|
*/
|
||||||
class LoginLinkRestEndpoint extends RestEndpoint {
|
class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
/**
|
/**
|
||||||
|
@ -26,37 +33,37 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
protected $rest_base = 'login_link';
|
protected $rest_base = 'login_link';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link generator list, with environment name as array key.
|
* Login-URL generator.
|
||||||
*
|
*
|
||||||
* @var ConnectionUrlGenerator[]
|
* @var ConnectionUrlGenerator
|
||||||
*/
|
*/
|
||||||
protected array $url_generators;
|
protected ConnectionUrlGenerator $url_generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param ConnectionUrlGenerator[] $url_generators Array of environment-specific URL generators.
|
* @param ConnectionUrlGenerator $url_generator Login-URL generator.
|
||||||
*/
|
*/
|
||||||
public function __construct( array $url_generators ) {
|
public function __construct( ConnectionUrlGenerator $url_generator ) {
|
||||||
$this->url_generators = $url_generators;
|
$this->url_generator = $url_generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure REST API routes.
|
* Configure REST API routes.
|
||||||
*/
|
*/
|
||||||
public function register_routes() {
|
public function register_routes() : void {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( $this, 'get_login_url' ),
|
'callback' => array( $this, 'get_login_url' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
'args' => array(
|
'args' => array(
|
||||||
'environment' => array(
|
'useSandbox' => array(
|
||||||
'required' => true,
|
'default' => 0,
|
||||||
'type' => 'string',
|
'type' => 'boolean',
|
||||||
|
'sanitize_callback' => array( $this, 'to_boolean' ),
|
||||||
),
|
),
|
||||||
'products' => array(
|
'products' => array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
|
@ -69,7 +76,6 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -82,20 +88,11 @@ class LoginLinkRestEndpoint extends RestEndpoint {
|
||||||
* @return WP_REST_Response The login URL or an error response.
|
* @return WP_REST_Response The login URL or an error response.
|
||||||
*/
|
*/
|
||||||
public function get_login_url( WP_REST_Request $request ) : WP_REST_Response {
|
public function get_login_url( WP_REST_Request $request ) : WP_REST_Response {
|
||||||
$environment = $request->get_param( 'environment' );
|
$use_sandbox = $request->get_param( 'useSandbox' );
|
||||||
$products = $request->get_param( 'products' );
|
$products = $request->get_param( 'products' );
|
||||||
|
|
||||||
if ( ! isset( $this->url_generators[ $environment ] ) ) {
|
|
||||||
return new WP_REST_Response(
|
|
||||||
array( 'error' => 'Invalid environment specified.' ),
|
|
||||||
400
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$url_generator = $this->url_generators[ $environment ];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$url = $url_generator->generate( $products );
|
$url = $this->url_generator->generate( $products, $use_sandbox );
|
||||||
|
|
||||||
return $this->return_success( $url );
|
return $this->return_success( $url );
|
||||||
} catch ( \Exception $e ) {
|
} catch ( \Exception $e ) {
|
||||||
|
|
|
@ -100,24 +100,20 @@ class OnboardingRestEndpoint extends RestEndpoint {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::READABLE,
|
'methods' => WP_REST_Server::READABLE,
|
||||||
'callback' => array( $this, 'get_details' ),
|
'callback' => array( $this, 'get_details' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( $this, 'update_details' ),
|
'callback' => array( $this, 'update_details' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,12 +86,10 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
|
||||||
register_rest_route(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base,
|
'/' . $this->rest_base,
|
||||||
array(
|
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::EDITABLE,
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( $this, 'refresh_status' ),
|
'callback' => array( $this, 'refresh_status' ),
|
||||||
'permission_callback' => array( $this, 'check_permission' ),
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes parameters based on a field mapping.
|
* Sanitizes and renames input parameters, based on a field mapping.
|
||||||
*
|
*
|
||||||
* This method iterates through a field map, applying sanitization methods
|
* This method iterates through a field map, applying sanitization methods
|
||||||
* to the corresponding values in the input parameters array.
|
* to the corresponding values in the input parameters array.
|
||||||
|
@ -122,7 +122,7 @@ abstract class RestEndpoint extends WC_REST_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes data for JavaScript based on a field mapping.
|
* Sanitizes and renames data for JavaScript, based on a field mapping.
|
||||||
*
|
*
|
||||||
* This method transforms the input data array according to the provided field map,
|
* This method transforms the input data array according to the provided field map,
|
||||||
* renaming keys to their JavaScript equivalents as specified in the mapping.
|
* renaming keys to their JavaScript equivalents as specified in the mapping.
|
||||||
|
@ -151,24 +151,28 @@ abstract class RestEndpoint extends WC_REST_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a value to a boolean.
|
* Sanitation callback: Convert a value to a boolean.
|
||||||
*
|
*
|
||||||
* @param mixed $value The value to convert.
|
* @param mixed $value The value to sanitize.
|
||||||
*
|
*
|
||||||
* @return bool|null The boolean value, or null if not set.
|
* @return bool|null The boolean value, or null if not set.
|
||||||
*/
|
*/
|
||||||
protected function to_boolean( $value ) : ?bool {
|
public function to_boolean( $value ) : ?bool {
|
||||||
return $value !== null ? (bool) $value : null;
|
return $value !== null ? (bool) $value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a value to a number.
|
* Sanitation callback: Convert a value to a number.
|
||||||
*
|
*
|
||||||
* @param mixed $value The value to convert.
|
* @param mixed $value The value to sanitize.
|
||||||
*
|
*
|
||||||
* @return int|float|null The numeric value, or null if not set.
|
* @return int|float|null The numeric value, or null if not set.
|
||||||
*/
|
*/
|
||||||
protected function to_number( $value ) {
|
public function to_number( $value ) {
|
||||||
return $value !== null ? ( is_numeric( $value ) ? $value + 0 : null ) : null;
|
if ( $value !== null ) {
|
||||||
|
$value = is_numeric( $value ) ? $value + 0 : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
||||||
try {
|
try {
|
||||||
$webhook_list = ( $this->webhook_endpoint->list() )[0];
|
$webhook_list = ( $this->webhook_endpoint->list() )[0];
|
||||||
$webhook_events = array_map(
|
$webhook_events = array_map(
|
||||||
function ( stdClass $webhook ) {
|
static function ( stdClass $webhook ) {
|
||||||
return strtolower( $webhook->name );
|
return strtolower( $webhook->name );
|
||||||
},
|
},
|
||||||
$webhook_list->event_types()
|
$webhook_list->event_types()
|
||||||
|
|
|
@ -10,10 +10,11 @@ declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Settings\Handler;
|
namespace WooCommerce\PayPalCommerce\Settings\Handler;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a listener that handles merchant-connection requests.
|
* Provides a listener that handles merchant-connection requests.
|
||||||
|
@ -31,13 +32,6 @@ class ConnectionListener {
|
||||||
*/
|
*/
|
||||||
private string $settings_page_id;
|
private string $settings_page_id;
|
||||||
|
|
||||||
/**
|
|
||||||
* Access to connection settings.
|
|
||||||
*
|
|
||||||
* @var CommonSettings
|
|
||||||
*/
|
|
||||||
private CommonSettings $settings;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access to the onboarding URL manager.
|
* Access to the onboarding URL manager.
|
||||||
*
|
*
|
||||||
|
@ -45,6 +39,13 @@ class ConnectionListener {
|
||||||
*/
|
*/
|
||||||
private OnboardingUrlManager $url_manager;
|
private OnboardingUrlManager $url_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication manager service, responsible to update connection details.
|
||||||
|
*
|
||||||
|
* @var AuthenticationManager
|
||||||
|
*/
|
||||||
|
private AuthenticationManager $authentication_manager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger instance, mainly used for debugging purposes.
|
* Logger instance, mainly used for debugging purposes.
|
||||||
*
|
*
|
||||||
|
@ -63,14 +64,19 @@ class ConnectionListener {
|
||||||
* Prepare the instance.
|
* Prepare the instance.
|
||||||
*
|
*
|
||||||
* @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 OnboardingUrlManager $url_manager Get OnboardingURL instances.
|
* @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
|
||||||
|
* @param AuthenticationManager $authentication_manager Authentication manager service.
|
||||||
* @param ?LoggerInterface $logger The logger, for debugging purposes.
|
* @param ?LoggerInterface $logger The logger, for debugging purposes.
|
||||||
*/
|
*/
|
||||||
public function __construct( string $settings_page_id, CommonSettings $settings, OnboardingUrlManager $url_manager, LoggerInterface $logger = null ) {
|
public function __construct(
|
||||||
|
string $settings_page_id,
|
||||||
|
OnboardingUrlManager $url_manager,
|
||||||
|
AuthenticationManager $authentication_manager,
|
||||||
|
LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
$this->settings_page_id = $settings_page_id;
|
$this->settings_page_id = $settings_page_id;
|
||||||
$this->settings = $settings;
|
|
||||||
$this->url_manager = $url_manager;
|
$this->url_manager = $url_manager;
|
||||||
|
$this->authentication_manager = $authentication_manager;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$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().
|
||||||
|
@ -82,6 +88,8 @@ class ConnectionListener {
|
||||||
*
|
*
|
||||||
* @param int $user_id The current user ID.
|
* @param int $user_id The current user ID.
|
||||||
* @param array $request Request details to process.
|
* @param array $request Request details to process.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException If the merchant ID does not match the ID previously set via OAuth.
|
||||||
*/
|
*/
|
||||||
public function process( int $user_id, array $request ) : void {
|
public function process( int $user_id, array $request ) : void {
|
||||||
$this->user_id = $user_id;
|
$this->user_id = $user_id;
|
||||||
|
@ -100,13 +108,13 @@ class ConnectionListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger->info( 'Found merchant data in request', $data );
|
$this->logger->info( 'Found OAuth merchant data in request', $data );
|
||||||
|
|
||||||
$this->store_data(
|
try {
|
||||||
$data['is_sandbox'],
|
$this->authentication_manager->finish_oauth_authentication( $data );
|
||||||
$data['merchant_id'],
|
} catch ( \Exception $e ) {
|
||||||
$data['merchant_email']
|
$this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,26 +168,11 @@ class ConnectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'is_sandbox' => $this->settings->get_sandbox(),
|
|
||||||
'merchant_id' => $merchant_id,
|
'merchant_id' => $merchant_id,
|
||||||
'merchant_email' => $merchant_email,
|
'merchant_email' => $merchant_email,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Returns the sanitized connection token from the incoming request.
|
||||||
*
|
*
|
||||||
|
|
410
modules/ppcp-settings/src/Service/AuthenticationManager.php
Normal file
410
modules/ppcp-settings/src/Service/AuthenticationManager.php
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Manages the merchant connection between this plugin and PayPal.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Settings\Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||||
|
|
||||||
|
use JsonException;
|
||||||
|
use Throwable;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\CommonSettings;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||||
|
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that manages the connection to PayPal.
|
||||||
|
*/
|
||||||
|
class AuthenticationManager {
|
||||||
|
/**
|
||||||
|
* Data model that stores the connection details.
|
||||||
|
*
|
||||||
|
* @var CommonSettings
|
||||||
|
*/
|
||||||
|
private CommonSettings $common_settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging instance.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URLs for the manual connection attempt, by environment.
|
||||||
|
*
|
||||||
|
* @var EnvironmentConfig<string>
|
||||||
|
*/
|
||||||
|
private EnvironmentConfig $connection_host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login API handler instances, by environment.
|
||||||
|
*
|
||||||
|
* @var EnvironmentConfig<LoginSeller>
|
||||||
|
*/
|
||||||
|
private EnvironmentConfig $login_endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Onboarding referrals data.
|
||||||
|
*
|
||||||
|
* @var PartnerReferralsData
|
||||||
|
*/
|
||||||
|
private PartnerReferralsData $referrals_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param CommonSettings $common_settings Data model that stores the connection details.
|
||||||
|
* @param EnvironmentConfig $connection_host API host for direct authentication.
|
||||||
|
* @param EnvironmentConfig $login_endpoint API handler to fetch merchant credentials.
|
||||||
|
* @param PartnerReferralsData $referrals_data Partner referrals data.
|
||||||
|
* @param ?LoggerInterface $logger Logging instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
CommonSettings $common_settings,
|
||||||
|
EnvironmentConfig $connection_host,
|
||||||
|
EnvironmentConfig $login_endpoint,
|
||||||
|
PartnerReferralsData $referrals_data,
|
||||||
|
?LoggerInterface $logger = null
|
||||||
|
) {
|
||||||
|
$this->common_settings = $common_settings;
|
||||||
|
$this->connection_host = $connection_host;
|
||||||
|
$this->login_endpoint = $login_endpoint;
|
||||||
|
$this->referrals_data = $referrals_data;
|
||||||
|
$this->logger = $logger ?: new NullLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns details about the currently connected merchant.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_account_details() : array {
|
||||||
|
return array(
|
||||||
|
'is_sandbox' => $this->common_settings->is_sandbox_merchant(),
|
||||||
|
'is_connected' => $this->common_settings->is_merchant_connected(),
|
||||||
|
'merchant_id' => $this->common_settings->get_merchant_id(),
|
||||||
|
'merchant_email' => $this->common_settings->get_merchant_email(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any connection details we currently have stored.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function disconnect() : void {
|
||||||
|
$this->logger->info( 'Disconnecting merchant from PayPal...' );
|
||||||
|
|
||||||
|
$this->common_settings->reset_merchant_data();
|
||||||
|
$this->common_settings->save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast, that the plugin disconnected from PayPal. This allows other
|
||||||
|
* modules to clean up merchant-related details, such as eligibility flags.
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_paypal_payments_merchant_disconnected' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided ID and secret have a valid format.
|
||||||
|
*
|
||||||
|
* Part of the "Direct Connection" (Manual Connection) flow.
|
||||||
|
*
|
||||||
|
* On failure, an Exception is thrown, while a successful check does not
|
||||||
|
* generate any return value.
|
||||||
|
*
|
||||||
|
* @param string $client_id The client ID.
|
||||||
|
* @param string $client_secret The client secret.
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException When invalid client ID or secret provided.
|
||||||
|
*/
|
||||||
|
public function validate_id_and_secret( string $client_id, string $client_secret ) : void {
|
||||||
|
if ( empty( $client_id ) ) {
|
||||||
|
throw new RuntimeException( 'No client ID provided.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( false === preg_match( '/^A[\w-]{79}$/', $client_secret ) ) {
|
||||||
|
throw new RuntimeException( 'Invalid client ID provided.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $client_secret ) ) {
|
||||||
|
throw new RuntimeException( 'No client secret provided.' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the current merchant, and then attempts to connect to a
|
||||||
|
* PayPal account using a client ID and secret.
|
||||||
|
*
|
||||||
|
* Part of the "Direct Connection" (Manual Connection) flow.
|
||||||
|
*
|
||||||
|
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||||
|
* @param string $client_id The client ID.
|
||||||
|
* @param string $client_secret The client secret.
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException When failed to retrieve payee.
|
||||||
|
*/
|
||||||
|
public function authenticate_via_direct_api( bool $use_sandbox, string $client_id, string $client_secret ) : void {
|
||||||
|
$this->disconnect();
|
||||||
|
|
||||||
|
$this->logger->info(
|
||||||
|
'Attempting manual connection to PayPal...',
|
||||||
|
array(
|
||||||
|
'sandbox' => $use_sandbox,
|
||||||
|
'client_id' => $client_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
|
||||||
|
|
||||||
|
$connection = new MerchantConnectionDTO(
|
||||||
|
$use_sandbox,
|
||||||
|
$client_id,
|
||||||
|
$client_secret,
|
||||||
|
$payee['merchant_id'],
|
||||||
|
$payee['email_address']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->update_connection_details( $connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided ID and auth-code have a valid format.
|
||||||
|
*
|
||||||
|
* Part of the "ISU Connection" (login via Popup) flow.
|
||||||
|
*
|
||||||
|
* On failure, an Exception is thrown, while a successful check does not
|
||||||
|
* generate any return value. Note, that we did not find official documentation
|
||||||
|
* on those values, so we only check if they are non-empty strings.
|
||||||
|
*
|
||||||
|
* @param string $shared_id The shared onboarding ID.
|
||||||
|
* @param string $auth_code The authorization code.
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException When invalid shared ID or auth provided.
|
||||||
|
*/
|
||||||
|
public function validate_id_and_auth_code( string $shared_id, string $auth_code ) : void {
|
||||||
|
if ( empty( $shared_id ) ) {
|
||||||
|
throw new RuntimeException( 'No onboarding ID provided.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $auth_code ) ) {
|
||||||
|
throw new RuntimeException( 'No authorization code provided.' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the current merchant, and then attempts to connect to a
|
||||||
|
* PayPal account the onboarding ID and authorization ID.
|
||||||
|
*
|
||||||
|
* Part of the "ISU Connection" (login via Popup) flow.
|
||||||
|
*
|
||||||
|
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||||
|
* @param string $shared_id The OAuth client ID.
|
||||||
|
* @param string $auth_code The OAuth authorization code.
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException When failed to retrieve payee.
|
||||||
|
*/
|
||||||
|
public function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
|
||||||
|
$this->disconnect();
|
||||||
|
|
||||||
|
$this->logger->info(
|
||||||
|
'Attempting OAuth login to PayPal...',
|
||||||
|
array(
|
||||||
|
'sandbox' => $use_sandbox,
|
||||||
|
'shared_id' => $shared_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$credentials = $this->get_credentials( $shared_id, $auth_code, $use_sandbox );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The merchant's email is set by `ConnectionListener`. That listener
|
||||||
|
* is invoked during the page reload, once the user clicks the blue
|
||||||
|
* "Return to Store" button in PayPal's login popup.
|
||||||
|
*/
|
||||||
|
$connection = $this->common_settings->get_merchant_data();
|
||||||
|
|
||||||
|
$connection->is_sandbox = $use_sandbox;
|
||||||
|
$connection->client_id = $credentials['client_id'];
|
||||||
|
$connection->client_secret = $credentials['client_secret'];
|
||||||
|
$connection->merchant_id = $credentials['merchant_id'];
|
||||||
|
|
||||||
|
$this->update_connection_details( $connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the merchant details in the final OAuth redirect and extracts
|
||||||
|
* missing credentials from the URL.
|
||||||
|
*
|
||||||
|
* @param array $request_data Array of request parameters to process.
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws RuntimeException Missing or invalid credentials.
|
||||||
|
*/
|
||||||
|
public function finish_oauth_authentication( array $request_data ) : void {
|
||||||
|
$merchant_id = $request_data['merchant_id'];
|
||||||
|
$merchant_email = $request_data['merchant_email'];
|
||||||
|
|
||||||
|
if ( empty( $merchant_id ) || empty( $merchant_email ) ) {
|
||||||
|
throw new RuntimeException( 'Missing merchant ID or email in request' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection = $this->common_settings->get_merchant_data();
|
||||||
|
|
||||||
|
if ( $connection->merchant_id && $connection->merchant_id !== $merchant_id ) {
|
||||||
|
throw new RuntimeException( 'Unexpected merchant ID in request' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$connection->merchant_id = $merchant_id;
|
||||||
|
$connection->merchant_email = $merchant_email;
|
||||||
|
|
||||||
|
$this->update_connection_details( $connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Internal helper methods
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payee object with the merchant data by creating a minimal PayPal order.
|
||||||
|
*
|
||||||
|
* Part of the "Direct Connection" (Manual Connection) flow.
|
||||||
|
*
|
||||||
|
* @param string $client_id The client ID.
|
||||||
|
* @param string $client_secret The client secret.
|
||||||
|
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||||
|
*
|
||||||
|
* @return array Payee details, containing 'merchant_id' and 'merchant_email' keys.
|
||||||
|
* @throws RuntimeException When failed to retrieve payee.
|
||||||
|
*/
|
||||||
|
private function request_payee(
|
||||||
|
string $client_id,
|
||||||
|
string $client_secret,
|
||||||
|
bool $use_sandbox
|
||||||
|
) : array {
|
||||||
|
$host = $this->connection_host->get_value( $use_sandbox );
|
||||||
|
|
||||||
|
$bearer = new PayPalBearer(
|
||||||
|
new InMemoryCache(),
|
||||||
|
$host,
|
||||||
|
$client_id,
|
||||||
|
$client_secret,
|
||||||
|
$this->logger,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
$orders = new Orders(
|
||||||
|
$host,
|
||||||
|
$bearer,
|
||||||
|
$this->logger
|
||||||
|
);
|
||||||
|
|
||||||
|
$request_body = array(
|
||||||
|
'intent' => 'CAPTURE',
|
||||||
|
'purchase_units' => array(
|
||||||
|
array(
|
||||||
|
'amount' => array(
|
||||||
|
'currency_code' => 'USD',
|
||||||
|
'value' => 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $orders->create( $request_body );
|
||||||
|
$body = json_decode( $response['body'], false, 512, JSON_THROW_ON_ERROR );
|
||||||
|
$order_id = $body->id;
|
||||||
|
|
||||||
|
$order_response = $orders->order( $order_id );
|
||||||
|
$order_body = json_decode( $order_response['body'], false, 512, JSON_THROW_ON_ERROR );
|
||||||
|
} catch ( JsonException $exception ) {
|
||||||
|
// Cast JsonException to a RuntimeException.
|
||||||
|
throw new RuntimeException( 'Could not decode JSON response: ' . $exception->getMessage() );
|
||||||
|
} catch ( Throwable $exception ) {
|
||||||
|
// Cast any other Throwable to a RuntimeException.
|
||||||
|
throw new RuntimeException( $exception->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
$pu = $order_body->purchase_units[0];
|
||||||
|
$payee = $pu->payee;
|
||||||
|
|
||||||
|
if ( ! is_object( $payee ) ) {
|
||||||
|
throw new RuntimeException( 'Payee not found.' );
|
||||||
|
}
|
||||||
|
if ( ! isset( $payee->merchant_id, $payee->email_address ) ) {
|
||||||
|
throw new RuntimeException( 'Payee info not found.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'merchant_id' => $payee->merchant_id,
|
||||||
|
'email_address' => $payee->email_address,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches merchant API credentials using a shared onboarding ID and
|
||||||
|
* authorization code.
|
||||||
|
*
|
||||||
|
* Part of the "ISU Connection" (login via Popup) flow.
|
||||||
|
*
|
||||||
|
* @param string $shared_id The shared onboarding ID.
|
||||||
|
* @param string $auth_code The authorization code.
|
||||||
|
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||||
|
* @return array
|
||||||
|
* @throws RuntimeException When failed to fetch credentials.
|
||||||
|
*/
|
||||||
|
private function get_credentials( string $shared_id, string $auth_code, bool $use_sandbox ) : array {
|
||||||
|
$login_handler = $this->login_endpoint->get_value( $use_sandbox );
|
||||||
|
$nonce = $this->referrals_data->nonce();
|
||||||
|
|
||||||
|
$response = $login_handler->credentials_for( $shared_id, $auth_code, $nonce );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'client_id' => (string) ( $response->client_id ?? '' ),
|
||||||
|
'client_secret' => (string) ( $response->client_secret ?? '' ),
|
||||||
|
'merchant_id' => (string) ( $response->payer_id ?? '' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the provided details in the data model.
|
||||||
|
*
|
||||||
|
* @param MerchantConnectionDTO $connection Connection details to persist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function update_connection_details( MerchantConnectionDTO $connection ) : void {
|
||||||
|
$this->logger->info(
|
||||||
|
'Updating connection details',
|
||||||
|
(array) $connection
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->common_settings->set_merchant_data( $connection );
|
||||||
|
$this->common_settings->save();
|
||||||
|
|
||||||
|
if ( $this->common_settings->is_merchant_connected() ) {
|
||||||
|
$this->logger->info( 'Merchant successfully connected to PayPal' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast that the plugin connected to a new PayPal merchant account.
|
||||||
|
* This is the right time to initialize merchant relative flags for the
|
||||||
|
* first time.
|
||||||
|
*/
|
||||||
|
do_action( 'woocommerce_paypal_payments_authenticated_merchant' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,9 +12,9 @@ namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||||
|
|
||||||
// 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;
|
||||||
|
@ -26,9 +26,9 @@ class ConnectionUrlGenerator {
|
||||||
/**
|
/**
|
||||||
* The partner referrals endpoint.
|
* The partner referrals endpoint.
|
||||||
*
|
*
|
||||||
* @var PartnerReferrals
|
* @var EnvironmentConfig<PartnerReferrals>
|
||||||
*/
|
*/
|
||||||
protected PartnerReferrals $partner_referrals;
|
protected EnvironmentConfig $partner_referrals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default partner referrals data.
|
* The default partner referrals data.
|
||||||
|
@ -44,13 +44,6 @@ class ConnectionUrlGenerator {
|
||||||
*/
|
*/
|
||||||
protected OnboardingUrlManager $url_manager;
|
protected OnboardingUrlManager $url_manager;
|
||||||
|
|
||||||
/**
|
|
||||||
* Which environment is used for the connection URL.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected string $environment = '';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The logger
|
* The logger
|
||||||
*
|
*
|
||||||
|
@ -63,36 +56,23 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* Initializes the cache and logger properties of the class.
|
* Initializes the cache and logger properties of the class.
|
||||||
*
|
*
|
||||||
* @param PartnerReferrals $partner_referrals PartnerReferrals for URL generation.
|
* @param EnvironmentConfig $partner_referrals PartnerReferrals for URL generation.
|
||||||
* @param PartnerReferralsData $referrals_data Default partner referrals data.
|
* @param PartnerReferralsData $referrals_data Default partner referrals data.
|
||||||
* @param string $environment Environment that is used to generate the URL.
|
|
||||||
* ['production'|'sandbox'].
|
|
||||||
* @param OnboardingUrlManager $url_manager Manages access to OnboardingUrl instances.
|
* @param OnboardingUrlManager $url_manager Manages access to OnboardingUrl instances.
|
||||||
* @param ?LoggerInterface $logger The logger object for logging messages.
|
* @param ?LoggerInterface $logger The logger object for logging messages.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
PartnerReferrals $partner_referrals,
|
EnvironmentConfig $partner_referrals,
|
||||||
PartnerReferralsData $referrals_data,
|
PartnerReferralsData $referrals_data,
|
||||||
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->environment = $environment;
|
|
||||||
$this->url_manager = $url_manager;
|
$this->url_manager = $url_manager;
|
||||||
$this->logger = $logger ?: new NullLogger();
|
$this->logger = $logger ?: new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the environment for which the URL is being generated.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function environment() : string {
|
|
||||||
return $this->environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a PayPal onboarding URL for merchant sign-up.
|
* Generates a PayPal onboarding URL for merchant sign-up.
|
||||||
*
|
*
|
||||||
|
@ -102,11 +82,12 @@ class ConnectionUrlGenerator {
|
||||||
*
|
*
|
||||||
* @param array $products An array of product identifiers to include in the sign-up process.
|
* @param array $products An array of product identifiers to include in the sign-up process.
|
||||||
* These determine the PayPal onboarding experience.
|
* These determine the PayPal onboarding experience.
|
||||||
|
* @param bool $use_sandbox Whether to generate a sandbox URL.
|
||||||
*
|
*
|
||||||
* @return string The generated PayPal onboarding URL.
|
* @return string The generated PayPal onboarding URL.
|
||||||
*/
|
*/
|
||||||
public function generate( array $products = array() ) : string {
|
public function generate( array $products = array(), bool $use_sandbox = false ) : string {
|
||||||
$cache_key = $this->cache_key( $products );
|
$cache_key = $this->cache_key( $products, $use_sandbox );
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
$onboarding_url = $this->url_manager->get( $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 );
|
||||||
|
@ -119,7 +100,7 @@ class ConnectionUrlGenerator {
|
||||||
|
|
||||||
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
||||||
|
|
||||||
$url = $this->generate_new_url( $products, $onboarding_url, $cache_key );
|
$url = $this->generate_new_url( $use_sandbox, $products, $onboarding_url, $cache_key );
|
||||||
|
|
||||||
if ( $url ) {
|
if ( $url ) {
|
||||||
$this->persist_url( $onboarding_url, $url );
|
$this->persist_url( $onboarding_url, $url );
|
||||||
|
@ -132,14 +113,17 @@ class ConnectionUrlGenerator {
|
||||||
* Generates a cache key from the environment and sorted product array.
|
* Generates a cache key from the environment and sorted product array.
|
||||||
*
|
*
|
||||||
* @param array $products Product identifiers that are part of the cache key.
|
* @param array $products Product identifiers that are part of the cache key.
|
||||||
|
* @param bool $for_sandbox Whether the cache contains a sandbox URL.
|
||||||
*
|
*
|
||||||
* @return string The cache key, defining the product list and environment.
|
* @return string The cache key, defining the product list and environment.
|
||||||
*/
|
*/
|
||||||
protected function cache_key( array $products = array() ) : string {
|
protected function cache_key( array $products, bool $for_sandbox ) : string {
|
||||||
|
$environment = $for_sandbox ? 'sandbox' : 'production';
|
||||||
|
|
||||||
// Sort products alphabetically, to improve cache implementation.
|
// Sort products alphabetically, to improve cache implementation.
|
||||||
sort( $products );
|
sort( $products );
|
||||||
|
|
||||||
return $this->environment() . '-' . implode( '-', $products );
|
return $environment . '-' . implode( '-', $products );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,13 +152,14 @@ class ConnectionUrlGenerator {
|
||||||
/**
|
/**
|
||||||
* Generates a new URL.
|
* Generates a new URL.
|
||||||
*
|
*
|
||||||
|
* @param bool $for_sandbox Whether to generate a sandbox URL.
|
||||||
* @param array $products The products array.
|
* @param array $products The products array.
|
||||||
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
* @param OnboardingUrl $onboarding_url The OnboardingUrl object.
|
||||||
* @param string $cache_key The cache key.
|
* @param string $cache_key The cache key.
|
||||||
*
|
*
|
||||||
* @return string The generated URL or an empty string on failure.
|
* @return string The generated URL or an empty string on failure.
|
||||||
*/
|
*/
|
||||||
protected function generate_new_url( array $products, OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
protected function generate_new_url( bool $for_sandbox, array $products, OnboardingUrl $onboarding_url, string $cache_key ) : string {
|
||||||
$query_args = array( 'displayMode' => 'minibrowser' );
|
$query_args = array( 'displayMode' => 'minibrowser' );
|
||||||
$onboarding_url->init();
|
$onboarding_url->init();
|
||||||
|
|
||||||
|
@ -189,7 +174,8 @@ class ConnectionUrlGenerator {
|
||||||
$data = $this->prepare_referral_data( $products, $onboarding_token );
|
$data = $this->prepare_referral_data( $products, $onboarding_token );
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$url = $this->partner_referrals->signup_link( $data );
|
$referral = $this->partner_referrals->get_value( $for_sandbox );
|
||||||
|
$url = $referral->signup_link( $data );
|
||||||
} catch ( Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
$this->logger->warning( 'Could not generate an onboarding URL for: ' . $cache_key );
|
$this->logger->warning( 'Could not generate an onboarding URL for: ' . $cache_key );
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Settings;
|
namespace WooCommerce\PayPalCommerce\Settings;
|
||||||
|
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
|
||||||
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
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;
|
||||||
|
@ -86,7 +87,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
$endpoint = $container->get( 'settings.switch-ui.endpoint' ) ? $container->get( 'settings.switch-ui.endpoint' ) : null;
|
$endpoint = $container->get( 'settings.ajax.switch_ui' ) ? $container->get( 'settings.ajax.switch_ui' ) : null;
|
||||||
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
|
@ -203,6 +204,29 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_merchant_disconnected',
|
||||||
|
static function () use ( $container ) : void {
|
||||||
|
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
||||||
|
assert( $onboarding_profile instanceof OnboardingProfile );
|
||||||
|
|
||||||
|
$onboarding_profile->set_completed( false );
|
||||||
|
$onboarding_profile->set_step( 0 );
|
||||||
|
$onboarding_profile->save();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_authenticated_merchant',
|
||||||
|
static function () use ( $container ) : void {
|
||||||
|
$onboarding_profile = $container->get( 'settings.data.onboarding' );
|
||||||
|
assert( $onboarding_profile instanceof OnboardingProfile );
|
||||||
|
|
||||||
|
$onboarding_profile->set_completed( true );
|
||||||
|
$onboarding_profile->save();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2919,6 +2919,15 @@
|
||||||
sprintf-js "^1.1.1"
|
sprintf-js "^1.1.1"
|
||||||
tannin "^1.2.0"
|
tannin "^1.2.0"
|
||||||
|
|
||||||
|
"@wordpress/icons@^10.14.0":
|
||||||
|
version "10.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@wordpress/icons/-/icons-10.14.0.tgz#a27298b438653a9a502eb4ee3b02b42ce516da2e"
|
||||||
|
integrity sha512-4S1AaBeqvTpsTC23y0+4WPiSyz7j+b7vJ4vQ4nqnPeBF7ZeC8J/UXWQnEuKY38n8TiutXljgagkEqGNC9pF2Mw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "7.25.7"
|
||||||
|
"@wordpress/element" "*"
|
||||||
|
"@wordpress/primitives" "*"
|
||||||
|
|
||||||
"@wordpress/is-shallow-equal@*":
|
"@wordpress/is-shallow-equal@*":
|
||||||
version "5.11.0"
|
version "5.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@wordpress/is-shallow-equal/-/is-shallow-equal-5.11.0.tgz#2f273d6d4de24a66a7a8316b770cf832d22bfc37"
|
resolved "https://registry.yarnpkg.com/@wordpress/is-shallow-equal/-/is-shallow-equal-5.11.0.tgz#2f273d6d4de24a66a7a8316b770cf832d22bfc37"
|
||||||
|
@ -2968,6 +2977,15 @@
|
||||||
resolved "https://registry.yarnpkg.com/@wordpress/prettier-config/-/prettier-config-4.11.0.tgz#6b3f9aa7e2698c0d78e644037c6778b5c1da12ce"
|
resolved "https://registry.yarnpkg.com/@wordpress/prettier-config/-/prettier-config-4.11.0.tgz#6b3f9aa7e2698c0d78e644037c6778b5c1da12ce"
|
||||||
integrity sha512-Aoc8+xWOyiXekodjaEjS44z85XK877LzHZqsQuhC0kNgneDLrKkwI5qNgzwzAMbJ9jI58MPqVISCOX0bDLUPbw==
|
integrity sha512-Aoc8+xWOyiXekodjaEjS44z85XK877LzHZqsQuhC0kNgneDLrKkwI5qNgzwzAMbJ9jI58MPqVISCOX0bDLUPbw==
|
||||||
|
|
||||||
|
"@wordpress/primitives@*":
|
||||||
|
version "4.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@wordpress/primitives/-/primitives-4.14.0.tgz#1769f45bc541fd48be2d57626a9f6bdece39942a"
|
||||||
|
integrity sha512-IZibRVbvWoIQ+uynH0N5bmfWz83hD8lJj6jJFhSFuALK+4U5mRGg6tl0ZV0YllR6cjheD9UhTmfrAcOx+gQAjA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "7.25.7"
|
||||||
|
"@wordpress/element" "*"
|
||||||
|
clsx "^2.1.1"
|
||||||
|
|
||||||
"@wordpress/priority-queue@*":
|
"@wordpress/priority-queue@*":
|
||||||
version "3.11.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@wordpress/priority-queue/-/priority-queue-3.11.0.tgz#01e1570a7a29372bb1d07cd22fd9cbc5b5d03b09"
|
resolved "https://registry.yarnpkg.com/@wordpress/priority-queue/-/priority-queue-3.11.0.tgz#01e1570a7a29372bb1d07cd22fd9cbc5b5d03b09"
|
||||||
|
@ -3976,6 +3994,11 @@ clone-deep@^4.0.1:
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
shallow-clone "^3.0.0"
|
shallow-clone "^3.0.0"
|
||||||
|
|
||||||
|
clsx@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||||
|
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||||
|
|
||||||
co@^4.6.0:
|
co@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
|
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
||||||
namespace WooCommerce\PayPalCommerce\Uninstall;
|
namespace WooCommerce\PayPalCommerce\Uninstall;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
|
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
|
||||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint;
|
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||||
use WooCommerce\PayPalCommerce\Uninstall\Assets\ClearDatabaseAssets;
|
use WooCommerce\PayPalCommerce\Uninstall\Assets\ClearDatabaseAssets;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||||
|
|
79
modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php
Normal file
79
modules/ppcp-wc-gateway/src/Helper/EnvironmentConfig.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Provides a type-safe configuration container for managing environment-specific values.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\WcGateway\Helper
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config class that can store and provide values for a production- and sandbox-environment.
|
||||||
|
*
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
class EnvironmentConfig {
|
||||||
|
/**
|
||||||
|
* Value for the production environment.
|
||||||
|
*
|
||||||
|
* @var T
|
||||||
|
*/
|
||||||
|
private $production_value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value for the sandbox environment.
|
||||||
|
*
|
||||||
|
* @var T
|
||||||
|
*/
|
||||||
|
private $sandbox_value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* Used by the`::create()` method after validating the data types of both values.
|
||||||
|
*
|
||||||
|
* @param T $production_value The value for the live environment.
|
||||||
|
* @param T $sandbox_value The value for the sandbox environment.
|
||||||
|
*/
|
||||||
|
private function __construct(
|
||||||
|
$production_value,
|
||||||
|
$sandbox_value
|
||||||
|
) {
|
||||||
|
$this->production_value = $production_value;
|
||||||
|
$this->sandbox_value = $sandbox_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a validated EnvironmentConfig.
|
||||||
|
*
|
||||||
|
* @template U
|
||||||
|
* @param string $data_type Expected type for the values (class name or primitive type).
|
||||||
|
* @param U $production_value Value for production environment.
|
||||||
|
* @param U $sandbox_value Value for the sandbox environment.
|
||||||
|
* @return self<U>
|
||||||
|
*/
|
||||||
|
public static function create( string $data_type, $production_value, $sandbox_value ) : self {
|
||||||
|
assert(
|
||||||
|
gettype( $production_value ) === $data_type || $production_value instanceof $data_type,
|
||||||
|
"Production value must be of type '$data_type'"
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
gettype( $sandbox_value ) === $data_type || $sandbox_value instanceof $data_type,
|
||||||
|
"Sandbox value must be of type '$data_type'"
|
||||||
|
);
|
||||||
|
|
||||||
|
return new self( $production_value, $sandbox_value );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value for the specified environment.
|
||||||
|
*
|
||||||
|
* @param bool $for_sandbox Whether to get the sandbox value.
|
||||||
|
* @return T The value for the specified environment.
|
||||||
|
*/
|
||||||
|
public function get_value( bool $for_sandbox = false ) {
|
||||||
|
return $for_sandbox ? $this->sandbox_value : $this->production_value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -550,16 +550,12 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
||||||
|
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_paypal_payments_rest_common_merchant_data',
|
'woocommerce_paypal_payments_rest_common_merchant_data',
|
||||||
function( array $merchant_data ) use ( $c ): array {
|
function( array $features ) use ( $c ): array {
|
||||||
if ( ! isset( $merchant_data['features'] ) ) {
|
|
||||||
$merchant_data['features'] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
||||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||||
|
|
||||||
$reference_transactions_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
$reference_transactions_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
||||||
$merchant_data['features']['save_paypal_and_venmo'] = array(
|
$features['save_paypal_and_venmo'] = array(
|
||||||
'enabled' => $reference_transactions_enabled,
|
'enabled' => $reference_transactions_enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -567,11 +563,11 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
||||||
assert( $dcc_product_status instanceof DCCProductStatus );
|
assert( $dcc_product_status instanceof DCCProductStatus );
|
||||||
|
|
||||||
$dcc_enabled = $dcc_product_status->dcc_is_active();
|
$dcc_enabled = $dcc_product_status->dcc_is_active();
|
||||||
$merchant_data['features']['advanced_credit_and_debit_cards'] = array(
|
$features['advanced_credit_and_debit_cards'] = array(
|
||||||
'enabled' => $dcc_enabled,
|
'enabled' => $dcc_enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $merchant_data;
|
return $features;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue