From 9e782c3270d79e988f087b747920d28d8912b75e Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Wed, 10 Sep 2025 13:55:00 +0300 Subject: [PATCH 1/2] feat(payment): PAYPAL-5809 Updated BT SDK version --- ...ntree-credit-card-payment-strategy.spec.ts | 5 +- ...-braintree-credit-card-payment-strategy.ts | 5 +- .../braintree-hosted-form.spec.ts | 69 +++++++++++++++++-- .../braintree-hosted-form.ts | 15 +++- .../src/braintree-script-loader.spec.ts | 31 +++++++++ .../src/braintree-sdk-scripts-integrity.ts | 27 ++++++++ .../src/braintree-sdk-verison.ts | 1 + .../src/braintree-sdk-version-manager.spec.ts | 19 +++++ .../src/braintree-sdk-version-manager.ts | 5 ++ packages/braintree-utils/src/index.ts | 2 + 10 files changed, 170 insertions(+), 9 deletions(-) diff --git a/packages/braintree-integration/src/braintree-credit-card/braintree-credit-card-payment-strategy.spec.ts b/packages/braintree-integration/src/braintree-credit-card/braintree-credit-card-payment-strategy.spec.ts index 11a4775636..b7e71a0831 100644 --- a/packages/braintree-integration/src/braintree-credit-card/braintree-credit-card-payment-strategy.spec.ts +++ b/packages/braintree-integration/src/braintree-credit-card/braintree-credit-card-payment-strategy.spec.ts @@ -73,7 +73,10 @@ describe('BraintreeCreditCardPaymentStrategy', () => { braintreeScriptLoader, window, ); - braintreeHostedForm = new BraintreeHostedForm(braintreeScriptLoader); + braintreeHostedForm = new BraintreeHostedForm( + braintreeScriptLoader, + braintreeSDKVersionManager, + ); braintreeCreditCardPaymentStrategy = new BraintreeCreditCardPaymentStrategy( paymentIntegrationService, braintreeIntegrationService, diff --git a/packages/braintree-integration/src/braintree-credit-card/create-braintree-credit-card-payment-strategy.ts b/packages/braintree-integration/src/braintree-credit-card/create-braintree-credit-card-payment-strategy.ts index 629e03edc3..6cf7fe1cec 100644 --- a/packages/braintree-integration/src/braintree-credit-card/create-braintree-credit-card-payment-strategy.ts +++ b/packages/braintree-integration/src/braintree-credit-card/create-braintree-credit-card-payment-strategy.ts @@ -33,7 +33,10 @@ const createBraintreeCreditCardPaymentStrategy: PaymentStrategyFactory< braintreeHostWindow, ); - const braintreeHostedForm = new BraintreeHostedForm(braintreeScriptLoader); + const braintreeHostedForm = new BraintreeHostedForm( + braintreeScriptLoader, + braintreeSDKVersionManager, + ); return new BraintreeCreditCardPaymentStrategy( paymentIntegrationService, diff --git a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts index ad49d786de..d6ea00abd5 100644 --- a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts +++ b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts @@ -1,6 +1,7 @@ -import { EventEmitter } from 'events'; +import {EventEmitter} from 'events'; import { + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BraintreeFormOptions, BraintreeHostedFields, BraintreeScriptLoader, @@ -14,11 +15,11 @@ import { PaymentInvalidFormError, } from '@bigcommerce/checkout-sdk/payment-integration-api'; -import { PaymentIntegrationServiceMock } from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; +import {PaymentIntegrationServiceMock} from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; -import { getScriptLoader } from '@bigcommerce/script-loader'; +import {getScriptLoader} from '@bigcommerce/script-loader'; -import { getBillingAddress } from '../mocks/braintree.mock'; +import {getBillingAddress} from '../mocks/braintree.mock'; import BraintreeHostedForm from './braintree-hosted-form'; @@ -74,7 +75,7 @@ describe('BraintreeHostedForm', () => { window, braintreeSDKVersionManager, ); - subject = new BraintreeHostedForm(braintreeScriptLoader); + subject = new BraintreeHostedForm(braintreeScriptLoader, braintreeSDKVersionManager); containers = [ appendContainer('cardCode'), @@ -90,6 +91,10 @@ describe('BraintreeHostedForm', () => { jest.spyOn(braintreeScriptLoader, 'loadHostedFields').mockResolvedValue({ create: jest.fn().mockResolvedValue({ on: jest.fn() }), }); + + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); }); afterEach(() => { @@ -152,6 +157,60 @@ describe('BraintreeHostedForm', () => { }); }); + it('creates and configures hosted fields with preventCursorJumps', async () => { + const createMock = jest.fn(); + const clientMock = { + ...getClientMock(), + request: expect.any(Function), + }; + + const options = { + fields: { + cvv: { + container: '#cardCode', + internalLabel: undefined, + placeholder: 'Card code', + }, + expirationDate: { + container: '#cardExpiry', + internalLabel: undefined, + placeholder: 'Card expiry', + }, + number: { + container: '#cardNumber', + internalLabel: undefined, + placeholder: 'Card number', + supportedCardBrands: { + 'american-express': false, + maestro: false, + }, + }, + cardholderName: { + container: '#cardName', + internalLabel: undefined, + placeholder: 'Card name', + }, + }, + styles: { + input: { color: '#000' }, + '.invalid': { color: '#f00', 'font-weight': 'bold' }, + ':focus': { color: '#00f' }, + }, + preventCursorJumps: true, + }; + + jest.spyOn(braintreeScriptLoader, 'loadHostedFields').mockResolvedValue({ + create: createMock, + }); + + await subject.initialize(formOptions, unsupportedCardBrands, 'clientToken'); + + expect(createMock).toHaveBeenCalledWith({ + ...options, + client: clientMock, + }); + }); + it('creates and configures hosted fields for stored card verification', async () => { const createMock = jest.fn(); const clientMock = { diff --git a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts index f2888bd1e7..f25bb1c1b7 100644 --- a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts +++ b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts @@ -1,6 +1,7 @@ import { Dictionary, isEmpty, isNil, omitBy } from 'lodash'; import { + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BraintreeBillingAddressRequestData, BraintreeClient, BraintreeFormErrorDataKeys, @@ -17,6 +18,7 @@ import { BraintreeHostedFieldsState, BraintreeHostedFormError, BraintreeScriptLoader, + BraintreeSDKVersionManager, BraintreeStoredCardFieldsMap, isBraintreeFormFieldsMap, isBraintreeHostedFormError, @@ -44,7 +46,10 @@ export default class BraintreeHostedForm { private clientToken?: string; private isInitializedHostedForm = false; - constructor(private braintreeScriptLoader: BraintreeScriptLoader) {} + constructor( + private braintreeScriptLoader: BraintreeScriptLoader, + private braintreeSDKVersionManager: BraintreeSDKVersionManager, + ) {} async initialize( options: BraintreeFormOptions, @@ -175,7 +180,13 @@ export default class BraintreeHostedForm { const client = await this.getClient(); const hostedFields = await this.braintreeScriptLoader.loadHostedFields(); - return hostedFields.create({ ...options, client }); + const currentSdkVersion = this.braintreeSDKVersionManager.getSDKVersion(); + const hostedFieldsOptions = + currentSdkVersion === BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION + ? { ...options, preventCursorJumps: true } + : options; + + return hostedFields.create({ ...hostedFieldsOptions, client }); } async getClient(): Promise { diff --git a/packages/braintree-utils/src/braintree-script-loader.spec.ts b/packages/braintree-utils/src/braintree-script-loader.spec.ts index af89de9bdb..05d2cd2bf8 100644 --- a/packages/braintree-utils/src/braintree-script-loader.spec.ts +++ b/packages/braintree-utils/src/braintree-script-loader.spec.ts @@ -10,6 +10,7 @@ import BraintreeScriptLoader from './braintree-script-loader'; import { BRAINTREE_SDK_SCRIPTS_INTEGRITY } from './braintree-sdk-scripts-integrity'; import { BRAINTREE_SDK_DEFAULT_VERSION, + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BRAINTREE_SDK_STABLE_VERSION, } from './braintree-sdk-verison'; import BraintreeSDKVersionManager from './braintree-sdk-version-manager'; @@ -56,6 +57,8 @@ describe('BraintreeScriptLoader', () => { BRAINTREE_SDK_SCRIPTS_INTEGRITY[BRAINTREE_SDK_DEFAULT_VERSION]; const braintreeSdkStableScriptsIntegrity = BRAINTREE_SDK_SCRIPTS_INTEGRITY[BRAINTREE_SDK_STABLE_VERSION]; + const braintreeSdkFixedHostedFieldsScriptsIntegrity = + BRAINTREE_SDK_SCRIPTS_INTEGRITY[BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION]; const thirdPartyBraintreeVersion = '3.123.4'; beforeEach(() => { @@ -129,6 +132,34 @@ describe('BraintreeScriptLoader', () => { expect(client).toBe(clientMock); }); + it('loads the client with fixed hosted fields version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + const client = await braintreeScriptLoader.loadClient(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/client.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.Client + ], + }, + }, + ); + expect(client).toBe(clientMock); + }); + it('loads the client with non-supported version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( thirdPartyBraintreeVersion, diff --git a/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts b/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts index 93d83bca35..da2c1620e6 100644 --- a/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts +++ b/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts @@ -1,6 +1,7 @@ import { BraintreeModuleName } from './braintree'; import { BRAINTREE_SDK_DEFAULT_VERSION, + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BRAINTREE_SDK_STABLE_VERSION, } from './braintree-sdk-verison'; @@ -57,4 +58,30 @@ export const BRAINTREE_SDK_SCRIPTS_INTEGRITY = { [BraintreeModuleName.Fastlane]: 'sha384-rhBL1hpZ71JqG+2TsT0Dih47mbjx8cjJCpeZjk9tw3df1gFMKfCTmMyZunhr7H4Y', }, + [BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION]: { + [BraintreeModuleName.Client]: + 'sha384-9ndM/mgw6O0A+bW00GkQepJmY6Q6A3qioH406Z3WiEn+kqE6+1z/bNfr083WS/rt', + [BraintreeModuleName.PaypalCheckout]: + 'sha384-lWM1jk4BOxoDNV6Kop7tVNvQgINlmEVz0c/OwM+HNHK3QC8xqwd54Ovrt2QRDTjh', + [BraintreeModuleName.Paypal]: + 'sha384-fXUmVxC2EMZBDXZ7eTHSTL/LFBrHej/MSXT/vTBXvra4lzgLNGvREAp+x0GYObuH', + [BraintreeModuleName.LocalPayment]: + 'sha384-pha1xGuGSkmZH5x9aqHvRaO4xLea7rmQ/79PFj9zxNzxuFcOWZg7gOP7FibygOEC', + [BraintreeModuleName.DataCollector]: + 'sha384-Q6PI2F9eekWbAxhroybl+aEcvqhdowN/EQlGwBUyJOzPFn9982u9xl6SU6XfGKzc', + [BraintreeModuleName.UsBankAccount]: + 'sha384-W3RLQSKJL1tQ3+Y7mP6tmkamtZ8bV4Yw/XxL9Jj5JtXHraD8loMF8hAIG5m2zK7F', + [BraintreeModuleName.GooglePayment]: + 'sha384-TITQMsFWA3elDNGP2mWz2oKBo4qjt+TlqzovcaPoeBiAh/wPBi6nae50ADdgU1/i', + [BraintreeModuleName.ThreeDSecure]: + 'sha384-YiycYN89ZlL/Evcaq4y1ajW1mhd7JYfZBDuOwHtIn4/Jnm20GL+/XCM+zYAwWD8D', + [BraintreeModuleName.VisaCheckout]: + 'sha384-/OdaTv9qrkKzjObtUe6yzfDhHozFW/+uXlrSl736sOZRJpjGQETvpYXqMFFg69xv', + [BraintreeModuleName.Venmo]: + 'sha384-dA+ojdW9vbJS4JavcXyOs9d5uZHJg5dNtYupoPSH2zsXIiYDVS5amLb/u8qmQ6oz', + [BraintreeModuleName.HostedFields]: + 'sha384-31ZkS58+iaWsW06ftW52zzVZFp43WPd7pNYRiddYynSNg6Oh7WGbSFGorAUH6T1z', + [BraintreeModuleName.Fastlane]: + 'sha384-jlbrwNvL3HvLAUCtLSmrnQ2GH1udCUrRS/bVmReAS5fuIVvcW8Pko/Gkg82tlgtr', + }, }; diff --git a/packages/braintree-utils/src/braintree-sdk-verison.ts b/packages/braintree-utils/src/braintree-sdk-verison.ts index 452975003a..c32c0f2c46 100644 --- a/packages/braintree-utils/src/braintree-sdk-verison.ts +++ b/packages/braintree-utils/src/braintree-sdk-verison.ts @@ -1,2 +1,3 @@ +export const BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION = '3.128.0'; export const BRAINTREE_SDK_DEFAULT_VERSION = '3.123.2'; export const BRAINTREE_SDK_STABLE_VERSION = '3.106.0'; diff --git a/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts b/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts index a2421d9d99..505e7d106b 100644 --- a/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts +++ b/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts @@ -10,6 +10,7 @@ import { import { BraintreeHostWindow } from './braintree'; import { BRAINTREE_SDK_DEFAULT_VERSION, + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BRAINTREE_SDK_STABLE_VERSION, } from './braintree-sdk-verison'; import BraintreeSDKVersionManager from './braintree-sdk-version-manager'; @@ -26,6 +27,7 @@ describe('BraintreeSDKVersionManager', () => { storeConfigMock.checkoutSettings.features = { 'PAYPAL-5636.update_braintree_sdk_version': false, + 'PAYPAL-5809.braintree_hosted_fields_fix_version': false, }; jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfig').mockReturnValue( @@ -59,6 +61,7 @@ describe('BraintreeSDKVersionManager', () => { storeConfigMock = getConfig().storeConfig; storeConfigMock.checkoutSettings.features = { 'PAYPAL-5636.update_braintree_sdk_version': true, + 'PAYPAL-5809.braintree_hosted_fields_fix_version': false, }; jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfig').mockReturnValue( @@ -68,6 +71,22 @@ describe('BraintreeSDKVersionManager', () => { expect(braintreeSDKVersionManager.getSDKVersion()).toBe(BRAINTREE_SDK_DEFAULT_VERSION); }); + it('get braintree sdk version with fixed hosted fields focus', () => { + storeConfigMock = getConfig().storeConfig; + storeConfigMock.checkoutSettings.features = { + 'PAYPAL-5809.braintree_hosted_fields_fix_version': true, + 'PAYPAL-5636.update_braintree_sdk_version': false, + }; + + jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfig').mockReturnValue( + storeConfigMock, + ); + + expect(braintreeSDKVersionManager.getSDKVersion()).toBe( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + }); + it('should get unmanageable version if exist in window.braintree', () => { Object.defineProperty(braintreeWindow, 'braintree', { value: { diff --git a/packages/braintree-utils/src/braintree-sdk-version-manager.ts b/packages/braintree-utils/src/braintree-sdk-version-manager.ts index b6336e52ce..0a21f62d77 100644 --- a/packages/braintree-utils/src/braintree-sdk-version-manager.ts +++ b/packages/braintree-utils/src/braintree-sdk-version-manager.ts @@ -6,6 +6,7 @@ import { isExperimentEnabled } from '@bigcommerce/checkout-sdk/utility'; import { BraintreeHostWindow } from './braintree'; import { BRAINTREE_SDK_DEFAULT_VERSION, + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BRAINTREE_SDK_STABLE_VERSION, } from './braintree-sdk-verison'; @@ -29,6 +30,10 @@ export default class BraintreeSDKVersionManager { return BRAINTREE_SDK_DEFAULT_VERSION; } + if (isExperimentEnabled(features, 'PAYPAL-5809.braintree_hosted_fields_fix_version')) { + return BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION; + } + return BRAINTREE_SDK_STABLE_VERSION; } diff --git a/packages/braintree-utils/src/index.ts b/packages/braintree-utils/src/index.ts index 5388b91b28..3b6614527c 100644 --- a/packages/braintree-utils/src/index.ts +++ b/packages/braintree-utils/src/index.ts @@ -10,6 +10,8 @@ export { default as createBraintreeSdk } from './create-braintree-sdk'; export { default as BraintreeSDKVersionManager } from './braintree-sdk-version-manager'; export { BRAINTREE_SDK_SCRIPTS_INTEGRITY } from './braintree-sdk-scripts-integrity'; export { BRAINTREE_SDK_STABLE_VERSION } from './braintree-sdk-verison'; +export { BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION } from './braintree-sdk-verison'; + export { default as mapToLegacyBillingAddress } from './map-to-legacy-billing-address'; export { default as mapToLegacyShippingAddress } from './map-to-legacy-shipping-address'; From 2f075c1906f40ab7774dc02cb066081fa4605714 Mon Sep 17 00:00:00 2001 From: "andrii.vitvitskyi" Date: Wed, 10 Sep 2025 13:58:35 +0300 Subject: [PATCH 2/2] feat(payment): PAYPAL-5809 Updated BT SDK version --- .../braintree-hosted-form.spec.ts | 15 +- .../braintree-hosted-form.ts | 2 +- .../src/braintree-script-loader.spec.ts | 273 +++++++++++++++++- .../src/braintree-sdk-scripts-integrity.ts | 2 +- .../src/braintree-sdk-version-manager.spec.ts | 10 +- .../src/braintree-sdk-version-manager.ts | 8 +- packages/braintree-utils/src/braintree.ts | 1 + packages/braintree-utils/src/index.ts | 8 +- 8 files changed, 303 insertions(+), 16 deletions(-) diff --git a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts index d6ea00abd5..0fb2deefc2 100644 --- a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts +++ b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.spec.ts @@ -1,6 +1,7 @@ -import {EventEmitter} from 'events'; +import { EventEmitter } from 'events'; import { + BRAINTREE_SDK_DEFAULT_VERSION, BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, BraintreeFormOptions, BraintreeHostedFields, @@ -15,11 +16,11 @@ import { PaymentInvalidFormError, } from '@bigcommerce/checkout-sdk/payment-integration-api'; -import {PaymentIntegrationServiceMock} from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; +import { PaymentIntegrationServiceMock } from '@bigcommerce/checkout-sdk/payment-integrations-test-utils'; -import {getScriptLoader} from '@bigcommerce/script-loader'; +import { getScriptLoader } from '@bigcommerce/script-loader'; -import {getBillingAddress} from '../mocks/braintree.mock'; +import { getBillingAddress } from '../mocks/braintree.mock'; import BraintreeHostedForm from './braintree-hosted-form'; @@ -105,6 +106,9 @@ describe('BraintreeHostedForm', () => { describe('#initialize', () => { it('creates and configures hosted fields', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_DEFAULT_VERSION, + ); const createMock = jest.fn(); const clientMock = { ...getClientMock(), @@ -212,6 +216,9 @@ describe('BraintreeHostedForm', () => { }); it('creates and configures hosted fields for stored card verification', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_DEFAULT_VERSION, + ); const createMock = jest.fn(); const clientMock = { ...getClientMock(), diff --git a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts index f25bb1c1b7..859b42b6eb 100644 --- a/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts +++ b/packages/braintree-integration/src/braintree-hosted-form/braintree-hosted-form.ts @@ -49,7 +49,7 @@ export default class BraintreeHostedForm { constructor( private braintreeScriptLoader: BraintreeScriptLoader, private braintreeSDKVersionManager: BraintreeSDKVersionManager, - ) {} + ) {} async initialize( options: BraintreeFormOptions, diff --git a/packages/braintree-utils/src/braintree-script-loader.spec.ts b/packages/braintree-utils/src/braintree-script-loader.spec.ts index 05d2cd2bf8..6605d7a208 100644 --- a/packages/braintree-utils/src/braintree-script-loader.spec.ts +++ b/packages/braintree-utils/src/braintree-script-loader.spec.ts @@ -153,7 +153,7 @@ describe('BraintreeScriptLoader', () => { integrity: braintreeSdkFixedHostedFieldsScriptsIntegrity[ BraintreeModuleName.Client - ], + ], }, }, ); @@ -258,6 +258,33 @@ describe('BraintreeScriptLoader', () => { expect(fastlane).toBe(fastlaneCreatorMock); }); + it('loads fastlane module with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + const fastlane = await braintreeScriptLoader.loadFastlane(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/fastlane.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.Fastlane + ], + }, + }, + ); + expect(fastlane).toBe(fastlaneCreatorMock); + }); + it('loads fastlane module with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -383,6 +410,34 @@ describe('BraintreeScriptLoader', () => { expect(paypalCheckout).toBe(paypalCheckoutMock); }); + it('loads the paypalCheckout with fixed hosted fields version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + const paypalCheckout = await braintreeScriptLoader.loadPaypalCheckout(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/paypal-checkout.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.PaypalCheckout + ], + }, + }, + ); + expect(paypalCheckout).toBe(paypalCheckout); + }); + it('loads PayPal checkout with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -508,6 +563,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads local payment methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadLocalPayment(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/local-payment.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.LocalPayment + ], + }, + }, + ); + }); + it('loads local payment methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -603,6 +685,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads google payment methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadGooglePayment(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/google-payment.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.GooglePayment + ], + }, + }, + ); + }); + it('loads google payment methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -697,6 +806,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads braintree paypal payment methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadPaypal(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/paypal.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.Paypal + ], + }, + }, + ); + }); + it('loads braintree paypal payment methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -791,6 +927,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads threeDSecure methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.load3DS(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/three-d-secure.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.ThreeDSecure + ], + }, + }, + ); + }); + it('loads threeDSecure methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -886,6 +1049,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads visaCheckout methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadVisaCheckout(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/visa-checkout.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.VisaCheckout + ], + }, + }, + ); + }); + it('loads visaCheckout methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -981,6 +1171,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads hostedFields methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadHostedFields(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/hosted-fields.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.HostedFields + ], + }, + }, + ); + }); + it('loads hostedFields methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -1075,6 +1292,33 @@ describe('BraintreeScriptLoader', () => { ); }); + it('loads venmoCheckout methods with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + + await braintreeScriptLoader.loadVenmoCheckout(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/venmo.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.Venmo + ], + }, + }, + ); + }); + it('loads venmoCheckout methods with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, @@ -1169,6 +1413,33 @@ describe('BraintreeScriptLoader', () => { expect(dataCollector).toBe(dataCollectorMock); }); + it('loads the data collector library with hosted fields fix version of braintree sdk', async () => { + jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValue( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); + const braintreeScriptLoader = new BraintreeScriptLoader( + scriptLoader, + mockWindow, + braintreeSDKVersionManager, + ); + const dataCollector = await braintreeScriptLoader.loadDataCollector(); + + expect(scriptLoader.loadScript).toHaveBeenCalledWith( + `//js.braintreegateway.com/web/${BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION}/js/data-collector.min.js`, + { + async: true, + attributes: { + crossorigin: 'anonymous', + integrity: + braintreeSdkFixedHostedFieldsScriptsIntegrity[ + BraintreeModuleName.DataCollector + ], + }, + }, + ); + expect(dataCollector).toBe(dataCollectorMock); + }); + it('loads the data collector library with default version of braintree sdk', async () => { jest.spyOn(braintreeSDKVersionManager, 'getSDKVersion').mockReturnValueOnce( BRAINTREE_SDK_DEFAULT_VERSION, diff --git a/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts b/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts index da2c1620e6..6c73bba897 100644 --- a/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts +++ b/packages/braintree-utils/src/braintree-sdk-scripts-integrity.ts @@ -60,7 +60,7 @@ export const BRAINTREE_SDK_SCRIPTS_INTEGRITY = { }, [BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION]: { [BraintreeModuleName.Client]: - 'sha384-9ndM/mgw6O0A+bW00GkQepJmY6Q6A3qioH406Z3WiEn+kqE6+1z/bNfr083WS/rt', + 'sha384-0WNxksIpRP+fYZiIdr12g6DBUQfLDzYCQJZtR/C8KkcwSGhPHfcFp3tTaGcMyFTq', [BraintreeModuleName.PaypalCheckout]: 'sha384-lWM1jk4BOxoDNV6Kop7tVNvQgINlmEVz0c/OwM+HNHK3QC8xqwd54Ovrt2QRDTjh', [BraintreeModuleName.Paypal]: diff --git a/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts b/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts index 505e7d106b..54f81e88b7 100644 --- a/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts +++ b/packages/braintree-utils/src/braintree-sdk-version-manager.spec.ts @@ -49,12 +49,18 @@ describe('BraintreeSDKVersionManager', () => { expect(braintreeSDKVersionManager.getSDKVersion()).toBe(BRAINTREE_SDK_STABLE_VERSION); }); - it('get default braintree sdk version if store config is not defined', () => { + it('get hosted fields fixed braintree sdk version if store config is not defined', () => { + jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfig').mockReturnValue( + storeConfigMock, + ); + jest.spyOn(paymentIntegrationService.getState(), 'getStoreConfig').mockReturnValueOnce( undefined, ); - expect(braintreeSDKVersionManager.getSDKVersion()).toBe(BRAINTREE_SDK_DEFAULT_VERSION); + expect(braintreeSDKVersionManager.getSDKVersion()).toBe( + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, + ); }); it('get default braintree sdk version', () => { diff --git a/packages/braintree-utils/src/braintree-sdk-version-manager.ts b/packages/braintree-utils/src/braintree-sdk-version-manager.ts index 0a21f62d77..a563e21835 100644 --- a/packages/braintree-utils/src/braintree-sdk-version-manager.ts +++ b/packages/braintree-utils/src/braintree-sdk-version-manager.ts @@ -26,14 +26,14 @@ export default class BraintreeSDKVersionManager { return preloadedVersion; } - if (isExperimentEnabled(features, 'PAYPAL-5636.update_braintree_sdk_version')) { - return BRAINTREE_SDK_DEFAULT_VERSION; - } - if (isExperimentEnabled(features, 'PAYPAL-5809.braintree_hosted_fields_fix_version')) { return BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION; } + if (isExperimentEnabled(features, 'PAYPAL-5636.update_braintree_sdk_version')) { + return BRAINTREE_SDK_DEFAULT_VERSION; + } + return BRAINTREE_SDK_STABLE_VERSION; } diff --git a/packages/braintree-utils/src/braintree.ts b/packages/braintree-utils/src/braintree.ts index fa9cee0573..b2abe5c38e 100644 --- a/packages/braintree-utils/src/braintree.ts +++ b/packages/braintree-utils/src/braintree.ts @@ -215,6 +215,7 @@ export interface BraintreeHostedFieldsCreatorConfig extends BraintreeModuleCreat '.valid'?: { [key: string]: string }; ':focus'?: { [key: string]: string }; }; + preventCursorJumps?: boolean; } export interface BraintreeHostedFieldOption { diff --git a/packages/braintree-utils/src/index.ts b/packages/braintree-utils/src/index.ts index 3b6614527c..58aaca0624 100644 --- a/packages/braintree-utils/src/index.ts +++ b/packages/braintree-utils/src/index.ts @@ -9,9 +9,11 @@ export { default as BraintreeSdk } from './braintree-sdk'; export { default as createBraintreeSdk } from './create-braintree-sdk'; export { default as BraintreeSDKVersionManager } from './braintree-sdk-version-manager'; export { BRAINTREE_SDK_SCRIPTS_INTEGRITY } from './braintree-sdk-scripts-integrity'; -export { BRAINTREE_SDK_STABLE_VERSION } from './braintree-sdk-verison'; -export { BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION } from './braintree-sdk-verison'; - +export { + BRAINTREE_SDK_STABLE_VERSION, + BRAINTREE_SDK_DEFAULT_VERSION, + BRAINTREE_SDK_HOSTED_FIELDS_FIX_VERSION, +} from './braintree-sdk-verison'; export { default as mapToLegacyBillingAddress } from './map-to-legacy-billing-address'; export { default as mapToLegacyShippingAddress } from './map-to-legacy-shipping-address';