From 44e6ccb5e53c79accbebdf7459735c8dc6c9163f Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Tue, 9 Jul 2024 14:51:34 -0400 Subject: [PATCH 1/8] Pass Blocks checkout appearance on init WooPay --- client/checkout/api/index.js | 4 ++++ includes/woopay/class-woopay-session.php | 28 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) 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/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 06fc2a93a35..31c896b6f06 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -525,6 +525,33 @@ public static function get_init_session_request( $order_id = null, $key = null, return $request; } + /** + * Recursively map an array. + * + * @param function $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. * @@ -546,6 +573,7 @@ public static function ajax_init_woopay() { $body = self::get_init_session_request( $order_id, $key, $billing_email ); $body['user_session'] = isset( $_REQUEST['user_session'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['user_session'] ) ) : null; + $body['appearance'] = isset( $_REQUEST['appearance'] ) ? self::array_map_recursive( array( __CLASS__, 'sanitize_string' ), $_REQUEST['appearance'] ) : null; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, Generic.Arrays.DisallowLongArraySyntax.Found $args = [ 'url' => WooPay_Utilities::get_woopay_rest_url( 'init' ), From 5105b9f3e4dee080c26ea44d14c420aad7087935 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Wed, 10 Jul 2024 12:57:38 -0400 Subject: [PATCH 2/8] Add changelog entry --- changelog/add-pass-blocks-checkout-appearance-on-init-woopay | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/add-pass-blocks-checkout-appearance-on-init-woopay 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 From 0255cd96a12c0e447b24e53e1540bc11c7bbf373 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Thu, 11 Jul 2024 17:51:18 -0400 Subject: [PATCH 3/8] Update Docblock class --- includes/woopay/class-woopay-session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 31c896b6f06..e9a8d7c026a 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -528,7 +528,7 @@ public static function get_init_session_request( $order_id = null, $key = null, /** * Recursively map an array. * - * @param function $callback The sanitize_text_field function. + * @param callable $callback The sanitize_text_field function. * @param array $array The nested array. * * @return array A new appearance array. From 3fe78b81bc2a0368a8079a74a291f36402d9e6ff Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Fri, 12 Jul 2024 10:47:19 -0400 Subject: [PATCH 4/8] Test appearance properties --- client/checkout/api/test/index.test.js | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) 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, From 464ade40ddd87fecb59507562b1a924bd1df9943 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Tue, 16 Jul 2024 17:54:47 -0400 Subject: [PATCH 5/8] Pass blocks checkout appearance on first party auth --- .../woopay/express-button/woopay-express-checkout-button.js | 2 ++ 1 file changed, 2 insertions(+) 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 ) { From bf2749fbaf6bfa54e6556be7649b96275787cf33 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Tue, 16 Jul 2024 20:03:07 -0400 Subject: [PATCH 6/8] Add appearance test --- .../woopay-express-checkout-button.test.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) 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(); } ); From 910687d402d5df5bdc78e1c446016bee0fc49ae3 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Wed, 17 Jul 2024 08:03:51 -0400 Subject: [PATCH 7/8] Pass Blocks checkout appearance to frontend get_woopay_session request --- .../checkout/woopay/express-button/express-checkout-iframe.js | 2 ++ 1 file changed, 2 insertions(+) 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 ) { From 1aef3f2f702a8e82773943cda45426b1e42ff168 Mon Sep 17 00:00:00 2001 From: hsingyuc Date: Wed, 17 Jul 2024 08:04:38 -0400 Subject: [PATCH 8/8] Get appearance variables from frontend get_woopay_session request --- includes/woopay/class-woopay-session.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index 61cbb1af0d1..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(); @@ -571,10 +575,10 @@ 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; - $body['appearance'] = isset( $_REQUEST['appearance'] ) ? self::array_map_recursive( array( __CLASS__, 'sanitize_string' ), $_REQUEST['appearance'] ) : null; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, Generic.Arrays.DisallowLongArraySyntax.Found $args = [ 'url' => WooPay_Utilities::get_woopay_rest_url( 'init' ),