Merge pull request #2985 from woocommerce/PCP-4096-allow-merchant-logout-disconnect

Allow merchant logout/disconnect (4096)
This commit is contained in:
Emili Castells 2025-01-14 13:02:19 +01:00 committed by GitHub
commit 60f722568e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 182 additions and 40 deletions

View file

@ -21,11 +21,11 @@ export default {
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
DO_DIRECT_API_AUTHENTICATION: 'COMMON:DO_DIRECT_API_AUTHENTICATION',
DO_OAUTH_AUTHENTICATION: 'COMMON:DO_OAUTH_AUTHENTICATION',
DO_DISCONNECT_MERCHANT: 'COMMON:DO_DISCONNECT_MERCHANT',
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
DO_START_WEBHOOK_SIMULATION: 'COMMON:DO_START_WEBHOOK_SIMULATION',
DO_CHECK_WEBHOOK_SIMULATION_STATE:
'COMMON:DO_CHECK_WEBHOOK_SIMULATION_STATE',
DO_CHECK_WEBHOOK_SIMULATION: 'COMMON:DO_CHECK_WEBHOOK_SIMULATION',
};

View file

@ -212,6 +212,15 @@ export const authenticateWithOAuth = function* (
};
};
/**
* Side effect. Checks webhook simulation.
*
* @return {Action} The action.
*/
export const disconnectMerchant = function* () {
return yield { type: ACTION_TYPES.DO_DISCONNECT_MERCHANT };
};
/**
* Side effect. Clears and refreshes the merchant data via a REST request.
*
@ -288,5 +297,5 @@ export const startWebhookSimulation = function* () {
* @return {Action} The action.
*/
export const checkWebhookSimulationState = function* () {
return yield { type: ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION_STATE };
return yield { type: ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION };
};

View file

@ -46,14 +46,26 @@ 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.
* REST path to perform the OAuth 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';
export const REST_OAUTH_AUTHENTICATION_PATH =
'/wc/v3/wc_paypal/authenticate/oauth';
/**
* REST path to disconnect the current merchant from PayPal.
*
* Used by: Controls
* See: AuthenticateRestEndpoint.php
*
* @type {string}
*/
export const REST_DISCONNECT_MERCHANT_PATH =
'/wc/v3/wc_paypal/authenticate/disconnect';
/**
* REST path to generate an ISU URL for the PayPal-login.
@ -66,24 +78,24 @@ export const REST_ISU_AUTHENTICATION_PATH = '/wc/v3/wc_paypal/authenticate/isu';
export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link';
/**
* REST path to fetch webhooks data or resubscribe webhooks,
* REST path to fetch webhooks data or resubscribe webhooks.
*
* Used by: Controls
* See: WebhookSettingsEndpoint.php
*
* @type {string}
*/
export const REST_WEBHOOKS = '/wc/v3/wc_paypal/webhook_settings';
export const REST_WEBHOOKS = '/wc/v3/wc_paypal/webhooks';
/**
* REST path to start webhook simulation and observe the state,
* REST path to start webhook simulation and observe the state.
*
* Used by: Controls
* See: WebhookSettingsEndpoint.php
*
* @type {string}
*/
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhook_simulate';
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhooks/simulate';
/**
* REST path to refresh the feature status.
@ -93,5 +105,4 @@ export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhook_simulate';
*
* @type {string}
*/
export const REST_REFRESH_FEATURES_PATH =
'/wc/v3/wc_paypal/refresh-feature-status';
export const REST_REFRESH_FEATURES_PATH = '/wc/v3/wc_paypal/refresh-features';

View file

