diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index c5cc6a1b9..aa9876069 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -182,6 +182,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( 'applepay.apple-product-status' ); + assert( $product_status instanceof AppleProductStatus ); + + $apple_pay_enabled = $product_status->is_active(); + + $merchant_data['features']['apple_pay'] = array( + 'enabled' => $apple_pay_enabled, + ); + + return $merchant_data; + } + ); + return true; } 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 2984e8074..f2d66e9a3 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/FeatureSettingsBlock.js @@ -11,42 +11,56 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => { } return ( - - { notes.map( ( note, index ) => ( - { note } - ) ) } - + <> + + { notes.map( ( note, index ) => ( + { note } + ) ) } + + ); }; return ( - -
- - { title } - { props.actionProps?.featureStatus && ( - <TitleBadge { ...props.actionProps?.badge } /> - ) } - - - { description } - { printNotes() } - -
- -
- { props.actionProps?.buttons.map( ( button ) => ( - - ) ) } -
-
-
+ ( + <> +
+ + { title } + { props.actionProps?.enabled && ( + <TitleBadge + { ...props.actionProps?.badge } + /> + ) } + + + { description } + { printNotes() } + +
+ +
+ { props.actionProps?.buttons.map( + ( button ) => ( + + ) + ) } +
+
+ + ), + ] } + /> ); }; 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 (
@@ -39,30 +71,54 @@ const TabOverview = () => { title={ __( 'Features', 'woocommerce-paypal-payments' ) } description={
-

{ __( 'Enable additional features…' ) }

-

{ __( 'Click Refresh…' ) }

-
} - contentItems={ featuresDefault.map( ( feature ) => ( + contentItems={ features.map( ( feature ) => ( ) ) } @@ -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..1982d7f42 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions.js +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -7,7 +7,7 @@ * @file */ -import { select } from '@wordpress/data'; +import { dispatch, select } from '@wordpress/data'; import ACTION_TYPES from './action-types'; import { STORE_NAME } from './constants'; @@ -189,3 +189,20 @@ export const connectViaIdAndSecret = function* () { export const refreshMerchantData = function* () { return yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT }; }; + +/** + * Side effect. + * Purges all feature status data via a REST request. + * Refreshes the merchant data via a REST request. + * + * @return {Action} The action. + */ +export const refreshFeatureStatuses = function* () { + const result = yield { type: ACTION_TYPES.DO_REFRESH_FEATURES }; + + if ( result && result.success ) { + return yield dispatch( STORE_NAME ).refreshMerchantData(); + } + + return result; +}; 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 @@ +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 ) { diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 8c807474e..f754a3cbe 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use Throwable; use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; @@ -547,6 +548,33 @@ function( string $installed_plugin_version ) use ( $c ) { } ); + 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(); + } + + $billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' ); + assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint ); + + $reference_transactions_enabled = $billing_agreements_endpoint->reference_transaction_enabled(); + $merchant_data['features']['save_paypal_and_venmo'] = array( + 'enabled' => $reference_transactions_enabled, + ); + + $dcc_product_status = $c->get( 'wcgateway.helper.dcc-product-status' ); + assert( $dcc_product_status instanceof DCCProductStatus ); + + $dcc_enabled = $dcc_product_status->dcc_is_active(); + $merchant_data['features']['advanced_credit_and_debit_cards'] = array( + 'enabled' => $dcc_enabled, + ); + + return $merchant_data; + } + ); + return true; }