Skip to content

Commit 26e32de

Browse files
wjrosaCopilotdiegocurbelo
authored
Introducing a banner to promote OC (#4495)
* Introducing a banner to promote OC * Unit tests * Fix tests * Correct illustration * Update client/settings/payment-methods/index.js Co-authored-by: Copilot <[email protected]> * Changelog and readme entries * Fix BNPL banner logic * Checking if OC is available * Fix tests * Fix title font weight * Adding learn more to the intro paragraph + activation button * Fix tests * Handling activation flow + OC setting toggle endpoint + oc context * Fix tests * Unit tests * Changing disabled value to 'no' instead of 'disabled' * Removing unnecessary setting of UPE flag * Removing unnecessary deregister method * Removing unnecessary version check * Update client/settings/payment-settings/promotional-banner/oc-promotion-banner.js Co-authored-by: Diego Curbelo <[email protected]> * Update includes/admin/class-wc-stripe-rest-oc-setting-toggle-controller.php Co-authored-by: Diego Curbelo <[email protected]> * Fix banner dismiss option persistence --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Diego Curbelo <[email protected]>
1 parent 729249a commit 26e32de

22 files changed

+932
-68
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*** Changelog ***
22

33
= 9.7.0 - xxxx-xx-xx =
4+
* Add - Introduces a new banner to promote the Optimized Checkout feature in the Stripe settings page for versions 9.8 and above
45
* Add - Introduces a new inbox note to promote the Optimized Checkout feature on version 9.8 and later
56
* Update - Removes BNPL payment methods (Klarna and Affirm) when other official plugins are active
67
* Fix - Moves the existing order lock functionality earlier in the order processing flow to prevent duplicate processing requests

client/settings/index.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SettingsManager from './settings-manager';
77
import PaymentGatewayManager from './payment-gateway-manager';
88
import UpeToggleContextProvider from './upe-toggle/provider';
99
import './styles.scss';
10+
import OCToggleContextProvider from 'wcstripe/settings/oc-toggle/provider';
1011

1112
const settingsContainer = document.getElementById(
1213
'wc-stripe-account-settings-container'
@@ -28,8 +29,15 @@ if ( settingsContainer ) {
2829
wc_stripe_settings_params.is_upe_checkout_enabled === '1'
2930
}
3031
>
31-
<StripeAccountConnectedNotice />
32-
<SettingsManager />
32+
<OCToggleContextProvider
33+
defaultIsOCEnabled={
34+
// eslint-disable-next-line camelcase
35+
wc_stripe_settings_params.is_oc_enabled === '1'
36+
}
37+
>
38+
<StripeAccountConnectedNotice />
39+
<SettingsManager />
40+
</OCToggleContextProvider>
3341
</UpeToggleContextProvider>,
3442
settingsContainer
3543
);
@@ -43,7 +51,14 @@ if ( paymentGatewayContainer ) {
4351
wc_stripe_settings_params.is_upe_checkout_enabled === '1'
4452
}
4553
>
46-
<PaymentGatewayManager />
54+
<OCToggleContextProvider
55+
defaultIsOCEnabled={
56+
// eslint-disable-next-line camelcase
57+
wc_stripe_settings_params.is_oc_enabled === '1'
58+
}
59+
>
60+
<PaymentGatewayManager />
61+
</OCToggleContextProvider>
4762
</UpeToggleContextProvider>,
4863
paymentGatewayContainer
4964
);
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import React, { useEffect, useContext } from 'react';
2+
import { render, waitFor } from '@testing-library/react';
3+
import apiFetch from '@wordpress/api-fetch';
4+
import OCToggleContextProvider from '../provider';
5+
import OCToggleContext from '../context';
6+
7+
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
8+
jest.mock( 'wcstripe/tracking', () => ( { recordEvent: jest.fn() } ) );
9+
jest.mock( '@wordpress/data', () => ( {
10+
useDispatch: jest.fn().mockReturnValue( {
11+
invalidateResolutionForStoreSelector: () => null,
12+
} ),
13+
} ) );
14+
15+
describe( 'OCToggleContextProvider', () => {
16+
afterEach( () => {
17+
jest.clearAllMocks();
18+
19+
apiFetch.mockResolvedValue( true );
20+
} );
21+
22+
afterAll( () => {
23+
jest.restoreAllMocks();
24+
} );
25+
26+
it( 'should render the initial state', () => {
27+
const childrenMock = jest.fn().mockReturnValue( null );
28+
render(
29+
<OCToggleContextProvider>
30+
<OCToggleContext.Consumer>
31+
{ childrenMock }
32+
</OCToggleContext.Consumer>
33+
</OCToggleContextProvider>
34+
);
35+
36+
expect( childrenMock ).toHaveBeenCalledWith( {
37+
isOCEnabled: false,
38+
setIsOCEnabled: expect.any( Function ),
39+
setIsOCEnabledLocally: expect.any( Function ),
40+
status: 'resolved',
41+
} );
42+
expect( apiFetch ).not.toHaveBeenCalled();
43+
} );
44+
45+
it( 'should render the initial state given a default value for isOCEnabled', () => {
46+
const childrenMock = jest.fn().mockReturnValue( null );
47+
render(
48+
<OCToggleContextProvider defaultIsOCEnabled={ true }>
49+
<OCToggleContext.Consumer>
50+
{ childrenMock }
51+
</OCToggleContext.Consumer>
52+
</OCToggleContextProvider>
53+
);
54+
55+
expect( childrenMock ).toHaveBeenCalledWith(
56+
expect.objectContaining( {
57+
isOCEnabled: true,
58+
} )
59+
);
60+
expect( apiFetch ).not.toHaveBeenCalled();
61+
} );
62+
63+
it( 'should locally update the value for isOCEnabled', () => {
64+
const childrenMock = jest.fn().mockReturnValue( null );
65+
66+
const LocallyUpdateOCDisabledFlagMock = () => {
67+
const { setIsOCEnabledLocally } = useContext( OCToggleContext );
68+
useEffect( () => {
69+
setIsOCEnabledLocally( false );
70+
}, [ setIsOCEnabledLocally ] );
71+
72+
return null;
73+
};
74+
75+
render(
76+
<OCToggleContextProvider defaultIsUpeEnabled={ true }>
77+
<LocallyUpdateOCDisabledFlagMock />
78+
<OCToggleContext.Consumer>
79+
{ childrenMock }
80+
</OCToggleContext.Consumer>
81+
</OCToggleContextProvider>
82+
);
83+
84+
expect( apiFetch ).not.toHaveBeenCalled();
85+
expect( childrenMock ).toHaveBeenCalledWith( {
86+
isOCEnabled: false,
87+
setIsOCEnabled: expect.any( Function ),
88+
setIsOCEnabledLocally: expect.any( Function ),
89+
status: 'resolved',
90+
} );
91+
} );
92+
93+
it( 'should call the API and resolve when setIsOCEnabled has been called', async () => {
94+
const childrenMock = jest.fn().mockReturnValue( null );
95+
96+
const UpdateUpeDisabledFlagMock = () => {
97+
const { setIsOCEnabled } = useContext( OCToggleContext );
98+
useEffect( () => {
99+
setIsOCEnabled( false );
100+
}, [ setIsOCEnabled ] );
101+
102+
return null;
103+
};
104+
105+
render(
106+
<OCToggleContextProvider defaultIsOCEnabled>
107+
<UpdateUpeDisabledFlagMock />
108+
<OCToggleContext.Consumer>
109+
{ childrenMock }
110+
</OCToggleContext.Consumer>
111+
</OCToggleContextProvider>
112+
);
113+
114+
expect( childrenMock ).toHaveBeenCalledWith( {
115+
isOCEnabled: true,
116+
setIsOCEnabled: expect.any( Function ),
117+
setIsOCEnabledLocally: expect.any( Function ),
118+
status: 'resolved',
119+
} );
120+
121+
expect( childrenMock ).toHaveBeenCalledWith( {
122+
isOCEnabled: true,
123+
setIsOCEnabled: expect.any( Function ),
124+
setIsOCEnabledLocally: expect.any( Function ),
125+
status: 'pending',
126+
} );
127+
128+
await waitFor( () =>
129+
expect( apiFetch ).toHaveBeenCalledWith( {
130+
path: '/wc/v3/wc_stripe/oc_setting_toggle',
131+
method: 'POST',
132+
// eslint-disable-next-line camelcase
133+
data: { is_oc_enabled: false },
134+
} )
135+
);
136+
137+
await waitFor( () => expect( apiFetch ).toHaveReturned() );
138+
139+
expect( childrenMock ).toHaveBeenCalledWith( {
140+
isOCEnabled: false,
141+
setIsOCEnabled: expect.any( Function ),
142+
setIsOCEnabledLocally: expect.any( Function ),
143+
status: 'resolved',
144+
} );
145+
} );
146+
} );

