mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-4242-non-acdc-country-onboarding
This commit is contained in:
commit
a540c80b3d
26 changed files with 1365 additions and 648 deletions
|
@ -42,10 +42,24 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
public function run( ContainerInterface $c ) : bool {
|
||||
add_action( 'after_setup_theme', fn() => $this->run_with_translations( $c ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up WP hooks that depend on translation features.
|
||||
* Runs after the theme setup, when translations are available, which is fired
|
||||
* before the `init` hook, which usually contains most of the logic.
|
||||
*
|
||||
* @param ContainerInterface $c The DI container.
|
||||
* @return void
|
||||
*/
|
||||
private function run_with_translations( ContainerInterface $c ) : void {
|
||||
// When Local APMs are disabled, none of the following hooks are needed.
|
||||
if ( ! $this->should_add_local_apm_gateways( $c ) ) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,8 +207,6 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +233,7 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* @return bool
|
||||
*/
|
||||
private function should_add_local_apm_gateways( ContainerInterface $container ) : bool {
|
||||
// Merchant onboarding must be completed.
|
||||
// APMs are only available after merchant onboarding is completed.
|
||||
$is_connected = $container->get( 'settings.flag.is-connected' );
|
||||
if ( ! $is_connected ) {
|
||||
/**
|
||||
|
@ -231,13 +243,8 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
* During the authentication process (which happens via a REST call)
|
||||
* the gateways need to be present, so they can be correctly
|
||||
* pre-configured for new merchants.
|
||||
*
|
||||
* TODO is there a cleaner solution for this?
|
||||
*/
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '' );
|
||||
|
||||
return str_contains( $request_uri, '/wp-json/wc/' );
|
||||
return $this->is_rest_request();
|
||||
}
|
||||
|
||||
// The general plugin functionality must be enabled.
|
||||
|
@ -251,4 +258,16 @@ class LocalAlternativePaymentMethodsModule implements ServiceModule, ExtendingMo
|
|||
return $settings->has( 'allow_local_apm_gateways' )
|
||||
&& $settings->get( 'allow_local_apm_gateways' ) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, whether the current request is trying to access a WooCommerce REST endpoint.
|
||||
*
|
||||
* @return bool True, if the request path matches the WC-Rest namespace.
|
||||
*/
|
||||
private function is_rest_request(): bool {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ?? '' );
|
||||
|
||||
return str_contains( $request_uri, '/wp-json/wc/' );
|
||||
}
|
||||
}
|
||||
|
|
35
modules/ppcp-settings/docs/glossary.md
Normal file
35
modules/ppcp-settings/docs/glossary.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Glossary
|
||||
|
||||
This document provides definitions and explanations of key terms used in the plugin.
|
||||
|
||||
---
|
||||
|
||||
## Eligibility
|
||||
|
||||
**Eligibility** determines whether a merchant can access a specific feature within the plugin. It is a boolean value (`true` or `false`) that depends on certain criteria, such as:
|
||||
|
||||
- **Country**: The merchant's location or the country where their business operates.
|
||||
- **Other Factors**: Additional conditions, such as subscription plans, business type, or compliance requirements.
|
||||
|
||||
If a merchant is **eligible** (`true`) for a feature, the feature will be visible and accessible in the plugin. If they are **not eligible** (`false`), the feature will be hidden or unavailable.
|
||||
|
||||
---
|
||||
|
||||
## Capability
|
||||
|
||||
**Capability** refers to the activation status of a feature for an eligible merchant. Even if a merchant is eligible for a feature, they may need to activate it in their PayPal dashboard to use it. Capability has two states:
|
||||
|
||||
- **Active**: The feature is enabled, and the merchant can configure and use it.
|
||||
- **Inactive**: The feature is not enabled, and the merchant will be guided on how to activate it (e.g., through instructions or prompts).
|
||||
|
||||
Capability ensures that eligible merchants have control over which features they want to use and configure within the plugin.
|
||||
|
||||
---
|
||||
|
||||
### Example Workflow
|
||||
|
||||
1. A merchant is **eligible** for a feature based on their country and other factors.
|
||||
2. If the feature is **active** (capability is enabled), the merchant can configure and use it.
|
||||
3. If the feature is **inactive**, the plugin will provide instructions on how to activate it.
|
||||
|
||||
---
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { PayLaterMessagingHooks } from '../../../data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
|
||||
const TabPayLaterMessaging = () => {
|
||||
const {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
import { reusableBlock } from '@wordpress/icons';
|
||||
|
||||
const FeatureDescription = ( { refreshHandler, isRefreshing } ) => {
|
||||
const buttonLabel = isRefreshing
|
||||
? __( 'Refreshing…', 'woocommerce-paypal-payments' )
|
||||
: __( 'Refresh', 'woocommerce-paypal-payments' );
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{ __(
|
||||
'Enable additional features and capabilities on your WooCommerce store.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ __(
|
||||
'Click Refresh to update your current features after making changes.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ refreshHandler }
|
||||
disabled={ isRefreshing }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ buttonLabel }
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureDescription;
|
|
@ -0,0 +1,70 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { FeatureSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
import { Content } from '../../../../../ReusableComponents/Elements';
|
||||
import { TITLE_BADGE_POSITIVE } from '../../../../../ReusableComponents/TitleBadge';
|
||||
import { selectTab, TAB_IDS } from '../../../../../../utils/tabSelector';
|
||||
import { setActiveModal } from '../../../../../../data/common/actions';
|
||||
|
||||
const FeatureItem = ( {
|
||||
isBusy,
|
||||
isSandbox,
|
||||
title,
|
||||
description,
|
||||
buttons,
|
||||
enabled,
|
||||
notes,
|
||||
} ) => {
|
||||
const getButtonUrl = ( button ) => {
|
||||
if ( button.urls ) {
|
||||
return isSandbox ? button.urls.sandbox : button.urls.live;
|
||||
}
|
||||
|
||||
return button.url;
|
||||
};
|
||||
|
||||
const visibleButtons = buttons.filter(
|
||||
( button ) =>
|
||||
! button.showWhen || // Learn more buttons
|
||||
( enabled && button.showWhen === 'enabled' ) ||
|
||||
( ! enabled && button.showWhen === 'disabled' )
|
||||
);
|
||||
const handleClick = async ( feature ) => {
|
||||
if ( feature.action?.type === 'tab' ) {
|
||||
const tabId = TAB_IDS[ feature.action.tab.toUpperCase() ];
|
||||
await selectTab( tabId, feature.action.section );
|
||||
}
|
||||
if ( feature.action?.modal ) {
|
||||
setActiveModal( feature.action.modal );
|
||||
}
|
||||
};
|
||||
|
||||
const actionProps = {
|
||||
isBusy,
|
||||
enabled,
|
||||
notes,
|
||||
buttons: visibleButtons.map( ( button ) => ( {
|
||||
...button,
|
||||
url: getButtonUrl( button ),
|
||||
onClick: () => handleClick( button ),
|
||||
} ) ),
|
||||
};
|
||||
|
||||
if ( enabled ) {
|
||||
actionProps.badge = {
|
||||
text: __( 'Active', 'woocommerce-paypal-payments' ),
|
||||
type: TITLE_BADGE_POSITIVE,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ title }
|
||||
description={ description }
|
||||
actionProps={ actionProps }
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeatureItem;
|
|
@ -0,0 +1,95 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import FeatureItem from './FeatureItem';
|
||||
import FeatureDescription from './FeatureDescription';
|
||||
import { ContentWrapper } from '../../../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
|
||||
import { useMerchantInfo } from '../../../../../../data/common/hooks';
|
||||
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
|
||||
import {
|
||||
NOTIFICATION_ERROR,
|
||||
NOTIFICATION_SUCCESS,
|
||||
} from '../../../../../ReusableComponents/Icons';
|
||||
import { useFeatures } from '../../../../../../data/features/hooks';
|
||||
|
||||
const Features = () => {
|
||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||
const { merchant } = useMerchantInfo();
|
||||
const { features, fetchFeatures } = useFeatures();
|
||||
const { refreshFeatureStatuses } = useDispatch( COMMON_STORE_NAME );
|
||||
const { createSuccessNotice, createErrorNotice } =
|
||||
useDispatch( noticesStore );
|
||||
|
||||
if ( ! features || features.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const refreshHandler = async () => {
|
||||
setIsRefreshing( true );
|
||||
try {
|
||||
const statusResult = await refreshFeatureStatuses();
|
||||
if ( ! statusResult?.success ) {
|
||||
throw new Error(
|
||||
statusResult?.message || 'Failed to refresh status'
|
||||
);
|
||||
}
|
||||
|
||||
const featuresResult = await fetchFeatures();
|
||||
if ( featuresResult.success ) {
|
||||
createSuccessNotice(
|
||||
__(
|
||||
'Features refreshed successfully.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
{ icon: NOTIFICATION_SUCCESS }
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
featuresResult?.message || 'Failed to fetch features'
|
||||
);
|
||||
}
|
||||
} catch ( error ) {
|
||||
createErrorNotice(
|
||||
sprintf(
|
||||
/* translators: %s: error message */
|
||||
__( 'Operation failed: %s', 'woocommerce-paypal-payments' ),
|
||||
error.message ||
|
||||
__( 'Unknown error', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
{ icon: NOTIFICATION_ERROR }
|
||||
);
|
||||
} finally {
|
||||
setIsRefreshing( false );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-features"
|
||||
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<FeatureDescription
|
||||
refreshHandler={ refreshHandler }
|
||||
isRefreshing={ isRefreshing }
|
||||
/>
|
||||
}
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
{ features.map( ( { id, enabled, ...feature } ) => (
|
||||
<FeatureItem
|
||||
key={ id }
|
||||
isBusy={ isRefreshing }
|
||||
isSandbox={ merchant.isSandbox }
|
||||
enabled={ enabled }
|
||||
{ ...feature }
|
||||
/>
|
||||
) ) }
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default Features;
|
|
@ -0,0 +1,72 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { FeatureSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
import {
|
||||
Content,
|
||||
ContentWrapper,
|
||||
} from '../../../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
|
||||
|
||||
const Help = () => {
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-help"
|
||||
title={ __( 'Help Center', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Access detailed guides and responsive support to streamline setup and enhance your experience.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __(
|
||||
'Documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Find detailed guides and resources to help you set up, manage, and optimize your PayPal integration.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View full documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __( 'Support', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Need help? Access troubleshooting tips or contact our support team for personalized assistance.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View support options',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help ',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
|
@ -0,0 +1,86 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { reusableBlock } from '@wordpress/icons';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
import { TodoSettingsBlock } from '../../../../../ReusableComponents/SettingsBlocks';
|
||||
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
|
||||
import { useTodos } from '../../../../../../data/todos/hooks';
|
||||
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
|
||||
import { STORE_NAME as TODOS_STORE_NAME } from '../../../../../../data/todos';
|
||||
import { NOTIFICATION_SUCCESS } from '../../../../../ReusableComponents/Icons';
|
||||
|
||||
const Todos = () => {
|
||||
const [ isResetting, setIsResetting ] = useState( false );
|
||||
const { todos, isReady: areTodosReady, dismissTodo } = useTodos();
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { setActiveModal, setActiveHighlight } =
|
||||
useDispatch( COMMON_STORE_NAME );
|
||||
const { resetDismissedTodos, setDismissedTodos } =
|
||||
useDispatch( TODOS_STORE_NAME );
|
||||
const { createSuccessNotice } = useDispatch( noticesStore );
|
||||
|
||||
const showTodos = areTodosReady && todos.length > 0;
|
||||
|
||||
const resetHandler = async () => {
|
||||
setIsResetting( true );
|
||||
try {
|
||||
await setDismissedTodos( [] );
|
||||
await resetDismissedTodos();
|
||||
|
||||
createSuccessNotice(
|
||||
__(
|
||||
'Dismissed items restored successfully.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
{ icon: NOTIFICATION_SUCCESS }
|
||||
);
|
||||
} finally {
|
||||
setIsResetting( false );
|
||||
}
|
||||
};
|
||||
|
||||
if ( ! showTodos ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-todo"
|
||||
title={ __( 'Things to do next', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<>
|
||||
<p>
|
||||
{ __(
|
||||
'Complete these tasks to keep your store updated with the latest products and services.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ resetHandler }
|
||||
disabled={ isResetting }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ isResetting
|
||||
? __( 'Restoring…', 'woocommerce-paypal-payments' )
|
||||
: __(
|
||||
'Restore dismissed Things To Do',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TodoSettingsBlock
|
||||
todosData={ todos }
|
||||
setActiveModal={ setActiveModal }
|
||||
setActiveHighlight={ setActiveHighlight }
|
||||
onDismissTodo={ dismissTodo }
|
||||
/>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default Todos;
|
|
@ -1,278 +0,0 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { TAB_IDS, selectTab } from '../../../../../utils/tabSelector';
|
||||
import { payLaterMessaging } from './pay-later-messaging';
|
||||
|
||||
export const getFeatures = ( setActiveModal ) => {
|
||||
const storeCountry = ppcpSettings?.storeCountry;
|
||||
const features = [
|
||||
{
|
||||
id: 'save_paypal_and_venmo',
|
||||
title: __( 'Save PayPal and Venmo', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Securely save PayPal and Venmo payment methods for subscriptions or return buyers.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.SETTINGS,
|
||||
'ppcp--save-payment-methods'
|
||||
);
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
urls: {
|
||||
sandbox:
|
||||
'https://www.sandbox.paypal.com/bizsignup/entry?product=ADVANCED_VAULTING',
|
||||
live: 'https://www.paypal.com/bizsignup/entry?product=ADVANCED_VAULTING',
|
||||
},
|
||||
showWhen: 'disabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://www.paypal.com/us/enterprise/payment-processing/accept-venmo',
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'advanced_credit_and_debit_cards',
|
||||
title: __(
|
||||
'Advanced Credit and Debit Cards',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
description: __(
|
||||
'Process major credit and debit cards including Visa, Mastercard, American Express and Discover.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'ppcp-credit-card-gateway' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
urls: {
|
||||
sandbox:
|
||||
'https://www.sandbox.paypal.com/bizsignup/entry?product=ppcp',
|
||||
live: 'https://www.paypal.com/bizsignup/entry?product=ppcp',
|
||||
},
|
||||
showWhen: 'disabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://developer.paypal.com/studio/checkout/advanced',
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'alternative_payment_methods',
|
||||
title: __(
|
||||
'Alternative Payment Methods',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
description: __(
|
||||
'Offer global, country-specific payment options for your customers.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-alternative-payments-card'
|
||||
);
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://developer.paypal.com/docs/checkout/apm/',
|
||||
showWhen: 'disabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://developer.paypal.com/docs/checkout/apm/',
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'google_pay',
|
||||
title: __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Let customers pay using their Google Pay wallet.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'ppcp-googlepay' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
urls: {
|
||||
sandbox:
|
||||
'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY',
|
||||
live: 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY',
|
||||
},
|
||||
showWhen: 'disabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://developer.paypal.com/docs/checkout/apm/google-pay/',
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
notes: [
|
||||
__(
|
||||
'¹PayPal Q2 Earnings-2021.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'apple_pay',
|
||||
title: __( 'Apple Pay', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Let customers pay using their Apple Pay wallet.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'ppcp-applepay' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __(
|
||||
'Domain registration',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
urls: {
|
||||
sandbox:
|
||||
'https://www.sandbox.paypal.com/uccservicing/apm/applepay',
|
||||
live: 'https://www.paypal.com/uccservicing/apm/applepay',
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
urls: {
|
||||
sandbox:
|
||||
'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY',
|
||||
live: 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY',
|
||||
},
|
||||
showWhen: 'disabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: 'https://developer.paypal.com/docs/checkout/apm/apple-pay/',
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const countryData = payLaterMessaging[ storeCountry ] || {};
|
||||
|
||||
if (
|
||||
!! window.ppcpSettings?.isPayLaterConfiguratorAvailable &&
|
||||
countryData
|
||||
) {
|
||||
const countryLocation = [
|
||||
'UK',
|
||||
'ES',
|
||||
'IT',
|
||||
'FR',
|
||||
'US',
|
||||
'DE',
|
||||
'AU',
|
||||
].includes( storeCountry )
|
||||
? storeCountry.toLowerCase()
|
||||
: 'us';
|
||||
features.push( {
|
||||
id: 'pay_later_messaging',
|
||||
title: __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ),
|
||||
description: __(
|
||||
'Let customers know they can buy now and pay later with PayPal. Adding this messaging can boost conversion rates and increase cart sizes by 39%¹, with no extra cost to you—plus, you get paid up front.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab( TAB_IDS.PAY_LATER_MESSAGING );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
},
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
url: `https://www.paypal.com/${ countryLocation }/business/accept-payments/checkout/installments`,
|
||||
class: 'small-button',
|
||||
},
|
||||
],
|
||||
} );
|
||||
}
|
||||
|
||||
return features;
|
||||
};
|
|
@ -1,371 +1,25 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { useState, useMemo } from '@wordpress/element';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { reusableBlock } from '@wordpress/icons';
|
||||
import { store as noticesStore } from '@wordpress/notices';
|
||||
|
||||
import {
|
||||
TodoSettingsBlock,
|
||||
FeatureSettingsBlock,
|
||||
} from '../../../ReusableComponents/SettingsBlocks';
|
||||
import {
|
||||
Content,
|
||||
ContentWrapper,
|
||||
CardActions,
|
||||
} from '../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../ReusableComponents/SettingsCard';
|
||||
import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge';
|
||||
import {
|
||||
CommonStoreName,
|
||||
TodosStoreName,
|
||||
CommonHooks,
|
||||
TodosHooks,
|
||||
} from '../../../../data';
|
||||
|
||||
import { getFeatures } from '../Components/Overview/features-config';
|
||||
|
||||
import {
|
||||
NOTIFICATION_ERROR,
|
||||
NOTIFICATION_SUCCESS,
|
||||
} from '../../../ReusableComponents/Icons';
|
||||
import Todos from '../Components/Overview/Todos/Todos';
|
||||
import Features from '../Components/Overview/Features/Features';
|
||||
import Help from '../Components/Overview/Help/Help';
|
||||
import { TodosHooks, CommonHooks, FeaturesHooks } from '../../../../data';
|
||||
import SpinnerOverlay from '../../../ReusableComponents/SpinnerOverlay';
|
||||
|
||||
const TabOverview = () => {
|
||||
const { isReady: areTodosReady } = TodosHooks.useStore();
|
||||
const { isReady: merchantIsReady } = CommonHooks.useStore();
|
||||
const { isReady: areTodosReady } = TodosHooks.useTodos();
|
||||
const { isReady: merchantIsReady } = CommonHooks.useMerchantInfo();
|
||||
const { isReady: featuresIsReady } = FeaturesHooks.useFeatures();
|
||||
|
||||
if ( ! areTodosReady || ! merchantIsReady ) {
|
||||
if ( ! areTodosReady || ! merchantIsReady || ! featuresIsReady ) {
|
||||
return <SpinnerOverlay asModal={ true } />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-tab-overview">
|
||||
<OverviewTodos />
|
||||
<OverviewFeatures />
|
||||
<OverviewHelp />
|
||||
<Todos />
|
||||
<Features />
|
||||
<Help />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabOverview;
|
||||
|
||||
const OverviewTodos = () => {
|
||||
const [ isResetting, setIsResetting ] = useState( false );
|
||||
const { todos, dismissTodo } = TodosHooks.useTodos();
|
||||
const { isReady: areTodosReady } = TodosHooks.useStore();
|
||||
const { setActiveModal, setActiveHighlight } =
|
||||
useDispatch( CommonStoreName );
|
||||
const { resetDismissedTodos, setDismissedTodos } =
|
||||
useDispatch( TodosStoreName );
|
||||
const { createSuccessNotice } = useDispatch( noticesStore );
|
||||
|
||||
const showTodos = areTodosReady && todos.length > 0;
|
||||
|
||||
const resetHandler = async () => {
|
||||
setIsResetting( true );
|
||||
try {
|
||||
await setDismissedTodos( [] );
|
||||
await resetDismissedTodos();
|
||||
|
||||
createSuccessNotice(
|
||||
__(
|
||||
'Dismissed items restored successfully.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
{ icon: NOTIFICATION_SUCCESS }
|
||||
);
|
||||
} finally {
|
||||
setIsResetting( false );
|
||||
}
|
||||
};
|
||||
|
||||
if ( ! showTodos ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-todo"
|
||||
title={ __( 'Things to do next', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<>
|
||||
<p>
|
||||
{ __(
|
||||
'Complete these tasks to keep your store updated with the latest products and services.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<CardActions>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ resetHandler }
|
||||
disabled={ isResetting }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ isResetting
|
||||
? __(
|
||||
'Restoring…',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
: __(
|
||||
'Restore dismissed Things To Do',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Button>
|
||||
</CardActions>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TodoSettingsBlock
|
||||
todosData={ todos }
|
||||
setActiveModal={ setActiveModal }
|
||||
setActiveHighlight={ setActiveHighlight }
|
||||
onDismissTodo={ dismissTodo }
|
||||
/>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
const OverviewFeatures = () => {
|
||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||
const { merchant, features: merchantFeatures } =
|
||||
CommonHooks.useMerchantInfo();
|
||||
const { refreshFeatureStatuses, setActiveModal } =
|
||||
useDispatch( CommonStoreName );
|
||||
const { createSuccessNotice, createErrorNotice } =
|
||||
useDispatch( noticesStore );
|
||||
|
||||
// Get the features data with access to setActiveModal
|
||||
const featuresData = useMemo(
|
||||
() => getFeatures( setActiveModal ),
|
||||
[ setActiveModal ]
|
||||
);
|
||||
|
||||
// Map merchant features status to the config
|
||||
const features = useMemo( () => {
|
||||
return featuresData.map( ( feature ) => {
|
||||
const merchantFeature = merchantFeatures?.[ feature.id ];
|
||||
return {
|
||||
...feature,
|
||||
enabled: merchantFeature?.enabled ?? false,
|
||||
};
|
||||
} );
|
||||
}, [ featuresData, merchantFeatures ] );
|
||||
|
||||
const refreshHandler = async () => {
|
||||
setIsRefreshing( true );
|
||||
|
||||
try {
|
||||
const result = await refreshFeatureStatuses();
|
||||
if ( result && ! result.success ) {
|
||||
const errorMessage = sprintf(
|
||||
/* translators: %s: error message */
|
||||
__(
|
||||
'Operation failed: %s Check WooCommerce logs for more details.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
result.message ||
|
||||
__( 'Unknown error', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
|
||||
createErrorNotice( errorMessage, {
|
||||
icon: NOTIFICATION_ERROR,
|
||||
} );
|
||||
console.error(
|
||||
'Failed to refresh features:',
|
||||
result.message || 'Unknown error'
|
||||
);
|
||||
} else {
|
||||
createSuccessNotice(
|
||||
__(
|
||||
'Features refreshed successfully.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
{
|
||||
icon: NOTIFICATION_SUCCESS,
|
||||
}
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setIsRefreshing( false );
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-features"
|
||||
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<OverviewFeatureDescription
|
||||
refreshHandler={ refreshHandler }
|
||||
isRefreshing={ isRefreshing }
|
||||
/>
|
||||
}
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
{ features.map( ( { id, ...feature } ) => (
|
||||
<OverviewFeatureItem
|
||||
key={ id }
|
||||
isBusy={ isRefreshing }
|
||||
isSandbox={ merchant.isSandbox }
|
||||
title={ feature.title }
|
||||
description={ feature.description }
|
||||
buttons={ feature.buttons }
|
||||
enabled={ feature.enabled }
|
||||
notes={ feature.notes }
|
||||
/>
|
||||
) ) }
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
||||
const OverviewFeatureItem = ( {
|
||||
isBusy,
|
||||
isSandbox,
|
||||
title,
|
||||
description,
|
||||
buttons,
|
||||
enabled,
|
||||
notes,
|
||||
} ) => {
|
||||
const getButtonUrl = ( button ) => {
|
||||
if ( button.urls ) {
|
||||
return isSandbox ? button.urls.sandbox : button.urls.live;
|
||||
}
|
||||
|
||||
return button.url;
|
||||
};
|
||||
|
||||
const visibleButtons = buttons.filter(
|
||||
( button ) =>
|
||||
! button.showWhen || // Learn more buttons
|
||||
( enabled && button.showWhen === 'enabled' ) ||
|
||||
( ! enabled && button.showWhen === 'disabled' )
|
||||
);
|
||||
|
||||
const actionProps = {
|
||||
isBusy,
|
||||
enabled,
|
||||
notes,
|
||||
buttons: visibleButtons.map( ( button ) => ( {
|
||||
...button,
|
||||
url: getButtonUrl( button ),
|
||||
} ) ),
|
||||
};
|
||||
|
||||
if ( enabled ) {
|
||||
actionProps.badge = {
|
||||
text: __( 'Active', 'woocommerce-paypal-payments' ),
|
||||
type: TITLE_BADGE_POSITIVE,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ title }
|
||||
description={ description }
|
||||
actionProps={ actionProps }
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
const OverviewFeatureDescription = ( { refreshHandler, isRefreshing } ) => {
|
||||
const buttonLabel = isRefreshing
|
||||
? __( 'Refreshing…', 'woocommerce-paypal-payments' )
|
||||
: __( 'Refresh', 'woocommerce-paypal-payments' );
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
{ __(
|
||||
'Enable additional features and capabilities on your WooCommerce store.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ __(
|
||||
'Click Refresh to update your current features after making changes.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<CardActions>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ refreshHandler }
|
||||
disabled={ isRefreshing }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ buttonLabel }
|
||||
</Button>
|
||||
</CardActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const OverviewHelp = () => {
|
||||
return (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-help"
|
||||
title={ __( 'Help Center', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Access detailed guides and responsive support to streamline setup and enhance your experience.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
contentContainer={ false }
|
||||
>
|
||||
<ContentWrapper>
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __(
|
||||
'Documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
'Find detailed guides and resources to help you set up, manage, and optimize your PayPal integration.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View full documentation',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
|
||||
<Content>
|
||||
<FeatureSettingsBlock
|
||||
title={ __( 'Support', 'woocommerce-paypal-payments' ) }
|
||||
description={ __(
|
||||
'Need help? Access troubleshooting tips or contact our support team for personalized assistance.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
buttons: [
|
||||
{
|
||||
type: 'tertiary',
|
||||
text: __(
|
||||
'View support options',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help ',
|
||||
},
|
||||
],
|
||||
} }
|
||||
/>
|
||||
</Content>
|
||||
</ContentWrapper>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -168,6 +168,7 @@ export const useMerchantInfo = () => {
|
|||
const { features } = useHooks();
|
||||
const merchant = useMerchant();
|
||||
const { refreshMerchantData, setMerchant } = useDispatch( STORE_NAME );
|
||||
const { isReady } = useStore();
|
||||
|
||||
const verifyLoginStatus = useCallback( async () => {
|
||||
const result = await refreshMerchantData();
|
||||
|
@ -193,6 +194,7 @@ export const useMerchantInfo = () => {
|
|||
merchant, // Merchant details
|
||||
features, // Eligible merchant features
|
||||
verifyLoginStatus, // Callback
|
||||
isReady,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data
|
||||
SET_TRANSIENT: 'ppcp/features/SET_TRANSIENT',
|
||||
|
||||
// Persistant data
|
||||
SET_FEATURES: 'ppcp/features/SET_FEATURES',
|
||||
HYDRATE: 'ppcp/features/HYDRATE',
|
||||
};
|
87
modules/ppcp-settings/resources/js/data/features/actions.js
Normal file
87
modules/ppcp-settings/resources/js/data/features/actions.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Action Creators: Define functions to create action objects.
|
||||
*
|
||||
* These functions update state or trigger side effects (e.g., async operations).
|
||||
* Actions are categorized as Transient or Side effect.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { REST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
* @property {string} type - The action type.
|
||||
* @property {Object?} payload - Optional payload for the action.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the full store details during app initialization.
|
||||
*
|
||||
* @param {{data: {}, flags?: {}}} payload
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const hydrate = ( payload ) => ( {
|
||||
type: ACTION_TYPES.HYDRATE,
|
||||
payload,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Generic transient-data updater.
|
||||
*
|
||||
* @param {string} prop Name of the property to update.
|
||||
* @param {any} value The new value of the property.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setTransient = ( prop, value ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { [ prop ]: value },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Marks the store as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Sets the features in the store.
|
||||
*
|
||||
* @param {Array} features The features to set.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setFeatures = ( features ) => ( {
|
||||
type: ACTION_TYPES.SET_FEATURES,
|
||||
payload: features,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Fetches features from the server.
|
||||
*
|
||||
* @return {Promise<Array>} The features data.
|
||||
*/
|
||||
export const fetchFeatures = async () => {
|
||||
try {
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
if ( response?.data ) {
|
||||
return {
|
||||
success: true,
|
||||
features: response.data.features,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
features: [],
|
||||
};
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Constants: Define store configuration values.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export const STORE_NAME = 'wc/paypal/features';
|
||||
export const REST_PATH = '/wc/v3/wc_paypal/features';
|
67
modules/ppcp-settings/resources/js/data/features/hooks.js
Normal file
67
modules/ppcp-settings/resources/js/data/features/hooks.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Hooks: Provide the main API for components to interact with the features store.
|
||||
*
|
||||
* These encapsulate store interactions, offering a consistent interface.
|
||||
* Hooks simplify data access and manipulation for components.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { STORE_NAME, REST_PATH } from './constants';
|
||||
|
||||
export const useFeatures = () => {
|
||||
const { features, isReady } = useSelect( ( select ) => {
|
||||
const store = select( STORE_NAME );
|
||||
|
||||
return {
|
||||
features: store.getFeatures() || [],
|
||||
isReady: select( STORE_NAME ).transientData()?.isReady || false,
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const { setFeatures, setIsReady } = useDispatch( STORE_NAME );
|
||||
|
||||
useEffect( () => {
|
||||
const loadInitialFeatures = async () => {
|
||||
try {
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
|
||||
if ( response?.data?.features ) {
|
||||
const featuresData = response.data.features;
|
||||
|
||||
if ( featuresData.length > 0 ) {
|
||||
await setFeatures( featuresData );
|
||||
await setIsReady( true );
|
||||
}
|
||||
}
|
||||
} catch ( error ) {}
|
||||
};
|
||||
|
||||
if ( ! isReady ) {
|
||||
loadInitialFeatures();
|
||||
}
|
||||
}, [ isReady, setFeatures, setIsReady ] );
|
||||
|
||||
return {
|
||||
features,
|
||||
isReady,
|
||||
fetchFeatures: async () => {
|
||||
try {
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
const featuresData = response.data?.features || [];
|
||||
|
||||
if ( featuresData.length > 0 ) {
|
||||
await setFeatures( featuresData );
|
||||
await setIsReady( true );
|
||||
return { success: true, features: featuresData };
|
||||
}
|
||||
return { success: false, features: [] };
|
||||
} catch ( error ) {
|
||||
return { success: false, error, message: error.message };
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
29
modules/ppcp-settings/resources/js/data/features/index.js
Normal file
29
modules/ppcp-settings/resources/js/data/features/index.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { createReduxStore, register } from '@wordpress/data';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as hooks from './hooks';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
* Combines custom controls with WordPress data controls.
|
||||
*
|
||||
* @return {boolean} True if initialization succeeded, false otherwise.
|
||||
*/
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
|
||||
return Boolean( wp.data.select( STORE_NAME ) );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
75
modules/ppcp-settings/resources/js/data/features/reducer.js
Normal file
75
modules/ppcp-settings/resources/js/data/features/reducer.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Reducer: Defines store structure and state updates for features module.
|
||||
*
|
||||
* Manages both transient (temporary) and persistent (saved) state.
|
||||
* The initial state must define all properties, as dynamic additions are not supported.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { createReducer, createReducerSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
// Store structure.
|
||||
|
||||
/**
|
||||
* Transient: Values that are _not_ saved to the DB (like app lifecycle-flags).
|
||||
* These reset on page reload.
|
||||
*/
|
||||
const defaultTransient = Object.freeze( {
|
||||
isReady: false,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent: Values that are loaded from and saved to the DB.
|
||||
* These represent the core features configuration.
|
||||
*/
|
||||
const defaultPersistent = Object.freeze( {
|
||||
features: [],
|
||||
} );
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ changeTransient, changePersistent ] = createReducerSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
/**
|
||||
* Reducer implementation mapping actions to state updates.
|
||||
*/
|
||||
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
/**
|
||||
* Updates temporary state values
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||
changeTransient( state, payload ),
|
||||
|
||||
/**
|
||||
* Updates features list
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload containing features array
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_FEATURES ]: ( state, payload ) => {
|
||||
return changePersistent( state, { features: payload } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes persistent state with data from the server
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Hydration payload containing server data
|
||||
* @param {Object} payload.data The features data to hydrate
|
||||
* @return {Object} Hydrated state
|
||||
*/
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||
changePersistent( state, payload.data ),
|
||||
} );
|
||||
|
||||
export default reducer;
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Resolvers: Handle asynchronous data fetching for the store.
|
||||
*
|
||||
* These functions update store state with data from external sources.
|
||||
* Each resolver corresponds to a specific selector (selector with same name must exist).
|
||||
* Resolvers are called automatically when selectors request unavailable data.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { REST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* Hydrates the features data from the API.
|
||||
*
|
||||
* @return {Object} Action to dispatch.
|
||||
*/
|
||||
export function getFeatures() {
|
||||
return async ( { dispatch } ) => {
|
||||
try {
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
|
||||
if ( response?.features ) {
|
||||
dispatch.setFeatures( response.features );
|
||||
dispatch.setIsReady( true );
|
||||
}
|
||||
} catch ( error ) {
|
||||
console.error( 'Error fetching features:', error );
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Selectors: Extract specific pieces of state from the store.
|
||||
*
|
||||
* These functions provide a consistent interface for accessing store data.
|
||||
* They allow components to retrieve data without knowing the store structure.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
const EMPTY_OBJ = Object.freeze( {} );
|
||||
const EMPTY_ARR = Object.freeze( [] );
|
||||
|
||||
const getState = ( state ) => state || EMPTY_OBJ;
|
||||
|
||||
export const persistentData = ( state ) => {
|
||||
return getState( state ).data || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const transientData = ( state ) => {
|
||||
const { data, ...transientState } = getState( state );
|
||||
return transientState || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
export const getFeatures = ( state ) => {
|
||||
const features = state?.features || persistentData( state ).features;
|
||||
return features || EMPTY_ARR;
|
||||
};
|
|
@ -6,6 +6,7 @@ import * as Settings from './settings';
|
|||
import * as Styling from './styling';
|
||||
import * as Todos from './todos';
|
||||
import * as PayLaterMessaging from './pay-later-messaging';
|
||||
import * as Features from './features';
|
||||
|
||||
const stores = [
|
||||
Onboarding,
|
||||
|
@ -15,6 +16,7 @@ const stores = [
|
|||
Styling,
|
||||
Todos,
|
||||
PayLaterMessaging,
|
||||
Features,
|
||||
];
|
||||
|
||||
stores.forEach( ( store ) => {
|
||||
|
@ -40,6 +42,7 @@ export const SettingsHooks = Settings.hooks;
|
|||
export const StylingHooks = Styling.hooks;
|
||||
export const TodosHooks = Todos.hooks;
|
||||
export const PayLaterMessagingHooks = PayLaterMessaging.hooks;
|
||||
export const FeaturesHooks = Features.hooks;
|
||||
|
||||
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
||||
export const CommonStoreName = Common.STORE_NAME;
|
||||
|
@ -48,6 +51,7 @@ export const SettingsStoreName = Settings.STORE_NAME;
|
|||
export const StylingStoreName = Styling.STORE_NAME;
|
||||
export const TodosStoreName = Todos.STORE_NAME;
|
||||
export const PayLaterMessagingStoreName = PayLaterMessaging.STORE_NAME;
|
||||
export const FeaturesStoreName = Features.STORE_NAME;
|
||||
|
||||
export * from './configuration';
|
||||
|
||||
|
|
|
@ -95,7 +95,8 @@ export const useStore = () => {
|
|||
|
||||
export const useTodos = () => {
|
||||
const { todos, fetchTodos, dismissTodo, setTodoCompleted } = useHooks();
|
||||
return { todos, fetchTodos, dismissTodo, setTodoCompleted };
|
||||
const { isReady } = useStore();
|
||||
return { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady };
|
||||
};
|
||||
|
||||
export const useDismissedTodos = () => {
|
||||
|
|
|
@ -10,7 +10,9 @@ declare( strict_types = 1 );
|
|||
namespace WooCommerce\PayPalCommerce\Settings;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
||||
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\Definition\FeaturesDefinition;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\Definition\PaymentMethodsDependenciesDefinition;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||
|
@ -21,6 +23,7 @@ use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
|
|||
use WooCommerce\PayPalCommerce\Settings\Data\Definition\TodosDefinition;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\AuthenticationRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\FeaturesRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Endpoint\PayLaterMessagingEndpoint;
|
||||
|
@ -33,6 +36,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\TodosRestEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\FeaturesEligibilityService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\TodosSortingAndFilteringService;
|
||||
|
@ -475,6 +479,67 @@ return array(
|
|||
$container->get( 'googlepay.eligible' ) && $capabilities['google_pay'] && ! $gateways['google_pay'],
|
||||
);
|
||||
},
|
||||
'settings.rest.features' => static function ( ContainerInterface $container ) : FeaturesRestEndpoint {
|
||||
return new FeaturesRestEndpoint(
|
||||
$container->get( 'settings.data.definition.features' ),
|
||||
$container->get( 'settings.rest.settings' )
|
||||
);
|
||||
},
|
||||
'settings.data.definition.features' => static function ( ContainerInterface $container ) : FeaturesDefinition {
|
||||
$features = apply_filters(
|
||||
'woocommerce_paypal_payments_rest_common_merchant_features',
|
||||
array()
|
||||
);
|
||||
|
||||
$payment_endpoint = $container->get( 'settings.rest.payment' );
|
||||
$settings = $payment_endpoint->get_details()->get_data();
|
||||
|
||||
// Settings status.
|
||||
$gateways = array(
|
||||
'card-button' => $settings['data']['ppcp-card-button-gateway']['enabled'] ?? false,
|
||||
);
|
||||
// Merchant capabilities, serve to show active or inactive badge and buttons.
|
||||
$capabilities = array(
|
||||
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
|
||||
'google_pay' => $features['google_pay']['enabled'] ?? false,
|
||||
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
|
||||
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
|
||||
'apm' => $features['alternative_payment_methods']['enabled'] ?? false,
|
||||
'paylater' => $features['pay_later_messaging']['enabled'] ?? false,
|
||||
);
|
||||
$merchant_capabilities = array(
|
||||
'save_paypal' => $capabilities['save_paypal'], // Save PayPal and Venmo eligibility.
|
||||
'acdc' => $capabilities['acdc'] && ! $gateways['card-button'], // Advanced credit and debit cards eligibility.
|
||||
'apm' => $capabilities['apm'], // Alternative payment methods eligibility.
|
||||
'google_pay' => $capabilities['acdc'] && $capabilities['google_pay'], // Google Pay eligibility.
|
||||
'apple_pay' => $capabilities['acdc'] && $capabilities['apple_pay'], // Apple Pay eligibility.
|
||||
'pay_later' => $capabilities['paylater'],
|
||||
);
|
||||
return new FeaturesDefinition(
|
||||
$container->get( 'settings.service.features_eligibilities' ),
|
||||
$container->get( 'settings.data.general' ),
|
||||
$merchant_capabilities
|
||||
);
|
||||
},
|
||||
'settings.service.features_eligibilities' => static function( ContainerInterface $container ): FeaturesEligibilityService {
|
||||
|
||||
$messages_apply = $container->get( 'button.helper.messages-apply' );
|
||||
assert( $messages_apply instanceof MessagesApply );
|
||||
$pay_later_eligible = $messages_apply->for_country();
|
||||
|
||||
$merchant_country = $container->get( 'api.shop.country' );
|
||||
$ineligible_countries = array( 'RU', 'BR', 'JP' );
|
||||
$apm_eligible = ! in_array( $merchant_country, $ineligible_countries, true );
|
||||
|
||||
return new FeaturesEligibilityService(
|
||||
$container->get( 'save-payment-methods.eligible' ), // Save PayPal and Venmo eligibility.
|
||||
$container->get( 'card-fields.eligible' ), // Advanced credit and debit cards eligibility.
|
||||
$apm_eligible, // Alternative payment methods eligibility.
|
||||
$container->get( 'googlepay.eligible' ), // Google Pay eligibility.
|
||||
$container->get( 'applepay.eligible' ), // Apple Pay eligibility.
|
||||
$pay_later_eligible, // Pay Later eligibility.
|
||||
);
|
||||
},
|
||||
'settings.service.todos_sorting' => static function ( ContainerInterface $container ) : TodosSortingAndFilteringService {
|
||||
return new TodosSortingAndFilteringService(
|
||||
$container->get( 'settings.data.todos' )
|
||||
|
|
312
modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php
Normal file
312
modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php
Normal file
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
/**
|
||||
* PayPal Commerce Features Definitions
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Data\Definition
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Data\Definition;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\FeaturesEligibilityService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||
|
||||
/**
|
||||
* Class FeaturesDefinition
|
||||
*
|
||||
* Provides the definitions for all available features in the system.
|
||||
* Each feature has a title, description, eligibility condition, and associated action.
|
||||
*/
|
||||
class FeaturesDefinition {
|
||||
|
||||
|
||||
/**
|
||||
* The features eligibility service.
|
||||
*
|
||||
* @var FeaturesEligibilityService
|
||||
*/
|
||||
protected FeaturesEligibilityService $eligibilities;
|
||||
|
||||
/**
|
||||
* The general settings service.
|
||||
*
|
||||
* @var GeneralSettings
|
||||
*/
|
||||
protected GeneralSettings $settings;
|
||||
|
||||
/**
|
||||
* The merchant capabilities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $merchant_capabilities;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param FeaturesEligibilityService $eligibilities The features eligibility service.
|
||||
* @param GeneralSettings $settings The general settings service.
|
||||
* @param array $merchant_capabilities The merchant capabilities.
|
||||
*/
|
||||
public function __construct(
|
||||
FeaturesEligibilityService $eligibilities,
|
||||
GeneralSettings $settings,
|
||||
array $merchant_capabilities
|
||||
) {
|
||||
$this->eligibilities = $eligibilities;
|
||||
$this->settings = $settings;
|
||||
$this->merchant_capabilities = $merchant_capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full list of feature definitions with their eligibility conditions.
|
||||
*
|
||||
* @return array The array of feature definitions.
|
||||
*/
|
||||
public function get(): array {
|
||||
$all_features = $this->all_available_features();
|
||||
$eligible_features = array();
|
||||
$eligibility_checks = $this->eligibilities->get_eligibility_checks();
|
||||
foreach ( $all_features as $feature_key => $feature ) {
|
||||
if ( $eligibility_checks[ $feature_key ]() ) {
|
||||
$eligible_features[ $feature_key ] = $feature;
|
||||
}
|
||||
}
|
||||
return $eligible_features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available features.
|
||||
*
|
||||
* @return array[] The array of all available features.
|
||||
*/
|
||||
public function all_available_features(): array {
|
||||
$paylater_countries = array(
|
||||
'UK',
|
||||
'ES',
|
||||
'IT',
|
||||
'FR',
|
||||
'US',
|
||||
'DE',
|
||||
'AU',
|
||||
);
|
||||
$store_country = $this->settings->get_woo_settings()['country'];
|
||||
$country_location = in_array( $store_country, $paylater_countries, true ) ? strtolower( $store_country ) : 'us';
|
||||
|
||||
return array(
|
||||
'save_paypal_and_venmo' => array(
|
||||
'title' => __( 'Save PayPal and Venmo', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'Securely save PayPal and Venmo payment methods for subscriptions or return buyers.', 'woocommerce-paypal-payments' ),
|
||||
'enabled' => $this->merchant_capabilities['save_paypal'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'settings',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
'urls' => array(
|
||||
'sandbox' => 'https://www.sandbox.paypal.com/bizsignup/entry?product=ADVANCED_VAULTING',
|
||||
'live' => 'https://www.paypal.com/bizsignup/entry?product=ADVANCED_VAULTING',
|
||||
),
|
||||
'showWhen' => 'disabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://www.paypal.com/us/enterprise/payment-processing/accept-venmo',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
),
|
||||
'advanced_credit_and_debit_cards' => array(
|
||||
'title' => __( 'Advanced Credit and Debit Cards', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'Process major credit and debit cards including Visa, Mastercard, American Express and Discover.', 'woocommerce-paypal-payments' ),
|
||||
'enabled' => $this->merchant_capabilities['acdc'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'payment_methods',
|
||||
'section' => 'ppcp-credit-card-gateway',
|
||||
'highlight' => 'ppcp-credit-card-gateway',
|
||||
'modal' => 'ppcp-credit-card-gateway',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
'urls' => array(
|
||||
'sandbox' => 'https://www.sandbox.paypal.com/bizsignup/entry?product=ppcp',
|
||||
'live' => 'https://www.paypal.com/bizsignup/entry?product=ppcp',
|
||||
),
|
||||
'showWhen' => 'disabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://developer.paypal.com/studio/checkout/advanced',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
),
|
||||
'alternative_payment_methods' => array(
|
||||
'title' => __( 'Alternative Payment Methods', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'Offer global, country-specific payment options for your customers.', 'woocommerce-paypal-payments' ),
|
||||
'enabled' => $this->merchant_capabilities['apm'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'payment_methods',
|
||||
'section' => 'ppcp-alternative-payments-card',
|
||||
'highlight' => 'ppcp-alternative-payments-card',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://developer.paypal.com/docs/checkout/apm/',
|
||||
'showWhen' => 'disabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://developer.paypal.com/docs/checkout/apm/',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
),
|
||||
'google_pay' => array(
|
||||
'title' => __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'Let customers pay using their Google Pay wallet.', 'woocommerce-paypal-payments' ),
|
||||
'enabled' => $this->merchant_capabilities['google_pay'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'payment_methods',
|
||||
'section' => 'ppcp-card-payments-card',
|
||||
'highlight' => 'ppcp-googlepay',
|
||||
'modal' => 'ppcp-googlepay',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
'urls' => array(
|
||||
'sandbox' => 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY',
|
||||
'live' => 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY',
|
||||
),
|
||||
'showWhen' => 'disabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://developer.paypal.com/docs/checkout/apm/google-pay/',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
'notes' => array(
|
||||
__( '¹PayPal Q2 Earnings-2021.', 'woocommerce-paypal-payments' ),
|
||||
),
|
||||
),
|
||||
'apple_pay' => array(
|
||||
'title' => __( 'Apple Pay', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'Let customers pay using their Apple Pay wallet.', 'woocommerce-paypal-payments' ),
|
||||
'enabled' => $this->merchant_capabilities['apple_pay'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'payment_methods',
|
||||
'section' => 'ppcp-card-payments-card',
|
||||
'highlight' => 'ppcp-applepay',
|
||||
'modal' => 'ppcp-applepay',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Domain registration', 'woocommerce-paypal-payments' ),
|
||||
'urls' => array(
|
||||
'sandbox' => 'https://www.sandbox.paypal.com/uccservicing/apm/applepay',
|
||||
'live' => 'https://www.paypal.com/uccservicing/apm/applepay',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
||||
'urls' => array(
|
||||
'sandbox' => 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY',
|
||||
'live' => 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY',
|
||||
),
|
||||
'showWhen' => 'disabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => 'https://developer.paypal.com/docs/checkout/apm/apple-pay/',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
),
|
||||
'pay_later' => array(
|
||||
'title' => __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ),
|
||||
'description' => __(
|
||||
'Let customers know they can buy now and pay later with PayPal. Adding this messaging can boost conversion rates and increase cart sizes by 39%¹, with no extra cost to you—plus, you get paid up front.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'enabled' => $this->merchant_capabilities['pay_later'],
|
||||
'buttons' => array(
|
||||
array(
|
||||
'type' => 'secondary',
|
||||
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
'action' => array(
|
||||
'type' => 'tab',
|
||||
'tab' => 'pay_later_messaging',
|
||||
),
|
||||
'showWhen' => 'enabled',
|
||||
'class' => 'small-button',
|
||||
),
|
||||
array(
|
||||
'type' => 'tertiary',
|
||||
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
||||
'url' => "https://www.paypal.com/$country_location/business/accept-payments/checkout/installments",
|
||||
'class' => 'small-button',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
99
modules/ppcp-settings/src/Endpoint/FeaturesRestEndpoint.php
Normal file
99
modules/ppcp-settings/src/Endpoint/FeaturesRestEndpoint.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
/**
|
||||
* REST endpoint to manage features.
|
||||
*
|
||||
* Provides endpoints for retrieving features via WP REST API routes.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
|
||||
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Response;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\Definition\FeaturesDefinition;
|
||||
|
||||
/**
|
||||
* REST controller for the features in the Overview tab.
|
||||
*
|
||||
* This API acts as the intermediary between the "external world" and our
|
||||
* internal data model. It's responsible for checking eligibility and
|
||||
* providing configuration data for features.
|
||||
*/
|
||||
class FeaturesRestEndpoint extends RestEndpoint {
|
||||
/**
|
||||
* The base path for this REST controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'features';
|
||||
|
||||
/**
|
||||
* The features definition instance.
|
||||
*
|
||||
* @var FeaturesDefinition
|
||||
*/
|
||||
protected FeaturesDefinition $features_definition;
|
||||
|
||||
/**
|
||||
* The settings endpoint instance.
|
||||
*
|
||||
* @var SettingsRestEndpoint
|
||||
*/
|
||||
protected SettingsRestEndpoint $settings;
|
||||
|
||||
/**
|
||||
* FeaturesRestEndpoint constructor.
|
||||
*
|
||||
* @param FeaturesDefinition $features_definition The features definition instance.
|
||||
* @param SettingsRestEndpoint $settings The settings endpoint instance.
|
||||
*/
|
||||
public function __construct(
|
||||
FeaturesDefinition $features_definition,
|
||||
SettingsRestEndpoint $settings
|
||||
) {
|
||||
$this->features_definition = $features_definition;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST API routes for features management.
|
||||
*/
|
||||
public function register_routes(): void {
|
||||
// GET /features - Get features list.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_features' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current features.
|
||||
*
|
||||
* @return WP_REST_Response The response containing features data.
|
||||
*/
|
||||
public function get_features(): WP_REST_Response {
|
||||
$features = array();
|
||||
foreach ( $this->features_definition->get() as $id => $feature ) {
|
||||
$features[] = array_merge(
|
||||
array( 'id' => $id ),
|
||||
$feature
|
||||
);
|
||||
}
|
||||
|
||||
return $this->return_success(
|
||||
array(
|
||||
'features' => $features,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
103
modules/ppcp-settings/src/Service/FeaturesEligibilityService.php
Normal file
103
modules/ppcp-settings/src/Service/FeaturesEligibilityService.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
/**
|
||||
* PayPal Commerce eligibility service for WooCommerce.
|
||||
*
|
||||
* This file contains the FeaturesEligibilityService class which manages eligibility checks
|
||||
* for various PayPal Commerce features including saving PayPal and Venmo, advanced credit and debit cards,
|
||||
* alternative payment methods, Google Pay, Apple Pay, and Pay Later.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||
|
||||
/**
|
||||
* Manages eligibility checks for various PayPal Commerce features.
|
||||
*/
|
||||
class FeaturesEligibilityService {
|
||||
/**
|
||||
* Whether saving PayPal and Venmo is eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_save_paypal_and_venmo_eligible;
|
||||
|
||||
/**
|
||||
* Whether advanced credit and debit cards are eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_advanced_credit_and_debit_cards_eligible;
|
||||
|
||||
/**
|
||||
* Whether alternative payment methods are eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_alternative_payment_methods_eligible;
|
||||
|
||||
/**
|
||||
* Whether Google Pay is eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_google_pay_eligible;
|
||||
|
||||
/**
|
||||
* Whether Apple Pay is eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_apple_pay_eligible;
|
||||
|
||||
/**
|
||||
* Whether Pay Later is eligible.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $is_pay_later_eligible;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bool $is_save_paypal_and_venmo_eligible Whether saving PayPal and Venmo is eligible.
|
||||
* @param bool $is_advanced_credit_and_debit_cards_eligible Whether advanced credit and debit cards are eligible.
|
||||
* @param bool $is_alternative_payment_methods_eligible Whether alternative payment methods are eligible.
|
||||
* @param bool $is_google_pay_eligible Whether Google Pay is eligible.
|
||||
* @param bool $is_apple_pay_eligible Whether Apple Pay is eligible.
|
||||
* @param bool $is_pay_later_eligible Whether Pay Later is eligible.
|
||||
*/
|
||||
public function __construct(
|
||||
bool $is_save_paypal_and_venmo_eligible,
|
||||
bool $is_advanced_credit_and_debit_cards_eligible,
|
||||
bool $is_alternative_payment_methods_eligible,
|
||||
bool $is_google_pay_eligible,
|
||||
bool $is_apple_pay_eligible,
|
||||
bool $is_pay_later_eligible
|
||||
) {
|
||||
$this->is_save_paypal_and_venmo_eligible = $is_save_paypal_and_venmo_eligible;
|
||||
$this->is_advanced_credit_and_debit_cards_eligible = $is_advanced_credit_and_debit_cards_eligible;
|
||||
$this->is_alternative_payment_methods_eligible = $is_alternative_payment_methods_eligible;
|
||||
$this->is_google_pay_eligible = $is_google_pay_eligible;
|
||||
$this->is_apple_pay_eligible = $is_apple_pay_eligible;
|
||||
$this->is_pay_later_eligible = $is_pay_later_eligible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all eligibility checks as callables.
|
||||
*
|
||||
* @return array<string, callable>
|
||||
*/
|
||||
public function get_eligibility_checks(): array {
|
||||
return array(
|
||||
'save_paypal_and_venmo' => fn() => $this->is_save_paypal_and_venmo_eligible,
|
||||
'advanced_credit_and_debit_cards' => fn() => $this->is_advanced_credit_and_debit_cards_eligible,
|
||||
'alternative_payment_methods' => fn() => $this->is_alternative_payment_methods_eligible,
|
||||
'google_pay' => fn() => $this->is_google_pay_eligible,
|
||||
'apple_pay' => fn() => $this->is_apple_pay_eligible,
|
||||
'pay_later' => fn() => $this->is_pay_later_eligible,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -258,6 +258,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
'styling' => $container->get( 'settings.rest.styling' ),
|
||||
'todos' => $container->get( 'settings.rest.todos' ),
|
||||
'pay_later_messaging' => $container->get( 'settings.rest.pay_later_messaging' ),
|
||||
'features' => $container->get( 'settings.rest.features' ),
|
||||
);
|
||||
|
||||
foreach ( $endpoints as $endpoint ) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue