🔀 Merge branch 'trunk'

# Conflicts:
#	modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js
This commit is contained in:
Philipp Stracker 2025-01-28 13:12:50 +01:00
commit 6aecfdad31
No known key found for this signature in database
70 changed files with 1178 additions and 821 deletions

View file

@ -44,7 +44,13 @@ return array(
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
); );
if ( wc_terms_and_conditions_page_id() > 0 ) { /**
* Replace wc_terms_and_conditions_page_id() function to avoid errors when to avoid errors because of early loading.
*/
$wc_terms_and_conditions_page_id = apply_filters( 'woocommerce_get_terms_page_id', get_option( 'woocommerce_terms_page_id' ) );
$wc_terms_and_conditions_page_id = apply_filters( 'woocommerce_terms_and_conditions_page_id', 0 < $wc_terms_and_conditions_page_id ? absint( $wc_terms_and_conditions_page_id ) : 0 );
if ( $wc_terms_and_conditions_page_id > 0 ) {
$label .= __( $label .= __(
'<div class="ppcp-notice ppcp-notice-warning"><p><span class="highlight">Important:</span> Your store has a <a href="/wp-admin/admin.php?page=wc-settings&tab=advanced" target="_blank">Terms and Conditions</a> page configured. Buyers who use a PayPal express payment method will not be able to consent to the terms on the <code>Classic Checkout</code>, as the final checkout confirmation will be skipped.</p></div>', '<div class="ppcp-notice ppcp-notice-warning"><p><span class="highlight">Important:</span> Your store has a <a href="/wp-admin/admin.php?page=wc-settings&tab=advanced" target="_blank">Terms and Conditions</a> page configured. Buyers who use a PayPal express payment method will not be able to consent to the terms on the <code>Classic Checkout</code>, as the final checkout confirmation will be skipped.</p></div>',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'

View file

@ -8,8 +8,8 @@
} }
@mixin hide-input-field() { @mixin hide-input-field() {
width: 100%; width: auto;
height: 100%; height: auto;
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;

View file

@ -19,7 +19,6 @@ $color-divider: #F0F0F0;
$color-error-red: #cc1818; $color-error-red: #cc1818;
$shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15); $shadow-card: 0 3px 6px 0 rgba(0, 0, 0, 0.15);
$shadow-selection-box: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
$color-gradient-dark: #001435; $color-gradient-dark: #001435;
$gradient-header: linear-gradient(87.03deg, #003087 -0.49%, #001E51 29.22%, $color-gradient-dark 100%); $gradient-header: linear-gradient(87.03deg, #003087 -0.49%, #001E51 29.22%, $color-gradient-dark 100%);
@ -71,4 +70,15 @@ $card-vertical-gap: 48px;
--block-separator-size: 1px; --block-separator-size: 1px;
--block-separator-color: var(--color-gray-200); --block-separator-color: var(--color-gray-200);
--block-action-gap: 16px; // Space between two consecutive action blocks. --block-action-gap: 16px; // Space between two consecutive action blocks.
// Default visual effects.
--box-shadow-active-item: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
--container-border-radius: 8px;
// Spinner/loader.
--spinner-size: 20px;
--spinner-overlay-width: 320px;
--spinner-overlay-height: 320px;
--spinner-overlay-color: #fafafa;
--spinner-overlay-box-shadow: var(--box-shadow-active-item);
} }

View file

@ -5,7 +5,6 @@
@import './reusable-components/button'; @import './reusable-components/button';
@import './reusable-components/elements'; @import './reusable-components/elements';
@import './reusable-components/fields'; @import './reusable-components/fields';
@import './reusable-components/hstack';
@import './reusable-components/navigation'; @import './reusable-components/navigation';
@import './reusable-components/onboarding-header'; @import './reusable-components/onboarding-header';
@import './reusable-components/payment-method-icons'; @import './reusable-components/payment-method-icons';
@ -16,6 +15,7 @@
@import './reusable-components/settings-toggle-block'; @import './reusable-components/settings-toggle-block';
@import './reusable-components/settings-wrapper'; @import './reusable-components/settings-wrapper';
@import './reusable-components/spinner-overlay'; @import './reusable-components/spinner-overlay';
@import './reusable-components/stack';
@import './reusable-components/tab-navigation'; @import './reusable-components/tab-navigation';
@import './reusable-components/title-badge'; @import './reusable-components/title-badge';
@import './reusable-components/welcome-docs'; @import './reusable-components/welcome-docs';

View file

@ -30,7 +30,7 @@
&--has-image-badge .ppcp-r-title-badge--info { &--has-image-badge .ppcp-r-title-badge--info {
display: block; display: block;
margin: 6px 0px 0px 0px; margin: 6px 0 0 0;
width: fit-content; width: fit-content;
} }
@ -42,9 +42,9 @@
flex-direction: column; flex-direction: column;
.ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge { .ppcp-r-badge-box__title-text:not(:empty) + .ppcp-r-badge-box__title-image-badge {
margin: 0px; margin: 0;
img:first-of-type { img:first-of-type {
margin: 0px; margin: 0;
} }
} }
} }

View file

@ -38,6 +38,8 @@ button.components-button, a.components-button {
/* style the button template */ /* style the button template */
text-align: center;
&:not(:disabled) { &:not(:disabled) {
@extend %button-style-default; @extend %button-style-default;
} }

View file

@ -29,7 +29,7 @@
.ppcp-r-title-badge { .ppcp-r-title-badge {
text-transform: none; text-transform: none;
margin-left: 6px; margin-left: 8px;
} }
} }

View file

@ -1,31 +0,0 @@
.ppcp-r-app {
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
-webkit-box-pack: center;
.components-h-stack {
flex-direction: row;
justify-content: flex-start;
gap: 32px;
}
.components-v-stack {
flex-direction: column;
justify-content: center;
}
// Fix layout for checkboxes inside a flex-stack.
.components-checkbox-control > .components-base-control__field > .components-flex {
flex-direction: row;
gap: 12px;
}
}
.ppcp--horizontal {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}

View file

@ -3,6 +3,7 @@
gap: 8px; gap: 8px;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
> img{ > img{
width: 38px; width: 38px;
height: 24px; height: 24px;

View file

@ -1,74 +1,80 @@
.ppcp-r-settings-block__payment-methods { // Grid layout.
&.ppcp-r-settings-block { .ppcp-r-settings-block.ppcp--grid > .ppcp--content {
display: flex; --block-separator-gap: 0;
flex-wrap: wrap;
flex-direction: row; display: grid;
gap: 16px; grid-template-columns: repeat(3, 1fr);
gap: 16px;
@media screen and (max-width: 767px) {
grid-template-columns: repeat(2, 1fr);
} }
&__item { @media screen and (max-width: 480px) {
grid-template-columns: 1fr;
}
}
// Theming & visual styles.
.ppcp-r-settings-block__payment-methods {
.ppcp-r-settings-block {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
width: calc(100% / 3 - 32px / 3);
border: 1px solid $color-gray-300; border: 1px solid $color-gray-300;
border-radius: var(--container-border-radius);
padding: 16px; padding: 16px;
border-radius: 8px;
min-height: 200px; min-height: 200px;
@media screen and (max-width: 767px) { .ppcp--method-inner {
width: calc(50% - 8px);
}
@media screen and (max-width: 480px) {
width: 100%;
}
&__inner {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
&__title-wrapper { .ppcp--method-title-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 0 0 8px 0; margin: 0 0 8px 0;
gap: 12px; gap: 12px;
} }
&__description { .ppcp--method-description {
p { p {
@include font(13, 20, 400);
margin: 0; margin: 0;
color: $color-text-tertiary; color: $color-text-tertiary;
@include font(13, 20, 400);
} }
margin: 0 0 12px 0; margin: 0 0 12px 0;
} }
&__title { .ppcp--method-title {
@include font(13, 20, 500); @include font(13, 20, 500);
color: $color-black; color: $color-black;
display: block; display: block;
} }
&__settings { .ppcp--method-settings {
line-height: 0; padding: 0;
transition: 0.2s ease-out transform; transition: 0.2s ease-out transform;
transform: rotate(0deg); transform: rotate(0deg);
zoom: 1.005; zoom: 1.005;
&:hover { &:hover {
transform: rotate(45deg); transform: rotate(45deg);
cursor: pointer;
} }
} }
.ppcp--method-icon {
width: 30px;
height: 30px;
}
button.is-secondary { button.is-secondary {
@include small-button; @include small-button;
} }
&__footer { .ppcp--method-footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View file

@ -1,4 +1,8 @@
.ppcp-r-select-box-wrapper { .ppcp-r-select-box-wrapper {
--box-border-color: var(--color-gray-200);
--box-outline-color: transparent;
--box-shadow: 0;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
gap: 32px; gap: 32px;
@ -6,92 +10,62 @@
} }
.ppcp-r-select-box { .ppcp-r-select-box {
border: 1px solid var(--box-border-color);
outline: 1px solid var(--box-outline-color);
box-shadow: var(--box-shadow);
position: relative; position: relative;
width: 100%; width: 100%;
border: 1px solid $color-gray-200;
outline: 1px solid transparent;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
gap: 16px; gap: 16px;
padding: 24px 16px 24px 16px; padding: 24px 16px 24px 16px;
transition: all 0.3s;
cursor: pointer;
&:hover {
--box-border-color: var(--color-gray-500);
--box-shadow: var(--box-shadow-active-item);
}
&.ppcp--selected { &.ppcp--selected {
border-color: $color-blueberry; --box-border-color: var(--color-blueberry);
outline-color: $color-blueberry; --box-outline-color: var(--color-blueberry);
box-shadow: $shadow-selection-box; --box-shadow: var(--box-shadow-active-item);
} }
&__radio-value { .ppcp--box-content {
@include hide-input-field;
&:checked {
+ .ppcp-r-select-box__radio-presentation {
background: $color-white;
width: 20px;
height: 20px;
border: 6px solid $color-blueberry;
}
}
}
&__checkbox-value {
@include hide-input-field;
&:not(:checked) + .ppcp-r-select-box__checkbox-presentation img {
display: none;
}
&:checked {
+ .ppcp-r-select-box__checkbox-presentation {
width: 20px;
height: 20px;
border: none;
img {
border-radius: 2px;
}
}
}
}
&__content {
display: flex; display: flex;
position: relative; position: relative;
pointer-events: none; pointer-events: none;
*:not(a){
*:not(a) {
pointer-events: none; pointer-events: none;
} }
a { a {
pointer-events: all; pointer-events: all;
} }
} }
&__title { .ppcp--box-title {
@include font(14, 20, 700); @include font(14, 20, 700);
color: $color-black; color: $color-black;
margin: 0 0 4px 0; margin: 0 0 4px 0;
display: block; display: block;
} }
&__description { .ppcp--box-description {
@include font(13, 20, 400); @include font(13, 20, 400);
color: $color-gray-700; color: $color-gray-700;
margin:0; margin: 0;
&:not(:last-child){ &:not(:last-child) {
margin-block-end:18px; margin-block-end: 18px;
} }
} }
&__radio-presentation { .ppcp--box-details {
@include fake-input-field(20px);
}
&__checkbox-presentation {
@include fake-input-field(2px);
}
&__additional-content{
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
@ -99,15 +73,18 @@
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
gap: 16px; gap: 16px;
padding: 18px 16px; padding: 18px 16px;
&__description {
.ppcp--box-description {
margin: 0 0 8px 0; margin: 0 0 8px 0;
} }
&__content {
.ppcp--box-content {
gap: 12px; gap: 12px;
} }
} }
@media screen and (max-width: 380px) { @media screen and (max-width: 380px) {
&__content > img { .ppcp--box-content > img {
max-width: 32px; max-width: 32px;
} }
} }

View file

@ -1,3 +1,8 @@
// Configuration for this module.
$width_container: 938px;
$width_header: 280px;
$width_gap: 24px;
/* /*
Styles the `SettingsCard` layout component. Styles the `SettingsCard` layout component.
@ -14,9 +19,9 @@
--card-layout: block; --card-layout: block;
@media screen and (min-width: 960px) { @media screen and (min-width: 960px) {
--card-width-header: 280px; --card-width-header: #{$width_header};
--card-width-content: 610px; --card-width-content: #{$width_container - $width_header - $width_gap};
--card-gap: 48px; --card-gap: #{$width_gap};
--card-layout: flex; --card-layout: flex;
} }
@ -46,6 +51,7 @@
&.ppcp--is-card { &.ppcp--is-card {
max-width: var(--card-width-content); max-width: var(--card-width-content);
border: 1px solid var(--color-gray-200); border: 1px solid var(--color-gray-200);
width: 100%;
border-radius: 4px; border-radius: 4px;
padding: 24px; padding: 24px;
} }

View file

@ -1,29 +1,26 @@
.ppcp-r { .ppcp-r-container {
&-container { max-width: var(--max-container-width, none);
max-width: var(--max-container-width, none); margin: 0 auto 0 35px;
margin-left: auto; }
margin-right: auto;
}
&-inner-container { .ppcp-r-inner-container {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 0 16px 48px; padding: 0 16px 48px;
box-sizing: content-box; box-sizing: content-box;
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
padding-bottom: 36px; padding-bottom: 36px;
} }
} }
&-settings { .ppcp-r-settings {
> * { > * {
margin-bottom: $card-vertical-gap; margin-bottom: $card-vertical-gap;
} }
> *:not(:last-child) { > *:not(:last-child) {
padding-bottom: $card-vertical-gap; padding-bottom: $card-vertical-gap;
border-bottom: 1px solid $color-gray-200; border-bottom: 1px solid $color-gray-200;
}
} }
} }

View file

@ -1,11 +1,13 @@
.ppcp-r-spinner-overlay { .ppcp-r-spinner-overlay {
position: absolute; width: var(--spinner-overlay-width);
left: 0; height: var(--spinner-overlay-height);
top: 0; box-shadow: var(--spinner-overlay-box-shadow);
right: 0;
bottom: 0;
z-index: 10;
background: var(--spinner-overlay-color); background: var(--spinner-overlay-color);
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 10;
.components-spinner { .components-spinner {
position: absolute; position: absolute;
@ -13,5 +15,15 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
margin: 0; margin: 0;
width: var(--spinner-size);
height: var(--spinner-size);
}
.ppcp--spinner-message {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: calc(50% + 20px + var(--spinner-size));
} }
} }

View file

@ -0,0 +1,24 @@
.ppcp-r-app {
.components-flex {
display: flex;
-webkit-box-align: stretch;
align-items: stretch;
-webkit-box-pack: center;
}
.components-flex.components-h-stack,
.ppcp--horizontal {
flex-direction: row;
align-items: center;
}
.ppcp--horizontal {
justify-content: space-between;
}
.components-flex.components-v-stack {
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
}

View file

@ -3,7 +3,6 @@
--wp-admin-border-width-focus: 3px; --wp-admin-border-width-focus: 3px;
max-width: var(--max-container-width); max-width: var(--max-container-width);
margin: 0 auto;
transition: max-width 0.2s; transition: max-width 0.2s;
.components-tab-panel__tabs { .components-tab-panel__tabs {

View file

@ -1,18 +1,22 @@
.ppcp-r-title-badge{ .ppcp-r-title-badge {
--badge-bg-color: #F5F5F5;
--badge-text-color: #2F2F2F;
color: var(--badge-text-color);
background-color: var(--badge-bg-color);
@include font(12, 16, 400); @include font(12, 16, 400);
padding: 4px 8px; padding: 4px 8px;
border-radius: 2px; border-radius: 2px;
white-space: nowrap; white-space: nowrap;
&--positive{
color: #144722; &.ppcp-r-title-badge--positive {
background-color: #DAFFE0; --badge-bg-color: #DAFFE0;
--badge-text-color: #144722;
} }
&--negative{
color:#5c0000; &.ppcp-r-title-badge--negative {
background-color: #faeded; --badge-bg-color: #faeded;
} --badge-text-color: #5c0000;
&--info{
color: #2F2F2F;
background-color: #F5F5F5;
} }
} }

View file

@ -11,6 +11,7 @@ body:has(.ppcp-r-container--onboarding) {
.nav-tab-wrapper.woo-nav-tab-wrapper, .nav-tab-wrapper.woo-nav-tab-wrapper,
.woocommerce-layout__header, .woocommerce-layout__header,
.wrap.woocommerce form > h2, .wrap.woocommerce form > h2,
#mainform .subsubsub,
#screen-meta-links { #screen-meta-links {
display: none !important; display: none !important;
visibility: hidden; visibility: hidden;

View file

@ -7,6 +7,9 @@
.ppcp-r-container--onboarding { .ppcp-r-container--onboarding {
--max-container-width: var(--max-width-onboarding); --max-container-width: var(--max-width-onboarding);
margin-left: auto;
margin-right: auto;
.ppcp-r-inner-container { .ppcp-r-inner-container {
max-width: var(--max-width-onboarding-content); max-width: var(--max-width-onboarding-content);
} }

View file

@ -107,33 +107,26 @@
} }
} }
.ppcp-r-feature-item { .ppcp-r-tab-overview-features {
padding-top: 32px; --block-header-gap: 12px;
border-top: 1px solid $color-gray-400; }
&__title { .ppcp-r-tab-overview-help {
@include font(16, 20, 600); --block-header-gap: 8px;
color: $color-black; }
display: block;
margin: 0 0 8px 0;
}
&__description { .ppcp-r-settings-block__feature {
@include font(14, 20, 400); .ppcp--action-buttons {
color: $color-gray-800;
margin: 0 0 18px 0;
}
&:not(:last-child) {
padding-bottom: 32px;
}
&__buttons {
display: flex; display: flex;
gap: 18px; gap: 18px;
.components-button.is-tertiary {
padding-left: 0;
padding-right: 0;
}
} }
&__notes { .ppcp--item-notes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -5,3 +5,20 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
// Fix the checkbox layout (add gap between checkbox and label).
.components-checkbox-control > .components-base-control__field > .components-flex {
flex-direction: row;
gap: 12px;
}
// Fix layout for radio groups inside a horizontal flex-stack.
.components-flex.components-h-stack > .components-radio-control {
width: 100%;
.components-radio-control__group-wrapper {
justify-content: flex-start;
flex-direction: row;
gap: 12px;
}
}

View file

@ -1,7 +1,7 @@
.ppcp-r-paylater-configurator { .ppcp-r-paylater-configurator {
display: flex; display: flex;
border: 1px solid var(--color-separators); border: 1px solid var(--color-separators);
border-radius: 8px; border-radius: var(--container-border-radius);
overflow: hidden; overflow: hidden;
font-family: "PayPalPro", sans-serif; font-family: "PayPalPro", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View file

@ -7,3 +7,25 @@
--block-header-gap: 0; --block-header-gap: 0;
--block-separator-size: 0; --block-separator-size: 0;
} }
.ppcp--webhooks {
.ppcp--webhook-list li {
list-style: none;
&::before {
content: '✔︎';
opacity: 0.35;
font-size: 0.75em;
line-height: 1.35;
display: inline-block;
margin-right: 8px;
}
}
}
.ppcp--webhook-resubscribe,
.ppcp--webhook-simulation {
.ppcp--action .components-button {
min-width: 160px;
display: block;
}
}

View file

@ -8,7 +8,7 @@
display: flex; display: flex;
border: 1px solid var(--color-separators); border: 1px solid var(--color-separators);
border-radius: 8px; border-radius: var(--container-border-radius);
.ppcp-r-settings-block { .ppcp-r-settings-block {
&.header-section { &.header-section {

View file

@ -9,7 +9,12 @@ const ControlButton = ( {
buttonLabel, buttonLabel,
} ) => ( } ) => (
<Action> <Action>
<Button isBusy={ isBusy } variant={ type } onClick={ onClick }> <Button
className="small-button"
isBusy={ isBusy }
variant={ type }
onClick={ onClick }
>
{ buttonLabel } { buttonLabel }
</Button> </Button>
</Action> </Action>

View file

@ -10,6 +10,7 @@ const ControlTextInput = ( {
} ) => ( } ) => (
<Action> <Action>
<TextControl <TextControl
__nextHasNoMarginBottom={ true }
className="ppcp-r-vertical-text-control" className="ppcp-r-vertical-text-control"
placeholder={ placeholder } placeholder={ placeholder }
value={ value } value={ value }

View file

@ -24,6 +24,7 @@ const Checkbox = ( {
return ( return (
<CheckboxControl <CheckboxControl
__nextHasNoMarginBottom={ true }
label={ label } label={ label }
value={ value } value={ value }
checked={ checked } checked={ checked }

View file

@ -1,15 +1,39 @@
import { PayPalCheckbox } from './index'; import { PayPalCheckbox } from './index';
import { useCallback } from '@wordpress/element';
const CheckboxGroup = ( { options, value, onChange } ) => { const CheckboxGroup = ( { name, options, value, onChange } ) => {
const handleChange = ( key, checked ) => { const handleChange = useCallback(
const getNewValue = () => { ( key, checked ) => {
if ( checked ) { const getNewValue = () => {
return [ ...value, key ]; if ( 'boolean' === typeof value ) {
} return checked;
return value.filter( ( val ) => val !== key ); }
};
onChange( getNewValue() ); if ( checked ) {
return [ ...value, key ];
}
return value.filter( ( val ) => val !== key );
};
onChange( getNewValue() );
},
[ onChange, value ]
);
const isItemChecked = ( checked, itemValue ) => {
if ( typeof checked === 'boolean' ) {
return checked;
}
if ( Array.isArray( value ) ) {
return value.includes( itemValue );
}
if ( typeof value === 'boolean' ) {
return value;
}
return value === itemValue;
}; };
return ( return (
@ -21,16 +45,14 @@ const CheckboxGroup = ( { options, value, onChange } ) => {
checked, checked,
disabled, disabled,
description, description,
tooltip,
} ) => ( } ) => (
<PayPalCheckbox <PayPalCheckbox
key={ itemValue } key={ name + itemValue }
value={ itemValue } value={ itemValue }
label={ label } label={ label }
checked={ checked } checked={ isItemChecked( checked, itemValue ) }
disabled={ disabled } disabled={ disabled }
description={ description } description={ description }
tooltip={ tooltip }
changeCallback={ handleChange } changeCallback={ handleChange }
/> />
) )

View file

@ -49,10 +49,12 @@ const OptionItem = ( {
} ) => { } ) => {
const boxClassName = classNames( 'ppcp-r-select-box', { const boxClassName = classNames( 'ppcp-r-select-box', {
'ppcp--selected': isSelected, 'ppcp--selected': isSelected,
'ppcp--multiselect': isMulti,
} ); } );
return ( return (
<div className={ boxClassName }> // eslint-disable-next-line jsx-a11y/label-has-associated-control -- label has a nested input control.
<label className={ boxClassName }>
<InputField <InputField
value={ itemValue } value={ itemValue }
isRadio={ ! isMulti } isRadio={ ! isMulti }
@ -60,22 +62,16 @@ const OptionItem = ( {
isSelected={ isSelected } isSelected={ isSelected }
/> />
<div className="ppcp-r-select-box__content"> <div className="ppcp--box-content">
<div className="ppcp-r-select-box__content-inner"> <div className="ppcp--box-content-inner">
<span className="ppcp-r-select-box__title"> <span className="ppcp--box-title">{ itemTitle }</span>
{ itemTitle } <p className="ppcp--box-description">{ itemDescription }</p>
</span>
<p className="ppcp-r-select-box__description">
{ itemDescription }
</p>
{ children && ( { children && (
<div className="ppcp-r-select-box__additional-content"> <div className="ppcp--box-details">{ children }</div>
{ children }
</div>
) } ) }
</div> </div>
</div> </div>
</div> </label>
); );
}; };

View file

@ -1,26 +0,0 @@
/**
* Temporary component, until the experimental HStack block editor component is stable.
*
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs
* @file
*/
import classNames from 'classnames';
const HStack = ( { className, spacing = 3, children } ) => {
const wrapperClass = classNames(
'components-flex components-h-stack',
className
);
const styles = {
gap: `calc(${ 4 * spacing }px)`,
};
return (
<div className={ wrapperClass } style={ styles }>
{ children }
</div>
);
};
export default HStack;

View file

@ -1,15 +1,20 @@
import { Icon } from '@wordpress/components';
import data from '../../utils/data'; import data from '../../utils/data';
const PaymentMethodIcon = ( props ) => { const PaymentMethodIcon = ( { icons, type } ) => {
if ( const validIcon = Array.isArray( icons ) && icons.includes( type );
( Array.isArray( props.icons ) &&
props.icons.includes( props.type ) ) || if ( validIcon || icons === 'all' ) {
props.icons === 'all' return (
) { <Icon
return data().getImage( 'icon-button-' + props.type + '.svg' ); icon={ data().getImage( 'icon-button-' + type + '.svg' ) }
className="ppcp--method-icon"
/>
);
} }
return <></>; return null;
}; };
export default PaymentMethodIcon; export default PaymentMethodIcon;

View file

@ -15,22 +15,6 @@ const SettingsBlock = ( {
'ppcp--horizontal': horizontalLayout, 'ppcp--horizontal': horizontalLayout,
} ); } );
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => {
if ( ! blockTitle && ! blockDescription ) {
return null;
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
};
return ( return (
<div className={ blockClassName }> <div className={ blockClassName }>
<BlockTitle <BlockTitle
@ -45,3 +29,19 @@ const SettingsBlock = ( {
}; };
export default SettingsBlock; export default SettingsBlock;
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => {
if ( ! blockTitle && ! blockDescription ) {
return null;
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
};

View file

@ -12,7 +12,7 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
} }
return ( return (
<span className="ppcp-r-feature-item__notes"> <span className="ppcp--item-notes">
{ notes.map( ( note, index ) => ( { notes.map( ( note, index ) => (
<span key={ index }>{ note }</span> <span key={ index }>{ note }</span>
) ) } ) ) }
@ -20,30 +20,39 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
); );
}; };
const renderButton = ( button ) => { const FeatureButton = ( {
const buttonElement = ( className,
<Button variant,
className={ button.class ? button.class : '' } text,
key={ button.text } isBusy,
isBusy={ props.actionProps?.isBusy } url,
variant={ button.type } urls,
onClick={ button.onClick } onClick,
> } ) => {
{ button.text } const buttonProps = {
</Button> className,
); isBusy,
variant,
};
// If there's a URL (either direct or in urls object), wrap in anchor tag if ( url || urls ) {
if ( button.url || button.urls ) { buttonProps.href = urls ? urls.live : url;
const href = button.urls ? button.urls.live : button.url; buttonProps.target = '_blank';
return ( }
<a href={ href } key={ button.text }> if ( ! buttonProps.href ) {
{ buttonElement } buttonProps.onClick = onClick;
</a>
);
} }
return buttonElement; return <Button { ...buttonProps }>{ text }</Button>;
};
const renderDescription = () => {
return (
<span
className="ppcp-r-feature-item__description ppcp-r-settings-block__feature__description"
dangerouslySetInnerHTML={ { __html: description } }
/>
);
}; };
return ( return (
@ -56,13 +65,33 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
) } ) }
</Title> </Title>
<Description className="ppcp-r-settings-block__feature__description"> <Description className="ppcp-r-settings-block__feature__description">
{ description } { renderDescription() }
{ printNotes() } { printNotes() }
</Description> </Description>
</Header> </Header>
<Action> <Action>
<div className="ppcp-r-feature-item__buttons"> <div className="ppcp--action-buttons">
{ props.actionProps?.buttons.map( renderButton ) } { props.actionProps?.buttons.map(
( {
class: className,
type,
text,
url,
urls,
onClick,
} ) => (
<FeatureButton
key={ text }
className={ className }
variant={ type }
text={ text }
isBusy={ props.actionProps.isBusy }
url={ url }
urls={ urls }
onClick={ onClick }
/>
)
) }
</div> </div>
</Action> </Action>
</SettingsBlock> </SettingsBlock>

View file

@ -2,7 +2,6 @@ import { ToggleControl } from '@wordpress/components';
import SettingsBlock from '../SettingsBlock'; import SettingsBlock from '../SettingsBlock';
import PaymentMethodIcon from '../PaymentMethodIcon'; import PaymentMethodIcon from '../PaymentMethodIcon';
import data from '../../../utils/data';
const PaymentMethodItemBlock = ( { const PaymentMethodItemBlock = ( {
paymentMethod, paymentMethod,
@ -11,33 +10,35 @@ const PaymentMethodItemBlock = ( {
isSelected, isSelected,
} ) => { } ) => {
return ( return (
<SettingsBlock className="ppcp-r-settings-block__payment-methods__item"> <SettingsBlock className="ppcp--method-item" separatorAndGap={ false }>
<div className="ppcp-r-settings-block__payment-methods__item__inner"> <div className="ppcp--method-inner">
<div className="ppcp-r-settings-block__payment-methods__item__title-wrapper"> <div className="ppcp--method-title-wrapper">
<PaymentMethodIcon { paymentMethod?.icon && (
icons={ [ paymentMethod.icon ] } <PaymentMethodIcon
type={ paymentMethod.icon } icons={ [ paymentMethod.icon ] }
/> type={ paymentMethod.icon }
<span className="ppcp-r-settings-block__payment-methods__item__title"> />
) }
<span className="ppcp--method-title">
{ paymentMethod.itemTitle } { paymentMethod.itemTitle }
</span> </span>
</div> </div>
<p className="ppcp-r-settings-block__payment-methods__item__description"> <p className="ppcp--method-description">
{ paymentMethod.itemDescription } { paymentMethod.itemDescription }
</p> </p>
<div className="ppcp-r-settings-block__payment-methods__item__footer"> <div className="ppcp--method-footer">
<ToggleControl <ToggleControl
__nextHasNoMarginBottom={ true } __nextHasNoMarginBottom={ true }
checked={ isSelected } checked={ isSelected }
onChange={ onSelect } onChange={ onSelect }
/> />
{ paymentMethod?.fields && onTriggerModal && ( { paymentMethod?.fields && onTriggerModal && (
<div <Button
className="ppcp-r-settings-block__payment-methods__item__settings" className="ppcp--method-settings"
onClick={ onTriggerModal } onClick={ onTriggerModal }
> >
{ data().getImage( 'icon-settings.svg' ) } <Icon icon={ cog } />
</div> </Button>
) } ) }
</div> </div>
</div> </div>

View file

@ -1,42 +1,38 @@
import SettingsBlock from '../SettingsBlock'; import SettingsBlock from '../SettingsBlock';
import PaymentMethodItemBlock from './PaymentMethodItemBlock'; import PaymentMethodItemBlock from './PaymentMethodItemBlock';
import { usePaymentMethods } from '../../../data/payment/hooks'; import { PaymentHooks } from '../../../data';
const PaymentMethodsBlock = ( { // TODO: This is not a reusable component, as it's connected to the Redux store.
paymentMethods, const PaymentMethodsBlock = ( { paymentMethods = [], onTriggerModal } ) => {
className = '', const { changePaymentSettings } = PaymentHooks.useStore();
onTriggerModal,
} ) => {
const { setPersistent } = usePaymentMethods();
if ( ! paymentMethods?.length ) { const handleSelect = ( methodId, isSelected ) =>
changePaymentSettings( methodId, {
enabled: isSelected,
} );
if ( ! paymentMethods.length ) {
return null; return null;
} }
const handleSelect = ( paymentMethod, isSelected ) => {
setPersistent( paymentMethod.id, {
...paymentMethod,
enabled: isSelected,
} );
};
return ( return (
<SettingsBlock <SettingsBlock className="ppcp--grid ppcp-r-settings-block__payment-methods">
className={ `ppcp-r-settings-block__payment-methods ${ className }` } { paymentMethods
> // Remove empty/invalid payment method entries.
{ paymentMethods.map( ( paymentMethod ) => ( .filter( ( m ) => m.id )
<PaymentMethodItemBlock .map( ( paymentMethod ) => (
key={ paymentMethod.id } <PaymentMethodItemBlock
paymentMethod={ paymentMethod } key={ paymentMethod.id }
isSelected={ paymentMethod.enabled } paymentMethod={ paymentMethod }
onSelect={ ( checked ) => isSelected={ paymentMethod.enabled }
handleSelect( paymentMethod, checked ) onSelect={ ( checked ) =>
} handleSelect( paymentMethod.id, checked )
onTriggerModal={ () => }
onTriggerModal?.( paymentMethod.id ) onTriggerModal={ () =>
} onTriggerModal?.( paymentMethod.id )
/> }
) ) } />
) ) }
</SettingsBlock> </SettingsBlock>
); );
}; };

View file

@ -1,55 +1,47 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { Content, ContentWrapper } from './Elements'; import { Content } from './Elements';
const SettingsCard = ( { const SettingsCard = ( {
id, id,
className: extraClassName, className,
title, title,
description, description,
children, children,
contentItems,
contentContainer = true, contentContainer = true,
} ) => { } ) => {
const className = classNames( 'ppcp-r-settings-card', extraClassName ); const cardClassNames = classNames( 'ppcp-r-settings-card', className );
const cardProps = {
const renderContent = () => { className: cardClassNames,
// If contentItems array is provided, wrap each item in Content component id,
if ( contentItems ) {
return (
<ContentWrapper>
{ contentItems.map( ( item ) => (
<Content key={ item.key } id={ item.key }>
{ item }
</Content>
) ) }
</ContentWrapper>
);
}
// Otherwise handle regular children with contentContainer prop
if ( contentContainer ) {
return <Content>{ children }</Content>;
}
return children;
}; };
return ( return (
<div id={ id } className={ className }> <div { ...cardProps }>
<div className="ppcp-r-settings-card__header"> <div className="ppcp-r-settings-card__header">
<div className="ppcp-r-settings-card__content-inner"> <div className="ppcp-r-settings-card__content-inner">
<span className="ppcp-r-settings-card__title"> <span className="ppcp-r-settings-card__title">
{ title } { title }
</span> </span>
<p className="ppcp-r-settings-card__description"> <div className="ppcp-r-settings-card__description">
{ description } { description }
</p> </div>
</div> </div>
</div> </div>
{ renderContent() }
<InnerContent showCards={ contentContainer }>
{ children }
</InnerContent>
</div> </div>
); );
}; };
export default SettingsCard; export default SettingsCard;
const InnerContent = ( { showCards, children } ) => {
if ( showCards ) {
return <Content>{ children }</Content>;
}
return children;
};

View file

@ -9,9 +9,7 @@ const SpinnerOverlay = ( { message = null } ) => {
return ( return (
<div className="ppcp-r-spinner-overlay"> <div className="ppcp-r-spinner-overlay">
{ message && ( { message && (
<span className="ppcp-r-spinner-overlay__message"> <span className="ppcp--spinner-message">{ message }</span>
{ message }
</span>
) } ) }
<Spinner /> <Spinner />
</div> </div>

View file

@ -0,0 +1,42 @@
/**
* Temporary component, until the experimental VStack/HStack block editor component is stable.
*
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-hstack--docs
* @see https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-vstack--docs
* @file
*/
import classNames from 'classnames';
const Stack = ( { type, className, spacing, children } ) => {
const wrapperClass = classNames(
'components-flex',
`components-${ type }-stack`,
className
);
const styles = {
gap: `calc(${ 4 * spacing }px)`,
};
return (
<div className={ wrapperClass } style={ styles }>
{ children }
</div>
);
};
export const HStack = ( { className, spacing = 3, children } ) => {
return (
<Stack type="h" className={ className } spacing={ spacing }>
{ children }
</Stack>
);
};
export const VStack = ( { className, spacing = 3, children } ) => {
return (
<Stack type="v" className={ className } spacing={ spacing }>
{ children }
</Stack>
);
};

View file

@ -1,134 +0,0 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../ReusableComponents/SettingsCard';
import { PaymentMethodsBlock } from '../../ReusableComponents/SettingsBlocks';
import { PaymentHooks } from '../../../data';
import { useActiveModal } from '../../../data/common/hooks';
import Modal from './TabSettingsElements/Blocks/Modal';
import { usePaymentMethods } from '../../../data/payment/hooks';
const TabPaymentMethods = () => {
const { paymentMethodsPayPalCheckout } =
PaymentHooks.usePaymentMethodsPayPalCheckout();
const { paymentMethodsOnlineCardPayments } =
PaymentHooks.usePaymentMethodsOnlineCardPayments();
const { paymentMethodsAlternative } =
PaymentHooks.usePaymentMethodsAlternative();
const { setPersistent } = usePaymentMethods();
const { activeModal, setActiveModal } = useActiveModal();
const getActiveMethod = () => {
if ( ! activeModal ) {
return null;
}
const allMethods = [
...paymentMethodsPayPalCheckout,
...paymentMethodsOnlineCardPayments,
...paymentMethodsAlternative,
];
return allMethods.find( ( method ) => method.id === activeModal );
};
return (
<div className="ppcp-r-payment-methods">
<SettingsCard
id="ppcp-paypal-checkout-card"
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
description={ __(
'Select your preferred checkout option with PayPal for easy payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-standard.svg"
contentContainer={ false }
>
<PaymentMethodsBlock
paymentMethods={ paymentMethodsPayPalCheckout }
onTriggerModal={ setActiveModal }
/>
</SettingsCard>
<SettingsCard
id="ppcp-card-payments-card"
title={ __(
'Online Card Payments',
'woocommerce-paypal-payments'
) }
description={ __(
'Select your preferred card payment options for efficient payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-online-methods.svg"
contentContainer={ false }
>
<PaymentMethodsBlock
paymentMethods={ paymentMethodsOnlineCardPayments }
onTriggerModal={ setActiveModal }
/>
</SettingsCard>
<SettingsCard
id="ppcp-alternative-payments-card"
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
description={ __(
'With alternative payment methods, customers across the globe can pay with their bank accounts and other local payment methods.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-alternative-methods.svg"
contentContainer={ false }
>
<PaymentMethodsBlock
paymentMethods={ paymentMethodsAlternative }
onTriggerModal={ setActiveModal }
/>
</SettingsCard>
{ activeModal && (
<Modal
method={ getActiveMethod() }
setModalIsVisible={ () => setActiveModal( null ) }
onSave={ ( methodId, settings ) => {
setPersistent( methodId, {
...getActiveMethod(),
title: settings.checkoutPageTitle,
description: settings.checkoutPageDescription,
} );
if ( 'paypalShowLogo' in settings ) {
setPersistent(
'paypalShowLogo',
settings.paypalShowLogo
);
}
if ( 'threeDSecure' in settings ) {
setPersistent(
'threeDSecure',
settings.threeDSecure
);
}
if ( 'fastlaneCardholderName' in settings ) {
setPersistent(
'fastlaneCardholderName',
settings.fastlaneCardholderName
);
}
if ( 'fastlaneDisplayWatermark' in settings ) {
setPersistent(
'fastlaneDisplayWatermark',
settings.fastlaneDisplayWatermark
);
}
setActiveModal( null );
} }
/>
) }
</div>
);
};
export default TabPaymentMethods;

View file

@ -1,4 +1,5 @@
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import Container from '../ReusableComponents/Container'; import Container from '../ReusableComponents/Container';
import SettingsCard from '../ReusableComponents/SettingsCard'; import SettingsCard from '../ReusableComponents/SettingsCard';
@ -9,7 +10,7 @@ const SendOnlyMessage = () => {
return ( return (
<> <>
<SettingsNavigation /> <SettingsNavigation canSave={ false } />
<Container page="settings"> <Container page="settings">
<SettingsCard <SettingsCard
title={ __( title={ __(
@ -45,6 +46,19 @@ const SendOnlyMessage = () => {
), ),
} } } }
/> />
<div>
<Button
href={ settingsPageUrl }
variant="primary"
className="small-button"
>
{ __(
'Go to WooCommerce settings',
'woocommerce-paypal-payments'
) }
</Button>
</div>
</SettingsCard> </SettingsCard>
</Container> </Container>
</> </>

View file

@ -2,21 +2,20 @@ import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import TopNavigation from '../../../ReusableComponents/TopNavigation'; import TopNavigation from '../../../ReusableComponents/TopNavigation';
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
import { useSaveSettings } from '../../../../hooks/useSaveSettings'; import { useSaveSettings } from '../../../../hooks/useSaveSettings';
const SettingsNavigation = () => { const SettingsNavigation = ( { canSave = true } ) => {
const { persistAll } = useSaveSettings(); const { persistAll } = useSaveSettings();
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' ); const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
return ( return (
<TopNavigation title={ title } exitOnTitleClick={ true }> <TopNavigation title={ title } exitOnTitleClick={ true }>
<BusyStateWrapper> { canSave && (
<Button variant="primary" onClick={ persistAll }> <Button variant="primary" onClick={ persistAll }>
{ __( 'Save', 'woocommerce-paypal-payments' ) } { __( 'Save', 'woocommerce-paypal-payments' ) }
</Button> </Button>
</BusyStateWrapper> ) }
</TopNavigation> </TopNavigation>
); );
}; };

View file

@ -1,8 +1,10 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { TAB_IDS, selectTab } from '../../../../../utils/tabSelector'; import { TAB_IDS, selectTab } from '../../../../../utils/tabSelector';
import { payLaterMessaging } from './pay-later-messaging';
const Features = { export const getFeatures = ( setActiveModal ) => {
getFeatures: ( setActiveModal ) => [ const storeCountry = ppcpSettings?.storeCountry;
const features = [
{ {
id: 'save_paypal_and_venmo', id: 'save_paypal_and_venmo',
title: __( 'Save PayPal and Venmo', 'woocommerce-paypal-payments' ), title: __( 'Save PayPal and Venmo', 'woocommerce-paypal-payments' ),
@ -39,7 +41,10 @@ const Features = {
{ {
type: 'tertiary', type: 'tertiary',
text: __( 'Learn more', 'woocommerce-paypal-payments' ), text: __( 'Learn more', 'woocommerce-paypal-payments' ),
url: 'https://developer.paypal.com/studio/checkout/standard', urls: {
sandbox: '#',
live: '#',
},
class: 'small-button', class: 'small-button',
}, },
], ],
@ -230,7 +235,16 @@ const Features = {
}, },
], ],
}, },
{ ];
const countryData = payLaterMessaging[ storeCountry ] || {};
// Add "Pay Later Messaging" to the feature list, if it's available.
if (
!! window.ppcpSettings?.isPayLaterConfiguratorAvailable &&
countryData
) {
features.push( {
id: 'pay_later_messaging', id: 'pay_later_messaging',
title: __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ), title: __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ),
description: __( description: __(
@ -269,8 +283,8 @@ const Features = {
class: 'small-button', class: 'small-button',
}, },
], ],
}, } );
], }
};
export default Features; return features;
};

View file

@ -0,0 +1,106 @@
import { __, sprintf } from '@wordpress/i18n';
export const payLaterMessaging = {
US: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. PayPals Pay Later helps boost merchants\' conversion rates and increases cart sizes by 39%%.¹ You get paid in full up front. <a target="_blank" href="%s">More about Pay Later</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/us/business/accept-payments/checkout/installments'
),
notes: [
__( '¹PayPal Q2 Earnings-2021.', 'woocommerce-paypal-payments' ),
],
},
GB: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. Pay in 3 gets a 216%% higher Average Order Value than a standard PayPal transaction.¹ Theres <strong>no extra cost</strong> and you get paid up front. <a target="_blank" href="%s">More about Pay in 3</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/uk/business/accept-payments/checkout/installments'
),
notes: [
__(
'Based on PayPal internal data from Q1 2022, results include Pay in 3 (UK).',
'woocommerce-paypal-payments'
),
],
},
FR: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. Pay in 4x gets a 65%% higher Average Order Value than a standard PayPal transaction.¹ <strong>There\'s no extra cost on top of your PayPal Checkout rate</strong>, and you get paid up front. <a target="_blank" href="%s">More about Pay in 4x</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/fr/business/accept-payments/checkout/installments'
),
notes: [
__(
'Internal Data Analysis of 1124 SMB across integrated partners and non-integrated partners, November 2022. SMB internally defined as up to 100,000€ in annual estimated ecommerce online payment volume.',
'woocommerce-paypal-payments'
),
],
},
AU: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. Pay in 4 gets more than a 100%% higher Average Order Value than a standard PayPal transaction.¹ Theres <strong>no extra cost</strong> and you get paid up front. <a target="_blank" href="%s">More about Pay in 4</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/au/business/accept-payments/checkout/installments'
),
notes: [
__(
'Based on PayPal internal data from Q1 2022, results include Pay in 4 (AU). Consumer eligibility applies.',
'woocommerce-paypal-payments'
),
],
},
IT: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. Pay in 3 installments gets about a 275%% higher Average Order Value than a standard PayPal transaction.¹ <strong>There\'s no extra cost on top of your PayPal Checkout rate</strong>, and you get paid up front. <a target="_blank" href="%s">More about Pay in 3 installments</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/it/business/accept-payments/checkout/installments'
),
notes: [
__(
'Based on PayPal internal data from Q1 2022, results include Pay in 3 installments (IT).',
'woocommerce-paypal-payments'
),
],
},
ES: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. Pay in 3 installments gets about a 275%% higher Average Order Value than a standard PayPal transaction.¹ <strong>There\'s no extra cost on top of your PayPal Checkout rate</strong>, and you get paid up front. <a target="_blank" href="%s">More about Pay in 3 installments</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/es/business/accept-payments/checkout/installments'
),
notes: [
__(
'Based on PayPal internal data from Q1 2022, results include Pay in 3 installments (ES).',
'woocommerce-paypal-payments'
),
],
},
DE: {
description: sprintf(
__(
'Your customers can already buy now and pay later with PayPal — add messaging to your site to let them know. When you offer your customers Pay Later options, 57%% will be more likely to buy from you again.¹ <strong>There\'s no extra cost</strong> and you get paid up front. <a target="_blank" href="%s">More about Pay Later</a>',
'woocommerce-paypal-payments'
),
'https://www.paypal.com/de/business/accept-payments/checkout/installments'
),
notes: [
__(
'Average order value in 2020 with PayPal installments compared to total PayPal sales.',
'woocommerce-paypal-payments'
),
],
},
};

View file

@ -6,20 +6,18 @@ import {
RadioControl, RadioControl,
} from '@wordpress/components'; } from '@wordpress/components';
import { useState } from '@wordpress/element'; import { useState } from '@wordpress/element';
import PaymentMethodModal from '../../../../ReusableComponents/PaymentMethodModal'; import PaymentMethodModal from '../../../../ReusableComponents/PaymentMethodModal';
import { import { PaymentHooks } from '../../../../../data';
usePaymentMethods,
usePaymentMethodsModal,
} from '../../../../../data/payment/hooks';
const Modal = ( { method, setModalIsVisible, onSave } ) => { const Modal = ( { method, setModalIsVisible, onSave } ) => {
const { paymentMethods } = usePaymentMethods(); const { paymentMethods } = PaymentHooks.usePaymentMethods();
const { const {
paypalShowLogo, paypalShowLogo,
threeDSecure, threeDSecure,
fastlaneCardholderName, fastlaneCardholderName,
fastlaneDisplayWatermark, fastlaneDisplayWatermark,
} = usePaymentMethodsModal(); } = PaymentHooks.usePaymentMethodsModal();
const [ settings, setSettings ] = useState( () => { const [ settings, setSettings ] = useState( () => {
if ( ! method?.id ) { if ( ! method?.id ) {
@ -68,6 +66,7 @@ const Modal = ( { method, setModalIsVisible, onSave } ) => {
return ( return (
<div className="ppcp-r-modal__field-row"> <div className="ppcp-r-modal__field-row">
<TextControl <TextControl
__nextHasNoMarginBottom={ true }
className="ppcp-r-vertical-text-control" className="ppcp-r-vertical-text-control"
label={ field.label } label={ field.label }
value={ settings[ key ] } value={ settings[ key ] }

View file

@ -4,9 +4,9 @@ import { Button } from '@wordpress/components';
import { import {
ControlTextInput, ControlTextInput,
ControlRadioGroup, ControlRadioGroup,
} from '../../../../ReusableComponents/Controls'; } from '../../../../../ReusableComponents/Controls';
import Accordion from '../../../../ReusableComponents/AccordionSection'; import Accordion from '../../../../../ReusableComponents/AccordionSection';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock'; import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const ConnectionDetails = ( { settings, updateFormValue } ) => { const ConnectionDetails = ( { settings, updateFormValue } ) => {
const isSandbox = settings.sandboxConnected; const isSandbox = settings.sandboxConnected;

View file

@ -4,7 +4,7 @@ import { CommonHooks } from '../../../../../../data';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock'; import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import { Title } from '../../../../../ReusableComponents/Elements'; import { Title } from '../../../../../ReusableComponents/Elements';
const HooksTableBlock = () => { const HooksListBlock = () => {
const { webhooks } = CommonHooks.useWebhooks(); const { webhooks } = CommonHooks.useWebhooks();
const { url, events } = webhooks; const { url, events } = webhooks;
@ -13,7 +13,7 @@ const HooksTableBlock = () => {
} }
return ( return (
<SettingsBlock separatorAndGap={ false }> <SettingsBlock separatorAndGap={ false } className="ppcp--webhooks">
<WebhookUrl url={ url } /> <WebhookUrl url={ url } />
<WebhookEvents events={ events } /> <WebhookEvents events={ events } />
</SettingsBlock> </SettingsBlock>
@ -37,7 +37,7 @@ const WebhookEvents = ( { events } ) => {
<Title> <Title>
{ __( 'Subscribed Events', 'woocommerce-paypal-payments' ) } { __( 'Subscribed Events', 'woocommerce-paypal-payments' ) }
</Title> </Title>
<ul> <ul className="ppcp--webhook-list">
{ events.map( ( event, index ) => ( { events.map( ( event, index ) => (
<li key={ index }>{ event }</li> <li key={ index }>{ event }</li>
) ) } ) ) }
@ -46,4 +46,4 @@ const WebhookEvents = ( { events } ) => {
); );
}; };
export default HooksTableBlock; export default HooksListBlock;

View file

@ -1,9 +1,9 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import Accordion from '../../../../ReusableComponents/AccordionSection'; import Accordion from '../../../../../ReusableComponents/AccordionSection';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock'; import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import { ControlSelect } from '../../../../ReusableComponents/Controls'; import { ControlSelect } from '../../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../data'; import { SettingsHooks } from '../../../../../../data';
const OtherSettings = () => { const OtherSettings = () => {
const { disabledCards, setDisabledCards } = SettingsHooks.useSettings(); const { disabledCards, setDisabledCards } = SettingsHooks.useSettings();

View file

@ -5,10 +5,10 @@ import {
ControlToggleButton, ControlToggleButton,
ControlTextInput, ControlTextInput,
ControlSelect, ControlSelect,
} from '../../../../ReusableComponents/Controls'; } from '../../../../../ReusableComponents/Controls';
import SettingsBlock from '../../../../ReusableComponents/SettingsBlock'; import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
import Accordion from '../../../../ReusableComponents/AccordionSection'; import Accordion from '../../../../../ReusableComponents/AccordionSection';
import { SettingsHooks } from '../../../../../data'; import { SettingsHooks } from '../../../../../../data';
const PaypalSettings = () => { const PaypalSettings = () => {
const { const {

View file

@ -59,6 +59,7 @@ const ResubscribeBlock = () => {
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
) } ) }
horizontalLayout={ true } horizontalLayout={ true }
className="ppcp--webhook-resubscribe"
> >
<ControlButton <ControlButton
type={ 'secondary' } type={ 'secondary' }

View file

@ -116,6 +116,7 @@ const SimulationBlock = () => {
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
) } ) }
horizontalLayout={ true } horizontalLayout={ true }
className="ppcp--webhook-simulation"
> >
<ControlButton <ControlButton
type={ 'secondary' } type={ 'secondary' }

View file

@ -6,7 +6,7 @@ import Accordion from '../../../../../ReusableComponents/AccordionSection';
import SimulationBlock from './SimulationBlock'; import SimulationBlock from './SimulationBlock';
import ResubscribeBlock from './ResubscribeBlock'; import ResubscribeBlock from './ResubscribeBlock';
import HooksTableBlock from './HooksTableBlock'; import HooksListBlock from './HooksListBlock';
import { SettingsHooks } from '../../../../../../data'; import { SettingsHooks } from '../../../../../../data';
const Troubleshooting = () => { const Troubleshooting = () => {
@ -43,7 +43,7 @@ const Troubleshooting = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status' 'https://woocommerce.com/document/woocommerce-paypal-payments/#webhook-status'
) } ) }
> >
<HooksTableBlock /> <HooksListBlock />
<ResubscribeBlock /> <ResubscribeBlock />
<SimulationBlock /> <SimulationBlock />
</SettingsBlock> </SettingsBlock>

View file

@ -4,10 +4,10 @@ import {
Content, Content,
ContentWrapper, ContentWrapper,
} from '../../../../ReusableComponents/Elements'; } from '../../../../ReusableComponents/Elements';
import ConnectionDetails from '../../../Overview/TabSettingsElements/Blocks/ConnectionDetails'; import ConnectionDetails from './Blocks/ConnectionDetails';
import Troubleshooting from '../../../Overview/TabSettingsElements/Blocks/Troubleshooting/Troubleshooting'; import Troubleshooting from './Blocks/Troubleshooting';
import PaypalSettings from '../../../Overview/TabSettingsElements/Blocks/PaypalSettings'; import PaypalSettings from './Blocks/PaypalSettings';
import OtherSettings from '../../../Overview/TabSettingsElements/Blocks/OtherSettings'; import OtherSettings from './Blocks/OtherSettings';
const ExpertSettings = () => { const ExpertSettings = () => {
const settings = {}; // dummy object const settings = {}; // dummy object

View file

@ -49,7 +49,7 @@ const LocationSelector = ( { location, setLocation } ) => {
) } ) }
</SelectStylingSection> </SelectStylingSection>
<CheckboxStylingSection <CheckboxStylingSection
className="location-activation" name="location-activation"
separatorAndGap={ false } separatorAndGap={ false }
options={ [ activateCheckbox ] } options={ [ activateCheckbox ] }
value={ isActive } value={ isActive }

View file

@ -9,8 +9,8 @@ const SectionPaymentMethods = ( { location } ) => {
return ( return (
<CheckboxStylingSection <CheckboxStylingSection
name="payment-methods"
title={ __( 'Payment Methods', 'woocommerce-paypal-payments' ) } title={ __( 'Payment Methods', 'woocommerce-paypal-payments' ) }
className="payment-methods"
options={ choices } options={ choices }
value={ paymentMethods } value={ paymentMethods }
onChange={ setPaymentMethods } onChange={ setPaymentMethods }

View file

@ -21,7 +21,7 @@ const SectionTagline = ( { location } ) => {
return ( return (
<CheckboxStylingSection <CheckboxStylingSection
className="tagline" name="tagline"
separatorAndGap={ false } separatorAndGap={ false }
options={ [ checkbox ] } options={ [ checkbox ] }
value={ tagline } value={ tagline }

View file

@ -20,7 +20,7 @@ const StylingSection = ( {
separatorAndGap={ separatorAndGap } separatorAndGap={ separatorAndGap }
> >
<Header> <Header>
<Title altStyle={ true } big={ bigTitle }> <Title noCaps={ true } big={ bigTitle }>
{ title } { title }
</Title> </Title>
<Description>{ description }</Description> <Description>{ description }</Description>

View file

@ -1,11 +1,12 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { CheckboxGroup } from '../../../../../ReusableComponents/Fields'; import { CheckboxGroup } from '../../../../../ReusableComponents/Fields';
import HStack from '../../../../../ReusableComponents/HStack'; import { VStack } from '../../../../../ReusableComponents/Stack';
import StylingSection from './StylingSection'; import StylingSection from './StylingSection';
const StylingSectionWithCheckboxes = ( { const StylingSectionWithCheckboxes = ( {
title, title,
name,
className = '', className = '',
description = '', description = '',
separatorAndGap = true, separatorAndGap = true,
@ -14,7 +15,14 @@ const StylingSectionWithCheckboxes = ( {
onChange, onChange,
children, children,
} ) => { } ) => {
className = classNames( 'ppcp--has-checkboxes', className ); className = classNames( 'ppcp--has-checkboxes', name, className );
if ( ! name ) {
console.error(
'Checkbox sections need a unique name! No name given to:',
title
);
}
return ( return (
<StylingSection <StylingSection
@ -23,13 +31,14 @@ const StylingSectionWithCheckboxes = ( {
description={ description } description={ description }
separatorAndGap={ separatorAndGap } separatorAndGap={ separatorAndGap }
> >
<HStack spacing={ 6 }> <VStack spacing={ 6 }>
<CheckboxGroup <CheckboxGroup
name={ name }
options={ options } options={ options }
value={ value } value={ value }
onChange={ onChange } onChange={ onChange }
/> />
</HStack> </VStack>
{ children } { children }
</StylingSection> </StylingSection>

View file

@ -1,7 +1,7 @@
import { RadioControl } from '@wordpress/components'; import { RadioControl } from '@wordpress/components';
import classNames from 'classnames'; import classNames from 'classnames';
import HStack from '../../../../../ReusableComponents/HStack'; import { HStack } from '../../../../../ReusableComponents/Stack';
import StylingSection from './StylingSection'; import StylingSection from './StylingSection';
const StylingSectionWithRadiobuttons = ( { const StylingSectionWithRadiobuttons = ( {

View file

@ -9,21 +9,48 @@ import {
TodoSettingsBlock, TodoSettingsBlock,
FeatureSettingsBlock, FeatureSettingsBlock,
} from '../../../ReusableComponents/SettingsBlocks'; } from '../../../ReusableComponents/SettingsBlocks';
import { Content, ContentWrapper } from '../../../ReusableComponents/Elements';
import SettingsCard from '../../../ReusableComponents/SettingsCard'; import SettingsCard from '../../../ReusableComponents/SettingsCard';
import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge'; import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge';
import { useMerchantInfo } from '../../../../data/common/hooks'; import { useMerchantInfo } from '../../../../data/common/hooks';
import { useTodos } from '../../../../data/todos/hooks';
import { STORE_NAME } from '../../../../data/common'; import { STORE_NAME } from '../../../../data/common';
import Features from '../Components/Overview/Features'; import { getFeatures } from '../Components/Overview/features-config';
import { todosData } from '../todo-items';
import { import {
NOTIFICATION_ERROR, NOTIFICATION_ERROR,
NOTIFICATION_SUCCESS, NOTIFICATION_SUCCESS,
} from '../../../ReusableComponents/Icons'; } from '../../../ReusableComponents/Icons';
const TabOverview = () => { const TabOverview = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false ); return (
<div className="ppcp-r-tab-overview">
{ todosData.length > 0 && (
<SettingsCard
className="ppcp-r-tab-overview-todo"
title={ __(
'Things to do next',
'woocommerce-paypal-payments'
) }
description={ __(
'Complete these tasks to keep your store updated with the latest products and services.',
'woocommerce-paypal-payments'
) }
>
<TodoSettingsBlock todosData={ todosData } />
</SettingsCard>
) }
const { todos, isReady: areTodosReady } = useTodos(); <OverviewFeatures />
<OverviewHelp />
</div>
);
};
export default TabOverview;
const OverviewFeatures = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false );
const { merchant, features: merchantFeatures } = useMerchantInfo(); const { merchant, features: merchantFeatures } = useMerchantInfo();
const { refreshFeatureStatuses, setActiveModal } = const { refreshFeatureStatuses, setActiveModal } =
useDispatch( STORE_NAME ); useDispatch( STORE_NAME );
@ -32,7 +59,7 @@ const TabOverview = () => {
// Get the features data with access to setActiveModal // Get the features data with access to setActiveModal
const featuresData = useMemo( const featuresData = useMemo(
() => Features.getFeatures( setActiveModal ), () => getFeatures( setActiveModal ),
[ setActiveModal ] [ setActiveModal ]
); );
@ -49,6 +76,7 @@ const TabOverview = () => {
const refreshHandler = async () => { const refreshHandler = async () => {
setIsRefreshing( true ); setIsRefreshing( true );
try { try {
const result = await refreshFeatureStatuses(); const result = await refreshFeatureStatuses();
if ( result && ! result.success ) { if ( result && ! result.success ) {
@ -79,122 +107,139 @@ const TabOverview = () => {
icon: NOTIFICATION_SUCCESS, icon: NOTIFICATION_SUCCESS,
} }
); );
console.log( 'Features refreshed successfully.' );
} }
} finally { } finally {
setIsRefreshing( false ); setIsRefreshing( false );
} }
}; };
// Don't render todos section until data is ready return (
const showTodos = areTodosReady && todos.length > 0; <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 ( return (
<div className="ppcp-r-tab-overview"> <Content>
{ showTodos && ( <FeatureSettingsBlock
<SettingsCard title={ title }
className="ppcp-r-tab-overview-todo" description={ description }
title={ __( actionProps={ actionProps }
'Things to do next',
'woocommerce-paypal-payments'
) }
description={ __(
'Complete these tasks to keep your store updated with the latest products and services.',
'woocommerce-paypal-payments'
) }
>
<TodoSettingsBlock todosData={ todos } />
</SettingsCard>
) }
<SettingsCard
className="ppcp-r-tab-overview-features"
title={ __( 'Features', 'woocommerce-paypal-payments' ) }
description={
<>
<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 } />
{ isRefreshing
? __(
'Refreshing…',
'woocommerce-paypal-payments'
)
: __(
'Refresh',
'woocommerce-paypal-payments'
) }
</Button>
</>
}
contentItems={ features.map( ( feature ) => {
return (
<FeatureSettingsBlock
key={ feature.id }
title={ feature.title }
description={ feature.description }
actionProps={ {
buttons: feature.buttons
.filter(
( button ) =>
! button.showWhen || // Learn more buttons
( feature.enabled &&
button.showWhen ===
'enabled' ) ||
( ! feature.enabled &&
button.showWhen === 'disabled' )
)
.map( ( button ) => ( {
...button,
url: button.urls
? merchant?.isSandbox
? button.urls.sandbox
: button.urls.live
: button.url,
} ) ),
isBusy: isRefreshing,
enabled: feature.enabled,
notes: feature.notes,
badge: feature.enabled
? {
text: __(
'Active',
'woocommerce-paypal-payments'
),
type: TITLE_BADGE_POSITIVE,
}
: undefined,
} }
/>
);
} ) }
/> />
</Content>
);
};
<SettingsCard const OverviewFeatureDescription = ( { refreshHandler, isRefreshing } ) => {
className="ppcp-r-tab-overview-help" const buttonLabel = isRefreshing
title={ __( 'Help Center', 'woocommerce-paypal-payments' ) } ? __( 'Refreshing…', 'woocommerce-paypal-payments' )
description={ __( : __( 'Refresh', 'woocommerce-paypal-payments' );
'Access detailed guides and responsive support to streamline setup and enhance your experience.',
return (
<>
<p>
{ __(
'Enable additional features and capabilities on your WooCommerce store.',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
) } ) }
contentItems={ [ </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>
</>
);
};
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 <FeatureSettingsBlock
key="documentation"
title={ __( title={ __(
'Documentation', 'Documentation',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
@ -211,13 +256,15 @@ const TabOverview = () => {
'View full documentation', 'View full documentation',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/', url: 'https://woocommerce.com/document/woocommerce-paypal-payments/ ',
}, },
], ],
} } } }
/>, />
</Content>
<Content>
<FeatureSettingsBlock <FeatureSettingsBlock
key="support"
title={ __( 'Support', 'woocommerce-paypal-payments' ) } title={ __( 'Support', 'woocommerce-paypal-payments' ) }
description={ __( description={ __(
'Need help? Access troubleshooting tips or contact our support team for personalized assistance.', 'Need help? Access troubleshooting tips or contact our support team for personalized assistance.',
@ -231,15 +278,13 @@ const TabOverview = () => {
'View support options', 'View support options',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help', url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help ',
}, },
], ],
} } } }
/>, />
] } </Content>
/> </ContentWrapper>
</div> </SettingsCard>
); );
}; };
export default TabOverview;

View file

@ -0,0 +1,123 @@
import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';
import SettingsCard from '../../../ReusableComponents/SettingsCard';
import { PaymentMethodsBlock } from '../../../ReusableComponents/SettingsBlocks';
import { PaymentHooks } from '../../../../data';
import { useActiveModal } from '../../../../data/common/hooks';
import Modal from '../Components/Payment/Modal';
const TabPaymentMethods = () => {
const methods = PaymentHooks.usePaymentMethods();
const { setPersistent, changePaymentSettings } = PaymentHooks.useStore();
const { activeModal, setActiveModal } = useActiveModal();
const getActiveMethod = () => {
if ( ! activeModal ) {
return null;
}
return methods.all.find( ( method ) => method.id === activeModal );
};
const handleSave = useCallback(
( methodId, settings ) => {
changePaymentSettings( methodId, {
title: settings.checkoutPageTitle,
description: settings.checkoutPageDescription,
} );
const persistentSettings = [
'paypalShowLogo',
'threeDSecure',
'fastlaneCardholderName',
'fastlaneDisplayWatermark',
];
persistentSettings.forEach( ( setting ) => {
if ( setting in settings ) {
// TODO: Create a dedicated setter for those values.
setPersistent( setting, settings[ setting ] );
}
} );
setActiveModal( null );
},
[ changePaymentSettings, setActiveModal, setPersistent ]
);
return (
<div className="ppcp-r-payment-methods">
<PaymentMethodCard
id="ppcp-paypal-checkout-card"
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
description={ __(
'Select your preferred checkout option with PayPal for easy payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-standard.svg"
methods={ methods.paypal }
onTriggerModal={ setActiveModal }
/>
<PaymentMethodCard
id="ppcp-card-payments-card"
title={ __(
'Online Card Payments',
'woocommerce-paypal-payments'
) }
description={ __(
'Select your preferred card payment options for efficient payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-online-methods.svg"
methods={ methods.cardPayment }
onTriggerModal={ setActiveModal }
/>
<PaymentMethodCard
id="ppcp-alternative-payments-card"
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
description={ __(
'With alternative payment methods, customers across the globe can pay with their bank accounts and other local payment methods.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-alternative-methods.svg"
methods={ methods.apm }
onTriggerModal={ setActiveModal }
/>
{ activeModal && (
<Modal
method={ getActiveMethod() }
setModalIsVisible={ () => setActiveModal( null ) }
onSave={ handleSave }
/>
) }
</div>
);
};
export default TabPaymentMethods;
const PaymentMethodCard = ( {
id,
title,
description,
icon,
methods,
onTriggerModal,
} ) => (
<SettingsCard
id={ id }
title={ title }
description={ description }
icon={ icon }
contentContainer={ false }
>
<PaymentMethodsBlock
paymentMethods={ methods }
onTriggerModal={ onTriggerModal }
/>
</SettingsCard>
);

View file

@ -1,7 +1,7 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import TabOverview from './TabOverview'; import TabOverview from './TabOverview';
import TabPaymentMethods from '../../Overview/TabPaymentMethods'; import TabPaymentMethods from './TabPaymentMethods';
import TabSettings from './TabSettings'; import TabSettings from './TabSettings';
import TabStyling from './TabStyling'; import TabStyling from './TabStyling';
import TabPayLaterMessaging from '../../Overview/TabPayLaterMessaging'; import TabPayLaterMessaging from '../../Overview/TabPayLaterMessaging';

View file

@ -12,6 +12,7 @@ export default {
SET_PERSISTENT: 'PAYMENT:SET_PERSISTENT', SET_PERSISTENT: 'PAYMENT:SET_PERSISTENT',
RESET: 'PAYMENT:RESET', RESET: 'PAYMENT:RESET',
HYDRATE: 'PAYMENT:HYDRATE', HYDRATE: 'PAYMENT:HYDRATE',
CHANGE_PAYMENT_SETTING: 'PAYMENT:CHANGE_PAYMENT_SETTING',
// Controls - always start with "DO_". // Controls - always start with "DO_".
DO_PERSIST_DATA: 'PAYMENT:DO_PERSIST_DATA', DO_PERSIST_DATA: 'PAYMENT:DO_PERSIST_DATA',

View file

@ -37,27 +37,17 @@ export const hydrate = ( payload ) => ( {
} ); } );
/** /**
* Transient. Marks the store as "ready", i.e., fully initialized. * Generic transient-data updater.
* *
* @param {boolean} isReady * @param {string} prop Name of the property to update.
* @param {any} value The new value of the property.
* @return {Action} The action. * @return {Action} The action.
*/ */
export const setIsReady = ( isReady ) => ( { export const setTransient = ( prop, value ) => ( {
type: ACTION_TYPES.SET_TRANSIENT, type: ACTION_TYPES.SET_TRANSIENT,
payload: { isReady }, payload: { [ prop ]: value },
} ); } );
/**
* Side effect. Triggers the persistence of store data to the server.
*
* @return {Action} The action.
*/
export const persist = function* () {
const data = yield select( STORE_NAME ).persistentData();
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
};
/** /**
* Generic persistent-data updater. * Generic persistent-data updater.
* *
@ -69,3 +59,34 @@ export const setPersistent = ( prop, value ) => ( {
type: ACTION_TYPES.SET_PERSISTENT, type: ACTION_TYPES.SET_PERSISTENT,
payload: { [ prop ]: value }, 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 );
/**
* Modify properties of a specific payment method.
*
* @param {string} id The payment method ID.
* @param {Object} props New props.
* @return {Action} The action.
*/
export const changePaymentSettings = ( id, props ) => ( {
type: ACTION_TYPES.CHANGE_PAYMENT_SETTING,
payload: { id, props },
} );
/**
* Side effect. Triggers the persistence of store data to the server.
*
* @return {Action} The action.
*/
export const persist = function* () {
const data = yield select( STORE_NAME ).persistentData();
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
};

View file

@ -7,60 +7,53 @@
* @file * @file
*/ */
import { useSelect, useDispatch } from '@wordpress/data'; import { useDispatch } from '@wordpress/data';
import { STORE_NAME } from './constants'; import { STORE_NAME } from './constants';
import { createHooksForStore } from '../utils';
const useTransient = ( key ) =>
useSelect(
( select ) => select( STORE_NAME ).transientData()?.[ key ],
[ key ]
);
const usePersistent = ( key ) =>
useSelect(
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
[ key ]
);
const useHooks = () => { const useHooks = () => {
const { persist, setPersistent } = useDispatch( STORE_NAME ); const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
const { persist, setPersistent, changePaymentSettings } =
useDispatch( STORE_NAME );
// Read-only flags and derived state. // Read-only flags and derived state.
// Nothing here yet. // Nothing here yet.
// Transient accessors. // Transient accessors.
const isReady = useTransient( 'isReady' ); const [ isReady ] = useTransient( 'isReady' );
// PayPal checkout. // PayPal checkout.
const paypal = usePersistent( 'ppcp-gateway' ); const [ paypal ] = usePersistent( 'ppcp-gateway' );
const venmo = usePersistent( 'venmo' ); const [ venmo ] = usePersistent( 'venmo' );
const payLater = usePersistent( 'pay-later' ); const [ payLater ] = usePersistent( 'pay-later' );
const creditCard = usePersistent( 'ppcp-card-button-gateway' ); const [ creditCard ] = usePersistent( 'ppcp-card-button-gateway' );
// Online card Payments. // Online card Payments.
const advancedCreditCard = usePersistent( 'ppcp-credit-card-gateway' ); const [ advancedCreditCard ] = usePersistent( 'ppcp-credit-card-gateway' );
const fastlane = usePersistent( 'ppcp-axo-gateway' ); const [ fastlane ] = usePersistent( 'ppcp-axo-gateway' );
const applePay = usePersistent( 'ppcp-applepay' ); const [ applePay ] = usePersistent( 'ppcp-applepay' );
const googlePay = usePersistent( 'ppcp-googlepay' ); const [ googlePay ] = usePersistent( 'ppcp-googlepay' );
// Alternative payment methods. // Alternative payment methods.
const bancontact = usePersistent( 'ppcp-bancontact' ); const [ bancontact ] = usePersistent( 'ppcp-bancontact' );
const blik = usePersistent( 'ppcp-blik' ); const [ blik ] = usePersistent( 'ppcp-blik' );
const eps = usePersistent( 'ppcp-eps' ); const [ eps ] = usePersistent( 'ppcp-eps' );
const ideal = usePersistent( 'ppcp-ideal' ); const [ ideal ] = usePersistent( 'ppcp-ideal' );
const mybank = usePersistent( 'ppcp-mybank' ); const [ mybank ] = usePersistent( 'ppcp-mybank' );
const p24 = usePersistent( 'ppcp-p24' ); const [ p24 ] = usePersistent( 'ppcp-p24' );
const trustly = usePersistent( 'ppcp-trustly' ); const [ trustly ] = usePersistent( 'ppcp-trustly' );
const multibanco = usePersistent( 'ppcp-multibanco' ); const [ multibanco ] = usePersistent( 'ppcp-multibanco' );
const pui = usePersistent( 'ppcp-pay-upon-invoice-gateway' ); const [ pui ] = usePersistent( 'ppcp-pay-upon-invoice-gateway' );
const oxxo = usePersistent( 'ppcp-oxxo-gateway' ); const [ oxxo ] = usePersistent( 'ppcp-oxxo-gateway' );
// Custom modal data. // Custom modal data.
const paypalShowLogo = usePersistent( 'paypalShowLogo' ); const [ paypalShowLogo ] = usePersistent( 'paypalShowLogo' );
const threeDSecure = usePersistent( 'threeDSecure' ); const [ threeDSecure ] = usePersistent( 'threeDSecure' );
const fastlaneCardholderName = usePersistent( 'fastlaneCardholderName' ); const [ fastlaneCardholderName ] = usePersistent(
const fastlaneDisplayWatermark = usePersistent( 'fastlaneCardholderName'
);
const [ fastlaneDisplayWatermark ] = usePersistent(
'fastlaneDisplayWatermark' 'fastlaneDisplayWatermark'
); );
@ -68,6 +61,7 @@ const useHooks = () => {
persist, persist,
isReady, isReady,
setPersistent, setPersistent,
changePaymentSettings,
paypal, paypal,
venmo, venmo,
payLater, payLater,
@ -94,21 +88,26 @@ const useHooks = () => {
}; };
export const useStore = () => { export const useStore = () => {
const { persist, isReady } = useHooks(); const { persist, isReady, setPersistent, changePaymentSettings } =
return { persist, isReady }; useHooks();
return { persist, isReady, setPersistent, changePaymentSettings };
}; };
export const usePaymentMethods = () => { export const usePaymentMethods = () => {
const { const {
setPersistent, // PayPal Checkout.
paypal, paypal,
venmo, venmo,
payLater, payLater,
creditCard, creditCard,
// Online card payments.
advancedCreditCard, advancedCreditCard,
fastlane, fastlane,
applePay, applePay,
googlePay, googlePay,
// Local APMs.
bancontact, bancontact,
blik, blik,
eps, eps,
@ -121,6 +120,25 @@ export const usePaymentMethods = () => {
oxxo, oxxo,
} = useHooks(); } = useHooks();
const payPalCheckout = [ paypal, venmo, payLater, creditCard ];
const onlineCardPayments = [
advancedCreditCard,
fastlane,
applePay,
googlePay,
];
const alternative = [
bancontact,
blik,
eps,
ideal,
mybank,
p24,
trustly,
multibanco,
pui,
oxxo,
];
const paymentMethods = [ const paymentMethods = [
paypal, paypal,
venmo, venmo,
@ -143,8 +161,10 @@ export const usePaymentMethods = () => {
]; ];
return { return {
setPersistent, all: paymentMethods,
paymentMethods, paypal: payPalCheckout,
cardPayment: onlineCardPayments,
apm: alternative,
}; };
}; };
@ -163,63 +183,3 @@ export const usePaymentMethodsModal = () => {
fastlaneDisplayWatermark, fastlaneDisplayWatermark,
}; };
}; };
export const usePaymentMethodsPayPalCheckout = () => {
const { paypal, venmo, payLater, creditCard } = useHooks();
const paymentMethodsPayPalCheckout = [
paypal,
venmo,
payLater,
creditCard,
];
return {
paymentMethodsPayPalCheckout,
};
};
export const usePaymentMethodsOnlineCardPayments = () => {
const { advancedCreditCard, fastlane, applePay, googlePay } = useHooks();
const paymentMethodsOnlineCardPayments = [
advancedCreditCard,
fastlane,
applePay,
googlePay,
];
return {
paymentMethodsOnlineCardPayments,
};
};
export const usePaymentMethodsAlternative = () => {
const {
bancontact,
blik,
eps,
ideal,
mybank,
p24,
trustly,
multibanco,
pui,
oxxo,
} = useHooks();
const paymentMethodsAlternative = [
bancontact,
blik,
eps,
ideal,
mybank,
p24,
trustly,
multibanco,
pui,
oxxo,
];
return {
paymentMethodsAlternative,
};
};

View file

@ -57,6 +57,20 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) => [ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
changePersistent( state, payload ), changePersistent( state, payload ),
[ ACTION_TYPES.CHANGE_PAYMENT_SETTING ]: ( state, payload ) => {
const methodId = payload.id;
const oldProps = state.data[ methodId ];
if ( ! oldProps || oldProps.id !== methodId ) {
return state;
}
return changePersistent( state, {
...state,
[ methodId ]: { ...oldProps, ...payload.props },
} );
},
[ ACTION_TYPES.RESET ]: ( state ) => { [ ACTION_TYPES.RESET ]: ( state ) => {
const cleanState = changeTransient( const cleanState = changeTransient(
changePersistent( state, defaultPersistent ), changePersistent( state, defaultPersistent ),

View file

@ -1,3 +1,5 @@
import { useCallback } from '@wordpress/element';
import { import {
CommonHooks, CommonHooks,
PaymentHooks, PaymentHooks,
@ -12,7 +14,7 @@ export const useSaveSettings = () => {
const { persist: persistSettings } = SettingsHooks.useStore(); const { persist: persistSettings } = SettingsHooks.useStore();
const { persist: persistStyling } = StylingHooks.useStore(); const { persist: persistStyling } = StylingHooks.useStore();
const persistAll = () => { const persistAll = useCallback( () => {
withActivity( withActivity(
'persist-methods', 'persist-methods',
'Save payment methods', 'Save payment methods',
@ -28,7 +30,7 @@ export const useSaveSettings = () => {
'Save styling details', 'Save styling details',
persistStyling persistStyling
); );
}; }, [ persistPayment, persistSettings, persistStyling, withActivity ] );
return { persistAll }; return { persistAll };
}; };

View file

@ -37,24 +37,18 @@ class SettingsModel {
$settings = get_option( self::OPTION_NAME, array() ); $settings = get_option( self::OPTION_NAME, array() );
$formatted = array( $formatted = array(
'invoicePrefix' => $settings['invoice_prefix'] ?? '', 'invoicePrefix' => $settings['invoice_prefix'] ?? '',
'authorizeOnly' => (bool) ( $settings['authorize_only'] ?? false ), 'authorizeOnly' => (bool) ( $settings['authorize_only'] ?? false ),
'captureVirtualOnlyOrders' => (bool) ( $settings['capture_virtual_only_orders'] ?? false ), 'captureVirtualOnlyOrders' => (bool) ( $settings['capture_virtual_only_orders'] ?? false ),
'savePaypalAndVenmo' => (bool) ( $settings['save_paypal_and_venmo'] ?? false ), 'savePaypalAndVenmo' => (bool) ( $settings['save_paypal_and_venmo'] ?? false ),
'saveCardDetails' => (bool) ( $settings['save_credit_card_and_debit_card'] ?? false ), 'saveCardDetails' => (bool) ( $settings['save_credit_card_and_debit_card'] ?? false ),
'payNowExperience' => (bool) ( $settings['pay_now_experience'] ?? false ), 'payNowExperience' => (bool) ( $settings['pay_now_experience'] ?? false ),
'sandboxAccountCredentials' => (bool) ( $settings['sandbox_account_credentials'] ?? false ), 'logging' => (bool) ( $settings['logging'] ?? false ),
'sandboxMode' => $settings['sandbox_mode'] ?? null, 'subtotalAdjustment' => $settings['subtotal_mismatch_fallback'] ?? null,
'sandboxEnabled' => (bool) ( $settings['sandbox_enabled'] ?? false ), 'brandName' => $settings['brand_name'] ?? '',
'sandboxClientId' => $settings['sandbox_client_id'] ?? '', 'softDescriptor' => $settings['soft_descriptor'] ?? '',
'sandboxSecretKey' => $settings['sandbox_secret_key'] ?? '', 'landingPage' => $settings['paypal_landing_page'] ?? null,
'sandboxConnected' => (bool) ( $settings['sandbox_connected'] ?? false ), 'buttonLanguage' => $settings['button_language'] ?? '',
'logging' => (bool) ( $settings['logging'] ?? false ),
'subtotalAdjustment' => $settings['subtotal_mismatch_fallback'] ?? null,
'brandName' => $settings['brand_name'] ?? '',
'softDescriptor' => $settings['soft_descriptor'] ?? '',
'landingPage' => $settings['paypal_landing_page'] ?? null,
'buttonLanguage' => $settings['button_language'] ?? '',
); );
return $formatted; return $formatted;
@ -78,12 +72,6 @@ class SettingsModel {
'save_paypal_and_venmo' => (bool) ( $data['savePaypalAndVenmo'] ?? false ), 'save_paypal_and_venmo' => (bool) ( $data['savePaypalAndVenmo'] ?? false ),
'save_credit_card_and_debit_card' => (bool) ( $data['saveCardDetails'] ?? false ), 'save_credit_card_and_debit_card' => (bool) ( $data['saveCardDetails'] ?? false ),
'pay_now_experience' => (bool) ( $data['payNowExperience'] ?? false ), 'pay_now_experience' => (bool) ( $data['payNowExperience'] ?? false ),
'sandbox_account_credentials' => (bool) ( $data['sandboxAccountCredentials'] ?? false ),
'sandbox_mode' => $data['sandboxMode'] ?? null,
'sandbox_enabled' => (bool) ( $data['sandboxEnabled'] ?? false ),
'sandbox_client_id' => $data['sandboxClientId'] ?? '',
'sandbox_secret_key' => $data['sandboxSecretKey'] ?? '',
'sandbox_connected' => (bool) ( $data['sandboxConnected'] ?? false ),
'logging' => (bool) ( $data['logging'] ?? false ), 'logging' => (bool) ( $data['logging'] ?? false ),
'subtotal_mismatch_fallback' => $data['subtotalAdjustment'] ?? null, 'subtotal_mismatch_fallback' => $data['subtotalAdjustment'] ?? null,
'brand_name' => $data['brandName'] ?? '', 'brand_name' => $data['brandName'] ?? '',

View file

@ -576,7 +576,7 @@ class PaymentRestEndpoint extends RestEndpoint {
'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 8009250304', 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 8009250304',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
'icon' => '', 'icon' => 'payment-method-oxxo',
'itemTitle' => __( 'OXXO', 'woocommerce-paypal-payments' ), 'itemTitle' => __( 'OXXO', 'woocommerce-paypal-payments' ),
'itemDescription' => __( 'itemDescription' => __(
'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 8009250304', 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 8009250304',
@ -669,7 +669,7 @@ class PaymentRestEndpoint extends RestEndpoint {
$gateway_settings[ $key ] = array( $gateway_settings[ $key ] = array(
'enabled' => 'yes' === $gateway->enabled, 'enabled' => 'yes' === $gateway->enabled,
'title' => $gateway->get_title(), 'title' => str_replace( '&amp;', '&', $gateway->get_title() ),
'description' => $gateway->get_description(), 'description' => $gateway->get_description(),
'id' => $this->gateways()[ $key ]['id'] ?? $key, 'id' => $this->gateways()[ $key ]['id'] ?? $key,
'icon' => $this->gateways()[ $key ]['icon'] ?? '', 'icon' => $this->gateways()[ $key ]['icon'] ?? '',
@ -687,7 +687,7 @@ class PaymentRestEndpoint extends RestEndpoint {
$gateway_settings['fastlaneCardholderName'] = $this->settings->get_fastlane_cardholder_name(); $gateway_settings['fastlaneCardholderName'] = $this->settings->get_fastlane_cardholder_name();
$gateway_settings['fastlaneDisplayWatermark'] = $this->settings->get_fastlane_display_watermark(); $gateway_settings['fastlaneDisplayWatermark'] = $this->settings->get_fastlane_display_watermark();
return $this->return_success( $gateway_settings ); return $this->return_success( apply_filters( 'woocommerce_paypal_payments_payment_methods', $gateway_settings ) );
} }
/** /**

View file

@ -9,6 +9,17 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings; namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BancontactGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BlikGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\EPSGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\IDealGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\MultibancoGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\MyBankGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\P24Gateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\TrustlyGateway;
use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint; use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint;
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile; use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\RestEndpoint;
@ -17,6 +28,11 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
@ -169,6 +185,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
'wcPaymentsTabUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout' ), 'wcPaymentsTabUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout' ),
'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, 'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'isPayLaterConfiguratorAvailable' => $is_pay_later_configurator_available, 'isPayLaterConfiguratorAvailable' => $is_pay_later_configurator_available,
'storeCountry' => $container->get( 'wcgateway.store-country' ),
); );
if ( $is_pay_later_configurator_available ) { if ( $is_pay_later_configurator_available ) {
@ -264,6 +281,69 @@ class SettingsModule implements ServiceModule, ExecutableModule {
} }
); );
add_filter(
'woocommerce_paypal_payments_payment_methods',
function( array $payment_methods ) use ( $container ) : array {
$all_payment_methods = $payment_methods;
$dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
assert( $dcc_product_status instanceof DCCProductStatus );
$googlepay_product_status = $container->get( 'googlepay.helpers.apm-product-status' );
assert( $googlepay_product_status instanceof ApmProductStatus );
$applepay_product_status = $container->get( 'applepay.apple-product-status' );
assert( $applepay_product_status instanceof AppleProductStatus );
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
assert( $dcc_applies instanceof DCCApplies );
// Unset BCDC if merchant is eligible for ACDC.
if ( $dcc_product_status->dcc_is_active() && ! $container->get( 'wcgateway.settings.allow_card_button_gateway' ) ) {
unset( $payment_methods[ CardButtonGateway::ID ] );
}
// Unset Venmo when store location is not United States.
if ( $container->get( 'api.shop.country' ) !== 'US' ) {
unset( $payment_methods['venmo'] );
}
// Unset if not eligible for Google Pay.
if ( ! $googlepay_product_status->is_active() ) {
unset( $payment_methods['ppcp-googlepay'] );
}
// Unset if not eligible for Apple Pay.
if ( ! $applepay_product_status->is_active() ) {
unset( $payment_methods['ppcp-applepay'] );
}
// Unset Fastlane if store location is not United States or merchant is not eligible for ACDC.
if ( $container->get( 'api.shop.country' ) !== 'US' || ! $dcc_product_status->dcc_is_active() ) {
unset( $payment_methods['ppcp-axo-gateway'] );
}
// For non-ACDC regions unset ACDC, local APMs and set BCDC.
if ( ! $dcc_applies ) {
unset( $payment_methods[ CreditCardGateway::ID ] );
unset( $payment_methods[ BancontactGateway::ID ] );
unset( $payment_methods[ BlikGateway::ID ] );
unset( $payment_methods[ EPSGateway::ID ] );
unset( $payment_methods[ IDealGateway::ID ] );
unset( $payment_methods[ MyBankGateway::ID ] );
unset( $payment_methods[ P24Gateway::ID ] );
unset( $payment_methods[ TrustlyGateway::ID ] );
unset( $payment_methods[ MultibancoGateway::ID ] );
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
unset( $payment_methods[ OXXO::ID ] );
$payment_methods[ CardButtonGateway::ID ] = $all_payment_methods[ CardButtonGateway::ID ];
}
return $payment_methods;
}
);
return true; return true;
} }

View file

@ -1659,9 +1659,12 @@ return array(
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface ); assert( $settings instanceof ContainerInterface );
return $settings->has( 'allow_card_button_gateway' ) ? return apply_filters(
'woocommerce_paypal_payments_enable_standard_card_button_gateway_settings',
$settings->has( 'allow_card_button_gateway' ) ?
(bool) $settings->get( 'allow_card_button_gateway' ) : (bool) $settings->get( 'allow_card_button_gateway' ) :
$container->get( 'wcgateway.settings.allow_card_button_gateway.default' ); $container->get( 'wcgateway.settings.allow_card_button_gateway.default' )
);
}, },
'wcgateway.settings.has_enabled_separate_button_gateways' => static function ( ContainerInterface $container ): bool { 'wcgateway.settings.has_enabled_separate_button_gateways' => static function ( ContainerInterface $container ): bool {
return (bool) $container->get( 'wcgateway.settings.allow_card_button_gateway' ); return (bool) $container->get( 'wcgateway.settings.allow_card_button_gateway' );