mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge pull request #2974 from woocommerce/PCP-4092-fix-settings-errors-on-trunk
Fix settings errors on trunk (4092)
This commit is contained in:
commit
fa5c325224
10 changed files with 201 additions and 84 deletions
|
@ -116,19 +116,13 @@ return array(
|
|||
return 'WC-';
|
||||
},
|
||||
'api.bearer' => static function ( ContainerInterface $container ): Bearer {
|
||||
$cache = new Cache( 'ppcp-paypal-bearer' );
|
||||
$key = $container->get( 'api.key' );
|
||||
$secret = $container->get( 'api.secret' );
|
||||
$host = $container->get( 'api.host' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
return new PayPalBearer(
|
||||
$cache,
|
||||
$host,
|
||||
$key,
|
||||
$secret,
|
||||
$logger,
|
||||
$settings
|
||||
$container->get( 'api.paypal-bearer-cache' ),
|
||||
$container->get( 'api.host' ),
|
||||
$container->get( 'api.key' ),
|
||||
$container->get( 'api.secret' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
$container->get( 'wcgateway.settings' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.partners' => static function ( ContainerInterface $container ) : PartnersEndpoint {
|
||||
|
@ -840,6 +834,9 @@ return array(
|
|||
$container->get( 'wcgateway.settings' )
|
||||
);
|
||||
},
|
||||
'api.paypal-bearer-cache' => static function( ContainerInterface $container ): Cache {
|
||||
return new Cache( 'ppcp-paypal-bearer' );
|
||||
},
|
||||
'api.client-credentials-cache' => static function( ContainerInterface $container ): Cache {
|
||||
return new Cache( 'ppcp-client-credentials-cache' );
|
||||
},
|
||||
|
|
|
@ -20,6 +20,8 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameI
|
|||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
|
||||
|
||||
/**
|
||||
* Class ApiModule
|
||||
|
@ -103,6 +105,34 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
|||
2
|
||||
);
|
||||
|
||||
/**
|
||||
* Flushes the API client caches.
|
||||
*/
|
||||
add_action(
|
||||
'woocommerce_paypal_payments_flush_api_cache',
|
||||
static function () use ( $c ) {
|
||||
$caches = array(
|
||||
'api.paypal-bearer-cache' => array(
|
||||
PayPalBearer::CACHE_KEY,
|
||||
),
|
||||
'api.client-credentials-cache' => array(
|
||||
SdkClientToken::CACHE_KEY,
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( $caches as $cache_id => $keys ) {
|
||||
$cache = $c->get( $cache_id );
|
||||
assert( $cache instanceof Cache );
|
||||
|
||||
foreach ( $keys as $key ) {
|
||||
if ( $cache->has( $key ) ) {
|
||||
$cache->delete( $key );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { todosData } from '../../../data/settings/tab-overview-todos-data';
|
|||
const TabOverview = () => {
|
||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||
|
||||
const { merchantFeatures } = useMerchantInfo();
|
||||
const { merchant, merchantFeatures } = useMerchantInfo();
|
||||
const { refreshFeatureStatuses, setActiveModal } =
|
||||
useDispatch( STORE_NAME );
|
||||
|
||||
|
|
|
@ -1,36 +1,46 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { CommonHooks } from '../../../../../../data';
|
||||
import { Title } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
|
||||
const HooksTableBlock = () => {
|
||||
const { webhooks } = CommonHooks.useWebhooks();
|
||||
const { url, events } = webhooks;
|
||||
|
||||
if ( ! url || ! events?.length ) {
|
||||
return <div>...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="ppcp-r-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="ppcp-r-table__hooks-url">
|
||||
{ __( 'URL', 'woocommerce-paypal-payments' ) }
|
||||
</th>
|
||||
<th className="ppcp-r-table__hooks-events">
|
||||
{ __(
|
||||
'Tracked events',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ppcp-r-table__hooks-url">
|
||||
{ webhooks?.url }
|
||||
</td>
|
||||
<td
|
||||
className="ppcp-r-table__hooks-events"
|
||||
dangerouslySetInnerHTML={ { __html: webhooks?.events } }
|
||||
></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<>
|
||||
<WebhookUrl url={ url } />
|
||||
<WebhookEvents events={ events } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const WebhookUrl = ( { url } ) => {
|
||||
return (
|
||||
<div>
|
||||
<Title>
|
||||
{ __( 'Notification URL', 'woocommerce-paypal-payments' ) }
|
||||
</Title>
|
||||
<p>{ url }</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WebhookEvents = ( { events } ) => {
|
||||
return (
|
||||
<div>
|
||||
<Title>
|
||||
{ __( 'Subscribed Events', 'woocommerce-paypal-payments' ) }
|
||||
</Title>
|
||||
<ul>
|
||||
{ events.map( ( event, index ) => (
|
||||
<li key={ index }>{ event }</li>
|
||||
) ) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ const Troubleshooting = ( { updateFormValue, settings } ) => {
|
|||
<SettingsBlock>
|
||||
<Header>
|
||||
<Title>
|
||||
{ __(
|
||||
'Subscribed PayPal webhooks',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
{ __( 'Webhooks', 'woocommerce-paypal-payments' ) }
|
||||
</Title>
|
||||
<Description>
|
||||
{ __(
|
||||
|
|
|
@ -23,7 +23,9 @@ export const resolvers = {
|
|||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const webhooks = yield apiFetch( { path: REST_WEBHOOKS } );
|
||||
|
||||
result.data = { ...result.data, ...webhooks.data };
|
||||
if ( webhooks.success && webhooks.data ) {
|
||||
result.webhooks = webhooks.data;
|
||||
}
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
|
|
|
@ -156,6 +156,7 @@ return array(
|
|||
$page_id,
|
||||
$container->get( 'settings.service.onboarding-url-manager' ),
|
||||
$container->get( 'settings.service.authentication_manager' ),
|
||||
$container->get( 'http.redirector' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
|
|
@ -9,12 +9,14 @@ declare( strict_types = 1 );
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use stdClass;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
use Throwable;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class WebhookSettingsEndpoint
|
||||
|
@ -60,8 +62,9 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
/**
|
||||
* WebhookSettingsEndpoint constructor.
|
||||
*
|
||||
* @param WebhookEndpoint $webhook_endpoint A list of subscribed webhooks and a webhook endpoint URL.
|
||||
* @param WebhookRegistrar $webhook_registrar A service that allows resubscribing webhooks.
|
||||
* @param WebhookEndpoint $webhook_endpoint A list of subscribed webhooks and a webhook
|
||||
* endpoint URL.
|
||||
* @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 ) {
|
||||
|
@ -73,7 +76,7 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
/**
|
||||
* Configure REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
public function register_routes() : void {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
|
@ -114,27 +117,28 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_webhooks(): WP_REST_Response {
|
||||
try {
|
||||
$webhook_list = ( $this->webhook_endpoint->list() )[0];
|
||||
$webhook_events = array_map(
|
||||
static function ( stdClass $webhook ) {
|
||||
return strtolower( $webhook->name );
|
||||
},
|
||||
$webhook_list->event_types()
|
||||
);
|
||||
|
||||
return $this->return_success(
|
||||
array(
|
||||
'webhooks' => array(
|
||||
'url' => $webhook_list->url(),
|
||||
'events' => implode( ', ', $webhook_events ),
|
||||
),
|
||||
)
|
||||
);
|
||||
} catch ( \Exception $error ) {
|
||||
return $this->return_error( 'Problem while fetching webhooks data' );
|
||||
public function get_webhooks() : WP_REST_Response {
|
||||
$webhooks = $this->get_webhook_data();
|
||||
if ( ! $webhooks ) {
|
||||
return $this->return_error( 'No webhooks found.' );
|
||||
}
|
||||
|
||||
try {
|
||||
$webhook_url = $webhooks->url();
|
||||
$webhook_events = array_map(
|
||||
static fn( stdClass $webhooks ) => strtolower( $webhooks->name ),
|
||||
$webhooks->event_types()
|
||||
);
|
||||
} catch ( Throwable $error ) {
|
||||
return $this->return_error( $error->getMessage() );
|
||||
}
|
||||
|
||||
return $this->return_success(
|
||||
array(
|
||||
'url' => $webhook_url,
|
||||
'events' => $webhook_events,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,10 +146,11 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function resubscribe_webhooks(): WP_REST_Response {
|
||||
public function resubscribe_webhooks() : WP_REST_Response {
|
||||
if ( ! $this->webhook_registrar->register() ) {
|
||||
return $this->return_error( 'Webhook subscription failed.' );
|
||||
}
|
||||
|
||||
return $this->get_webhooks();
|
||||
}
|
||||
|
||||
|
@ -154,9 +159,10 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function simulate_webhooks_start(): WP_REST_Response {
|
||||
public function simulate_webhooks_start() : WP_REST_Response {
|
||||
try {
|
||||
$this->webhook_simulation->start();
|
||||
|
||||
return $this->return_success( array() );
|
||||
} catch ( \Exception $error ) {
|
||||
return $this->return_error( $error->getMessage() );
|
||||
|
@ -168,18 +174,32 @@ class WebhookSettingsEndpoint extends RestEndpoint {
|
|||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function check_simulated_webhook_state(): WP_REST_Response {
|
||||
public function check_simulated_webhook_state() : WP_REST_Response {
|
||||
try {
|
||||
$state = $this->webhook_simulation->get_state();
|
||||
|
||||
return $this->return_success(
|
||||
array(
|
||||
'state' => $state,
|
||||
)
|
||||
array( 'state' => $state )
|
||||
);
|
||||
|
||||
} catch ( \Exception $error ) {
|
||||
return $this->return_error( $error->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the Webhooks API response object.
|
||||
*
|
||||
* @return Webhook|null The webhook data instance, or null.
|
||||
*/
|
||||
private function get_webhook_data() : ?Webhook {
|
||||
try {
|
||||
$api_response = $this->webhook_endpoint->list();
|
||||
|
||||
return $api_response[0] ?? null;
|
||||
} catch ( Throwable $error ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ use RuntimeException;
|
|||
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
|
||||
|
||||
/**
|
||||
* Provides a listener that handles merchant-connection requests.
|
||||
|
@ -46,6 +48,14 @@ class ConnectionListener {
|
|||
*/
|
||||
private AuthenticationManager $authentication_manager;
|
||||
|
||||
/**
|
||||
* A redirector-instance to redirect the merchant after authentication.
|
||||
* ™
|
||||
*
|
||||
* @var RedirectorInterface
|
||||
*/
|
||||
private RedirectorInterface $redirector;
|
||||
|
||||
/**
|
||||
* Logger instance, mainly used for debugging purposes.
|
||||
*
|
||||
|
@ -66,17 +76,20 @@ class ConnectionListener {
|
|||
* @param string $settings_page_id Current plugin settings page ID.
|
||||
* @param OnboardingUrlManager $url_manager Get OnboardingURL instances.
|
||||
* @param AuthenticationManager $authentication_manager Authentication manager service.
|
||||
* @param RedirectorInterface $redirector Redirect-handler.
|
||||
* @param ?LoggerInterface $logger The logger, for debugging purposes.
|
||||
*/
|
||||
public function __construct(
|
||||
string $settings_page_id,
|
||||
OnboardingUrlManager $url_manager,
|
||||
AuthenticationManager $authentication_manager,
|
||||
RedirectorInterface $redirector,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->settings_page_id = $settings_page_id;
|
||||
$this->url_manager = $url_manager;
|
||||
$this->authentication_manager = $authentication_manager;
|
||||
$this->redirector = $redirector;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
|
||||
// Initialize as "guest", the real ID is provided via process().
|
||||
|
@ -115,6 +128,8 @@ class ConnectionListener {
|
|||
} catch ( \Exception $e ) {
|
||||
$this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
|
||||
}
|
||||
|
||||
$this->redirect_after_authentication();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +140,7 @@ class ConnectionListener {
|
|||
*
|
||||
* @return bool True, if the request contains valid connection details.
|
||||
*/
|
||||
protected function is_valid_request( array $request ) : bool {
|
||||
private function is_valid_request( array $request ) : bool {
|
||||
if ( $this->user_id < 1 || ! $this->settings_page_id ) {
|
||||
return false;
|
||||
}
|
||||
|
@ -157,7 +172,7 @@ class ConnectionListener {
|
|||
* @return array Structured array with 'is_sandbox', 'merchant_id', and 'merchant_email' keys,
|
||||
* or an empty array on failure.
|
||||
*/
|
||||
protected function extract_data( array $request ) : array {
|
||||
private function extract_data( array $request ) : array {
|
||||
$this->logger->info( 'Extracting connection data from request...' );
|
||||
|
||||
$merchant_id = $this->get_merchant_id_from_request( $request );
|
||||
|
@ -173,6 +188,17 @@ class ConnectionListener {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the browser page at the end of the authentication flow.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function redirect_after_authentication() : void {
|
||||
$redirect_url = $this->get_onboarding_redirect_url();
|
||||
|
||||
$this->redirector->redirect( $redirect_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sanitized connection token from the incoming request.
|
||||
*
|
||||
|
@ -180,7 +206,7 @@ class ConnectionListener {
|
|||
*
|
||||
* @return string The sanitized token, or an empty string.
|
||||
*/
|
||||
protected function get_token_from_request( array $request ) : string {
|
||||
private function get_token_from_request( array $request ) : string {
|
||||
return $this->sanitize_string( $request['ppcpToken'] ?? '' );
|
||||
}
|
||||
|
||||
|
@ -191,7 +217,7 @@ class ConnectionListener {
|
|||
*
|
||||
* @return string The sanitized merchant ID, or an empty string.
|
||||
*/
|
||||
protected function get_merchant_id_from_request( array $request ) : string {
|
||||
private function get_merchant_id_from_request( array $request ) : string {
|
||||
return $this->sanitize_string( $request['merchantIdInPayPal'] ?? '' );
|
||||
}
|
||||
|
||||
|
@ -206,7 +232,7 @@ class ConnectionListener {
|
|||
*
|
||||
* @return string The sanitized merchant email, or an empty string.
|
||||
*/
|
||||
protected function get_merchant_email_from_request( array $request ) : string {
|
||||
private function get_merchant_email_from_request( array $request ) : string {
|
||||
return $this->sanitize_merchant_email( $request['merchantId'] ?? '' );
|
||||
}
|
||||
|
||||
|
@ -217,7 +243,7 @@ class ConnectionListener {
|
|||
*
|
||||
* @return string Sanitized value.
|
||||
*/
|
||||
protected function sanitize_string( string $value ) : string {
|
||||
private function sanitize_string( string $value ) : string {
|
||||
return trim( sanitize_text_field( wp_unslash( $value ) ) );
|
||||
}
|
||||
|
||||
|
@ -228,7 +254,22 @@ class ConnectionListener {
|
|||
*
|
||||
* @return string Sanitized email address.
|
||||
*/
|
||||
protected function sanitize_merchant_email( string $email ) : string {
|
||||
private function sanitize_merchant_email( string $email ) : string {
|
||||
return sanitize_text_field( str_replace( ' ', '+', $email ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL opened at the end of onboarding.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_onboarding_redirect_url() : string {
|
||||
/**
|
||||
* The URL opened at the end of onboarding after saving the merchant ID/email.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_onboarding_redirect_url',
|
||||
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=' . Settings::CONNECTION_TAB_ID )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
|
||||
/**
|
||||
* Class that manages the connection to PayPal.
|
||||
|
@ -115,6 +116,12 @@ class AuthenticationManager {
|
|||
* modules to clean up merchant-related details, such as eligibility flags.
|
||||
*/
|
||||
do_action( 'woocommerce_paypal_payments_merchant_disconnected' );
|
||||
|
||||
/**
|
||||
* Request to flush caches after disconnecting the merchant. While there
|
||||
* is no need for it here, it's good house-keeping practice to clean up.
|
||||
*/
|
||||
do_action( 'woocommerce_paypal_payments_flush_api_cache' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,12 +406,24 @@ class AuthenticationManager {
|
|||
if ( $this->common_settings->is_merchant_connected() ) {
|
||||
$this->logger->info( 'Merchant successfully connected to PayPal' );
|
||||
|
||||
/**
|
||||
* Request to flush caches before authenticating the merchant, to
|
||||
* ensure the new merchant does not use stale data from previous
|
||||
* connections.
|
||||
*/
|
||||
do_action( 'woocommerce_paypal_payments_flush_api_cache' );
|
||||
|
||||
/**
|
||||
* 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' );
|
||||
|
||||
/**
|
||||
* Subscribe the new merchant to relevant PayPal webhooks.
|
||||
*/
|
||||
do_action( WebhookRegistrar::EVENT_HOOK );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue