diff --git a/changelog.txt b/changelog.txt index 67d2073a0a..fd975b1abe 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,7 @@ * Fix - Ensure state and postal code are optional in express checkout for Gulf countries (UAE, Bahrain, Kuwait, Oman, Qatar) * Dev - Removes the `_wcstripe_feature_upe` feature flag and the related method from the `WC_Stripe_Feature_Flags` class * Dev - Fixes some incorrect subscriptions support implementations for payment methods +* Add - New promotional banner to highlight the Stripe Tax extension for OCS-enabled merchants * Fix - Ensure correct express checkout prices in block cart and checkout with non-default decimal configuration * Fix - Disable express checkout when Amazon Pay is disabled and the only method * Fix - Don't allow WP-Cron jobs to detach payment methods on staging sites diff --git a/client/settings/payment-settings/constants.js b/client/settings/payment-settings/constants.js index 09f54630a0..efff5368b3 100644 --- a/client/settings/payment-settings/constants.js +++ b/client/settings/payment-settings/constants.js @@ -4,3 +4,4 @@ export const NEW_CHECKOUT_EXPERIENCE_APMS_BANNER = 'new-checkout-experience-apms'; export const BNPL_PROMOTION_BANNER = 'bnpl_promotion_banner'; export const OC_PROMOTION_BANNER = 'oc_promotion_banner'; +export const STRIPE_TAX_BANNER = 'stripe-tax-banner'; diff --git a/client/settings/payment-settings/promotional-banner/__tests__/get-promotional-banner-type.test.js b/client/settings/payment-settings/promotional-banner/__tests__/get-promotional-banner-type.test.js index 47ab2e9207..bd8f80967b 100644 --- a/client/settings/payment-settings/promotional-banner/__tests__/get-promotional-banner-type.test.js +++ b/client/settings/payment-settings/promotional-banner/__tests__/get-promotional-banner-type.test.js @@ -5,6 +5,7 @@ import { NEW_CHECKOUT_EXPERIENCE_BANNER, OC_PROMOTION_BANNER, RECONNECT_BANNER, + STRIPE_TAX_BANNER, } from 'wcstripe/settings/payment-settings/constants'; import { PAYMENT_METHOD_CARD, @@ -33,6 +34,30 @@ describe( 'getPromotionalBannerType', () => { ) ).toBe( RECONNECT_BANNER ); } ); + it( 'Stripe Tax banner', () => { + global.wc_stripe_settings_params = { + is_oc_available: true, + }; + + const accountData = { + testmode: false, + oauth_connections: { + live: { connected: true }, + }, + }; + const isUpeEnabled = true; + const isOCEnabled = true; + const enabledPaymentMethodIds = [ PAYMENT_METHOD_CARD ]; + + expect( + getPromotionalBannerType( + accountData, + isUpeEnabled, + isOCEnabled, + enabledPaymentMethodIds + ) + ).toBe( STRIPE_TAX_BANNER ); + } ); it( 'OC promotion banner', () => { global.wc_stripe_settings_params = { is_oc_available: true, diff --git a/client/settings/payment-settings/promotional-banner/__tests__/oc-promotion-banner.test.js b/client/settings/payment-settings/promotional-banner/__tests__/oc-promotion-banner.test.js index c02df22d16..27626aeed5 100644 --- a/client/settings/payment-settings/promotional-banner/__tests__/oc-promotion-banner.test.js +++ b/client/settings/payment-settings/promotional-banner/__tests__/oc-promotion-banner.test.js @@ -54,12 +54,6 @@ describe( 'OC promotional banner', () => { } ); it( 'should make an API call to dismiss the banner on button click', async () => { - // Keep the original function. - const reload = window.location.reload; - Object.defineProperty( window, 'location', { - value: { reload: jest.fn() }, - } ); - const dismissNoticeMock = jest.fn( () => Promise.resolve( { data: {} } ) ); @@ -77,11 +71,6 @@ describe( 'OC promotional banner', () => { await userEvent.click( dismissButton ); } ); expect( dismissNoticeMock ).toHaveBeenCalled(); - - // Set the original function back to keep further tests working as expected. - Object.defineProperty( window, 'location', { - value: { reload }, - } ); } ); it( 'should attempt to enable OC when clicking the "Activate now" button', async () => { diff --git a/client/settings/payment-settings/promotional-banner/__tests__/stripe-tax-banner.test.js b/client/settings/payment-settings/promotional-banner/__tests__/stripe-tax-banner.test.js new file mode 100644 index 0000000000..c3d2e1396f --- /dev/null +++ b/client/settings/payment-settings/promotional-banner/__tests__/stripe-tax-banner.test.js @@ -0,0 +1,95 @@ +import { act, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { StripeTaxBanner } from '../stripe-tax-banner'; +import apiFetch from '@wordpress/api-fetch'; +import { recordEvent } from 'wcstripe/tracking'; + +jest.mock( '@wordpress/api-fetch' ); + +jest.mock( 'wcstripe/tracking', () => ( { + recordEvent: jest.fn(), +} ) ); + +describe( 'Stripe Tax banner', () => { + const setShowPromotionalBanner = jest.fn(); + + beforeEach( () => { + apiFetch.mockImplementation( + jest.fn( () => Promise.resolve( { data: {} } ) ) + ); + } ); + + afterEach( () => { + jest.clearAllMocks(); + } ); + + it( 'should render the Stripe Tax banner', () => { + const { getByText } = render( + + ); + expect( + getByText( 'Automate tax compliance with Stripe Tax' ) + ).toBeInTheDocument(); + expect( + getByText( + /Automatically calculate and collect sales tax, value-added tax \(VAT\), and goods and services tax \(GST\) wherever you sell./ + ) + ).toBeInTheDocument(); + } ); + + it( 'should make an API call to dismiss the banner on button click', async () => { + const dismissNoticeMock = jest.fn( () => + Promise.resolve( { data: {} } ) + ); + apiFetch.mockImplementation( dismissNoticeMock ); + + const { getByText } = render( + + ); + const dismissButton = getByText( 'Dismiss' ); + + await act( async () => { + await userEvent.click( dismissButton ); + } ); + expect( dismissNoticeMock ).toHaveBeenCalled(); + } ); + + it( 'should open the main page when clicking the "Get Stripe Tax" button', async () => { + // Keep the original function at hand. + const open = window.open; + + Object.defineProperty( window, 'open', { + value: jest.fn(), + } ); + + const { getByText } = render( + + ); + const activateButton = getByText( 'Get Stripe Tax' ); + + await act( async () => { + await userEvent.click( activateButton ); + } ); + + expect( recordEvent ).toHaveBeenCalledWith( + 'wcstripe_stripe_tax_banner_button_click', + {} + ); + + expect( window.open ).toHaveBeenCalledWith( + 'https://woocommerce.com/products/stripe-tax/', + '_blank' + ); + + // Set the original function back to keep further tests working as expected. + Object.defineProperty( window, 'open', { + value: open, + } ); + } ); +} ); diff --git a/client/settings/payment-settings/promotional-banner/get-promotional-banner-type.js b/client/settings/payment-settings/promotional-banner/get-promotional-banner-type.js index 57337699d5..2a20542564 100644 --- a/client/settings/payment-settings/promotional-banner/get-promotional-banner-type.js +++ b/client/settings/payment-settings/promotional-banner/get-promotional-banner-type.js @@ -5,6 +5,7 @@ import { NEW_CHECKOUT_EXPERIENCE_BANNER, OC_PROMOTION_BANNER, RECONNECT_BANNER, + STRIPE_TAX_BANNER, } from 'wcstripe/settings/payment-settings/constants'; import { BNPL_METHODS, @@ -39,6 +40,12 @@ export const getPromotionalBannerType = ( if ( oauthConnected === false ) { return RECONNECT_BANNER; + } else if ( + // eslint-disable-next-line camelcase + wc_stripe_settings_params?.is_oc_available && + isOCEnabled + ) { + return STRIPE_TAX_BANNER; } else if ( // eslint-disable-next-line camelcase wc_stripe_settings_params?.is_oc_available && diff --git a/client/settings/payment-settings/promotional-banner/illustrations/stripe-tax.svg b/client/settings/payment-settings/promotional-banner/illustrations/stripe-tax.svg new file mode 100644 index 0000000000..e75394ea02 --- /dev/null +++ b/client/settings/payment-settings/promotional-banner/illustrations/stripe-tax.svg @@ -0,0 +1,6 @@ + + + Tax + + + \ No newline at end of file diff --git a/client/settings/payment-settings/promotional-banner/index.js b/client/settings/payment-settings/promotional-banner/index.js index b1f24582ae..d08cd52488 100644 --- a/client/settings/payment-settings/promotional-banner/index.js +++ b/client/settings/payment-settings/promotional-banner/index.js @@ -5,6 +5,7 @@ import { BNPL_PROMOTION_BANNER, NEW_CHECKOUT_EXPERIENCE_APMS_BANNER, OC_PROMOTION_BANNER, + STRIPE_TAX_BANNER, } from '../constants'; import { ReConnectAccountBanner } from 'wcstripe/settings/payment-settings/promotional-banner/re-connect-account-banner'; import { NewCheckoutExperienceAPMsBanner } from 'wcstripe/settings/payment-settings/promotional-banner/new-checkout-experience-apms-banner'; @@ -12,6 +13,7 @@ import { NewCheckoutExperienceBanner } from 'wcstripe/settings/payment-settings/ import { BNPLPromotionBanner } from 'wcstripe/settings/payment-settings/promotional-banner/bnpl-promotion-banner'; import { BannerCard } from 'wcstripe/settings/payment-settings/promotional-banner/banner-layout'; import { OCPromotionBanner } from 'wcstripe/settings/payment-settings/promotional-banner/oc-promotion-banner'; +import { StripeTaxBanner } from 'wcstripe/settings/payment-settings/promotional-banner/stripe-tax-banner'; const PromotionalBanner = ( { setShowPromotionalBanner, @@ -31,6 +33,13 @@ const PromotionalBanner = ( { /> ); break; + case STRIPE_TAX_BANNER: + BannerContent = ( + + ); + break; case OC_PROMOTION_BANNER: BannerContent = (

