From e5159ec69bd45ccd92aead045fb1505610dc42af Mon Sep 17 00:00:00 2001 From: Himad M Date: Mon, 16 Dec 2024 13:12:38 +0100 Subject: [PATCH] New Settings UI: Implement logic for features and refresh button. --- .../ppcp-googlepay/src/GooglepayModule.php | 20 +++ .../SettingsBlocks/FeatureSettingsBlock.js | 2 +- .../Screens/Overview/TabOverview.js | 84 +++++++++-- .../resources/js/data/common/action-types.js | 1 + .../resources/js/data/common/actions.js | 11 ++ .../resources/js/data/common/constants.js | 11 ++ .../resources/js/data/common/controls.js | 24 ++++ modules/ppcp-settings/services.php | 8 ++ .../src/Endpoint/CommonRestEndpoint.php | 5 + .../Endpoint/RefreshFeatureStatusEndpoint.php | 132 ++++++++++++++++++ modules/ppcp-settings/src/SettingsModule.php | 1 + 11 files changed, 283 insertions(+), 16 deletions(-) create mode 100644 modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index a13ebf498..01d5f8fae 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -232,6 +232,26 @@ function( array $locations, string $setting_name ): array { 2 ); + add_filter( + 'woocommerce_paypal_payments_rest_common_merchant_data', + function ( array $merchant_data ) use ( $c ): array { + if ( ! isset( $merchant_data['features'] ) ) { + $merchant_data['features'] = array(); + } + + $product_status = $c->get( 'googlepay.helpers.apm-product-status' ); + assert( $product_status instanceof ApmProductStatus ); + + $google_pay_enabled = $product_status->is_active(); + + $merchant_data['features']['google_pay'] = array( + 'enabled' => $google_pay_enabled, + ); + + return $merchant_data; + } + ); + return true; } } diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js index 7b6010de6..f2d66e9a3 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js @@ -31,7 +31,7 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
{ title } - { props.actionProps?.featureStatus && ( + { props.actionProps?.enabled && ( <TitleBadge { ...props.actionProps?.badge } /> diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js index fe3e64218..fb7fe30c6 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabOverview.js @@ -6,10 +6,42 @@ import TodoSettingsBlock from '../../ReusableComponents/SettingsBlocks/TodoSetti import FeatureSettingsBlock from '../../ReusableComponents/SettingsBlocks/FeatureSettingsBlock'; import { TITLE_BADGE_POSITIVE } from '../../ReusableComponents/TitleBadge'; import data from '../../../utils/data'; +import { useMerchantInfo } from '../../../data/common/hooks'; +import { useDispatch } from '@wordpress/data'; +import { STORE_NAME } from '../../../data/common'; const TabOverview = () => { const [ todos, setTodos ] = useState( [] ); const [ todosData, setTodosData ] = useState( todosDataDefault ); + const [ isRefreshing, setIsRefreshing ] = useState( false ); + + const { merchant } = useMerchantInfo(); + const { refreshFeatureStatuses } = useDispatch( STORE_NAME ); + + const features = featuresDefault.map( ( feature ) => { + const merchantFeature = merchant?.features?.[ feature.id ]; + return { + ...feature, + enabled: merchantFeature?.enabled ?? false, + }; + } ); + + const refreshHandler = async () => { + setIsRefreshing( true ); + + const result = await refreshFeatureStatuses(); + + if ( result && ! result.success ) { + console.error( + 'Failed to refresh features:', + result.message || 'Unknown error' + ); + } else { + console.log( 'Features refreshed successfully.' ); + } + + setIsRefreshing( false ); + }; return ( <div className="ppcp-r-tab-overview"> @@ -39,30 +71,54 @@ const TabOverview = () => { title={ __( 'Features', 'woocommerce-paypal-payments' ) } description={ <div> - <p>{ __( 'Enable additional features…' ) }</p> - <p>{ __( 'Click Refresh…' ) }</p> - <Button variant="tertiary"> + <p> + { __( + 'Enable additional features…', + 'woocommerce-paypal-payments' + ) } + </p> + <p> + { __( + 'Click Refresh…', + 'woocommerce-paypal-payments' + ) } + </p> + <Button + variant="tertiary" + onClick={ refreshHandler } + disabled={ isRefreshing } + > { data().getImage( 'icon-refresh.svg' ) } - { __( 'Refresh', 'woocommerce-paypal-payments' ) } + { isRefreshing + ? __( + 'Refreshing…', + 'woocommerce-paypal-payments' + ) + : __( + 'Refresh', + 'woocommerce-paypal-payments' + ) } </Button> </div> } - contentItems={ featuresDefault.map( ( feature ) => ( + contentItems={ features.map( ( feature ) => ( <FeatureSettingsBlock key={ feature.id } title={ feature.title } description={ feature.description } actionProps={ { buttons: feature.buttons, - featureStatus: feature.featureStatus, + enabled: feature.enabled, notes: feature.notes, - badge: { - text: __( - 'Active', - 'woocommerce-paypal-payments' - ), - type: TITLE_BADGE_POSITIVE, - }, + badge: feature.enabled + ? { + text: __( + 'Active', + 'woocommerce-paypal-payments' + ), + type: TITLE_BADGE_POSITIVE, + } + : undefined, } } /> ) ) } @@ -133,7 +189,6 @@ const featuresDefault = [ 'Advanced Credit and Debit Cards', 'woocommerce-paypal-payments' ), - featureStatus: true, description: __( 'Process major credit and debit cards including Visa, Mastercard, American Express and Discover.', 'woocommerce-paypal-payments' @@ -181,7 +236,6 @@ const featuresDefault = [ 'Let customers pay using their Google Pay wallet.', 'woocommerce-paypal-payments' ), - featureStatus: true, buttons: [ { type: 'secondary', diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js index 34e831508..ac08cdcf7 100644 --- a/modules/ppcp-settings/resources/js/data/common/action-types.js +++ b/modules/ppcp-settings/resources/js/data/common/action-types.js @@ -23,4 +23,5 @@ export default { DO_SANDBOX_LOGIN: 'COMMON:DO_SANDBOX_LOGIN', DO_PRODUCTION_LOGIN: 'COMMON:DO_PRODUCTION_LOGIN', DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT', + DO_REFRESH_FEATURES: 'DO_REFRESH_FEATURES', }; diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js index 7dd13206e..be52ff1d8 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions.js +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -189,3 +189,14 @@ export const connectViaIdAndSecret = function* () { export const refreshMerchantData = function* () { return yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT }; }; + +/** + * Side effect. + * Purges all features status data via a REST request. + * Refreshes the merchant data via a REST request. + * + * @return {Action} The action. + */ +export const refreshFeatureStatuses = function* () { + return yield { type: ACTION_TYPES.DO_REFRESH_FEATURES }; +}; diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js index 9499ef069..c67b1fef0 100644 --- a/modules/ppcp-settings/resources/js/data/common/constants.js +++ b/modules/ppcp-settings/resources/js/data/common/constants.js @@ -53,3 +53,14 @@ export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual'; * @type {string} */ export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link'; + +/** + * REST path to refresh the feature status. + * + * Used by: Controls + * See: RefreshFeatureStatusEndpoint.php + * + * @type {string} + */ +export const REST_REFRESH_FEATURES_PATH = + '/wc/v3/wc_paypal/refresh-feature-status'; diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js index 7845f335f..e095d6acb 100644 --- a/modules/ppcp-settings/resources/js/data/common/controls.js +++ b/modules/ppcp-settings/resources/js/data/common/controls.js @@ -16,6 +16,7 @@ import { REST_MANUAL_CONNECTION_PATH, REST_CONNECTION_URL_PATH, REST_HYDRATE_MERCHANT_PATH, + REST_REFRESH_FEATURES_PATH, } from './constants'; import ACTION_TYPES from './action-types'; @@ -121,4 +122,27 @@ export const controls = { return result; }, + + async [ ACTION_TYPES.DO_REFRESH_FEATURES ]() { + let result = null; + + try { + result = await apiFetch( { + path: REST_REFRESH_FEATURES_PATH, + method: 'POST', + } ); + + if ( result.success ) { + result = await dispatch( STORE_NAME ).refreshMerchantData(); + } + } catch ( e ) { + result = { + success: false, + error: e, + message: e.message, + }; + } + + return result; + }, }; diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index c1eeca241..349c13350 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\ConnectManualRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint; +use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\SwitchSettingsUiEndpoint; use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator; use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager; @@ -70,6 +71,13 @@ 'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint { return new CommonRestEndpoint( $container->get( 'settings.data.common' ) ); }, + 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { + return new RefreshFeatureStatusEndpoint( + $container->get( 'wcgateway.settings' ), + new Cache( 'ppcp-timeout' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, 'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint { return new ConnectManualRestEndpoint( $container->get( 'api.paypal-host-production' ), diff --git a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php index 3c0131759..7524e7e31 100644 --- a/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/CommonRestEndpoint.php @@ -206,6 +206,11 @@ protected function add_merchant_info( array $extra_data ) : array { $this->merchant_info_map ); + $extra_data['merchant'] = apply_filters( + 'woocommerce_paypal_payments_rest_common_merchant_data', + $extra_data['merchant'], + ); + return $extra_data; } diff --git a/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php b/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php new file mode 100644 index 000000000..d8fc2760e --- /dev/null +++ b/modules/ppcp-settings/src/Endpoint/RefreshFeatureStatusEndpoint.php @@ -0,0 +1,132 @@ +<?php +/** + * REST endpoint to refresh feature status. + * + * @package WooCommerce\PayPalCommerce\Settings\Endpoint + */ + +declare(strict_types=1); + +namespace WooCommerce\PayPalCommerce\Settings\Endpoint; + +use WP_REST_Server; +use WP_REST_Response; +use WP_REST_Request; +use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; + +/** + * REST controller for refreshing feature status. + */ +class RefreshFeatureStatusEndpoint extends RestEndpoint { + /** + * The base path for this REST controller. + * + * @var string + */ + protected $rest_base = 'refresh-feature-status'; + + /** + * Cache timeout in seconds. + * + * @var int + */ + private const TIMEOUT = 60; + + /** + * Cache key for tracking request timeouts. + * + * @var string + */ + private const CACHE_KEY = 'refresh_feature_status_timeout'; + + /** + * The settings. + * + * @var ContainerInterface + */ + protected ContainerInterface $settings; + + /** + * The cache. + * + * @var Cache + */ + protected Cache $cache; + + /** + * The logger. + * + * @var LoggerInterface + */ + protected LoggerInterface $logger; + + /** + * Constructor. + * + * @param ContainerInterface $settings The settings. + * @param Cache $cache The cache. + * @param LoggerInterface $logger The logger. + */ + public function __construct( + ContainerInterface $settings, + Cache $cache, + LoggerInterface $logger + ) { + $this->settings = $settings; + $this->cache = $cache; + $this->logger = $logger; + } + + /** + * 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, 'refresh_status' ), + 'permission_callback' => array( $this, 'check_permission' ), + ), + ) + ); + } + + /** + * Handles the refresh status request. + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_REST_Response + */ + public function refresh_status( WP_REST_Request $request ): WP_REST_Response { + $now = time(); + $last_request_time = $this->cache->get( self::CACHE_KEY ) ?: 0; + $seconds_missing = $last_request_time + self::TIMEOUT - $now; + + if ( $seconds_missing > 0 ) { + return $this->return_error( + sprintf( + // translators: %1$s is the number of seconds remaining. + __( 'Wait %1$s seconds before trying again.', 'woocommerce-paypal-payments' ), + $seconds_missing + ) + ); + } + + $this->cache->set( self::CACHE_KEY, $now, self::TIMEOUT ); + + do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings ); + + $this->logger->info( 'Feature status refreshed successfully' ); + + return $this->return_success( + array( + 'message' => __( 'Feature status refreshed successfully.', 'woocommerce-paypal-payments' ), + ) + ); + } +} diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 7cb55bb02..f2d7267e0 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -181,6 +181,7 @@ static function () use ( $container ) : void { $container->get( 'settings.rest.common' ), $container->get( 'settings.rest.connect_manual' ), $container->get( 'settings.rest.login_link' ), + $container->get( 'settings.rest.refresh_feature_status' ), ); foreach ( $endpoints as $endpoint ) {