diff --git a/changelog/add-pass-blocks-checkout-appearance-on-init-woopay b/changelog/add-pass-blocks-checkout-appearance-on-init-woopay new file mode 100644 index 00000000000..ae92e257557 --- /dev/null +++ b/changelog/add-pass-blocks-checkout-appearance-on-init-woopay @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Pass Blocks checkout appearance on init WooPay diff --git a/client/checkout/api/index.js b/client/checkout/api/index.js index a5fa5991fbe..a59682bd725 100644 --- a/client/checkout/api/index.js +++ b/client/checkout/api/index.js @@ -11,6 +11,7 @@ import { getExpressCheckoutAjaxURL, getExpressCheckoutConfig, } from 'utils/express-checkout'; +import { getAppearance } from 'checkout/upe-styles'; /** * Handles generic connections to the server and Stripe. @@ -459,8 +460,11 @@ export default class WCPayAPI { this.isWooPayRequesting = true; const wcAjaxUrl = getConfig( 'wcAjaxUrl' ); const nonce = getConfig( 'initWooPayNonce' ); + const appearance = getAppearance( 'blocks_checkout' ); + return this.request( buildAjaxURL( wcAjaxUrl, 'init_woopay' ), { _wpnonce: nonce, + appearance: appearance, email: userEmail, user_session: woopayUserSession, order_id: getConfig( 'order_id' ), diff --git a/client/checkout/api/test/index.test.js b/client/checkout/api/test/index.test.js index 93b135555dd..d1941846d5b 100644 --- a/client/checkout/api/test/index.test.js +++ b/client/checkout/api/test/index.test.js @@ -22,6 +22,33 @@ jest.mock( 'wcpay/utils/checkout', () => ( { getConfig: jest.fn(), } ) ); +const mockAppearance = { + rules: { + '.Block': {}, + '.Input': {}, + '.Input--invalid': {}, + '.Label': {}, + '.Tab': {}, + '.Tab--selected': {}, + '.Tab:hover': {}, + '.TabIcon--selected': { + color: undefined, + }, + '.TabIcon:hover': { + color: undefined, + }, + '.Text': {}, + '.Text--redirect': {}, + }, + theme: 'stripe', + variables: { + colorBackground: '#ffffff', + colorText: undefined, + fontFamily: undefined, + fontSizeBase: undefined, + }, +}; + describe( 'WCPayAPI', () => { test( 'does not initialize woopay if already requesting', async () => { buildAjaxURL.mockReturnValue( 'https://example.org/' ); @@ -48,6 +75,7 @@ describe( 'WCPayAPI', () => { getConfig.mockImplementation( ( key ) => { const mockProperties = { initWooPayNonce: 'foo', + appearance: mockAppearance, order_id: 1, key: 'testkey', billing_email: 'test@example.com', @@ -60,6 +88,7 @@ describe( 'WCPayAPI', () => { expect( request ).toHaveBeenLastCalledWith( 'https://example.org/', { _wpnonce: 'foo', + appearance: mockAppearance, email: 'foo@bar.com', user_session: 'qwerty123', order_id: 1, diff --git a/client/checkout/woopay/express-button/express-checkout-iframe.js b/client/checkout/woopay/express-button/express-checkout-iframe.js index 2454564c88c..3e567320aa6 100644 --- a/client/checkout/woopay/express-button/express-checkout-iframe.js +++ b/client/checkout/woopay/express-button/express-checkout-iframe.js @@ -16,6 +16,7 @@ import { appendRedirectionParams, } from '../utils'; import { getTracksIdentity } from 'tracks'; +import { getAppearance } from 'wcpay/checkout/upe-styles'; export const expressCheckoutIframe = async ( api, context, emailSelector ) => { const woopayEmailInput = await getTargetElement( emailSelector ); @@ -106,6 +107,7 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), + appearance: getAppearance( 'blocks_checkout' ), } ).then( ( response ) => { if ( response?.data?.session ) { diff --git a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js index b3ac7c41f3f..438d53c71d9 100644 --- a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js +++ b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js @@ -70,6 +70,32 @@ describe( 'WoopayExpressCheckoutButton', () => { const mockRequest = jest.fn().mockResolvedValue( true ); const mockAddToCart = jest.fn().mockResolvedValue( true ); const api = new WCPayAPI( {}, mockRequest ); + const mockAppearance = { + rules: { + '.Block': {}, + '.Input': {}, + '.Input--invalid': {}, + '.Label': {}, + '.Tab': {}, + '.Tab--selected': {}, + '.Tab:hover': {}, + '.TabIcon--selected': { + color: undefined, + }, + '.TabIcon:hover': { + color: undefined, + }, + '.Text': {}, + '.Text--redirect': {}, + }, + theme: 'stripe', + variables: { + colorBackground: '#ffffff', + colorText: undefined, + fontFamily: undefined, + fontSizeBase: undefined, + }, + }; beforeEach( () => { expressCheckoutIframe.mockImplementation( () => jest.fn() ); @@ -145,6 +171,8 @@ describe( 'WoopayExpressCheckoutButton', () => { return 'testkey'; case 'order_id': return 1; + case 'appearance': + return mockAppearance; default: return 'foo'; } @@ -170,6 +198,7 @@ describe( 'WoopayExpressCheckoutButton', () => { order_id: 1, key: 'testkey', billing_email: 'test@test.com', + appearance: mockAppearance, } ); expect( expressCheckoutIframe ).not.toHaveBeenCalled(); } ); diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index b7896b0549a..8011dec8eef 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -21,6 +21,7 @@ import { deleteSkipWooPayCookie, } from 'wcpay/checkout/woopay/utils'; import WooPayFirstPartyAuth from 'wcpay/checkout/woopay/express-button/woopay-first-party-auth'; +import { getAppearance } from 'wcpay/checkout/upe-styles'; const BUTTON_WIDTH_THRESHOLD = 140; @@ -268,6 +269,7 @@ export const WoopayExpressCheckoutButton = ( { order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), + appearance: getAppearance( 'blocks_checkout' ), } ) .then( async ( response ) => { if ( response?.blog_id && response?.data?.session ) { diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 02aa58a38ec..8488354ad4f 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -319,8 +319,10 @@ public static function get_frontend_init_session_request() { $key = ! empty( $_POST['key'] ) ? sanitize_text_field( wp_unslash( $_POST['key'] ) ) : null; $billing_email = ! empty( $_POST['billing_email'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_email'] ) ) : null; // phpcs:enable + // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, Generic.Arrays.DisallowLongArraySyntax.Found + $appearance = ! empty( $_POST['appearance'] ) ? self::array_map_recursive( array( __CLASS__, 'sanitize_string' ), $_POST['appearance'] ) : null; - $session = self::get_init_session_request( $order_id, $key, $billing_email ); + $session = self::get_init_session_request( $order_id, $key, $billing_email, null, $appearance ); return WooPay_Utilities::encrypt_and_sign_data( $session ); } @@ -416,9 +418,10 @@ private static function get_user_email( $user ) { * @param string|null $key Pay-for-order key. * @param string|null $billing_email Pay-for-order billing email. * @param WP_REST_Request|null $woopay_request The WooPay request object. + * @param array $appearance Merchant appearance. * @return array The initial session request data without email and user_session. */ - public static function get_init_session_request( $order_id = null, $key = null, $billing_email = null, $woopay_request = null ) { + public static function get_init_session_request( $order_id = null, $key = null, $billing_email = null, $woopay_request = null, $appearance = null ) { $user = wp_get_current_user(); $is_pay_for_order = null !== $order_id; $order = wc_get_order( $order_id ); @@ -500,6 +503,7 @@ public static function get_init_session_request( $order_id = null, $key = null, ], ], 'tracks_user_identity' => WC_Payments::woopay_tracker()->tracks_get_identity(), + 'appearance' => $appearance, ]; $woopay_adapted_extensions = new WooPay_Adapted_Extensions(); @@ -526,6 +530,33 @@ public static function get_init_session_request( $order_id = null, $key = null, return $request; } + /** + * Recursively map an array. + * + * @param callable $callback The sanitize_text_field function. + * @param array $array The nested array. + * + * @return array A new appearance array. + */ + private static function array_map_recursive( $callback, $array ) { + $func = function ( $item ) use ( &$func, &$callback ) { + return is_array( $item ) ? array_map( $func, $item ) : call_user_func( $callback, $item ); + }; + + return array_map( $func, $array ); + } + + /** + * Sanitize a string. + * + * @param string $item A string. + * + * @return string The sanitized string. + */ + private static function sanitize_string( $item ) { + return sanitize_text_field( wp_unslash( $item ) ); + } + /** * Used to initialize woopay session. * @@ -544,8 +575,9 @@ public static function ajax_init_woopay() { $order_id = ! empty( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : null; $key = ! empty( $_POST['key'] ) ? sanitize_text_field( wp_unslash( $_POST['key'] ) ) : null; $billing_email = ! empty( $_POST['billing_email'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_email'] ) ) : null; + $appearance = ! empty( $_POST['appearance'] ) ? self::array_map_recursive( array( __CLASS__, 'sanitize_string' ), $_POST['appearance'] ) : null; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, Generic.Arrays.DisallowLongArraySyntax.Found - $body = self::get_init_session_request( $order_id, $key, $billing_email ); + $body = self::get_init_session_request( $order_id, $key, $billing_email, null, $appearance ); $body['user_session'] = isset( $_REQUEST['user_session'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['user_session'] ) ) : null; $args = [