Skip to content

Commit bddc0fc

Browse files
wjrosadiegocurbeloMayisha
authored
Layout setting for the Optimized Checkout (#4604)
* OC layout setting * Working version * Minor fixes * Changelog and readme entries * Fix typo * Fix tests * Unit test * Unit test * Unit test * Unit test * Fix tests * Style update * Style update * Update includes/admin/stripe-settings.php Co-authored-by: Diego Curbelo <[email protected]> * Update includes/payment-methods/class-wc-stripe-upe-payment-gateway.php Co-authored-by: Mayisha <[email protected]> * Applying internationalization + fix constant visibility --------- Co-authored-by: Diego Curbelo <[email protected]> Co-authored-by: Mayisha <[email protected]>
1 parent 8692d21 commit bddc0fc

File tree

14 files changed

+188
-25
lines changed

14 files changed

+188
-25
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.9.0 - xxxx-xx-xx =
4+
* Add - Setting to allow merchants to control the layout of the Optimized Checkout payment element on the checkout page
45
* Fix - Removes the credit card payment method requirement for the Optimized Checkout feature
56
* Fix - Payment method test instructions not showing up for the Optimized Checkout payment element
67
* Add - Includes a new notice to highlight the Optimized Checkout feature above the payment methods list in the Stripe settings page

client/blocks/upe/upe-deferred-intent-creation/payment-processor.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from 'wcstripe/stripe-utils/cash-app-limit-notice-handler';
2525
import { isLinkEnabled, validateBlikCode } from 'wcstripe/stripe-utils';
2626
import {
27+
OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT,
2728
PAYMENT_METHOD_BLIK,
2829
PAYMENT_METHOD_CASHAPP,
2930
} from 'wcstripe/stripe-utils/constants';
@@ -87,7 +88,9 @@ const getStripeElementOptions = () => {
8788
options = {
8889
...options,
8990
layout: {
90-
type: 'accordion',
91+
type:
92+
getBlocksConfiguration()?.OCLayout ||
93+
OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT,
9194
radios: false,
9295
},
9396
};

client/classic/upe/payment-processing.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '../../stripe-utils';
1919
import { getFontRulesFromPage } from '../../styles/upe';
2020
import {
21+
OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT,
2122
PAYMENT_INTENT_STATUS_REQUIRES_ACTION,
2223
PAYMENT_METHOD_BLIK,
2324
PAYMENT_METHOD_BOLETO,
@@ -197,7 +198,9 @@ async function createStripePaymentElement( api, paymentMethodType ) {
197198
paymentElementOptions = {
198199
...paymentElementOptions,
199200
layout: {
200-
type: 'accordion',
201+
type:
202+
getStripeServerData()?.OCLayout ||
203+
OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT,
201204
radios: false,
202205
},
203206
};

client/data/settings/hooks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export const useIsShortAccountStatementEnabled = makeSettingsHook(
166166
export const useDebugLog = makeSettingsHook( 'is_debug_log_enabled' );
167167
export const useIsUpeEnabled = makeSettingsHook( 'is_upe_enabled' );
168168
export const useIsOCEnabled = makeSettingsHook( 'is_oc_enabled' );
169+
export const useOCLayout = makeSettingsHook( 'oc_layout' );
169170
export const useIsPMCEnabled = makeReadOnlySettingsHook(
170171
'is_pmc_enabled',
171172
false

client/settings/advanced-settings-section/__tests__/index.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,30 @@ import {
88
useGetSavingError,
99
useSettings,
1010
useIsOCEnabled,
11+
useOCLayout,
1112
} from 'wcstripe/data';
1213

1314
jest.mock( 'wcstripe/data', () => ( {
1415
useDebugLog: jest.fn(),
1516
useIsUpeEnabled: jest.fn(),
1617
useIsOCEnabled: jest.fn(),
18+
useOCLayout: jest.fn(),
1719
useGetSavingError: jest.fn(),
1820
useSettings: jest.fn(),
1921
} ) );
2022

23+
jest.mock( '@woocommerce/navigation', () => ( {
24+
getQuery: jest.fn().mockReturnValue( {} ),
25+
} ) );
26+
2127
describe( 'AdvancedSettings', () => {
2228
beforeEach( () => {
2329
global.wc_stripe_settings_params = { is_oc_available: false };
2430

2531
useDebugLog.mockReturnValue( [ true, jest.fn() ] );
2632
useIsUpeEnabled.mockReturnValue( [ true, jest.fn() ] );
2733
useIsOCEnabled.mockReturnValue( [ false, jest.fn() ] );
34+
useOCLayout.mockReturnValue( [ 'accordion', jest.fn() ] );
2835
useGetSavingError.mockReturnValue( null );
2936

3037
// Set `isLoading` to false so `LoadableSettingsSection` can render.
@@ -76,4 +83,19 @@ describe( 'AdvancedSettings', () => {
7683
)
7784
).toBeInTheDocument();
7885
} );
86+
87+
it( 'should display the Optimized Checkout layout setting if the Optimized Checkout feature is enabled', () => {
88+
global.wc_stripe_settings_params = { is_oc_available: true };
89+
90+
useIsOCEnabled.mockReturnValue( [ true, jest.fn() ] );
91+
92+
render( <AdvancedSettings /> );
93+
94+
expect(
95+
screen.queryByText(
96+
'Choose between a vertical accordion layout and a horizontal tabs layout to display payment methods.'
97+
)
98+
).toBeInTheDocument();
99+
expect( screen.queryByText( 'Layout' ) ).toBeInTheDocument();
100+
} );
79101
} );
Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
import { render, screen } from '@testing-library/react';
2-
import React, { useContext } from 'react';
2+
import React from 'react';
33
import userEvent from '@testing-library/user-event';
44
import OptimizedCheckoutFeature from 'wcstripe/settings/advanced-settings-section/optimized-checkout-feature';
5-
import UpeToggleContext from 'wcstripe/settings/upe-toggle/context';
5+
import { useIsOCEnabled, useIsUpeEnabled, useOCLayout } from 'wcstripe/data';
66

77
jest.useFakeTimers();
88

9+
jest.mock( 'wcstripe/data', () => ( {
10+
useIsUpeEnabled: jest.fn(),
11+
useIsOCEnabled: jest.fn(),
12+
useOCLayout: jest.fn(),
13+
} ) );
14+
15+
jest.mock( '@woocommerce/navigation', () => ( {
16+
getQuery: jest.fn().mockReturnValue( {} ),
17+
} ) );
18+
919
describe( 'Optimized Checkout Element feature setting', () => {
10-
const setIsOCEnabled = jest.fn().mockImplementation();
20+
beforeEach( () => {
21+
useIsUpeEnabled.mockReturnValue( [ true, jest.fn() ] );
22+
useIsOCEnabled.mockReturnValue( [ false, jest.fn() ] );
23+
useOCLayout.mockReturnValue( [ 'accordion', jest.fn() ] );
24+
} );
1125

1226
it( 'should render', () => {
13-
render(
14-
<OptimizedCheckoutFeature setIsOCEnabled={ setIsOCEnabled } />
15-
);
27+
render( <OptimizedCheckoutFeature /> );
1628

1729
expect(
1830
screen.queryByText(
@@ -21,22 +33,26 @@ describe( 'Optimized Checkout Element feature setting', () => {
2133
).toBeInTheDocument();
2234
} );
2335

24-
it( 'should be disabled when UPE is disabled', () => {
25-
const UpdateUpeDisabledFlagMock = () => {
26-
const { setIsUpeEnabled } = useContext( UpeToggleContext );
27-
setTimeout( () => {
28-
setIsUpeEnabled( false );
29-
}, 1000 );
30-
return null;
31-
};
32-
33-
render(
34-
<div>
35-
<UpdateUpeDisabledFlagMock />
36-
<OptimizedCheckoutFeature setIsOCEnabled={ setIsOCEnabled } />
37-
</div>
36+
it( 'should disable the OC setting on click', () => {
37+
const setIsOCEnabledMock = jest.fn();
38+
useIsOCEnabled.mockReturnValue( [ true, setIsOCEnabledMock ] );
39+
40+
render( <OptimizedCheckoutFeature /> );
41+
42+
const OCCheckbox = screen.getByTestId(
43+
'optimized-checkout-element-checkbox'
3844
);
3945

46+
userEvent.click( OCCheckbox );
47+
48+
expect( setIsOCEnabledMock ).toHaveBeenCalled();
49+
} );
50+
51+
it( 'should be disabled when UPE is disabled', () => {
52+
useIsUpeEnabled.mockReturnValue( [ false, jest.fn() ] );
53+
54+
render( <OptimizedCheckoutFeature /> );
55+
4056
const checkbox = screen.getByTestId(
4157
'optimized-checkout-element-checkbox'
4258
);
@@ -48,4 +64,32 @@ describe( 'Optimized Checkout Element feature setting', () => {
4864
expect( checkbox ).toBeDisabled();
4965
expect( checkbox ).not.toBeChecked();
5066
} );
67+
68+
it( 'layout setting should be available when OC is enabled', () => {
69+
useIsOCEnabled.mockReturnValue( [ true, jest.fn() ] );
70+
71+
render( <OptimizedCheckoutFeature /> );
72+
73+
const label = screen.getByText( 'Layout' );
74+
expect( label ).toBeInTheDocument();
75+
76+
const help = screen.getByText(
77+
'Choose between a vertical accordion layout and a horizontal tabs layout to display payment methods.'
78+
);
79+
expect( help ).toBeInTheDocument();
80+
} );
81+
82+
it( 'triggers the hook when changing the layout setting', () => {
83+
useIsOCEnabled.mockReturnValue( [ true, jest.fn() ] );
84+
85+
const setLayoutMock = jest.fn();
86+
useOCLayout.mockReturnValue( [ 'accordion', setLayoutMock ] );
87+
88+
render( <OptimizedCheckoutFeature /> );
89+
90+
expect( setLayoutMock ).not.toHaveBeenCalled();
91+
92+
userEvent.click( screen.getByLabelText( 'Tabs' ) );
93+
expect( setLayoutMock ).toHaveBeenCalledWith( 'tabs' );
94+
} );
5195
} );

client/settings/advanced-settings-section/optimized-checkout-feature.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
import { __ } from '@wordpress/i18n';
22
import { createInterpolateElement } from '@wordpress/element';
3-
import { CheckboxControl, ExternalLink } from '@wordpress/components';
3+
import {
4+
CheckboxControl,
5+
ExternalLink,
6+
RadioControl,
7+
} from '@wordpress/components';
48
import React, { useEffect, useRef } from 'react';
59
import { getQuery } from '@woocommerce/navigation';
6-
import { useIsUpeEnabled } from '../../data';
10+
import styled from '@emotion/styled';
11+
import { useIsOCEnabled, useIsUpeEnabled, useOCLayout } from '../../data';
712

8-
const OptimizedCheckoutFeature = ( { isOCEnabled, setIsOCEnabled } ) => {
13+
const StyledRadioControl = styled( RadioControl )`
14+
legend {
15+
margin-bottom: 12px;
16+
}
17+
.components-radio-control__option {
18+
padding-top: 6px;
19+
margin-bottom: 0;
20+
}
21+
`;
22+
23+
const OptimizedCheckoutFeature = () => {
24+
const [ isOCEnabled, setIsOCEnabled ] = useIsOCEnabled();
25+
const [ OCLayout, setOCLayout ] = useOCLayout();
926
const [ isUpeEnabled ] = useIsUpeEnabled();
1027
const headingRef = useRef( null );
1128

@@ -29,6 +46,10 @@ const OptimizedCheckoutFeature = ( { isOCEnabled, setIsOCEnabled } ) => {
2946
}
3047
}, [ headingRef ] );
3148

49+
const handleLayoutChange = ( value ) => {
50+
setOCLayout( value );
51+
};
52+
3253
return (
3354
<>
3455
<h4 ref={ headingRef }>
@@ -58,6 +79,30 @@ const OptimizedCheckoutFeature = ( { isOCEnabled, setIsOCEnabled } ) => {
5879
onChange={ setIsOCEnabled }
5980
disabled={ ! isUpeEnabled }
6081
/>
82+
{ isOCEnabled && (
83+
<StyledRadioControl
84+
label={ __( 'Layout', 'woocommerce-gateway-stripe' ) }
85+
help={ __(
86+
'Choose between a vertical accordion layout and a horizontal tabs layout to display payment methods.',
87+
'woocommerce-gateway-stripe'
88+
) }
89+
selected={ OCLayout }
90+
options={ [
91+
{
92+
label: __(
93+
'Accordion',
94+
'woocommerce-gateway-stripe'
95+
),
96+
value: 'accordion',
97+
},
98+
{
99+
label: __( 'Tabs', 'woocommerce-gateway-stripe' ),
100+
value: 'tabs',
101+
},
102+
] }
103+
onChange={ handleLayoutChange }
104+
/>
105+
) }
61106
</>
62107
);
63108
};

client/stripe-utils/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,10 @@ export const PAYMENT_METHOD_UNAVAILABLE_REASONS = {
173173
UNSUPPORTED_CURRENCY: 'unsupported_currency',
174174
OFFICIAL_PLUGIN_CONFLICT: 'official_plugin_conflict',
175175
};
176+
177+
/**
178+
* Default layout for the optimized checkout
179+
*
180+
* @type {string}
181+
*/
182+
export const OPTIMIZED_CHECKOUT_DEFAULT_LAYOUT = 'accordion';

includes/admin/class-wc-rest-stripe-settings-controller.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ public function register_routes() {
8080
'type' => 'boolean',
8181
'validate_callback' => 'rest_validate_request_arg',
8282
],
83+
'oc_layout' => [
84+
'description' => __( 'The Optimized Checkout layout (accordion or tabs).', 'woocommerce-gateway-stripe' ),
85+
'type' => 'string',
86+
'enum' => array_keys( $form_fields['optimized_checkout_layout']['options'] ?? [] ),
87+
'validate_callback' => 'rest_validate_request_arg',
88+
],
8389
'amazon_pay_button_size' => [
8490
'description' => __( 'Express checkout button sizes.', 'woocommerce-gateway-stripe' ),
8591
'type' => 'string',
@@ -242,6 +248,7 @@ public function get_settings() {
242248
'is_debug_log_enabled' => 'yes' === $this->gateway->get_option( 'logging' ),
243249
'is_upe_enabled' => $is_upe_enabled,
244250
'is_oc_enabled' => 'yes' === $this->gateway->get_option( 'optimized_checkout_element' ),
251+
'oc_layout' => $this->gateway->get_validated_option( 'optimized_checkout_layout' ),
245252
'is_pmc_enabled' => 'yes' === $this->gateway->get_option( 'pmc_enabled' ),
246253
]
247254
);
@@ -535,6 +542,7 @@ private function update_payment_request_settings( WP_REST_Request $request ) {
535542
private function update_oc_settings( WP_REST_Request $request ) {
536543
$attributes = [
537544
'is_oc_enabled' => 'optimized_checkout_element',
545+
'oc_layout' => 'optimized_checkout_layout',
538546
];
539547
foreach ( $attributes as $request_key => $attribute ) {
540548
$value = $request->get_param( $request_key );

includes/admin/class-wc-stripe-settings-controller.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ public function admin_scripts( $hook_suffix ) {
280280
'is_amazon_pay_available' => WC_Stripe_Feature_Flags::is_amazon_pay_available(),
281281
'is_oc_available' => WC_Stripe_Feature_Flags::is_oc_available() && WC_Stripe_Feature_Flags::is_upe_checkout_enabled(),
282282
'is_oc_enabled' => $is_oc_enabled,
283+
'oc_layout' => $this->get_gateway()->get_validated_option( 'optimized_checkout_layout' ),
283284
'oauth_nonce' => wp_create_nonce( 'wc_stripe_get_oauth_urls' ),
284285
'is_sepa_tokens_enabled' => 'yes' === $this->gateway->get_option( 'sepa_tokens_for_other_methods', 'no' ),
285286
'has_affirm_gateway_plugin' => WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_AFFIRM ),

0 commit comments

Comments
 (0)