client/settings/oc-toggle/context.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createContext } from 'react';
2+
3+
const OCToggleContext = createContext( {
4+
isOCEnabled: false,
5+
setIsOCEnabled: () => null,
6+
status: 'resolved',
7+
} );
8+
9+
export default OCToggleContext;

client/settings/oc-toggle/provider.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useDispatch } from '@wordpress/data';
2+
import { useCallback, useMemo, useState } from 'react';
3+
import apiFetch from '@wordpress/api-fetch';
4+
import { NAMESPACE, STORE_NAME } from 'wcstripe/data/constants';
5+
import OCToggleContext from 'wcstripe/settings/oc-toggle/context';
6+
7+
const OCToggleContextProvider = ( { children, defaultIsOCEnabled } ) => {
8+
const [ isOCEnabled, setIsOCEnabled ] = useState(
9+
Boolean( defaultIsOCEnabled )
10+
);
11+
const [ status, setStatus ] = useState( 'resolved' );
12+
const { invalidateResolutionForStoreSelector } = useDispatch( STORE_NAME );
13+
14+
const updateSettingLocally = useCallback(
15+
( value ) => {
16+
const sanitizedValue = Boolean( value );
17+
setIsOCEnabled( sanitizedValue );
18+
},
19+
[ setIsOCEnabled ]
20+
);
21+
22+
const updateSetting = useCallback(
23+
( value ) => {
24+
setStatus( 'pending' );
25+
26+
const sanitizedValue = Boolean( value );
27+
28+
return apiFetch( {
29+
path: `${ NAMESPACE }/oc_setting_toggle`,
30+
method: 'POST',
31+
data: { is_oc_enabled: sanitizedValue },
32+
} )
33+
.then( () => {
34+
invalidateResolutionForStoreSelector( 'getSettings' );
35+
setIsOCEnabled( sanitizedValue );
36+
setStatus( 'resolved' );
37+
} )
38+
.catch( () => {
39+
setStatus( 'error' );
40+
} );
41+
},
42+
[ setStatus, setIsOCEnabled, invalidateResolutionForStoreSelector ]
43+
);
44+
45+
const contextValue = useMemo(
46+
() => ( {
47+
isOCEnabled,
48+
setIsOCEnabled: updateSetting,
49+
setIsOCEnabledLocally: updateSettingLocally,
50+
status,
51+
} ),
52+
[ isOCEnabled, updateSetting, updateSettingLocally, status ]
53+
);
54+
55+
return (
56+
<OCToggleContext.Provider value={ contextValue }>
57+
{ children }
58+
</OCToggleContext.Provider>
59+
);
60+
};
61+
62+
export default OCToggleContextProvider;

