mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge remote-tracking branch 'origin/trunk' into PCP-3930-Make-the-webhook-resubscribe/simulate-logic-usable-in-React-application
# Conflicts: # modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/ButtonSettingsBlock.js # modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettingsElements/Blocks/Troubleshooting.js # modules/ppcp-settings/resources/js/data/common/action-types.js # modules/ppcp-settings/resources/js/data/common/constants.js # modules/ppcp-settings/src/SettingsModule.php
This commit is contained in:
commit
40b1b0d280
31 changed files with 747 additions and 1386 deletions
|
@ -182,6 +182,26 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,26 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
"build": "wp-scripts build --webpack-src-dir=resources/js --output-path=assets"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@woocommerce/navigation": "~8.1.0",
|
||||
"@wordpress/data": "^10.10.0",
|
||||
"@wordpress/data-controls": "^4.10.0",
|
||||
"@wordpress/scripts": "^30.3.0"
|
||||
"@wordpress/scripts": "^30.3.0",
|
||||
"classnames": "^2.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@paypal/react-paypal-js": "^8.7.0",
|
||||
"@woocommerce/settings": "^1.0.0",
|
||||
"react-select": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from '@wordpress/components';
|
||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useAccordionState } from '../../hooks/useAccordionState';
|
||||
|
||||
// Provide defaults for all layout components so the generic version just works.
|
||||
|
@ -24,6 +22,13 @@ const DefaultDescription = ( { children } ) => (
|
|||
<div className="ppcp-r-accordion__description">{ children }</div>
|
||||
);
|
||||
|
||||
const AccordionContent = ( { isOpen, children } ) => {
|
||||
if ( ! isOpen || ! children ) {
|
||||
return null;
|
||||
}
|
||||
return <div className="ppcp-r-accordion__content">{ children }</div>;
|
||||
};
|
||||
|
||||
const Accordion = ( {
|
||||
title,
|
||||
id = '',
|
||||
|
@ -65,9 +70,7 @@ const Accordion = ( {
|
|||
) }
|
||||
</Header>
|
||||
</button>
|
||||
{ isOpen && children && (
|
||||
<div className="ppcp-r-accordion__content">{ children }</div>
|
||||
) }
|
||||
<AccordionContent isOpen={ isOpen }>{ children }</AccordionContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,25 +9,19 @@ import {
|
|||
} from './SettingsBlockElements';
|
||||
|
||||
const SettingsAccordion = ( { title, description, children, ...props } ) => (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__accordion"
|
||||
components={ [
|
||||
() => (
|
||||
<Accordion
|
||||
title={ title }
|
||||
description={ description }
|
||||
Header={ Header }
|
||||
TitleWrapper={ TitleWrapper }
|
||||
Title={ Title }
|
||||
Action={ Action }
|
||||
Description={ Description }
|
||||
>
|
||||
{ children }
|
||||
</Accordion>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__accordion">
|
||||
<Accordion
|
||||
title={ title }
|
||||
description={ description }
|
||||
Header={ Header }
|
||||
TitleWrapper={ TitleWrapper }
|
||||
Title={ Title }
|
||||
Action={ Action }
|
||||
Description={ Description }
|
||||
>
|
||||
{ children }
|
||||
</Accordion>
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default SettingsAccordion;
|
||||
|
|
|
@ -3,33 +3,25 @@ import SettingsBlock from './SettingsBlock';
|
|||
import { Header, Title, Action, Description } from './SettingsBlockElements';
|
||||
|
||||
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__button"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
<Header>
|
||||
<Title>{ title }</Title>
|
||||
<Description>{ description }</Description>
|
||||
</Header>
|
||||
<Action>
|
||||
<Button
|
||||
isBusy={ props.actionProps?.isBusy }
|
||||
variant={ props.actionProps?.buttonType }
|
||||
onClick={
|
||||
props.actionProps?.callback
|
||||
? () => props.actionProps.callback()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{ props.actionProps.value }
|
||||
</Button>
|
||||
</Action>
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
|
||||
<Header>
|
||||
<Title>{ title }</Title>
|
||||
<Description>{ description }</Description>
|
||||
</Header>
|
||||
<Action>
|
||||
<Button
|
||||
isBusy={ props.actionProps?.isBusy }
|
||||
variant={ props.actionProps?.buttonType }
|
||||
onClick={
|
||||
props.actionProps?.callback
|
||||
? () => props.actionProps.callback()
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{ props.actionProps.value }
|
||||
</Button>
|
||||
</Action>
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default ButtonSettingsBlock;
|
||||
|
|
|
@ -11,56 +11,42 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="ppcp-r-feature-item__notes">
|
||||
{ notes.map( ( note, index ) => (
|
||||
<span key={ index }>{ note }</span>
|
||||
) ) }
|
||||
</span>
|
||||
</>
|
||||
<span className="ppcp-r-feature-item__notes">
|
||||
{ notes.map( ( note, index ) => (
|
||||
<span key={ index }>{ note }</span>
|
||||
) ) }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__feature"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
<Header>
|
||||
<Title>
|
||||
{ title }
|
||||
{ props.actionProps?.featureStatus && (
|
||||
<TitleBadge
|
||||
{ ...props.actionProps?.badge }
|
||||
/>
|
||||
) }
|
||||
</Title>
|
||||
<Description className="ppcp-r-settings-block__feature__description">
|
||||
{ description }
|
||||
{ printNotes() }
|
||||
</Description>
|
||||
</Header>
|
||||
<Action>
|
||||
<div className="ppcp-r-feature-item__buttons">
|
||||
{ props.actionProps?.buttons.map(
|
||||
( button ) => (
|
||||
<Button
|
||||
href={ button.url }
|
||||
key={ button.text }
|
||||
variant={ button.type }
|
||||
>
|
||||
{ button.text }
|
||||
</Button>
|
||||
)
|
||||
) }
|
||||
</div>
|
||||
</Action>
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__feature">
|
||||
<Header>
|
||||
<Title>
|
||||
{ title }
|
||||
{ props.actionProps?.enabled && (
|
||||
<TitleBadge { ...props.actionProps?.badge } />
|
||||
) }
|
||||
</Title>
|
||||
<Description className="ppcp-r-settings-block__feature__description">
|
||||
{ description }
|
||||
{ printNotes() }
|
||||
</Description>
|
||||
</Header>
|
||||
<Action>
|
||||
<div className="ppcp-r-feature-item__buttons">
|
||||
{ props.actionProps?.buttons.map( ( button ) => (
|
||||
<Button
|
||||
href={ button.url }
|
||||
key={ button.text }
|
||||
variant={ button.type }
|
||||
>
|
||||
{ button.text }
|
||||
</Button>
|
||||
) ) }
|
||||
</div>
|
||||
</Action>
|
||||
</SettingsBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -42,28 +42,20 @@ const InputSettingsBlock = ( {
|
|||
order = DEFAULT_ELEMENT_ORDER,
|
||||
...props
|
||||
} ) => (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__input"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
{ order.map( ( elementKey ) => {
|
||||
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
|
||||
return RenderElement ? (
|
||||
<RenderElement
|
||||
key={ elementKey }
|
||||
title={ title }
|
||||
description={ description }
|
||||
supplementaryLabel={ supplementaryLabel }
|
||||
actionProps={ props.actionProps }
|
||||
/>
|
||||
) : null;
|
||||
} ) }
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__input">
|
||||
{ order.map( ( elementKey ) => {
|
||||
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
|
||||
return RenderElement ? (
|
||||
<RenderElement
|
||||
key={ elementKey }
|
||||
title={ title }
|
||||
description={ description }
|
||||
supplementaryLabel={ supplementaryLabel }
|
||||
actionProps={ props.actionProps }
|
||||
/>
|
||||
) : null;
|
||||
} ) }
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default InputSettingsBlock;
|
||||
|
|
|
@ -5,56 +5,43 @@ import PaymentMethodIcon from '../PaymentMethodIcon';
|
|||
import data from '../../../utils/data';
|
||||
|
||||
const PaymentMethodItemBlock = ( props ) => {
|
||||
const [ paymentMethodState, setPaymentMethodState ] = useState();
|
||||
const [ toggleIsChecked, setToggleIsChecked ] = useState( false );
|
||||
const [ modalIsVisible, setModalIsVisible ] = useState( false );
|
||||
const Modal = props?.modal;
|
||||
|
||||
const handleCheckboxState = ( checked ) => {
|
||||
setPaymentMethodState( checked ? props.id : null );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsBlock
|
||||
className="ppcp-r-settings-block__payment-methods__item"
|
||||
components={ [
|
||||
() => (
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__inner">
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__title-wrapper">
|
||||
<PaymentMethodIcon
|
||||
icons={ [ props.icon ] }
|
||||
type={ props.icon }
|
||||
/>
|
||||
<span className="ppcp-r-settings-block__payment-methods__item__title">
|
||||
{ props.title }
|
||||
</span>
|
||||
<SettingsBlock className="ppcp-r-settings-block__payment-methods__item">
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__inner">
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__title-wrapper">
|
||||
<PaymentMethodIcon
|
||||
icons={ [ props.icon ] }
|
||||
type={ props.icon }
|
||||
/>
|
||||
<span className="ppcp-r-settings-block__payment-methods__item__title">
|
||||
{ props.title }
|
||||
</span>
|
||||
</div>
|
||||
<p className="ppcp-r-settings-block__payment-methods__item__description">
|
||||
{ props.description }
|
||||
</p>
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__footer">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom={ true }
|
||||
checked={ toggleIsChecked }
|
||||
onChange={ setToggleIsChecked }
|
||||
/>
|
||||
{ Modal && (
|
||||
<div
|
||||
className="ppcp-r-settings-block__payment-methods__item__settings"
|
||||
onClick={ () => setModalIsVisible( true ) }
|
||||
>
|
||||
{ data().getImage( 'icon-settings.svg' ) }
|
||||
</div>
|
||||
<p className="ppcp-r-settings-block__payment-methods__item__description">
|
||||
{ props.description }
|
||||
</p>
|
||||
<div className="ppcp-r-settings-block__payment-methods__item__footer">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom={ true }
|
||||
checked={ props.id === paymentMethodState }
|
||||
onChange={ handleCheckboxState }
|
||||
/>
|
||||
{ Modal && (
|
||||
<div
|
||||
className="ppcp-r-settings-block__payment-methods__item__settings"
|
||||
onClick={ () =>
|
||||
setModalIsVisible( true )
|
||||
}
|
||||
>
|
||||
{ data().getImage(
|
||||
'icon-settings.svg'
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</SettingsBlock>
|
||||
{ Modal && modalIsVisible && (
|
||||
<Modal setModalIsVisible={ setModalIsVisible } />
|
||||
) }
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import { useState, useCallback } from '@wordpress/element';
|
||||
import SettingsBlock from './SettingsBlock';
|
||||
import PaymentMethodItemBlock from './PaymentMethodItemBlock';
|
||||
|
||||
const PaymentMethodsBlock = ( { paymentMethods, className = '' } ) => {
|
||||
const [ selectedMethod, setSelectedMethod ] = useState( null );
|
||||
|
||||
const handleSelect = useCallback( ( methodId, isSelected ) => {
|
||||
setSelectedMethod( isSelected ? methodId : null );
|
||||
}, [] );
|
||||
|
||||
if ( paymentMethods.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -9,19 +16,18 @@ const PaymentMethodsBlock = ( { paymentMethods, className = '' } ) => {
|
|||
return (
|
||||
<SettingsBlock
|
||||
className={ `ppcp-r-settings-block__payment-methods ${ className }` }
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
{ paymentMethods.map( ( paymentMethod ) => (
|
||||
<PaymentMethodItemBlock
|
||||
key={ paymentMethod.id }
|
||||
{ ...paymentMethod }
|
||||
/>
|
||||
) ) }
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
>
|
||||
{ paymentMethods.map( ( paymentMethod ) => (
|
||||
<PaymentMethodItemBlock
|
||||
key={ paymentMethod.id }
|
||||
{ ...paymentMethod }
|
||||
isSelected={ selectedMethod === paymentMethod.id }
|
||||
onSelect={ ( checked ) =>
|
||||
handleSelect( paymentMethod.id, checked )
|
||||
}
|
||||
/>
|
||||
) ) }
|
||||
</SettingsBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,37 +11,32 @@ const RadioSettingsBlock = ( {
|
|||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__radio ppcp-r-settings-block--expert-rdb"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
<Header>
|
||||
<Title>{ title }</Title>
|
||||
<Description>{ description }</Description>
|
||||
</Header>
|
||||
{ options.map( ( option ) => (
|
||||
<PayPalRdbWithContent
|
||||
key={ option.id }
|
||||
id={ option.id }
|
||||
name={ props.actionProps?.name }
|
||||
value={ option.value }
|
||||
currentValue={ props.actionProps?.currentValue }
|
||||
handleRdbState={ ( newValue ) =>
|
||||
props.actionProps?.callback(
|
||||
props.actionProps?.key,
|
||||
newValue
|
||||
)
|
||||
}
|
||||
label={ option.label }
|
||||
description={ option.description }
|
||||
toggleAdditionalContent={ true }
|
||||
>
|
||||
{ option.additionalContent }
|
||||
</PayPalRdbWithContent>
|
||||
) ) }
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
>
|
||||
<Header>
|
||||
<Title>{ title }</Title>
|
||||
<Description>{ description }</Description>
|
||||
</Header>
|
||||
{ options.map( ( option ) => (
|
||||
<PayPalRdbWithContent
|
||||
key={ option.id }
|
||||
id={ option.id }
|
||||
name={ props.actionProps?.name }
|
||||
value={ option.value }
|
||||
currentValue={ props.actionProps?.currentValue }
|
||||
handleRdbState={ ( newValue ) =>
|
||||
props.actionProps?.callback(
|
||||
props.actionProps?.key,
|
||||
newValue
|
||||
)
|
||||
}
|
||||
label={ option.label }
|
||||
description={ option.description }
|
||||
toggleAdditionalContent={ true }
|
||||
>
|
||||
{ option.additionalContent }
|
||||
</PayPalRdbWithContent>
|
||||
) ) }
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default RadioSettingsBlock;
|
||||
|
|
|
@ -35,27 +35,19 @@ const SelectSettingsBlock = ( {
|
|||
order = DEFAULT_ELEMENT_ORDER,
|
||||
...props
|
||||
} ) => (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__select"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
{ order.map( ( elementKey ) => {
|
||||
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
|
||||
return RenderElement ? (
|
||||
<RenderElement
|
||||
key={ elementKey }
|
||||
title={ title }
|
||||
description={ description }
|
||||
actionProps={ props.actionProps }
|
||||
/>
|
||||
) : null;
|
||||
} ) }
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__select">
|
||||
{ order.map( ( elementKey ) => {
|
||||
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
|
||||
return RenderElement ? (
|
||||
<RenderElement
|
||||
key={ elementKey }
|
||||
title={ title }
|
||||
description={ description }
|
||||
actionProps={ props.actionProps }
|
||||
/>
|
||||
) : null;
|
||||
} ) }
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default SelectSettingsBlock;
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
const SettingsBlock = ( { className, components = [] } ) => {
|
||||
const SettingsBlock = ( { className, children } ) => {
|
||||
const blockClassName = [ 'ppcp-r-settings-block', className ].filter(
|
||||
Boolean
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ blockClassName.join( ' ' ) }>
|
||||
{ components.map( ( Component, index ) => (
|
||||
<Component key={ index } />
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
return <div className={ blockClassName.join( ' ' ) }>{ children }</div>;
|
||||
};
|
||||
|
||||
export default SettingsBlock;
|
||||
|
|
|
@ -3,35 +3,25 @@ import SettingsBlock from './SettingsBlock';
|
|||
import { Header, Title, Action, Description } from './SettingsBlockElements';
|
||||
|
||||
const ToggleSettingsBlock = ( { title, description, ...props } ) => (
|
||||
<SettingsBlock
|
||||
{ ...props }
|
||||
className="ppcp-r-settings-block__toggle"
|
||||
components={ [
|
||||
() => (
|
||||
<Action>
|
||||
<ToggleControl
|
||||
className="ppcp-r-settings-block__toggle-control"
|
||||
__nextHasNoMarginBottom={ true }
|
||||
checked={ props.actionProps?.value }
|
||||
onChange={ ( newValue ) =>
|
||||
props.actionProps?.callback(
|
||||
props.actionProps?.key,
|
||||
newValue
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Action>
|
||||
),
|
||||
() => (
|
||||
<Header>
|
||||
{ title && <Title>{ title }</Title> }
|
||||
{ description && (
|
||||
<Description>{ description }</Description>
|
||||
) }
|
||||
</Header>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<SettingsBlock { ...props } className="ppcp-r-settings-block__toggle">
|
||||
<Action>
|
||||
<ToggleControl
|
||||
className="ppcp-r-settings-block__toggle-control"
|
||||
__nextHasNoMarginBottom={ true }
|
||||
checked={ props.actionProps?.value }
|
||||
onChange={ ( newValue ) =>
|
||||
props.actionProps?.callback(
|
||||
props.actionProps?.key,
|
||||
newValue
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Action>
|
||||
<Header>
|
||||
{ title && <Title>{ title }</Title> }
|
||||
{ description && <Description>{ description }</Description> }
|
||||
</Header>
|
||||
</SettingsBlock>
|
||||
);
|
||||
|
||||
export default ToggleSettingsBlock;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useEffect, useState } from '@wordpress/element';
|
||||
import { TabPanel } from '@wordpress/components';
|
||||
import { getQuery, updateQueryString } from '@woocommerce/navigation';
|
||||
|
||||
import { getQuery, updateQueryString } from '../../utils/navigation';
|
||||
|
||||
const TabNavigation = ( { tabs } ) => {
|
||||
const { panel } = getQuery();
|
||||
|
@ -30,7 +31,7 @@ const TabNavigation = ( { tabs } ) => {
|
|||
);
|
||||
|
||||
useEffect( () => {
|
||||
updateQueryString( { panel: activePanel }, '/', getQuery() );
|
||||
updateQueryString( { panel: activePanel } );
|
||||
}, [ activePanel ] );
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,49 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { Button, Icon } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { reusableBlock } from '@wordpress/icons';
|
||||
|
||||
import SettingsCard from '../../ReusableComponents/SettingsCard';
|
||||
import TodoSettingsBlock from '../../ReusableComponents/SettingsBlocks/TodoSettingsBlock';
|
||||
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 { 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();
|
||||
|
||||
// TODO: Implement the refresh logic, remove this debug code -- PCP-4024
|
||||
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 +73,54 @@ const TabOverview = () => {
|
|||
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
|
||||
description={
|
||||
<div>
|
||||
<p>{ __( 'Enable additional features…' ) }</p>
|
||||
<p>{ __( 'Click Refresh…' ) }</p>
|
||||
<Button variant="tertiary">
|
||||
{ data().getImage( 'icon-refresh.svg' ) }
|
||||
{ __( 'Refresh', 'woocommerce-paypal-payments' ) }
|
||||
<p>
|
||||
{ __(
|
||||
'Enable additional features…',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ __(
|
||||
'Click Refresh…',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</p>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ refreshHandler }
|
||||
disabled={ isRefreshing }
|
||||
>
|
||||
<Icon icon={ reusableBlock } size={ 18 } />
|
||||
{ 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,
|
||||
} }
|
||||
/>
|
||||
) ) }
|
||||
|
@ -71,6 +129,7 @@ const TabOverview = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// TODO: This list should be refactored into a separate module, maybe utils/thingsToDoNext.js
|
||||
const todosDataDefault = [
|
||||
{
|
||||
value: 'paypal_later_messaging',
|
||||
|
@ -106,6 +165,7 @@ const todosDataDefault = [
|
|||
},
|
||||
];
|
||||
|
||||
// TODO: Hardcoding this list here is not the best idea. Can we move this to a REST API response?
|
||||
const featuresDefault = [
|
||||
{
|
||||
id: 'save_paypal_and_venmo',
|
||||
|
@ -133,7 +193,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 +240,6 @@ const featuresDefault = [
|
|||
'Let customers pay using their Google Pay wallet.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
featureStatus: true,
|
||||
buttons: [
|
||||
{
|
||||
type: 'secondary',
|
||||
|
|
|
@ -9,55 +9,40 @@ import {
|
|||
|
||||
const OrderIntent = ( { updateFormValue, settings } ) => {
|
||||
return (
|
||||
<SettingsBlock
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
<Header>
|
||||
<Title>
|
||||
{ __(
|
||||
'Order Intent',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Title>
|
||||
<Description>
|
||||
{ __(
|
||||
'Choose between immediate capture or authorization-only, with manual capture in the Order section.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Description>
|
||||
</Header>
|
||||
</>
|
||||
),
|
||||
() => (
|
||||
<>
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Authorize Only',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'authorizeOnly',
|
||||
value: settings.authorizeOnly,
|
||||
} }
|
||||
/>
|
||||
<SettingsBlock>
|
||||
<Header>
|
||||
<Title>
|
||||
{ __( 'Order Intent', 'woocommerce-paypal-payments' ) }
|
||||
</Title>
|
||||
<Description>
|
||||
{ __(
|
||||
'Choose between immediate capture or authorization-only, with manual capture in the Order section.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Description>
|
||||
</Header>
|
||||
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Capture Virtual-Only Orders',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'captureVirtualOnlyOrders',
|
||||
value: settings.captureVirtualOnlyOrders,
|
||||
} }
|
||||
/>
|
||||
</>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
<ToggleSettingsBlock
|
||||
title={ __( 'Authorize Only', 'woocommerce-paypal-payments' ) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'authorizeOnly',
|
||||
value: settings.authorizeOnly,
|
||||
} }
|
||||
/>
|
||||
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Capture Virtual-Only Orders',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'captureVirtualOnlyOrders',
|
||||
value: settings.captureVirtualOnlyOrders,
|
||||
} }
|
||||
/>
|
||||
</SettingsBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,82 +1,73 @@
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
Header,
|
||||
SettingsBlock,
|
||||
ToggleSettingsBlock,
|
||||
Title,
|
||||
Description,
|
||||
} from '../../../../ReusableComponents/SettingsBlocks';
|
||||
import { Header } from '../../../../ReusableComponents/SettingsBlocks/SettingsBlockElements';
|
||||
|
||||
const SavePaymentMethods = ( { updateFormValue, settings } ) => {
|
||||
return (
|
||||
<SettingsBlock
|
||||
className="ppcp-r-settings-block--save-payment-methods"
|
||||
components={ [
|
||||
() => (
|
||||
<>
|
||||
<Header>
|
||||
<Title>
|
||||
{ __(
|
||||
'Save payment methods',
|
||||
<SettingsBlock className="ppcp-r-settings-block--save-payment-methods">
|
||||
<Header>
|
||||
<Title>
|
||||
{ __(
|
||||
'Save payment methods',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Title>
|
||||
<Description>
|
||||
{ __(
|
||||
"Securely store customers' payment methods for future payments and subscriptions, simplifying checkout and enabling recurring transactions.",
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Description>
|
||||
</Header>
|
||||
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Save PayPal and Venmo',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={
|
||||
<div
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: sprintf(
|
||||
/* translators: 1: URL to Pay Later documentation, 2: URL to Alternative Payment Methods documentation */
|
||||
__(
|
||||
'Securely store your customers\' PayPal accounts for a seamless checkout experience. <br />This will disable all <a target="_blank" rel="noreferrer" href="%1$s">Pay Later</a> features and <a target="_blank" rel="noreferrer" href="%2$s">Alternative Payment Methods</a> on your site.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Title>
|
||||
<Description>
|
||||
{ __(
|
||||
'Securely store customers’ payment methods for future payments and subscriptions, simplifying checkout and enabling recurring transactions.',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
</Description>
|
||||
</Header>
|
||||
</>
|
||||
),
|
||||
() => (
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Save PayPal and Venmo',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={
|
||||
<div
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: sprintf(
|
||||
/* translators: 1: URL to Pay Later documentation, 2: URL to Alternative Payment Methods documentation */
|
||||
__(
|
||||
'Securely store your customers\' PayPal accounts for a seamless checkout experience. <br />This will disable all <a target="_blank" rel="noreferrer" href="%1$s">Pay Later</a> features and <a target="_blank" rel="noreferrer" href="%2$s">Alternative Payment Methods</a> on your site.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
|
||||
),
|
||||
} }
|
||||
/>
|
||||
}
|
||||
actionProps={ {
|
||||
value: settings.savePaypalAndVenmo,
|
||||
callback: updateFormValue,
|
||||
key: 'savePaypalAndVenmo',
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#pay-later',
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/#alternative-payment-methods'
|
||||
),
|
||||
} }
|
||||
/>
|
||||
),
|
||||
() => (
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Save Credit and Debit Cards',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
"Securely store your customer's credit card.",
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'saveCreditCardAndDebitCard',
|
||||
value: settings.saveCreditCardAndDebitCard,
|
||||
} }
|
||||
/>
|
||||
),
|
||||
] }
|
||||
/>
|
||||
}
|
||||
actionProps={ {
|
||||
value: settings.savePaypalAndVenmo,
|
||||
callback: updateFormValue,
|
||||
key: 'savePaypalAndVenmo',
|
||||
} }
|
||||
/>
|
||||
|
||||
<ToggleSettingsBlock
|
||||
title={ __(
|
||||
'Save Credit and Debit Cards',
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
description={ __(
|
||||
"Securely store your customer's credit card.",
|
||||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
actionProps={ {
|
||||
callback: updateFormValue,
|
||||
key: 'saveCreditCardAndDebitCard',
|
||||
value: settings.saveCreditCardAndDebitCard,
|
||||
} }
|
||||
/>
|
||||
</SettingsBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ const TabStyling = () => {
|
|||
return (
|
||||
<div className="ppcp-r-styling">
|
||||
<div className="ppcp-r-styling__settings">
|
||||
<SectionIntro />
|
||||
<SectionIntro location={ location } />
|
||||
<SectionLocations
|
||||
locationOptions={ locationOptions }
|
||||
location={ location }
|
||||
|
@ -157,20 +157,17 @@ const TabStylingSection = ( props ) => {
|
|||
);
|
||||
};
|
||||
|
||||
const SectionIntro = () => {
|
||||
const buttonStyleDescription = sprintf(
|
||||
// translators: %s: Link to Classic checkout page
|
||||
__(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Classic Checkout page</a>. Checkout Buttons must be enabled to display the PayPal gateway on the Checkout page.'
|
||||
),
|
||||
'#'
|
||||
);
|
||||
const SectionIntro = ( { location } ) => {
|
||||
const { description, descriptionLink } =
|
||||
defaultLocationSettings[ location ];
|
||||
const buttonStyleDescription = sprintf( description, descriptionLink );
|
||||
|
||||
return (
|
||||
<TabStylingSection
|
||||
className="ppcp-r-styling__section--rc ppcp-r-styling__section--empty"
|
||||
title={ __( 'Button Styling', 'wooocommerce-paypal-payments' ) }
|
||||
description={ buttonStyleDescription }
|
||||
></TabStylingSection>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -321,6 +318,7 @@ const SectionButtonPreview = ( { locationSettings } ) => {
|
|||
clientId: 'test',
|
||||
merchantId: 'QTQX5NP6N9WZU',
|
||||
components: 'buttons,googlepay',
|
||||
'disable-funding': 'card',
|
||||
'buyer-country': 'US',
|
||||
currency: 'USD',
|
||||
} }
|
||||
|
|
|
@ -23,5 +23,6 @@ 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',
|
||||
DO_WEBHOOKS_DATA: 'COMMON:DO_WEBHOOKS_DATA',
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
@ -192,5 +192,30 @@ export const connectViaIdAndSecret = function* () {
|
|||
* @return {Action} The action.
|
||||
*/
|
||||
export const refreshMerchantData = function* () {
|
||||
return yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT };
|
||||
const result = yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT };
|
||||
|
||||
if ( result.success && result.merchant ) {
|
||||
yield hydrate( result );
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
// TODO: Review if we can get the updated feature details in the result.data instead of
|
||||
// doing a second refreshMerchantData() request.
|
||||
yield refreshMerchantData();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -55,3 +55,14 @@ export const REST_MANUAL_CONNECTION_PATH = '/wc/v3/wc_paypal/connect_manual';
|
|||
export const REST_CONNECTION_URL_PATH = '/wc/v3/wc_paypal/login_link';
|
||||
export const REST_WEBHOOKS = '/wc/v3/wc_paypal/webhook_settings';
|
||||
export const REST_WEBHOOKS_SIMULATE = '/wc/v3/wc_paypal/webhooks_simulate';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
|
|
@ -7,22 +7,21 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import {
|
||||
STORE_NAME,
|
||||
REST_PERSIST_PATH,
|
||||
REST_MANUAL_CONNECTION_PATH,
|
||||
REST_CONNECTION_URL_PATH,
|
||||
REST_HYDRATE_MERCHANT_PATH,
|
||||
REST_REFRESH_FEATURES_PATH,
|
||||
} from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
|
@ -33,10 +32,8 @@ export const controls = {
|
|||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_SANDBOX_LOGIN ]() {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( {
|
||||
return apiFetch( {
|
||||
path: REST_CONNECTION_URL_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
|
@ -45,20 +42,16 @@ export const controls = {
|
|||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_PRODUCTION_LOGIN ]( { products } ) {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( {
|
||||
return apiFetch( {
|
||||
path: REST_CONNECTION_URL_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
|
@ -67,13 +60,11 @@ export const controls = {
|
|||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_MANUAL_CONNECTION ]( {
|
||||
|
@ -81,10 +72,8 @@ export const controls = {
|
|||
clientSecret,
|
||||
useSandbox,
|
||||
} ) {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( {
|
||||
return await apiFetch( {
|
||||
path: REST_MANUAL_CONNECTION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
|
@ -94,31 +83,36 @@ export const controls = {
|
|||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
result = await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
|
||||
|
||||
if ( result.success && result.merchant ) {
|
||||
await dispatch( STORE_NAME ).hydrate( result );
|
||||
}
|
||||
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
|
||||
} catch ( e ) {
|
||||
result = {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
return result;
|
||||
async [ ACTION_TYPES.DO_REFRESH_FEATURES ]() {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_REFRESH_FEATURES_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -25,26 +25,56 @@ export const defaultLocationSettings = {
|
|||
value: 'cart',
|
||||
label: __( 'Cart', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...cartAndExpressCheckoutSettings },
|
||||
// translators: %s: Link to Cart page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Cart page</a> and select which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'classic-checkout': {
|
||||
value: 'classic-checkout',
|
||||
label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Classic Checkout page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Classic Checkout page</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'express-checkout': {
|
||||
value: 'express-checkout',
|
||||
label: __( 'Express Checkout', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...cartAndExpressCheckoutSettings },
|
||||
// translators: %s: Link to Express Checkout location
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Express Checkout location</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'mini-cart': {
|
||||
value: 'mini-cart',
|
||||
label: __( 'Mini Cart', 'woocommerce-paypel-payements' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Mini Cart
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Mini Cart</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'product-page': {
|
||||
value: 'product-page',
|
||||
label: __( 'Product Page', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Product Page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Product Page</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -57,10 +87,6 @@ export const paymentMethodOptions = [
|
|||
value: 'paylater',
|
||||
label: __( 'Pay Later', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'card',
|
||||
label: __( 'Debit or Credit Card', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'googlepay',
|
||||
label: __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||
|
|
39
modules/ppcp-settings/resources/js/utils/navigation.js
Normal file
39
modules/ppcp-settings/resources/js/utils/navigation.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { addQueryArgs } from '@wordpress/url';
|
||||
|
||||
const getLocation = () => window.location;
|
||||
|
||||
const pushHistory = ( path ) => window.history.pushState( { path }, '', path );
|
||||
|
||||
/**
|
||||
* Get the current path from the browser.
|
||||
*
|
||||
* @return {string} Current path.
|
||||
*/
|
||||
export const getPath = () => getLocation().pathname;
|
||||
|
||||
/**
|
||||
* Get the current query string, parsed into an object, from history.
|
||||
*
|
||||
* @return {Object} Current query object, defaults to empty object.
|
||||
*/
|
||||
export const getQuery = () =>
|
||||
Object.fromEntries( new URLSearchParams( getLocation().search ) );
|
||||
|
||||
/**
|
||||
* Updates the query parameters of the current page.
|
||||
*
|
||||
* @param {Object} query Object of params to be updated.
|
||||
* @throws {TypeError} If the query is not an object.
|
||||
*/
|
||||
export const updateQueryString = ( query ) =>
|
||||
pushHistory( getNewPath( query ) );
|
||||
|
||||
/**
|
||||
* Return a URL with set query parameters.
|
||||
*
|
||||
* @param {Object} query Object of params to be updated.
|
||||
* @param {string} basePath Optional. Define the path for the new URL.
|
||||
* @return {string} Updated URL merging query params into existing params.
|
||||
*/
|
||||
export const getNewPath = ( query, basePath = getPath() ) =>
|
||||
addQueryArgs( basePath, { ...getQuery(), ...query } );
|
|
@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
|||
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\Endpoint\WebhookSettingsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
||||
|
@ -71,6 +72,13 @@ return array(
|
|||
'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' ),
|
||||
|
|
|
@ -209,6 +209,11 @@ class CommonRestEndpoint extends RestEndpoint {
|
|||
$this->merchant_info_map
|
||||
);
|
||||
|
||||
$extra_data['merchant'] = apply_filters(
|
||||
'woocommerce_paypal_payments_rest_common_merchant_data',
|
||||
$extra_data['merchant'],
|
||||
);
|
||||
|
||||
return $extra_data;
|
||||
}
|
||||
|
||||
|
|
|
@ -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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -181,7 +181,8 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
$container->get( 'settings.rest.common' ),
|
||||
$container->get( 'settings.rest.connect_manual' ),
|
||||
$container->get( 'settings.rest.login_link' ),
|
||||
$container->get('settings.rest.webhooks')
|
||||
$container->get('settings.rest.webhooks'),
|
||||
$container->get( 'settings.rest.refresh_feature_status' ),
|
||||
);
|
||||
|
||||
foreach ( $endpoints as $endpoint ) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,7 @@ use Exception;
|
|||
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 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
}
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue