Skip to content

Commit b3731e2

Browse files
wjrosaannemirasol
andauthored
Display a notice if taxes vary by customer's shipping address (#3550)
* Display a notice when taxes may differ due being based on shipping address * Changelog and readme entries * Adding specific unit tests * Doc comment update * Polishing up the main implementation * Changelog and readme entries fix * Update includes/payment-methods/class-wc-stripe-express-checkout-element.php Co-authored-by: Anne Mirasol <[email protected]> * Moving the delay time to a new constant + add doc block + do the same for existing Cash App constant --------- Co-authored-by: Anne Mirasol <[email protected]>
1 parent 6a076e3 commit b3731e2

File tree

9 files changed

+178
-37
lines changed

9 files changed

+178
-37
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
= 8.9.0 - xxxx-xx-xx =
4+
* Fix - Display a notice if taxes vary by customer's billing address when checking out using the Stripe Express Checkout Element.
45
* Tweak - Makes the new Stripe Express Checkout Element enabled by default.
56
* Dev - Add multiple unit tests for the Stripe Express Checkout Element implementation (for both frontend and backend).
67
* Fix - Check if taxes are enabled when applying ECE tax compatibility check.

client/blocks/express-checkout/hooks.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useCallback } from '@wordpress/element';
2+
import { __ } from '@wordpress/i18n';
23
import { useStripe, useElements } from '@stripe/react-stripe-js';
34
import {
45
onAbortPaymentHandler,
@@ -8,6 +9,8 @@ import {
89
onConfirmHandler,
910
} from 'wcstripe/express-checkout/event-handler';
1011
import {
12+
displayExpressCheckoutNotice,
13+
expressCheckoutNoticeDelay,
1114
getExpressCheckoutButtonStyleSettings,
1215
getExpressCheckoutData,
1316
normalizeLineItems,
@@ -43,7 +46,7 @@ export const useExpressCheckout = ( {
4346
};
4447

4548
const onButtonClick = useCallback(
46-
( event ) => {
49+
async ( event ) => {
4750
const getShippingRates = () => {
4851
// shippingData.shippingRates[ 0 ].shipping_rates will be non-empty
4952
// only when the express checkout element's default shipping address
@@ -82,6 +85,20 @@ export const useExpressCheckout = ( {
8285

8386
// Click event from WC Blocks.
8487
onClick();
88+
89+
if ( getExpressCheckoutData( 'taxes_based_on_billing' ) ) {
90+
displayExpressCheckoutNotice(
91+
__(
92+
'Final taxes charged can differ based on your actual billing address when using Express Checkout buttons (Link, Google Pay or Apple Pay).',
93+
'woocommerce-gateway-stripe'
94+
),
95+
'info',
96+
[ 'ece-taxes-info' ]
97+
);
98+
// Wait for the notice to be displayed before proceeding.
99+
await expressCheckoutNoticeDelay();
100+
}
101+
85102
// Global click event handler to ECE.
86103
onClickHandler( event );
87104
event.resolve( options );

client/data/constants.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
11
export const NAMESPACE = '/wc/v3/wc_stripe';
22
export const STORE_NAME = 'wc/stripe';
3+
4+
/**
5+
* The amount threshold for displaying the notice.
6+
*
7+
* @type {number} The threshold amount.
8+
*/
9+
export const CASH_APP_NOTICE_AMOUNT_THRESHOLD = 200000;
10+
11+
/**
12+
* Wait time in ms for a notice to be displayed in ECE before proceeding with the checkout process.
13+
*
14+
* Reasons for this value:
15+
* - We cannot display an alert message because it blocks the default ECE process
16+
* - The delay cannot be higher than 1s due to Stripe JS limitations (it times out after 1s)
17+
*
18+
* @type {number} The delay in milliseconds.
19+
*/
20+
export const EXPRESS_CHECKOUT_NOTICE_DELAY = 700;

client/entrypoints/express-checkout/index.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { debounce } from 'lodash';
44
import jQuery from 'jquery';
55
import WCStripeAPI from '../../api';
66
import {
7+
displayExpressCheckoutNotice,
78
displayLoginConfirmation,
9+
expressCheckoutNoticeDelay,
810
getExpressCheckoutButtonAppearance,
911
getExpressCheckoutButtonStyleSettings,
1012
getExpressCheckoutData,
@@ -147,13 +149,26 @@ jQuery( function ( $ ) {
147149
);
148150
} );
149151

150-
eceButton.on( 'click', function ( event ) {
152+
eceButton.on( 'click', async function ( event ) {
151153
// If login is required for checkout, display redirect confirmation dialog.
152154
if ( getExpressCheckoutData( 'login_confirmation' ) ) {
153155
displayLoginConfirmation( event.expressPaymentType );
154156
return;
155157
}
156158

159+
if ( getExpressCheckoutData( 'taxes_based_on_billing' ) ) {
160+
displayExpressCheckoutNotice(
161+
__(
162+
'Final taxes charged can differ based on your actual billing address when using Express Checkout buttons (Link, Google Pay or Apple Pay).',
163+
'woocommerce-gateway-stripe'
164+
),
165+
'info',
166+
[ 'ece-taxes-info' ]
167+
);
168+
// Wait for the notice to be displayed before proceeding.
169+
await expressCheckoutNoticeDelay();
170+
}
171+
157172
if ( getExpressCheckoutData( 'is_product_page' ) ) {
158173
const addToCartButton = $( '.single_add_to_cart_button' );
159174

@@ -456,24 +471,7 @@ jQuery( function ( $ ) {
456471
payment.paymentFailed( { reason: 'fail' } );
457472
onAbortPaymentHandler( payment, message );
458473

459-
$( '.woocommerce-error' ).remove();
460-
461-
const $container = $( '.woocommerce-notices-wrapper' ).first();
462-
463-
if ( $container.length ) {
464-
$container.append(
465-
$( '<div class="woocommerce-error" />' ).text( message )
466-
);
467-
468-
$( 'html, body' ).animate(
469-
{
470-
scrollTop: $container
471-
.find( '.woocommerce-error' )
472-
.offset().top,
473-
},
474-
600
475-
);
476-
}
474+
displayExpressCheckoutNotice( message, 'error' );
477475
},
478476

479477
attachProductPageEventListeners: ( elements ) => {

client/express-checkout/utils/__tests__/index.test.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/**
22
* Internal dependencies
33
*/
4-
import { getErrorMessageFromNotice, getExpressCheckoutData } from '..';
4+
import { screen, render } from '@testing-library/react';
5+
import {
6+
displayExpressCheckoutNotice,
7+
getErrorMessageFromNotice,
8+
getExpressCheckoutData,
9+
} from '..';
510

611
describe( 'Express checkout utils', () => {
712
test( 'getExpressCheckoutData returns null for missing option', () => {
@@ -37,4 +42,45 @@ describe( 'Express checkout utils', () => {
3742
'Error: Payment failed.alert("hello")'
3843
);
3944
} );
45+
46+
describe( 'displayExpressCheckoutNotice', () => {
47+
afterEach( () => {
48+
document.getElementsByTagName( 'body' )[ 0 ].innerHTML = '';
49+
} );
50+
51+
const additionalClasses = [ 'class-2', 'class-3' ];
52+
const createWrapper = () => {
53+
const wrapper = document.createElement( 'div' );
54+
wrapper.classList.add( 'woocommerce-notices-wrapper' );
55+
document.body.appendChild( wrapper );
56+
};
57+
58+
test( 'with info', async () => {
59+
function App() {
60+
createWrapper();
61+
displayExpressCheckoutNotice(
62+
'Test message',
63+
'info',
64+
additionalClasses
65+
);
66+
return <div />;
67+
}
68+
render( <App /> );
69+
expect( screen.queryByRole( 'note' ) ).toBeInTheDocument();
70+
} );
71+
72+
test( 'with error', () => {
73+
function App() {
74+
createWrapper();
75+
displayExpressCheckoutNotice(
76+
'Test message',
77+
'error',
78+
additionalClasses
79+
);
80+
return <div />;
81+
}
82+
render( <App /> );
83+
expect( screen.queryByRole( 'note' ) ).toBeInTheDocument();
84+
} );
85+
} );
4086
} );

client/express-checkout/utils/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* global wc_stripe_express_checkout_params */
2+
import jQuery from 'jquery';
23
import { isLinkEnabled, getPaymentMethodTypes } from 'wcstripe/stripe-utils';
34
import { getBlocksConfiguration } from 'wcstripe/blocks/utils';
5+
import { EXPRESS_CHECKOUT_NOTICE_DELAY } from 'wcstripe/data/constants';
46

57
export * from './normalize';
68

@@ -306,3 +308,62 @@ export const getPaymentMethodTypesForExpressMethod = ( paymentMethodType ) => {
306308

307309
return paymentMethodTypes;
308310
};
311+
312+
/**
313+
* Display a notice on the checkout page (for Express Checkout Element).
314+
*
315+
* @param {string} message The message to display.
316+
* @param {string} type The type of notice.
317+
* @param {Array} additionalClasses Additional classes to add to the notice.
318+
*/
319+
export const displayExpressCheckoutNotice = (
320+
message,
321+
type,
322+
additionalClasses
323+
) => {
324+
const isBlockCheckout = getExpressCheckoutData( 'has_block' );
325+
const mainNoticeClass = `woocommerce-${ type }`;
326+
let classNames = [ mainNoticeClass ];
327+
if ( additionalClasses ) {
328+
classNames = classNames.concat( additionalClasses );
329+
}
330+
331+
// Remove any existing notices.
332+
jQuery( '.' + classNames.join( '.' ) ).remove();
333+
334+
const containerClass = isBlockCheckout
335+
? 'wc-block-components-main'
336+
: 'woocommerce-notices-wrapper';
337+
const $container = jQuery( '.' + containerClass ).first();
338+
339+
if ( $container.length ) {
340+
const note = jQuery(
341+
`<div class="${ classNames.join( ' ' ) }" role="note" />`
342+
).text( message );
343+
if ( isBlockCheckout ) {
344+
$container.prepend( note );
345+
} else {
346+
$container.append( note );
347+
}
348+
349+
// Scroll to notices.
350+
jQuery( 'html, body' ).animate(
351+
{
352+
scrollTop: $container.find( `.${ mainNoticeClass }` ).offset()
353+
.top,
354+
},
355+
600
356+
);
357+
}
358+
};
359+
360+
/**
361+
* Delay for a short period of time before proceeding with the checkout process.
362+
*
363+
* @return {Promise<void>} A promise that resolves after the delay.
364+
*/
365+
export const expressCheckoutNoticeDelay = async () => {
366+
await new Promise( ( resolve ) =>
367+
setTimeout( resolve, EXPRESS_CHECKOUT_NOTICE_DELAY )
368+
);
369+
};

client/stripe-utils/cash-app-limit-notice-handler.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { __ } from '@wordpress/i18n';
22
import { callWhenElementIsAvailable } from 'wcstripe/blocks/upe/call-when-element-is-available';
3-
4-
/** The amount threshold for displaying the notice. */
5-
export const CASH_APP_NOTICE_AMOUNT_THRESHOLD = 200000;
3+
import { CASH_APP_NOTICE_AMOUNT_THRESHOLD } from 'wcstripe/data/constants';
64

75
/** The class name for the limit notice element. */
86
const LIMIT_NOTICE_CLASSNAME = 'wc-block-checkout__payment-method-limit-notice';

includes/payment-methods/class-wc-stripe-express-checkout-element.php

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,15 @@ public function get_login_redirect_url( $redirect ) {
169169
*/
170170
public function javascript_params() {
171171
return [
172-
'ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
173-
'stripe' => [
172+
'ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
173+
'stripe' => [
174174
'publishable_key' => 'yes' === $this->stripe_settings['testmode'] ? $this->stripe_settings['test_publishable_key'] : $this->stripe_settings['publishable_key'],
175175
'allow_prepaid_card' => apply_filters( 'wc_stripe_allow_prepaid_card', true ) ? 'yes' : 'no',
176176
'locale' => WC_Stripe_Helper::convert_wc_locale_to_stripe_locale( get_locale() ),
177177
'is_link_enabled' => WC_Stripe_UPE_Payment_Method_Link::is_link_enabled(),
178178
'is_express_checkout_enabled' => $this->express_checkout_helper->is_express_checkout_enabled(),
179179
],
180-
'nonce' => [
180+
'nonce' => [
181181
'payment' => wp_create_nonce( 'wc-stripe-express-checkout' ),
182182
'shipping' => wp_create_nonce( 'wc-stripe-express-checkout-shipping' ),
183183
'get_cart_details' => wp_create_nonce( 'wc-stripe-get-cart-details' ),
@@ -189,20 +189,21 @@ public function javascript_params() {
189189
'clear_cart' => wp_create_nonce( 'wc-stripe-clear-cart' ),
190190
'pay_for_order' => wp_create_nonce( 'wc-stripe-pay-for-order' ),
191191
],
192-
'i18n' => [
192+
'i18n' => [
193193
'no_prepaid_card' => __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' ),
194194
/* translators: Do not translate the [option] placeholder */
195195
'unknown_shipping' => __( 'Unknown shipping option "[option]".', 'woocommerce-gateway-stripe' ),
196196
],
197-
'checkout' => $this->express_checkout_helper->get_checkout_data(),
198-
'button' => $this->express_checkout_helper->get_button_settings(),
199-
'is_pay_for_order' => $this->express_checkout_helper->is_pay_for_order_page(),
200-
'has_block' => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ),
201-
'login_confirmation' => $this->express_checkout_helper->get_login_confirmation_settings(),
202-
'is_product_page' => $this->express_checkout_helper->is_product(),
203-
'is_checkout_page' => $this->express_checkout_helper->is_checkout(),
204-
'product' => $this->express_checkout_helper->get_product_data(),
205-
'is_cart_page' => is_cart(),
197+
'checkout' => $this->express_checkout_helper->get_checkout_data(),
198+
'button' => $this->express_checkout_helper->get_button_settings(),
199+
'is_pay_for_order' => $this->express_checkout_helper->is_pay_for_order_page(),
200+
'has_block' => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ),
201+
'login_confirmation' => $this->express_checkout_helper->get_login_confirmation_settings(),
202+
'is_product_page' => $this->express_checkout_helper->is_product(),
203+
'is_checkout_page' => $this->express_checkout_helper->is_checkout(),
204+
'product' => $this->express_checkout_helper->get_product_data(),
205+
'is_cart_page' => is_cart(),
206+
'taxes_based_on_billing' => wc_tax_enabled() && get_option( 'woocommerce_tax_based_on' ) === 'billing',
206207
];
207208
}
208209

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
111111
== Changelog ==
112112

113113
= 8.9.0 - xxxx-xx-xx =
114+
* Fix - Display a notice if taxes vary by customer's billing address when checking out using the Stripe Express Checkout Element.
114115
* Tweak - Makes the new Stripe Express Checkout Element enabled by default.
115116
* Dev - Add multiple unit tests for the Stripe Express Checkout Element implementation (for both frontend and backend).
116117
* Fix - Check if taxes are enabled when applying ECE tax compatibility check.

0 commit comments

Comments
 (0)