client/settings/payment-methods/index.js

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
/* global wc_stripe_settings_params */
22
import { __ } from '@wordpress/i18n';
3-
import React, { useContext, useState } from 'react';
3+
import React from 'react';
44
import { ExternalLink } from '@wordpress/components';
55
import SettingsSection from '../settings-section';
66
import PaymentRequestSection from '../payment-request-section';
77
import GeneralSettingsSection from '../general-settings-section';
88
import LoadableSettingsSection from '../loadable-settings-section';
99
import DisplayOrderCustomizationNotice from '../display-order-customization-notice';
10-
import {
11-
BNPL_PROMOTION_BANNER,
12-
NEW_CHECKOUT_EXPERIENCE_BANNER,
13-
} from 'wcstripe/settings/payment-settings/constants';
10+
import { NEW_CHECKOUT_EXPERIENCE_BANNER } from 'wcstripe/settings/payment-settings/constants';
1411
import PromotionalBanner from 'wcstripe/settings/payment-settings/promotional-banner';
15-
import UpeToggleContext from 'wcstripe/settings/upe-toggle/context';
16-
import { useAccount } from 'wcstripe/data/account';
17-
import { useEnabledPaymentMethodIds } from 'wcstripe/data';
18-
import { getPromotionalBannerType } from 'wcstripe/settings/payment-settings/promotional-banner/get-promotional-banner-type';
1912