- { __( '', 'woocommerce-gateway-stripe' ) } { interpolateComponents( { mixedString: __( "Optimize your checkout experience for more sales by dynamically displaying the most relevant payment methods you've enabled for each customer. {{docLink}}Learn more{{/docLink}} about Stripe's Optimized Checkout Suite.", diff --git a/client/settings/payment-settings/promotional-banner/stripe-tax-banner.js b/client/settings/payment-settings/promotional-banner/stripe-tax-banner.js new file mode 100644 index 0000000000..174efafd43 --- /dev/null +++ b/client/settings/payment-settings/promotional-banner/stripe-tax-banner.js @@ -0,0 +1,109 @@ +import { React } from 'react'; +import styled from '@emotion/styled'; +import interpolateComponents from '@automattic/interpolate-components'; +import { __ } from '@wordpress/i18n'; +import apiFetch from '@wordpress/api-fetch'; +import CardBody from 'wcstripe/settings/card-body'; +import illustration from 'wcstripe/settings/payment-settings/promotional-banner/illustrations/stripe-tax.svg'; +import { + BannerIllustration, + ButtonsRow, + CardColumn, + CardInner, + DismissButton, + MainCTALink, +} from 'wcstripe/settings/payment-settings/promotional-banner/banner-layout'; +import { recordEvent } from 'wcstripe/tracking'; +import { ExternalLink } from '@wordpress/components'; + +const BannerIllustrationStripeTax = styled( BannerIllustration )` + width: 80px; + margin: 10px; + + @media ( min-width: 600px ) { + margin-bottom: -30px; + } +`; + +const ButtonsRowStripeTax = styled( ButtonsRow )` + @media ( min-width: 600px ) { + margin-bottom: 0.7em; + } +`; + +const ColumnIllustration = styled( CardColumn )` + @media ( max-width: 599px ) { + text-align: center; + } +`; + +const TitleStripeTax = styled.h4` + margin-top: 0.6em !important; + font-weight: 500; +`; + +export const StripeTaxBanner = ( { setShowPromotionalBanner } ) => { + const handleBannerDismiss = () => { + apiFetch( { + path: '/wc/v3/wc_stripe/settings/notice', + method: 'POST', + data: { wc_stripe_show_stripe_tax_banner: 'no' }, + } ).finally( () => { + setShowPromotionalBanner( false ); + } ); + }; + + const handleButtonClick = () => { + recordEvent( 'wcstripe_stripe_tax_banner_button_click', {} ); + window.open( 'https://woocommerce.com/products/stripe-tax/', '_blank' ); + }; + + return ( + + + + + { __( + 'Automate tax compliance with Stripe Tax', + 'woocommerce-gateway-stripe' + ) } + +

+ { interpolateComponents( { + mixedString: __( + 'Automatically calculate and collect sales tax, value-added tax (VAT), and goods and services tax (GST) wherever you sell. {{docLink}}Learn more{{/docLink}} about how Stripe Tax keeps you compliant.', + 'woocommerce-gateway-stripe' + ), + components: { + docLink: ( + + ), + }, + } ) } +

+ + + + + + + + { __( 'Get Stripe Tax', 'woocommerce-gateway-stripe' ) } + + + { __( 'Dismiss', 'woocommerce-gateway-stripe' ) } + + + + ); +}; diff --git a/client/settings/settings-manager/index.js b/client/settings/settings-manager/index.js index ebc62c6540..00db4997e5 100644 --- a/client/settings/settings-manager/index.js +++ b/client/settings/settings-manager/index.js @@ -18,6 +18,7 @@ import { getPromotionalBannerType } from 'wcstripe/settings/payment-settings/pro import { BNPL_PROMOTION_BANNER, OC_PROMOTION_BANNER, + STRIPE_TAX_BANNER, } from 'wcstripe/settings/payment-settings/constants'; const StyledTabPanel = styled( TabPanel )` @@ -59,6 +60,13 @@ const SettingsManager = () => { ) { initialBannerState = true; } + if ( + promotionalBannerType === STRIPE_TAX_BANNER && + // eslint-disable-next-line camelcase + wc_stripe_settings_params?.show_stripe_tax_banner === '1' + ) { + initialBannerState = true; + } if ( promotionalBannerType === OC_PROMOTION_BANNER && // eslint-disable-next-line camelcase diff --git a/includes/admin/class-wc-stripe-settings-controller.php b/includes/admin/class-wc-stripe-settings-controller.php index 757631d26d..498152e313 100644 --- a/includes/admin/class-wc-stripe-settings-controller.php +++ b/includes/admin/class-wc-stripe-settings-controller.php @@ -263,6 +263,10 @@ public function admin_scripts( $hook_suffix ) { // Show the OC promotional banner only if OC is disabled && ! $is_oc_enabled; + $show_stripe_tax_banner = get_option( 'wc_stripe_show_stripe_tax_banner', 'yes' ) === 'yes' + // Show the Stripe Tax banner only if OC is enabled + && $is_oc_enabled; + $params = [ 'time' => time(), 'i18n_out_of_sync' => $message, @@ -273,6 +277,7 @@ public function admin_scripts( $hook_suffix ) { 'show_optimized_checkout_notice' => get_option( 'wc_stripe_show_optimized_checkout_notice', 'yes' ) === 'yes' ? true : false, 'show_bnpl_promotional_banner' => $show_bnpl_promotion_banner, 'show_oc_promotional_banner' => $show_oc_promotion_banner, + 'show_stripe_tax_banner' => $show_stripe_tax_banner, 'is_test_mode' => $this->get_gateway()->is_in_test_mode(), 'plugin_version' => WC_STRIPE_VERSION, 'account_country' => $this->account->get_account_country(), diff --git a/readme.txt b/readme.txt index 8c700a2964..403d659c07 100644 --- a/readme.txt +++ b/readme.txt @@ -123,6 +123,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o * Fix - Ensure state and postal code are optional in express checkout for Gulf countries (UAE, Bahrain, Kuwait, Oman, Qatar) * Dev - Removes the `_wcstripe_feature_upe` feature flag and the related method from the `WC_Stripe_Feature_Flags` class * Dev - Fixes some incorrect subscriptions support implementations for payment methods +* Add - New promotional banner to highlight the Stripe Tax extension for OCS-enabled merchants * Fix - Ensure correct express checkout prices in block cart and checkout with non-default decimal configuration * Fix - Disable express checkout when Amazon Pay is disabled and the only method * Fix - Don't allow WP-Cron jobs to detach payment methods on staging sites