diff --git a/.changeset/mighty-ladybugs-worry.md b/.changeset/mighty-ladybugs-worry.md new file mode 100644 index 00000000..2dbe52a2 --- /dev/null +++ b/.changeset/mighty-ladybugs-worry.md @@ -0,0 +1,5 @@ +--- +'@adyen/adyen-web': patch +--- + +Detect when the value of a data-cse attribute is not supported, and don't create a SF for it diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts index 8c130a59..434f794b 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createCardSecuredFields.test.ts @@ -47,7 +47,7 @@ describe("Testing setupSecuredField's createCardSecuredFields functionality", () SecuredFieldMock.mockClear(); }); - test("setupSecuredField's createCardSecuredFields function should call the onBrand callback", async () => { + test("setupSecuredField's createCardSecuredFields function, as a single-branded card, should call the onBrand callback", async () => { myCSF.state.type = 'mc'; myCSF.isSingleBrandedCard = true; diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts index 32a07d52..c3205598 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.test.ts @@ -1,4 +1,4 @@ -import { setupSecuredField } from './createSecuredFields'; +import { createSecuredFields, setupSecuredField } from './createSecuredFields'; import { DATA_ENCRYPTED_FIELD_ATTR, ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, SF_CONFIG_TIMEOUT } from '../../configuration/constants'; import { SecuredFields } from '../../types'; import Language from '../../../../../../language'; @@ -18,12 +18,14 @@ let MySecuredField; const myCSF = { state: { type: 'card', hasSeparateDateFields: null, securedFields: {} as SecuredFields, iframeCount: 0, originalNumIframes: 2, numIframes: 2 }, config: { shouldDisableIOSArrowKeys: null }, - props: { i18n: new Language('en-US', {}) }, + props: { rootNode: null, i18n: new Language('en-US', {}) }, callbacks: { onLoad: jest.fn(() => {}), onTouchstartIOS: jest.fn(() => {}) }, + createSecuredFields, setupSecuredField, + createNonCardSecuredFields: jest.fn(() => {}), encryptedAttrName: DATA_ENCRYPTED_FIELD_ATTR, destroySecuredFields: jest.fn(() => { console.log('### createSecuredFields.test::calling destroySecuredFields:: '); @@ -52,6 +54,7 @@ const dummyObj = { foo: 'bar' }; describe('Testing CSFs setupSecuredField functionality', () => { beforeEach(() => { console.log = jest.fn(() => {}); + console.warn = jest.fn(() => {}); MySecuredField = { fieldType: ENCRYPTED_CARD_NUMBER, @@ -108,6 +111,23 @@ describe('Testing CSFs setupSecuredField functionality', () => { SecuredFieldMock.mockClear(); }); + test('Calling setupSecuredField with an unsupported value for the data-cse attribute should see that a SF is not created and that a warning is given', () => { + const rootNode = document.createElement('div'); + const unsupportedEl = makeDiv('encryptedCustomField'); + rootNode.appendChild(unsupportedEl); + myCSF.props.rootNode = rootNode; + + const numIframes: number = myCSF.createSecuredFields(); + + expect(numIframes).toEqual(0); + + expect(console.warn).toBeCalledWith( + `WARNING: 'encryptedCustomField' is not a valid type for the '${DATA_ENCRYPTED_FIELD_ATTR}' attribute. A SecuredField will not be created for this element.` + ); + + expect(myCSF.createNonCardSecuredFields).toBeCalledWith([]); + }); + test('Calling setupSecuredField to see that an "encryptedCardNumber" SF is created and stored in state', () => { myCSF.setupSecuredField(makeDiv(ENCRYPTED_CARD_NUMBER)); diff --git a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts index 309cdc3d..5689d925 100644 --- a/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts +++ b/packages/lib/src/components/internal/SecuredFields/lib/CSF/extensions/createSecuredFields.ts @@ -6,7 +6,8 @@ import { DATA_ENCRYPTED_FIELD_ATTR, DATA_INFO, DATA_UID, - SF_CONFIG_TIMEOUT + SF_CONFIG_TIMEOUT, + ALL_SECURED_FIELDS } from '../../configuration/constants'; import { existy } from '../../utilities/commonUtils'; import cardType from '../utils/cardType'; @@ -22,8 +23,17 @@ import AdyenCheckoutError from '../../../../../../core/Errors/AdyenCheckoutError export function createSecuredFields(): number { this.encryptedAttrName = DATA_ENCRYPTED_FIELD_ATTR; - // Detect DOM elements that qualify as securedField holders - const securedFields: HTMLElement[] = select(this.props.rootNode, `[${this.encryptedAttrName}]`); + // Detect DOM elements that qualify as securedField holders & filter them for valid types + const securedFields: HTMLElement[] = select(this.props.rootNode, `[${this.encryptedAttrName}]`).filter(field => { + const fieldType: string = getAttribute(field, this.encryptedAttrName); + const isValidType = ALL_SECURED_FIELDS.includes(fieldType); + if (!isValidType) { + console.warn( + `WARNING: '${fieldType}' is not a valid type for the '${this.encryptedAttrName}' attribute. A SecuredField will not be created for this element.` + ); + } + return isValidType; + }); /** * cvcPolicy - 'required' | 'optional' | 'hidden' diff --git a/packages/playground/src/pages/Cards/Cards.js b/packages/playground/src/pages/Cards/Cards.js index 8d657b78..5907a63b 100644 --- a/packages/playground/src/pages/Cards/Cards.js +++ b/packages/playground/src/pages/Cards/Cards.js @@ -8,7 +8,7 @@ import '../../style.scss'; import { MockReactApp } from './MockReactApp'; import { searchFunctionExample } from '../../utils'; -const onlyShowCard = true; +const onlyShowCard = false; const showComps = { clickToPay: true, @@ -53,7 +53,7 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsResponse = // Stored Card if (!onlyShowCard && showComps.storedCard) { if (checkout.paymentMethodsResponse.storedPaymentMethods && checkout.paymentMethodsResponse.storedPaymentMethods.length > 0) { - const storedCardData = checkout.paymentMethodsResponse.storedPaymentMethods[2]; + const storedCardData = checkout.paymentMethodsResponse.storedPaymentMethods[0]; window.storedCard = checkout .create('card', { ...storedCardData, diff --git a/packages/playground/src/pages/SecuredFields/SecuredFields.js b/packages/playground/src/pages/SecuredFields/SecuredFields.js index b2db21e1..2f900e7d 100644 --- a/packages/playground/src/pages/SecuredFields/SecuredFields.js +++ b/packages/playground/src/pages/SecuredFields/SecuredFields.js @@ -227,7 +227,7 @@ function handlePaymentResult(result, component) { function startPayment(component) { if (!component.isValid) return component.showValidation(); - const allow3DS2 = paymentsConfig.additionalData.allow3DS2 || false; + const allow3DS2 = paymentsConfig.additionalData?.allow3DS2 || false; const riskdata = checkout.modules.risk.data;