2013
const PaymentMethodsDescription = () => {
2114
return (
@@ -54,29 +47,22 @@ const PaymentRequestDescription = () => (
5447
</>
5548
);
5649

57-
const PaymentMethodsPanel = ( { onSaveChanges } ) => {
58-
const { data } = useAccount();
59-
const { isUpeEnabled, setIsUpeEnabled } = useContext( UpeToggleContext );
60-
const [ enabledPaymentMethodIds ] = useEnabledPaymentMethodIds();
61-
const promotionalBannerType = getPromotionalBannerType(
62-
data,
63-
isUpeEnabled,
64-
enabledPaymentMethodIds
65-
);
66-
const [ showPromotionalBanner, setShowPromotionalBanner ] = useState(
67-
promotionalBannerType === BNPL_PROMOTION_BANNER
68-
? // eslint-disable-next-line camelcase
69-
wc_stripe_settings_params?.show_bnpl_promotional_banner === '1'
70-
: true
71-
);
72-
50+
const PaymentMethodsPanel = ( {
51+
onSaveChanges,
52+
setShowPromotionalBanner,
53+
showPromotionalBanner,
54+
promotionalBannerType,
55+
setIsOCEnabled,
56+
setIsUpeEnabled,
57+
} ) => {
7358
return (
7459
<>
7560
{ showPromotionalBanner && (
7661
<SettingsSection>
7762
<PromotionalBanner
7863
setShowPromotionalBanner={ setShowPromotionalBanner }
7964
setIsUpeEnabled={ setIsUpeEnabled }
65+
setIsOCEnabled={ setIsOCEnabled }
8066
promotionalBannerType={ promotionalBannerType }
8167
oauthUrl={
8268
// eslint-disable-next-line camelcase

client/settings/payment-settings/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ export const NEW_CHECKOUT_EXPERIENCE_BANNER = 'new-checkout-experience';
33
export const NEW_CHECKOUT_EXPERIENCE_APMS_BANNER =
44
'new-checkout-experience-apms';
55
export const BNPL_PROMOTION_BANNER = 'bnpl_promotion_banner';
6-
export const BNPL_PROMOTION_BANNER_TARGET_VERSION = '9.7';
6+
export const OC_PROMOTION_BANNER = 'oc_promotion_banner';

client/settings/payment-settings/index.js

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* global wc_stripe_settings_params */
22
import { __ } from '@wordpress/i18n';
3-
import { React, useContext, useState } from 'react';
3+
import { React, useState } from 'react';
44
import { ExternalLink } from '@wordpress/components';
55
import SettingsSection from '../settings-section';
66
import PaymentsAndTransactionsSection from '../payments-and-transactions-section';
@@ -12,11 +12,6 @@ import LoadableSettingsSection from 'wcstripe/settings/loadable-settings-section
1212
import './style.scss';
1313
import LoadableAccountSection from 'wcstripe/settings/loadable-account-section';
1414
import PromotionalBanner from 'wcstripe/settings/payment-settings/promotional-banner';
15-
import UpeToggleContext from 'wcstripe/settings/upe-toggle/context';
16-
import { useAccount } from 'wcstripe/data/account';
17-
import { useEnabledPaymentMethodIds } from 'wcstripe/data';
18-
import { getPromotionalBannerType } from 'wcstripe/settings/payment-settings/promotional-banner/get-promotional-banner-type';
19-
import { BNPL_PROMOTION_BANNER } from 'wcstripe/settings/payment-settings/constants';
2015

2116
const GeneralSettingsDescription = () => (
2217
<>
@@ -71,24 +66,16 @@ const PaymentsAndTransactionsDescription = () => (
7166
</>
7267
);
7368

74-
const PaymentSettingsPanel = () => {
69+
const PaymentSettingsPanel = ( {
70+
showPromotionalBanner,
71+
setShowPromotionalBanner,
72+
promotionalBannerType,
73+
setIsOCEnabled,
74+
setIsUpeEnabled,
75+
} ) => {
7576
// @todo - deconstruct modalType and setModalType from useModalType custom hook
7677
const [ modalType, setModalType ] = useState( '' );
7778
const [ keepModalContent, setKeepModalContent ] = useState( false );
78-
const { isUpeEnabled, setIsUpeEnabled } = useContext( UpeToggleContext );
79-
const { data } = useAccount();
80-
const [ enabledPaymentMethodIds ] = useEnabledPaymentMethodIds();
81-
const promotionalBannerType = getPromotionalBannerType(
82-
data,
83-
isUpeEnabled,
84-
enabledPaymentMethodIds
85-
);
86-
const [ showPromotionalBanner, setShowPromotionalBanner ] = useState(
87-
promotionalBannerType === BNPL_PROMOTION_BANNER
88-
? // eslint-disable-next-line camelcase
89-
wc_stripe_settings_params?.show_bnpl_promotional_banner === '1'
90-
: true
91-
);
9279

9380
const handleModalDismiss = () => {
9481
setModalType( '' );
@@ -115,6 +102,7 @@ const PaymentSettingsPanel = () => {
115102
setShowPromotionalBanner
116103
}
117104
setIsUpeEnabled={ setIsUpeEnabled }
105+
setIsOCEnabled={ setIsOCEnabled }
118106
promotionalBannerType={ promotionalBannerType }
119107
oauthUrl={
120108
// eslint-disable-next-line camelcase

0 commit comments

Comments
 (0)