Merge pull request #3097 from woocommerce/PCP-4183-manual-connection-credentials-are-not-working

Manual Connection credentials are not working (4183)
This commit is contained in:
Emili Castells 2025-02-12 16:47:28 +01:00 committed by GitHub
commit 8258c1c993
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 87 additions and 46 deletions

View file

@ -18,6 +18,7 @@ const DataStoreControl = React.forwardRef(
control: ControlComponent,
value: externalValue,
onChange,
onConfirm = null,
delay = 300,
...props
},
@ -25,7 +26,9 @@ const DataStoreControl = React.forwardRef(
) => {
const [ internalValue, setInternalValue ] = useState( externalValue );
const onChangeRef = useRef( onChange );
const onConfirmRef = useRef( onConfirm );
onChangeRef.current = onChange;
onConfirmRef.current = onConfirm;
const debouncedUpdate = useRef(
debounce( ( value ) => {
@ -36,7 +39,7 @@ const DataStoreControl = React.forwardRef(
useEffect( () => {
setInternalValue( externalValue );
debouncedUpdate?.cancel();
}, [ externalValue ] );
}, [ debouncedUpdate, externalValue ] );
useEffect( () => {
return () => debouncedUpdate?.cancel();
@ -50,12 +53,25 @@ const DataStoreControl = React.forwardRef(
[ debouncedUpdate ]
);
const handleKeyDown = useCallback(
( event ) => {
if ( onConfirmRef.current && event.key === 'Enter' ) {
event.preventDefault();
debouncedUpdate.flush();
onConfirmRef.current();
return false;
}
},
[ debouncedUpdate ]
);
return (
<ControlComponent
ref={ ref }
{ ...props }
value={ internalValue }
onChange={ handleChange }
onKeyDown={ handleKeyDown }
/>
);
}

View file

@ -157,6 +157,7 @@ const ManualConnectionForm = () => {
label={ clientIdLabel }
value={ manualClientId }
onChange={ setManualClientId }
onConfirm={ handleManualConnect }
className={ classNames( {
'ppcp--has-error': ! clientValid,
} ) }
@ -173,6 +174,7 @@ const ManualConnectionForm = () => {
label={ secretKeyLabel }
value={ manualClientSecret }
onChange={ setManualClientSecret }
onConfirm={ handleManualConnect }
type="password"
/>
<Button

View file

@ -12,6 +12,7 @@ export default {
SET_PERSISTENT: 'ppcp/common/SET_PERSISTENT',
RESET: 'ppcp/common/RESET',
HYDRATE: 'ppcp/common/HYDRATE',
SET_MERCHANT: 'ppcp/common/SET_MERCHANT',
RESET_MERCHANT: 'ppcp/common/RESET_MERCHANT',
// Activity management (advanced solution that replaces the isBusy state).

View file

@ -110,6 +110,17 @@ export const setManualConnectionMode = ( useManualConnection ) =>
export const setWebhooks = ( webhooks ) =>
setPersistent( 'webhooks', webhooks );
/**
* Replace merchant details in the store.
*
* @param {Object} merchant - The new merchant details.
* @return {Action} The action.
*/
export const setMerchant = ( merchant ) => ( {
type: ACTION_TYPES.SET_MERCHANT,
payload: { merchant },
} );
/**
* Reset merchant details in the store.
*

View file

@ -141,18 +141,27 @@ export const useWebhooks = () => {
export const useMerchantInfo = () => {
const { isReady, features } = useHooks();
const merchant = useMerchant();
const { refreshMerchantData } = useDispatch( STORE_NAME );
const { refreshMerchantData, setMerchant } = useDispatch( STORE_NAME );
const verifyLoginStatus = useCallback( async () => {
const result = await refreshMerchantData();
if ( ! result.success ) {
if ( ! result.success || ! result.merchant ) {
throw new Error( result?.message || result?.error?.message );
}
const newMerchant = result.merchant;
// Verify if the server state is "connected" and we have a merchant ID.
return merchant?.isConnected && merchant?.id;
}, [ refreshMerchantData, merchant ] );
if ( newMerchant?.isConnected && newMerchant?.id ) {
// Update the verified merchant details in Redux.
setMerchant( newMerchant );
return true;
}
return false;
}, [ refreshMerchantData, setMerchant ] );
return {
isReady,
@ -225,6 +234,8 @@ export const useBusyState = () => {
);
return {
startActivity,
stopActivity,
withActivity, // HOC
isBusy, // Boolean.
};

View file

@ -114,6 +114,10 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
features: Object.freeze( { ...defaultTransient.features } ),
} ),
[ ACTION_TYPES.SET_MERCHANT ]: ( state, payload ) => {
return changePersistent( state, { merchant: payload.merchant } );
},
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
const newState = changePersistent( state, payload.data );

View file

@ -10,19 +10,7 @@ const PAYPAL_PARTNER_SDK_URL =
const MESSAGES = {
CONNECTED: __( 'Connected to PayPal', 'woocommerce-paypal-payments' ),
POPUP_BLOCKED: __(
'Popup blocked. Please allow popups for this site to connect to PayPal.',
'woocommerce-paypal-payments'
),
SANDBOX_ERROR: __(
'Could not generate a Sandbox login link.',
'woocommerce-paypal-payments'
),
PRODUCTION_ERROR: __(
'Could not generate a login link.',
'woocommerce-paypal-payments'
),
MANUAL_ERROR: __(
API_ERROR: __(
'Could not connect to PayPal. Please make sure your Client ID and Secret Key are correct.',
'woocommerce-paypal-payments'
),
@ -33,17 +21,16 @@ const MESSAGES = {
};
const ACTIVITIES = {
CONNECT_SANDBOX: 'ISU_LOGIN_SANDBOX',
CONNECT_PRODUCTION: 'ISU_LOGIN_PRODUCTION',
CONNECT_ISU: 'ISU_LOGIN',
CONNECT_MANUAL: 'MANUAL_LOGIN',
OAUTH_VERIFY: 'oauth/login',
API_LOGIN: 'auth/api-login',
API_VERIFY: 'auth/verify-login',
};
export const useHandleOnboardingButton = ( isSandbox ) => {
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
const { productionOnboardingUrl } = CommonHooks.useProduction();
const products = OnboardingHooks.useDetermineProducts();
const { withActivity } = CommonHooks.useBusyState();
const { withActivity, startActivity } = CommonHooks.useBusyState();
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
const [ scriptLoaded, setScriptLoaded ] = useState( false );
@ -123,16 +110,15 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
* 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
);
}
startActivity(
ACTIVITIES.OAUTH_VERIFY,
'Validating the connection details'
);
await authenticateWithOAuth(
sharedId,
authCode,
'sandbox' === environment
);
};
@ -168,11 +154,13 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
};
};
// Base connection is only used for API login (manual connection).
const useConnectionBase = () => {
const { setCompleted } = OnboardingHooks.useSteps();
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );
const { verifyLoginStatus } = CommonHooks.useMerchantInfo();
const { withActivity } = CommonHooks.useBusyState();
return {
handleFailed: ( res, genericMessage ) => {
@ -180,18 +168,26 @@ const useConnectionBase = () => {
createErrorNotice( res?.message ?? genericMessage );
},
handleCompleted: async () => {
try {
const loginSuccessful = await verifyLoginStatus();
await withActivity(
ACTIVITIES.API_VERIFY,
'Verifying Authentication',
async () => {
try {
const loginSuccessful = await verifyLoginStatus();
if ( loginSuccessful ) {
createSuccessNotice( MESSAGES.CONNECTED );
await setCompleted( true );
} else {
createErrorNotice( MESSAGES.LOGIN_FAILED );
if ( loginSuccessful ) {
createSuccessNotice( MESSAGES.CONNECTED );
await setCompleted( true );
} else {
createErrorNotice( MESSAGES.LOGIN_FAILED );
}
} catch ( error ) {
createErrorNotice(
error.message ?? MESSAGES.LOGIN_FAILED
);
}
}
} catch ( error ) {
createErrorNotice( error.message ?? MESSAGES.LOGIN_FAILED );
}
);
},
createErrorNotice,
};
@ -218,7 +214,7 @@ export const useDirectAuthentication = () => {
const handleDirectAuthentication = async ( connectionDetails ) => {
return withActivity(
ACTIVITIES.CONNECT_MANUAL,
ACTIVITIES.API_LOGIN,
'Connecting manually via Client ID and Secret',
async () => {
let data;
@ -250,7 +246,7 @@ export const useDirectAuthentication = () => {
if ( res.success ) {
await handleCompleted();
} else {
handleFailed( res, MESSAGES.MANUAL_ERROR );
handleFailed( res, MESSAGES.API_ERROR );
}
return res.success;

View file

@ -175,7 +175,7 @@ class AuthenticationRestEndpoint extends RestEndpoint {
}
$account = $this->authentication_manager->get_account_details();
$response = $this->sanitize_for_javascript( $this->response_map, $account );
$response = $this->sanitize_for_javascript( $account, $this->response_map );
return $this->return_success( $response );
}