@ -11,11 +11,12 @@ import apiFetch from '@wordpress/api-fetch';
import {
REST_PERSIST_PATH,
REST_DIRECT_AUTHENTICATION_PATH,
REST_CONNECTION_URL_PATH,
REST_HYDRATE_MERCHANT_PATH,
REST_REFRESH_FEATURES_PATH,
REST_ISU_AUTHENTICATION_PATH,
REST_DIRECT_AUTHENTICATION_PATH,
REST_OAUTH_AUTHENTICATION_PATH,
REST_DISCONNECT_MERCHANT_PATH,
REST_WEBHOOKS,
REST_WEBHOOKS_SIMULATE,
} from './constants';
@ -82,7 +83,7 @@ export const controls = {
} ) {
try {
return await apiFetch( {
path: REST_ISU_AUTHENTICATION_PATH,
path: REST_OAUTH_AUTHENTICATION_PATH,
method: 'POST',
data: {
sharedId,
@ -98,6 +99,13 @@ export const controls = {
}
},
async [ ACTION_TYPES.DO_DISCONNECT_MERCHANT ]() {
return await apiFetch( {
path: REST_DISCONNECT_MERCHANT_PATH,
method: 'POST',
} );
},
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
try {
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
@ -138,7 +146,7 @@ export const controls = {
} );
},
async [ ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION_STATE ]() {
async [ ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION ]() {
return await apiFetch( {
path: REST_WEBHOOKS_SIMULATE,
} );

View file

@ -77,6 +77,8 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
// Keep "read-only" details and initialization flags.
cleanState.wooSettings = { ...state.wooSettings };
cleanState.merchant = { ...state.merchant };
cleanState.features = { ...state.features };
cleanState.isReady = true;
return cleanState;

View file

@ -5,6 +5,7 @@ export const addDebugTools = ( context, modules ) => {
return;
}
// Dump the current state of all our Redux stores.
context.dumpStore = async () => {
/* eslint-disable no-console */
if ( ! console?.groupCollapsed ) {
@ -32,21 +33,46 @@ export const addDebugTools = ( context, modules ) => {
/* eslint-enable no-console */
};
// Reset all Redux stores to their initial state.
context.resetStore = () => {
const stores = [ OnboardingStoreName, CommonStoreName ];
const stores = [];
const { isConnected } = wp.data.select( CommonStoreName ).merchant();
if ( isConnected ) {
// Make sure the Onboarding wizard is "completed".
const onboarding = wp.data.dispatch( OnboardingStoreName );
onboarding.setCompleted( true );
onboarding.persist();
// Reset all stores, except for the onboarding store.
stores.push( CommonStoreName );
// TODO: Add other stores here once they are available.
} else {
// Only reset the common & onboarding stores to restart the onboarding wizard.
stores.push( CommonStoreName );
stores.push( OnboardingStoreName );
}
stores.forEach( ( storeName ) => {
const store = wp.data.dispatch( storeName );
// eslint-disable-next-line no-console
console.log( `Reset store: ${ storeName }...` );
store.reset();
store.persist();
} );
};
context.startOnboarding = () => {
const onboarding = wp.data.dispatch( OnboardingStoreName );
onboarding.setCompleted( false );
onboarding.setStep( 0 );
onboarding.persist();
// Disconnect the merchant and display the onboarding wizard.
context.disconnect = () => {
const common = wp.data.dispatch( CommonStoreName );
common.disconnectMerchant();
// eslint-disable-next-line no-console
console.log( 'Disconnected from PayPal. Reloading the page...' );
window.location.reload();
};
};

View file

@ -67,6 +67,14 @@ class AuthenticationRestEndpoint extends RestEndpoint {
* Configure REST API routes.
*/
public function register_routes() : void {
/**
* POST /wp-json/wc/v3/wc_paypal/authenticate/direct
* {
* clientId
* clientSecret
* useSandbox
* }
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/direct',
@ -97,12 +105,20 @@ class AuthenticationRestEndpoint extends RestEndpoint {
)
);
/**
* POST /wp-json/wc/v3/wc_paypal/authenticate/oauth
* {
* sharedId
* authCode
* useSandbox
* }
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/isu',
'/' . $this->rest_base . '/oauth',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connect_isu' ),
'callback' => array( $this, 'connect_oauth' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'sharedId' => array(
@ -123,6 +139,19 @@ class AuthenticationRestEndpoint extends RestEndpoint {
),
)
);
/**
* POST /wp-json/wc/v3/wc_paypal/authenticate/disconnect
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/disconnect',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'disconnect' ),
'permission_callback' => array( $this, 'check_permission' ),
)
);
}
/**
@ -152,14 +181,14 @@ class AuthenticationRestEndpoint extends RestEndpoint {
}
/**
* ISU login: Retrieves clientId and clientSecret using a sharedId and authCode.
* OAuth 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
* This is the final step in the UI-driven login via the OAuth 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 {
public function connect_oauth( 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' );
@ -176,4 +205,15 @@ class AuthenticationRestEndpoint extends RestEndpoint {
return $this->return_success( $response );
}
/**
* Disconnect the merchant and clear the authentication details.
*
* @return WP_REST_Response
*/
public function disconnect() : WP_REST_Response {
$this->authentication_manager->disconnect();
return $this->return_success( 'OK' );
}
}

View file

@ -110,7 +110,10 @@ class CommonRestEndpoint extends RestEndpoint {
/**
* Configure REST API routes.
*/
public function register_routes() {
public function register_routes() : void {
/**
* GET /wp-json/wc/v3/wc_paypal/common
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
@ -121,6 +124,12 @@ class CommonRestEndpoint extends RestEndpoint {
)
);
/**
* POST /wp-json/wc/v3/wc_paypal/common
* {
* // Fields mentioned in $field_map[]['js_name']
* }
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
@ -131,6 +140,9 @@ class CommonRestEndpoint extends RestEndpoint {
)
);
/**
* GET /wp-json/wc/v3/wc_paypal/common/merchant
*/
register_rest_route(
$this->namespace,
"/$this->rest_base/merchant",

View file

@ -52,6 +52,13 @@ class LoginLinkRestEndpoint extends RestEndpoint {
* Configure REST API routes.
*/
public function register_routes() : void {
/**
* POST /wp-json/wc/v3/wc_paypal/login_link
* {
* useSandbox
* products
* }
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,

View file

@ -96,7 +96,10 @@ class OnboardingRestEndpoint extends RestEndpoint {
/**
* Configure REST API routes.
*/
public function register_routes() {
public function register_routes() : void {
/**
* GET /wp-json/wc/v3/wc_paypal/onboarding
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
@ -107,6 +110,12 @@ class OnboardingRestEndpoint extends RestEndpoint {
)
);
/**
* POST /wp-json/wc/v3/wc_paypal/onboarding
* {
* // Fields mentioned in $field_map[]['js_name']
* }
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,

View file

@ -25,7 +25,7 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
*
* @var string
*/
protected $rest_base = 'refresh-feature-status';
protected $rest_base = 'refresh-features';
/**
* Cache timeout in seconds.
@ -82,7 +82,10 @@ class RefreshFeatureStatusEndpoint extends RestEndpoint {
/**
* Configure REST API routes.
*/
public function register_routes() {
public function register_routes() : void {
/**
* POST /wp-json/wc/v3/wc_paypal/refresh-features
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,

View file

@ -29,14 +29,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
*
* @var string
*/
protected $rest_base = 'webhook_settings';
/**
* Endpoint base to start webhook simulation and check the state
*
* @var string
*/
protected string $rest_simulate_base = 'webhook_simulate';
protected $rest_base = 'webhooks';
/**
* Application webhook endpoint
@ -67,7 +60,11 @@ class WebhookSettingsEndpoint extends RestEndpoint {
* @param WebhookRegistrar $webhook_registrar A service that allows resubscribing webhooks.
* @param WebhookSimulation $webhook_simulation A service that allows webhook simulations.
*/
public function __construct( WebhookEndpoint $webhook_endpoint, WebhookRegistrar $webhook_registrar, WebhookSimulation $webhook_simulation ) {
public function __construct(
WebhookEndpoint $webhook_endpoint,
WebhookRegistrar $webhook_registrar,
WebhookSimulation $webhook_simulation
) {
$this->webhook_endpoint = $webhook_endpoint;
$this->webhook_registrar = $webhook_registrar;
$this->webhook_simulation = $webhook_simulation;
@ -77,6 +74,10 @@ class WebhookSettingsEndpoint extends RestEndpoint {
* Configure REST API routes.
*/
public function register_routes() : void {
/**
* GET /wp-json/wc/v3/wc_paypal/webhooks
* POST /wp-json/wc/v3/wc_paypal/webhooks
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
@ -94,9 +95,13 @@ class WebhookSettingsEndpoint extends RestEndpoint {
)
);
/**
* GET /wp-json/wc/v3/wc_paypal/webhooks/simulate
* POST /wp-json/wc/v3/wc_paypal/webhooks/simulate
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_simulate_base,
'/' . $this->rest_base . '/simulate',
array(
array(
'methods' => WP_REST_Server::READABLE,

View file

@ -122,6 +122,11 @@ class AuthenticationManager {
* is no need for it here, it's good house-keeping practice to clean up.
*/
do_action( 'woocommerce_paypal_payments_flush_api_cache' );
/**
* Clear the APM eligibility flags from the default settings object.
*/
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', null );
}
/**
@ -420,6 +425,11 @@ class AuthenticationManager {
*/
do_action( 'woocommerce_paypal_payments_authenticated_merchant' );
/**
* Clear the APM eligibility flags from the default settings object.
*/
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', null );
/**
* Subscribe the new merchant to relevant PayPal webhooks.
*/