Skip to content

Commit

Permalink
Merge branch 'develop' into fix/token-retrieval-per-payment-method
Browse files Browse the repository at this point in the history
  • Loading branch information
timur27 authored Jun 25, 2024
2 parents d08abaf + 9fd1870 commit 886b452
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 145 deletions.
4 changes: 4 additions & 0 deletions changelog/add-8413-compute-styles-on-add-payment-method-page
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Add a separate transient to save UPE appearance styles for the Add Payment Method standalone page. Correct regression that prevented proper styles calculation in the shortcode checkout.
16 changes: 12 additions & 4 deletions client/checkout/classic/event-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jQuery( function ( $ ) {
} );

$( document.body ).on( 'updated_checkout', () => {
maybeMountStripePaymentElement();
maybeMountStripePaymentElement( 'shortcode_checkout' );
injectStripePMMEContainers();
} );

Expand Down Expand Up @@ -112,7 +112,11 @@ jQuery( function ( $ ) {
} );

if ( $addPaymentMethodForm.length || $payForOrderForm.length ) {
maybeMountStripePaymentElement();
maybeMountStripePaymentElement( 'add_payment_method' );
}

if ( $payForOrderForm.length ) {
maybeMountStripePaymentElement( 'shortcode_checkout' );
}

$addPaymentMethodForm.on( 'submit', function () {
Expand Down Expand Up @@ -214,13 +218,17 @@ jQuery( function ( $ ) {
}
}

async function maybeMountStripePaymentElement() {
async function maybeMountStripePaymentElement( elementsLocation ) {
if (
$( '.wcpay-upe-element' ).length &&
! $( '.wcpay-upe-element' ).children().length
) {
for ( const upeElement of $( '.wcpay-upe-element' ).toArray() ) {
await mountStripePaymentElement( api, upeElement );
await mountStripePaymentElement(
api,
upeElement,
elementsLocation
);
restrictPaymentMethodToLocation( upeElement );
}
maybeEnableStripeLink( api );
Expand Down
49 changes: 41 additions & 8 deletions client/checkout/classic/payment-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,24 @@ for ( const paymentMethodType in getUPEConfig( 'paymentMethodsConfig' ) ) {
* it is simply returned.
*
* @param {Object} api The API object used to save the UPE configuration.
* @param {string} elementsLocation The location of the UPE elements.
* @return {Promise<Object>} The appearance object for the UPE.
*/
async function initializeAppearance( api ) {
const appearance = getUPEConfig( 'upeAppearance' );
async function initializeAppearance( api, elementsLocation ) {
const upeConfigMap = {
shortcode_checkout: 'upeAppearance',
add_payment_method: 'upeAddPaymentMethodAppearance',
};
const upeConfigProperty =
upeConfigMap[ elementsLocation ] ?? 'upeAppearance';
const appearance = getUPEConfig( upeConfigProperty );
if ( appearance ) {
return Promise.resolve( appearance );
}

return await api.saveUPEAppearance(
getAppearance( 'shortcode_checkout' ),
'shortcode_checkout'
getAppearance( elementsLocation ),
elementsLocation
);
}

Expand Down Expand Up @@ -202,9 +209,14 @@ function createStripePaymentMethod(
*
* @param {Object} api The API object used to create the Stripe payment element.
* @param {string} paymentMethodType The type of Stripe payment method to create.
* @param {string} elementsLocation The location of the UPE elements.
* @return {Object} A promise that resolves with the created Stripe payment element.
*/
async function createStripePaymentElement( api, paymentMethodType ) {
async function createStripePaymentElement(
api,
paymentMethodType,
elementsLocation
) {
const amount = Number( getUPEConfig( 'cartTotal' ) );
const paymentMethodTypes = getPaymentMethodTypes( paymentMethodType );
const options = {
Expand All @@ -213,7 +225,7 @@ async function createStripePaymentElement( api, paymentMethodType ) {
amount: amount,
paymentMethodCreation: 'manual',
paymentMethodTypes: paymentMethodTypes,
appearance: await initializeAppearance( api ),
appearance: await initializeAppearance( api, elementsLocation ),
fonts: getFontRulesFromPage(),
};

Expand Down Expand Up @@ -373,8 +385,13 @@ export function maybeEnableStripeLink( api ) {
*
* @param {Object} api The API object.
* @param {string} domElement The selector of the DOM element of particular payment method to mount the UPE element to.
* @param {string} elementsLocation Thhe location of the UPE element.
**/
export async function mountStripePaymentElement( api, domElement ) {
export async function mountStripePaymentElement(
api,
domElement,
elementsLocation
) {
try {
if ( ! fingerprint ) {
const { visitorId } = await getFingerprint();
Expand Down Expand Up @@ -404,7 +421,11 @@ export async function mountStripePaymentElement( api, domElement ) {

const upeElement =
gatewayUPEComponents[ paymentMethodType ].upeElement ||
( await createStripePaymentElement( api, paymentMethodType ) );
( await createStripePaymentElement(
api,
paymentMethodType,
elementsLocation
) );
upeElement.mount( domElement );
upeElement.on( 'loaderror', ( e ) => {
// setting the flag to true to prevent the form from being submitted.
Expand Down Expand Up @@ -565,3 +586,15 @@ export const processPayment = (
// Prevent WC Core default form submission (see woocommerce/assets/js/frontend/checkout.js) from happening.
return false;
};

/**
* Used only for testing, resets the gatewayUPEComponents internal cache of elements for a given property.
*
* @param {string} paymentMethodType The paymentMethodType we want to remove the upeElement from.
* @return {void}
*/
export function __resetGatewayUPEComponentsElement( paymentMethodType ) {
if ( gatewayUPEComponents[ paymentMethodType ]?.upeElement ) {
delete gatewayUPEComponents[ paymentMethodType ].upeElement;
}
}
213 changes: 112 additions & 101 deletions client/checkout/classic/test/payment-processing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
mountStripePaymentElement,
processPayment,
renderTerms,
__resetGatewayUPEComponentsElement,
} from '../payment-processing';
import { getAppearance } from '../../upe-styles';
import { getUPEConfig } from 'wcpay/utils/checkout';
Expand Down Expand Up @@ -142,108 +143,118 @@ describe( 'Stripe Payment Element mounting', () => {
jest.clearAllMocks();
} );

test( 'initializes the appearance when it is not set and saves it', async () => {
getUPEConfig.mockImplementation( ( argument ) => {
if (
argument === 'wcBlocksUPEAppearance' ||
argument === 'upeAppearance'
) {
return null;
}

if ( argument === 'paymentMethodsConfig' ) {
return {
card: {
label: 'Card',
forceNetworkSavedCards: true,
},
giropay: {
label: 'Giropay',
forceNetworkSavedCards: false,
},
ideal: {
label: 'iDEAL',
forceNetworkSavedCards: false,
},
sepa: {
label: 'SEPA',
forceNetworkSavedCards: false,
},
};
}

if ( argument === 'currency' ) {
return 'eur';
}
} );

// Create a mock function to track the event dispatch for tokenization-form.js execution
const dispatchMock = jest.fn();
document.body.dispatchEvent = dispatchMock;

const appearanceMock = { backgroundColor: '#fff' };
getAppearance.mockReturnValue( appearanceMock );
getFingerprint.mockImplementation( () => {
return 'fingerprint';
} );

mockDomElement.dataset.paymentMethodType = 'giropay';

await mountStripePaymentElement( apiMock, mockDomElement );

expect( getAppearance ).toHaveBeenCalled();
expect( apiMock.saveUPEAppearance ).toHaveBeenCalledWith(
appearanceMock,
'shortcode_checkout'
);
expect( dispatchMock ).toHaveBeenCalled();
} );

test( 'does not call getAppearance or saveUPEAppearance if appearance is already set', async () => {
const appearanceMock = { backgroundColor: '#fff' };
getAppearance.mockReturnValue( appearanceMock );
getFingerprint.mockImplementation( () => {
return 'fingerprint';
} );
getUPEConfig.mockImplementation( ( argument ) => {
if ( argument === 'currency' ) {
return 'eur';
}

if ( argument === 'upeAppearance' ) {
return {
backgroundColor: '#fff',
};
}

if ( argument === 'paymentMethodsConfig' ) {
return {
ideal: {
label: 'iDEAL',
forceNetworkSavedCards: false,
},
card: {
label: 'Card',
forceNetworkSavedCards: true,
},
giropay: {
label: 'Giropay',
forceNetworkSavedCards: false,
},
sepa: {
label: 'SEPA',
forceNetworkSavedCards: false,
},
};
}
[
{
elementsLocation: 'shortcode_checkout',
expectedProperty: 'upeAppearance',
},
{
elementsLocation: 'add_payment_method',
expectedProperty: 'upeAddPaymentMethodAppearance',
},
{
elementsLocation: 'other',
expectedProperty: 'upeAppearance',
},
].forEach( ( { elementsLocation, expectedProperty } ) => {
describe( `when elementsLocation is ${ elementsLocation }`, () => {
beforeEach( () => {
__resetGatewayUPEComponentsElement( 'giropay' );
} );

test( 'initializes the appearance when it is not set and saves it', async () => {
getUPEConfig.mockImplementation( ( argument ) => {
if (
argument === 'upeAddPaymentMethodAppearance' ||
argument === 'upeAppearance'
) {
return null;
}

if ( argument === 'paymentMethodsConfig' ) {
return {
giropay: {
label: 'Giropay',
forceNetworkSavedCards: false,
},
};
}

if ( argument === 'currency' ) {
return 'eur';
}
} );

// Create a mock function to track the event dispatch for tokenization-form.js execution
const dispatchMock = jest.fn();
document.body.dispatchEvent = dispatchMock;

const appearanceMock = { backgroundColor: '#fff' };
getAppearance.mockReturnValue( appearanceMock );
getFingerprint.mockImplementation( () => {
return 'fingerprint';
} );

mockDomElement.dataset.paymentMethodType = 'giropay';

await mountStripePaymentElement(
apiMock,
mockDomElement,
elementsLocation
);

expect( getAppearance ).toHaveBeenCalled();
expect( apiMock.saveUPEAppearance ).toHaveBeenCalledWith(
appearanceMock,
elementsLocation
);
expect( getUPEConfig ).toHaveBeenCalledWith( expectedProperty );
expect( dispatchMock ).toHaveBeenCalled();
} );

test( 'does not call getAppearance or saveUPEAppearance if appearance is already set', async () => {
const appearanceMock = { backgroundColor: '#fff' };
getAppearance.mockReturnValue( appearanceMock );
getFingerprint.mockImplementation( () => {
return 'fingerprint';
} );
getUPEConfig.mockImplementation( ( argument ) => {
if ( argument === 'currency' ) {
return 'eur';
}

if (
argument === 'upeAppearance' ||
argument === 'upeAddPaymentMethodAppearance'
) {
return {
backgroundColor: '#fff',
};
}

if ( argument === 'paymentMethodsConfig' ) {
return {
giropay: {
label: 'Giropay',
forceNetworkSavedCards: false,
},
};
}
} );

mockDomElement.dataset.paymentMethodType = 'giropay';

await mountStripePaymentElement(
apiMock,
mockDomElement,
elementsLocation
);

expect( getUPEConfig ).toHaveBeenCalledWith( expectedProperty );
expect( getAppearance ).not.toHaveBeenCalled();
expect( apiMock.saveUPEAppearance ).not.toHaveBeenCalled();
} );
} );

mockDomElement.dataset.paymentMethodType = 'giropay';

await mountStripePaymentElement( apiMock, mockDomElement );

expect( getAppearance ).not.toHaveBeenCalled();
expect( apiMock.saveUPEAppearance ).not.toHaveBeenCalled();
} );

test( 'Prevents from mounting when no figerprint is available', async () => {
Expand Down
4 changes: 2 additions & 2 deletions client/checkout/upe-styles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
getBackgroundColor,
} from './utils.js';

const appearanceSelectors = {
export const appearanceSelectors = {
default: {
hiddenContainer: '#wcpay-hidden-div',
hiddenInput: '#wcpay-hidden-input',
Expand Down Expand Up @@ -148,7 +148,7 @@ const appearanceSelectors = {
case 'blocks_checkout':
appearanceSelector = this.blocksCheckout;
break;
case 'classic_checkout':
case 'shortcode_checkout':
appearanceSelector = this.classicCheckout;
break;
case 'bnpl_product_page':
Expand Down
Loading

0 comments on commit 886b452

Please sign in to comment.