From 2c6635f243df8ca522041fb2ee2994b90d74f620 Mon Sep 17 00:00:00 2001 From: SerhiiFilkovskyi Date: Wed, 7 May 2025 16:40:55 +0300 Subject: [PATCH 1/3] feature(payment): clone utils files to bigcommerce-payment-utils package --- .../bigcommerce-payments-utils/.eslintrc.json | 51 +- .../bigcommerce-payments-utils/jest.config.js | 2 + ...igcommerce-payments-fastlane-utils.spec.ts | 310 ++++++++++ .../bigcommerce-payments-fastlane-utils.ts | 281 +++++++++ .../src/bigcommerce-payments-sdk.spec.ts | 249 ++++++++ .../src/bigcommerce-payments-sdk.ts | 249 ++++++++ .../src/bigcommerce-payments-types.ts | 545 ++++++++++++++++++ ...igcommerce-payments-fastlane-utils.spec.ts | 10 + ...ate-bigcommerce-payments-fastlane-utils.ts | 7 + .../create-bigcommerce-payments-sdk.spec.ts | 10 + .../src/create-bigcommerce-payments-sdk.ts | 7 + .../bigcommerce-payments-utils/src/index.ts | 22 + ...igcommerce-payments-payment-method.mock.ts | 73 +++ ...pal-fastlane-authentication-result.mock.ts | 60 ++ .../src/mocks/get-paypal-fastlane-sdk.mock.ts | 9 + .../src/mocks/get-paypal-fastlane.mock.ts | 52 ++ .../src/mocks/index.ts | 7 + .../src/utils/get-fastlane-styles.spec.ts | 41 ++ .../src/utils/get-fastlane-styles.ts | 119 ++++ ...l-messages-styles-from-bnpl-config.spec.ts | 36 ++ ...paypal-messages-styles-from-bnpl-config.ts | 47 ++ .../src/utils/index.ts | 4 + ...ents-accelerated-checkout-customer.spec.ts | 25 + ...-payments-accelerated-checkout-customer.ts | 16 + ...igcommerce-payments-provider-error.spec.ts | 45 ++ .../is-bigcommerce-payments-provider-error.ts | 22 + .../utils/is-paypal-fastlane-customer.spec.ts | 21 + .../src/utils/is-paypal-fastlane-customer.ts | 17 + .../bigcommerce-payments-utils/tsconfig.json | 8 +- 29 files changed, 2335 insertions(+), 10 deletions(-) create mode 100644 packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts create mode 100644 packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts create mode 100644 packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts create mode 100644 packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.ts create mode 100644 packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts create mode 100644 packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts create mode 100644 packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-authentication-result.mock.ts create mode 100644 packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-sdk.mock.ts create mode 100644 packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane.mock.ts create mode 100644 packages/bigcommerce-payments-utils/src/mocks/index.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/index.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.spec.ts create mode 100644 packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.ts diff --git a/packages/bigcommerce-payments-utils/.eslintrc.json b/packages/bigcommerce-payments-utils/.eslintrc.json index 5626944bd1..ddfa261195 100644 --- a/packages/bigcommerce-payments-utils/.eslintrc.json +++ b/packages/bigcommerce-payments-utils/.eslintrc.json @@ -1,18 +1,53 @@ { - "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "extends": [ + "../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], "overrides": [ { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": {} + "files": [ + "*.ts", + "*.tsx" + ], + "rules": { + "@typescript-eslint/naming-convention": "off", + "@typescript-eslint/consistent-type-assertions": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unnecessary-condition": "off" + } }, { - "files": ["*.ts", "*.tsx"], - "rules": {} + "files": [ + "*.spec.ts" + ], + "rules": { + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/consistent-type-assertions": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/await-thenable": "off", + "jest/no-conditional-expect": "off", + "@typescript-eslint/no-floating-promises": "off", + "jest/valid-expect": "off", + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-unsafe-call": "off" + } }, { - "files": ["*.js", "*.jsx"], - "rules": {} + "files": [ + "*.mock.ts" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } } ] } diff --git a/packages/bigcommerce-payments-utils/jest.config.js b/packages/bigcommerce-payments-utils/jest.config.js index 7400fd718d..f4e0b215f9 100644 --- a/packages/bigcommerce-payments-utils/jest.config.js +++ b/packages/bigcommerce-payments-utils/jest.config.js @@ -4,11 +4,13 @@ module.exports = { globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json', + diagnostics: false, }, }, transform: { '^.+\\.[tj]sx?$': 'ts-jest', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + setupFilesAfterEnv: ['../../jest-setup.js'], coverageDirectory: '../../coverage/packages/bigcommerce-payments-utils', }; diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts new file mode 100644 index 0000000000..7dab838a40 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts @@ -0,0 +1,310 @@ +import { + PaymentMethodClientUnavailableError, + UntrustedShippingCardVerificationType, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { BrowserStorage } from '@bigcommerce/checkout-sdk/storage'; + +import BigCommercePaymentsFastlaneUtils from './bigcommerce-payments-fastlane-utils'; +import { + BigCommercePaymentsHostWindow, + PayPalFastlaneAuthenticationState, + PayPalFastlaneSdk, +} from './bigcommerce-payments-types'; +import { getPayPalFastlaneAuthenticationResultMock, getPayPalFastlaneSdk } from './mocks'; + +describe('BigCommercePaymentsFastlaneUtils', () => { + let browserStorage: BrowserStorage; + let paypalFastlaneSdk: PayPalFastlaneSdk; + let subject: BigCommercePaymentsFastlaneUtils; + + const methodIdMock = 'bigcommercepaymentsacceleratedcheckout'; // TODO:double check if this is correct + const authenticationResultMock = getPayPalFastlaneAuthenticationResultMock(); + + const bcAddressMock = { + address1: 'addressLine1', + address2: 'addressLine2', + city: 'addressCity', + company: 'BigCommerce', + country: 'United States', + countryCode: 'US', + customFields: [], + firstName: 'John', + lastName: 'Doe', + phone: '15551113344', + postalCode: '03004', + stateOrProvince: 'addressState', + stateOrProvinceCode: 'addressState', + }; + + const paypalToBcAddressMock = { + ...bcAddressMock, + id: 1, + country: 'US', + type: 'paypal-address', + }; + + const paypalToBcInstrumentMock = { + bigpayToken: 'nonce/token', + brand: 'Visa', + defaultInstrument: false, + expiryMonth: '12', + expiryYear: '2030', + iin: '', + last4: '1111', + method: 'bigcommercepaymentsacceleratedcheckout', + provider: 'bigcommercepaymentsacceleratedcheckout', + trustedShippingAddress: false, + untrustedShippingCardVerificationMode: UntrustedShippingCardVerificationType.PAN, + type: 'card', + }; + + beforeEach(() => { + browserStorage = new BrowserStorage('paypalFastlane'); + paypalFastlaneSdk = getPayPalFastlaneSdk(); + + subject = new BigCommercePaymentsFastlaneUtils(browserStorage); + + jest.spyOn(Date, 'now').mockImplementation(() => 1); + }); + + afterEach(() => { + (window as BigCommercePaymentsHostWindow).paypalFastlane = undefined; + + jest.resetAllMocks(); + jest.restoreAllMocks(); + + localStorage.clear(); + }); + + describe('#initializePayPalFastlane', () => { + it('initializes paypal fastlane with paypal sdk', async () => { + jest.spyOn(paypalFastlaneSdk, 'Fastlane'); + + await subject.initializePayPalFastlane(paypalFastlaneSdk, false); + + expect(paypalFastlaneSdk.Fastlane).toHaveBeenCalled(); + }); + + it('sets axo to sandbox mode if test mode is enabled', async () => { + jest.spyOn(Storage.prototype, 'setItem').mockImplementation(jest.fn); + + await subject.initializePayPalFastlane(paypalFastlaneSdk, true); + + expect(window.localStorage.setItem).toHaveBeenCalledWith('fastlaneEnv', 'sandbox'); + expect(window.localStorage.setItem).toHaveBeenCalledWith('axoEnv', 'sandbox'); + }); + }); + + describe('#getPayPalFastlaneOrThrow', () => { + it('successfully returns paypal fastlane with no errors', async () => { + const expectedResult = await subject.initializePayPalFastlane(paypalFastlaneSdk, false); + + expect(subject.getPayPalFastlaneOrThrow()).toEqual(expectedResult); + }); + + it('throws an error if paypal fastlane did not initialize before', () => { + try { + subject.getPayPalFastlaneOrThrow(); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + }); + + describe('#lookupCustomerOrThrow', () => { + const testEmail = 'john@doe.com'; + + it('successfully triggers lookup method with provided email', async () => { + const paypalConnectMock = await subject.initializePayPalFastlane( + paypalFastlaneSdk, + false, + ); + + await subject.lookupCustomerOrThrow(testEmail); + + expect(paypalConnectMock.identity.lookupCustomerByEmail).toHaveBeenCalledWith( + testEmail, + ); + }); + + it('throws an error if paypal fastlane did not initialize before', async () => { + try { + await subject.lookupCustomerOrThrow(testEmail); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + }); + + describe('#triggerAuthenticationFlowOrThrow', () => { + const customerContextIdMock = 'ryanRecognised123'; + + it('successfully triggers authentication flow with provided customer id and styles', async () => { + const paypalFastlaneMock = await subject.initializePayPalFastlane( + paypalFastlaneSdk, + false, + ); + + await subject.triggerAuthenticationFlowOrThrow(customerContextIdMock); + + expect(paypalFastlaneMock.identity.triggerAuthenticationFlow).toHaveBeenCalledWith( + customerContextIdMock, + ); + }); + + it('throws an error if paypal fastlane did not initialize before', async () => { + try { + await subject.triggerAuthenticationFlowOrThrow(customerContextIdMock); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + }); + + describe('#updateStorageSessionId', () => { + const sessionIdMock = 'cartId123'; + + it('sets session id to browser storage', () => { + jest.spyOn(browserStorage, 'setItem'); + + subject.updateStorageSessionId(false, sessionIdMock); + + expect(browserStorage.setItem).toHaveBeenCalledWith('sessionId', sessionIdMock); + }); + + it('removes session id from browser storage', () => { + jest.spyOn(browserStorage, 'removeItem'); + + subject.updateStorageSessionId(true, sessionIdMock); + + expect(browserStorage.removeItem).toHaveBeenCalledWith('sessionId'); + }); + }); + + describe('#getStorageSessionId', () => { + it('returns session id to browser storage', () => { + jest.spyOn(browserStorage, 'getItem'); + + subject.getStorageSessionId(); + + expect(browserStorage.getItem).toHaveBeenCalledWith('sessionId'); + }); + }); + + describe('#mapPayPalFastlaneProfileToBcCustomerData', () => { + it('returns default "empty" data if authenticationResult is undefined', () => { + expect(subject.mapPayPalFastlaneProfileToBcCustomerData(methodIdMock, {})).toEqual({ + authenticationState: PayPalFastlaneAuthenticationState.UNRECOGNIZED, + addresses: [], + billingAddress: undefined, + shippingAddress: undefined, + instruments: [], + }); + }); + + it('returns mapped PayPal Fastlane Profile to BC like data', () => { + expect( + subject.mapPayPalFastlaneProfileToBcCustomerData( + methodIdMock, + authenticationResultMock, + ), + ).toEqual({ + authenticationState: PayPalFastlaneAuthenticationState.SUCCEEDED, + addresses: [paypalToBcAddressMock], + billingAddress: paypalToBcAddressMock, + shippingAddress: paypalToBcAddressMock, + instruments: [paypalToBcInstrumentMock], + }); + }); + }); + + describe('#mapBcToPayPalInstrument()', () => { + it('maps and returns PayPal Instrument mapped to BC shape', () => { + const result = subject.mapPayPalToBcInstrument( + methodIdMock, + authenticationResultMock.profileData.card, + ); + + expect(result).toEqual([paypalToBcInstrumentMock]); + }); + }); + + describe('#mapBcToPayPalAddress()', () => { + it('maps and returns PayPal Address based on provided BC address', () => { + const result = subject.mapBcToPayPalAddress(bcAddressMock); + + expect(result).toEqual({ + addressLine1: 'addressLine1', + addressLine2: 'addressLine2', + adminArea1: 'addressState', + adminArea2: 'addressCity', + company: 'BigCommerce', + countryCode: 'US', + postalCode: '03004', + }); + }); + + it('set adminArea1 with stateOrProvince value if stateOrProvinceCode is empty', () => { + const addressMock = { + address1: 'addressLine1', + address2: 'addressLine2', + city: 'addressCity', + company: 'BigCommerce', + country: 'United States', + countryCode: 'US', + customFields: [], + firstName: 'John', + lastName: 'Doe', + phone: '15551113344', + postalCode: '03004', + stateOrProvince: 'addressState1', + stateOrProvinceCode: '', + }; + const result = subject.mapBcToPayPalAddress(addressMock); + + expect(result).toEqual({ + addressLine1: 'addressLine1', + addressLine2: 'addressLine2', + adminArea1: 'addressState1', + adminArea2: 'addressCity', + company: 'BigCommerce', + countryCode: 'US', + postalCode: '03004', + }); + }); + }); + + describe('#mapPayPalToBcAddress()', () => { + it('maps and returns PayPal Address based on provided BC address', () => { + const result = subject.mapPayPalToBcAddress( + authenticationResultMock.profileData.shippingAddress.address, + authenticationResultMock.profileData.shippingAddress.name, + authenticationResultMock.profileData.shippingAddress.phoneNumber, + [], + ); + + expect(result).toEqual(paypalToBcAddressMock); + }); + }); + + describe('#filterAddresses()', () => { + it('returns only one address if provided addresses are the same', () => { + const result = subject.filterAddresses([paypalToBcAddressMock, paypalToBcAddressMock]); + + expect(result).toHaveLength(1); + }); + + it('returns an array of addresses if provided addresses are different', () => { + const result = subject.filterAddresses([ + paypalToBcAddressMock, + { + ...paypalToBcAddressMock, + firstName: 'John', + lastName: 'Son', + }, + ]); + + expect(result).toHaveLength(2); + }); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts new file mode 100644 index 0000000000..cad3d3c2da --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts @@ -0,0 +1,281 @@ +import { isEqual, omit } from 'lodash'; + +import { + Address, + CardInstrument, + CustomerAddress, + PaymentMethodClientUnavailableError, + UntrustedShippingCardVerificationType, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; +import { BrowserStorage } from '@bigcommerce/checkout-sdk/storage'; + +import { + BigCommercePaymentsHostWindow, + PayPalFastlane, + PayPalFastlaneAddress, + PayPalFastlaneAuthenticationResult, + PayPalFastlaneAuthenticationState, + PayPalFastlaneLookupCustomerByEmailResult, + PayPalFastlaneProfileCard, + PayPalFastlaneProfileName, + PayPalFastlaneProfilePhone, + PayPalFastlaneProfileToBcCustomerDataMappingResult, + PayPalFastlaneSdk, + PayPalFastlaneStylesOption, +} from './bigcommerce-payments-types'; + +export default class BigCommercePaymentsFastlaneUtils { + private window: BigCommercePaymentsHostWindow; + + constructor(private browserStorage: BrowserStorage) { + this.window = window; + } + + async initializePayPalFastlane( + paypalFastlaneSdk: PayPalFastlaneSdk, + isTestModeEnabled: boolean, + styles?: PayPalFastlaneStylesOption, + ): Promise { + if (isTestModeEnabled) { + window.localStorage.setItem('fastlaneEnv', 'sandbox'); + window.localStorage.setItem('axoEnv', 'sandbox'); // TODO: remove if this key does not use on PayPal side + } + + if (!this.window.paypalFastlane) { + const defaultStyles = { + root: { + backgroundColorPrimary: 'transparent', + }, + }; + + this.window.paypalFastlane = await paypalFastlaneSdk.Fastlane({ + styles: styles || defaultStyles, + }); + } + + return this.window.paypalFastlane; + } + + getPayPalFastlaneOrThrow(): PayPalFastlane { + if (!this.window.paypalFastlane) { + throw new PaymentMethodClientUnavailableError(); + } + + return this.window.paypalFastlane; + } + + /** + * + * Detects the customer to PayPal Fastlane relation and + * returns customerContextId to use it for authentication + * + */ + async lookupCustomerOrThrow(email: string): Promise { + const paypalFastlane = this.getPayPalFastlaneOrThrow(); + + return paypalFastlane.identity.lookupCustomerByEmail(email); + } + + /** + * + * Triggers authentication flow (shows OTP popup) if the customer recognised as PayPal Fastlane user + * and returns PayPal Fastlane Profile data to use it in BC checkout + * + */ + async triggerAuthenticationFlowOrThrow( + customerContextId?: string, + ): Promise { + if (!customerContextId) { + return {}; + } + + const paypalFastlane = this.getPayPalFastlaneOrThrow(); + + return paypalFastlane.identity.triggerAuthenticationFlow(customerContextId); + } + + /** + * + * 'updateStorageSessionId' method is used to: + * - set session id after user was authenticated (or unrecognised) to trigger authentication after page refresh + * - remove sessionId from browser storage if the customer canceled PayPal Fastlane Authentication + * + * Flow info: + * If user unrecognised then the lookup method will be working but the OTP will not be shown + * If user recognised and not canceled then the lookup method will be working and the OTP will be shown only if needed + * If user cancels the OPT then OTP will not be triggered after page refresh + * + */ + updateStorageSessionId(shouldBeRemoved: boolean, sessionId?: string): void { + if (shouldBeRemoved) { + // TODO: Should be rewritten to cookies implementation + this.browserStorage.removeItem('sessionId'); + } else { + // TODO: Should be rewritten to cookies implementation + this.browserStorage.setItem('sessionId', sessionId); + } + } + + getStorageSessionId(): string { + // TODO: Should be rewritten to cookies implementation + return this.browserStorage.getItem('sessionId') || ''; + } + + /** + * + * 'mapPayPalFastlaneProfileToBcCustomerData' method is responsible for: + * - mapping PayPal Fastlane Profile data to BC data shape + * - returning mapped data to use for updating PaymentProviderCustomer state and + * update shipping and billing addresses + * + */ + mapPayPalFastlaneProfileToBcCustomerData( + methodId: string, + authenticationResult: PayPalFastlaneAuthenticationResult, + ): PayPalFastlaneProfileToBcCustomerDataMappingResult { + const { authenticationState, profileData } = authenticationResult; + + const paypalBillingAddress = profileData?.card?.paymentSource?.card?.billingAddress; + const paypalShippingAddress = profileData?.shippingAddress; + const paypalProfileName = profileData?.name; + const paypalInstrument = profileData?.card; + + const shippingAddress = paypalShippingAddress + ? this.mapPayPalToBcAddress( + paypalShippingAddress.address, + paypalShippingAddress.name, + paypalShippingAddress.phoneNumber, + ) + : undefined; + const billingAddress = + paypalBillingAddress && paypalProfileName + ? this.mapPayPalToBcAddress( + paypalBillingAddress, + paypalProfileName, + paypalShippingAddress?.phoneNumber, + ) + : undefined; + const instruments = paypalInstrument + ? this.mapPayPalToBcInstrument(methodId, paypalInstrument) + : []; + + const addresses = this.filterAddresses([shippingAddress, billingAddress]); + + return { + authenticationState: + authenticationState || PayPalFastlaneAuthenticationState.UNRECOGNIZED, + addresses, + billingAddress, + shippingAddress, + instruments, + }; + } + + mapPayPalToBcInstrument( + methodId: string, + instrument: PayPalFastlaneProfileCard, + ): CardInstrument[] { + const { id, paymentSource } = instrument; + const { brand, expiry, lastDigits } = paymentSource.card; + + const [expiryYear, expiryMonth] = expiry.split('-'); + + return [ + { + bigpayToken: id, + brand, + defaultInstrument: false, + expiryMonth, + expiryYear, + iin: '', + last4: lastDigits, + method: methodId, + provider: methodId, + trustedShippingAddress: false, + untrustedShippingCardVerificationMode: UntrustedShippingCardVerificationType.PAN, + type: 'card', + }, + ]; + } + + mapBcToPayPalAddress(address?: Address): PayPalFastlaneAddress { + return { + company: address?.company || '', + addressLine1: address?.address1 || '', + addressLine2: address?.address2 || '', + adminArea1: address?.stateOrProvinceCode || address?.stateOrProvince || '', + adminArea2: address?.city || '', + postalCode: address?.postalCode || '', + countryCode: address?.countryCode || '', + }; + } + + mapPayPalToBcAddress( + address: PayPalFastlaneAddress, + profileName: PayPalFastlaneProfileName, + phone?: PayPalFastlaneProfilePhone, + customFields?: CustomerAddress['customFields'], + ): CustomerAddress { + const [firstName, lastName] = profileName.fullName.split(' '); + + const phoneData = { + nationalNumber: phone?.nationalNumber || '', + countryCode: phone?.countryCode || '', + }; + + return { + id: Date.now(), + type: 'paypal-address', + firstName: profileName.firstName || firstName || '', + lastName: profileName.lastName || lastName || '', + company: address.company || '', + address1: address.addressLine1, + address2: address.addressLine2 || '', + city: address.adminArea2, + stateOrProvince: address.adminArea1, + stateOrProvinceCode: address.adminArea1, + country: address.countryCode || '', // TODO: update country with valid naming + countryCode: address.countryCode || '', + postalCode: address.postalCode, + phone: phoneData.countryCode + phoneData.nationalNumber, + customFields: customFields || [], + }; + } + + /** + * + * This method is responsible for filtering PayPal Fastlane addresses if they are the same + * and returns an array of addresses to use them for shipping and/or billing address selections + * so the customer will be able to use addresses from PayPal Fastlane in checkout flow + * + */ + filterAddresses(addresses: Array): CustomerAddress[] { + return addresses.reduce( + (customerAddresses: CustomerAddress[], currentAddress: CustomerAddress | undefined) => { + if (!currentAddress) { + return customerAddresses; + } + + const sameAddressInTheArray = customerAddresses.some((customerAddress) => + this.isEqualAddresses(customerAddress, currentAddress), + ); + + return sameAddressInTheArray + ? customerAddresses + : [...customerAddresses, currentAddress]; + }, + [], + ); + } + + private isEqualAddresses( + firstAddress: CustomerAddress, + secondAddress: CustomerAddress, + ): boolean { + return isEqual(this.normalizeAddress(firstAddress), this.normalizeAddress(secondAddress)); + } + + private normalizeAddress(address: CustomerAddress) { + return omit(address, ['id', 'phone']); + } +} diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts new file mode 100644 index 0000000000..3f77774cfd --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts @@ -0,0 +1,249 @@ +import { createScriptLoader, ScriptLoader } from '@bigcommerce/script-loader'; + +import { + MissingDataError, + PaymentMethod, + PaymentMethodClientUnavailableError, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; + +import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; +import { + BigCommercePaymentsHostWindow, + PayPalApmSdk, + PayPalFastlaneSdk, + PayPalMessagesSdk, +} from './bigcommerce-payments-types'; +import { + getBigCommercePaymentsAcceleratedCheckoutPaymentMethod, + getPayPalFastlaneSdk, +} from './mocks'; + +describe('BigCommercePaymentsSdk', () => { + let loader: ScriptLoader; + let paymentMethod: PaymentMethod; + let paypalFastlaneSdk: PayPalFastlaneSdk; + let subject: BigCommercePaymentsSdk; + let mockAPMPaymentMethod: PaymentMethod; + + const paypalMessagesSdk: PayPalMessagesSdk = { + Messages: jest.fn(), + }; + + const paypalApmsSdk: PayPalApmSdk = { + Buttons: jest.fn(), + PaymentFields: jest.fn(), + }; + + const sessionId = '8a232bf4-d9ba-4621-a1a9-ed8f685f92d1'; + const expectedSessionId = sessionId.replace(/-/g, ''); + + beforeEach(() => { + loader = createScriptLoader(); + paymentMethod = getBigCommercePaymentsAcceleratedCheckoutPaymentMethod(); + mockAPMPaymentMethod = { + ...paymentMethod, + id: 'oxxo', + initializationData: { + ...paymentMethod.initializationData, + enabledAlternativePaymentMethods: ['oxxo'], + availableAlternativePaymentMethods: ['oxxo'], + }, + }; + paypalFastlaneSdk = getPayPalFastlaneSdk(); + subject = new BigCommercePaymentsSdk(loader); + + jest.spyOn(loader, 'loadScript').mockImplementation(() => { + (window as BigCommercePaymentsHostWindow).paypalFastlaneSdk = paypalFastlaneSdk; + (window as BigCommercePaymentsHostWindow).paypalMessages = paypalMessagesSdk; + (window as BigCommercePaymentsHostWindow).paypalApms = paypalApmsSdk; + + return Promise.resolve(); + }); + }); + + afterEach(() => { + (window as BigCommercePaymentsHostWindow).paypalFastlaneSdk = undefined; + (window as BigCommercePaymentsHostWindow).paypalMessages = undefined; + (window as BigCommercePaymentsHostWindow).paypalApms = undefined; + + jest.clearAllMocks(); + }); + + describe('#getPayPalFastlaneSdk()', () => { + it('throws an error if clientId is not defined in payment method while getting configuration for PayPal Sdk', async () => { + const mockPaymentMethod = { + ...paymentMethod, + initializationData: { + ...paymentMethod.initializationData, + clientId: undefined, + }, + }; + + try { + await subject.getPayPalFastlaneSdk(mockPaymentMethod, 'USD', sessionId); + } catch (error: unknown) { + expect(error).toBeInstanceOf(MissingDataError); + } + }); + + it('loads PayPal Fastlane sdk script', async () => { + await subject.getPayPalFastlaneSdk(paymentMethod, 'USD', sessionId); + + expect(loader.loadScript).toHaveBeenCalledWith( + 'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane¤cy=USD&intent=capture', + { + async: true, + attributes: { + 'data-client-metadata-id': expectedSessionId, + 'data-namespace': 'paypalFastlaneSdk', + 'data-partner-attribution-id': '1123JLKJASD12', + 'data-user-id-token': 'asdcvY7XFSQasd', + }, + }, + ); + }); + + // TODO: remove this test when A/B testing will be finished + it('loads PayPal Fastlane Sdk script with connectClientToken for bigcommercepaymentstypescreditcards method', async () => { + const mockPaymentMethod = { + ...paymentMethod, + methodId: 'bigcommercepaymentstypescreditcards', // TODO: double check if this is correct + initializationData: { + ...paymentMethod.initializationData, + clientToken: undefined, + connectClientToken: 'connectClientToken123', + }, + }; + + await subject.getPayPalFastlaneSdk(mockPaymentMethod, 'USD', sessionId); + + expect(loader.loadScript).toHaveBeenCalledWith( + 'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane¤cy=USD&intent=capture', + { + async: true, + attributes: { + 'data-client-metadata-id': expectedSessionId, + 'data-namespace': 'paypalFastlaneSdk', + 'data-partner-attribution-id': '1123JLKJASD12', + 'data-user-id-token': 'connectClientToken123', + }, + }, + ); + }); + + it('throws an error if there was an issue with loading PayPal Fastlane Sdk', async () => { + jest.spyOn(loader, 'loadScript').mockImplementation(jest.fn()); + + try { + await subject.getPayPalFastlaneSdk(paymentMethod, 'USD', sessionId); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + + it('returns PayPal Fastlane Sdk', async () => { + const result = await subject.getPayPalFastlaneSdk(paymentMethod, 'USD', sessionId); + + expect(result).toEqual(paypalFastlaneSdk); + }); + }); + + describe('#getPayLaterMessages()', () => { + it('throws an error if clientId is not defined in payment method while getting configuration for PayPal Sdk', async () => { + const mockPaymentMethod = { + ...paymentMethod, + initializationData: { + ...paymentMethod.initializationData, + clientId: undefined, + }, + }; + + try { + await subject.getPayPalMessages(mockPaymentMethod, 'USD'); + } catch (error: unknown) { + expect(error).toBeInstanceOf(MissingDataError); + } + }); + + it('loads PayLater Messages sdk script', async () => { + await subject.getPayPalMessages(paymentMethod, 'USD'); + + expect(loader.loadScript).toHaveBeenCalledWith( + 'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&components=messages¤cy=USD', + { + async: true, + attributes: { + 'data-namespace': 'paypalMessages', + 'data-partner-attribution-id': '1123JLKJASD12', + }, + }, + ); + }); + + it('throws an error if there was an issue with loading paylater messages sdk', async () => { + jest.spyOn(loader, 'loadScript').mockImplementation(jest.fn()); + + try { + await subject.getPayPalMessages(paymentMethod, 'USD'); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + + it('returns PayPal Messages Sdk', async () => { + const result = await subject.getPayPalMessages(paymentMethod, 'USD'); + + expect(result).toEqual(paypalMessagesSdk); + }); + }); + + describe('#getPayPalApmsSdk()', () => { + it('throws an error if clientId is not defined in payment method while getting configuration for PayPal Sdk', async () => { + try { + await subject.getPayPalApmsSdk( + { + ...mockAPMPaymentMethod, + initializationData: { + ...mockAPMPaymentMethod.initializationData, + clientId: undefined, + }, + }, + 'USD', + ); + } catch (error: unknown) { + expect(error).toBeInstanceOf(MissingDataError); + } + }); + + it('loads APMs sdk script', async () => { + await subject.getPayPalApmsSdk(mockAPMPaymentMethod, 'USD'); + + expect(loader.loadScript).toHaveBeenCalledWith( + 'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&enable-funding=oxxo&commit=true&components=buttons%2Cpayment-fields¤cy=USD&intent=capture', + { + async: true, + attributes: { + 'data-namespace': 'paypalApms', + 'data-partner-attribution-id': '1123JLKJASD12', + }, + }, + ); + }); + + it('throws an error if there was an issue with loading APMs sdk', async () => { + jest.spyOn(loader, 'loadScript').mockImplementation(jest.fn()); + + try { + await subject.getPayPalApmsSdk(mockAPMPaymentMethod, 'USD'); + } catch (error: unknown) { + expect(error).toBeInstanceOf(PaymentMethodClientUnavailableError); + } + }); + + it('returns PayPal APMs Sdk', async () => { + const result = await subject.getPayPalApmsSdk(paymentMethod, 'USD'); + + expect(result).toEqual(paypalApmsSdk); + }); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts new file mode 100644 index 0000000000..d2658764e8 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts @@ -0,0 +1,249 @@ +import { ScriptLoader } from '@bigcommerce/script-loader'; + +import { + MissingDataError, + MissingDataErrorType, + PaymentMethod, + PaymentMethodClientUnavailableError, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; + +import { + BigCommercePaymentsHostWindow, + BigCommercePaymentsInitializationData, + PayPalFastlaneSdk, + PayPalMessagesSdk, + PayPalSdkConfig, +} from './bigcommerce-payments-types'; + +export default class BigCommercePaymentsSdk { + private window: BigCommercePaymentsHostWindow; + + constructor(private scriptLoader: ScriptLoader) { + this.window = window; + } + + async getPayPalFastlaneSdk( + paymentMethod: PaymentMethod, + currencyCode: string, + sessionId: string, + ): Promise { + if (!this.window.paypalFastlaneSdk) { + const config = this.getPayPalFastlaneSdkConfiguration( + paymentMethod, + currencyCode, + sessionId, + ); + + await this.loadPayPalSdk(config); + + if (!this.window.paypalFastlaneSdk) { + throw new PaymentMethodClientUnavailableError(); + } + } + + return this.window.paypalFastlaneSdk; + } + + async getPayPalApmsSdk( + paymentMethod: PaymentMethod, + currencyCode: string, + ) { + if (!this.window.paypalApms) { + const config = this.getPayPalApmSdkConfiguration(paymentMethod, currencyCode); + + await this.loadPayPalSdk(config); + + if (!this.window.paypalApms) { + throw new PaymentMethodClientUnavailableError(); + } + } + + return this.window.paypalApms; + } + + async getPayPalMessages( + paymentMethod: PaymentMethod, + currencyCode: string, + ): Promise { + if (!this.window.paypalMessages) { + const paypalSdkMessagesConfig = this.getPayPalSdkMessagesConfiguration( + paymentMethod, + currencyCode, + ); + + await this.loadPayPalSdk(paypalSdkMessagesConfig); + + if (!this.window.paypalMessages) { + throw new PaymentMethodClientUnavailableError(); + } + } + + return this.window.paypalMessages; + } + + /** + * + * loadPayPalSdk is a paypal sdk script loader + * which loads paypal sdk based on provided configuration + * + */ + private async loadPayPalSdk({ options, attributes }: PayPalSdkConfig): Promise { + const scriptOptions = this.transformConfig(options); + const scriptAttributes = this.transformConfig(attributes); + + const paypalSdkUrl = 'https://www.paypal.com/sdk/js'; + const scriptQuery = new URLSearchParams(scriptOptions).toString(); + const scriptSrc = `${paypalSdkUrl}?${scriptQuery}`; + + await this.scriptLoader.loadScript(scriptSrc, { + async: true, + attributes: scriptAttributes, + }); + } + + /** + * + * Configurations section + * + */ + private getPayPalFastlaneSdkConfiguration( + paymentMethod: PaymentMethod, + currencyCode: string, + sessionId: string, + ): PayPalSdkConfig { + const { clientToken, initializationData } = paymentMethod; + + if (!initializationData || !initializationData.clientId) { + throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod); + } + + const { + intent, + clientId, + merchantId, + attributionId, + connectClientToken, // TODO: remove when PPCP Fastlane A/B testing will be finished + } = initializationData; + + return { + options: { + 'client-id': clientId, + 'merchant-id': merchantId, + commit: true, + components: ['fastlane'], + currency: currencyCode, + intent, + }, + attributes: { + 'data-client-metadata-id': sessionId.replace(/-/g, ''), + 'data-namespace': 'paypalFastlaneSdk', + 'data-partner-attribution-id': attributionId, + 'data-user-id-token': connectClientToken || clientToken, + }, + }; + } + + private getPayPalApmSdkConfiguration( + paymentMethod: PaymentMethod, + currencyCode: string, + ): PayPalSdkConfig { + const { initializationData } = paymentMethod; + + if (!initializationData || !initializationData.clientId) { + throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod); + } + + const { + intent, + clientId, + merchantId, + buyerCountry, + attributionId, + isDeveloperModeApplicable, + availableAlternativePaymentMethods = [], + enabledAlternativePaymentMethods = [], + } = initializationData; + + const enableAPMsFunding = enabledAlternativePaymentMethods; + const disableAPMsFunding = availableAlternativePaymentMethods.filter( + (apm: string) => !enabledAlternativePaymentMethods.includes(apm), + ); + + return { + options: { + 'client-id': clientId, + 'merchant-id': merchantId, + 'enable-funding': enableAPMsFunding.length > 0 ? enableAPMsFunding : undefined, + 'disable-funding': disableAPMsFunding.length > 0 ? disableAPMsFunding : undefined, + commit: true, + components: ['buttons', 'payment-fields'], + currency: currencyCode, + intent, + ...(isDeveloperModeApplicable && { 'buyer-country': buyerCountry }), + }, + attributes: { + 'data-partner-attribution-id': attributionId, + 'data-namespace': 'paypalApms', + }, + }; + } + + private getPayPalSdkMessagesConfiguration( + paymentMethod: PaymentMethod, + currencyCode: string, + ): PayPalSdkConfig { + const { initializationData } = paymentMethod; + + if (!initializationData || !initializationData.clientId) { + throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod); + } + + const { clientId, merchantId, attributionId, isDeveloperModeApplicable, buyerCountry } = + initializationData; + + return { + options: { + 'client-id': clientId, + 'merchant-id': merchantId, + components: ['messages'], + currency: currencyCode, + ...(isDeveloperModeApplicable && { 'buyer-country': buyerCountry }), + }, + attributes: { + 'data-namespace': 'paypalMessages', + 'data-partner-attribution-id': attributionId, + }, + }; + } + + /** + * + * Utils methods + * + */ + private transformConfig>(config: T): Record { + let transformedConfig = {}; + + const keys = Object.keys(config) as Array; + + keys.forEach((key) => { + const value = config[key]; + + if ( + value === undefined || + value === null || + value === '' || + (Array.isArray(value) && value.length === 0) + ) { + return; + } + + transformedConfig = { + ...transformedConfig, + [key]: Array.isArray(value) ? value.join(',') : value, + }; + }); + + return transformedConfig; + } +} diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts new file mode 100644 index 0000000000..5d907dd363 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts @@ -0,0 +1,545 @@ +import { CardInstrument, CustomerAddress } from '@bigcommerce/checkout-sdk/payment-integration-api'; + +/** + * + * BigCommerce Payments Funding sources + * + */ +export type FundingType = string[]; +export type EnableFundingType = FundingType | string; + +/** + * + * BigCommerce Payments Initialization Data + * + */ +export interface BigCommercePaymentsInitializationData { + attributionId?: string; + availableAlternativePaymentMethods: FundingType; + buttonStyle?: PayPalButtonStyleOptions; + buyerCountry?: string; + clientId: string; + clientToken?: string; + fastlaneStyles?: FastlaneStylesSettings; + connectClientToken?: string; // TODO: remove when PPCP Fastlane A/B test will be finished + enabledAlternativePaymentMethods: FundingType; + isDeveloperModeApplicable?: boolean; + intent?: BigCommercePaymentsIntent; + isAcceleratedCheckoutEnabled?: boolean; // PayPal Fastlane related + isFastlaneShippingOptionAutoSelectEnabled?: boolean; // PayPal Fastlane related + isFastlaneStylingEnabled?: boolean; + isHostedCheckoutEnabled?: boolean; + isBigCommercePaymentsAnalyticsV2Enabled?: boolean; // PayPal Fastlane related //TODO: doublecheck BE Team + isPayPalCreditAvailable?: boolean; + isVenmoEnabled?: boolean; + isGooglePayEnabled?: boolean; + merchantId?: string; + orderId?: string; + shouldRenderFields?: boolean; + shouldRunAcceleratedCheckout?: boolean; // TODO: remove when PPCP Fastlane A/B test will be finished + paymentButtonStyles?: Record; + paypalBNPLConfiguration?: PayPalBNPLConfigurationItem[]; +} + +/** + * + * BigCommercePaymentsHostWindow contains different + * PayPal Sdk instances for different purposes + * + */ +export interface BigCommercePaymentsHostWindow extends Window { + paypalFastlane?: PayPalFastlane; + paypalFastlaneSdk?: PayPalFastlaneSdk; + paypalMessages?: PayPalMessagesSdk; + paypalApms?: PayPalApmSdk; +} + +/** + * + * PayPal SDK config + * + */ +export interface PayPalSdkConfig { + options: { + 'client-id'?: string; + 'merchant-id'?: string; + 'buyer-country'?: string; + 'enable-funding'?: EnableFundingType; + 'disable-funding'?: FundingType; + currency?: string; + commit?: boolean; + intent?: BigCommercePaymentsIntent; + components?: PayPalSdkComponents; + }; + attributes: { + 'data-client-metadata-id'?: string; + 'data-partner-attribution-id'?: string; + 'data-user-id-token'?: string; + 'data-namespace'?: string; + 'data-client-token'?: string; + }; +} + +export enum BigCommercePaymentsIntent { + AUTHORIZE = 'authorize', + CAPTURE = 'capture', +} + +export type PayPalSdkComponents = Array<'fastlane' | 'messages' | 'buttons' | 'payment-fields'>; + +/** + * + * PayPal Sdk instances + * + */ +export interface PayPalFastlaneSdk { + Fastlane(options?: PayPalFastlaneOptions): Promise; +} + +export interface PayPalMessagesSdk { + Messages(options: MessagingOptions): MessagingRender; +} + +export interface PayPalApmSdk { + Buttons(options: BigCommercePaymentsButtonsOptions): BigCommercePaymentsButtons; + PaymentFields( + options: BigCommercePaymentsPaymentFieldsOptions, + ): BigCommercePaymentsPaymentFields; +} + +/** + * + * BigCommerce Payments Buttons + * + */ +export interface BigCommercePaymentsButtons { + render(id: string): void; + close(): void; + isEligible(): boolean; +} + +export interface BigCommercePaymentsButtonsOptions { + style?: PayPalButtonStyleOptions; + fundingSource: string; + createOrder(): Promise; + onApprove( + data: PayPalButtonApproveCallbackPayload, + actions: PayPalButtonApproveCallbackActions, + ): Promise | void; + onInit?( + data: PayPalButtonInitCallbackPayload, + actions: PayPalButtonInitCallbackActions, + ): Promise; + onClick?( + data: PayPalButtonClickCallbackPayload, + actions: PayPalButtonClickCallbackActions, + ): Promise | void; + onError?(error: Error): void; + onCancel?(): void; +} + +export interface PayPalButtonClickCallbackPayload { + fundingSource: string; +} + +export interface PayPalButtonClickCallbackActions { + reject(): void; + resolve(): void; +} + +export interface PayPalButtonInitCallbackPayload { + correlationID: string; +} + +export interface PayPalButtonInitCallbackActions { + disable(): void; + enable(): void; +} + +export interface PayPalButtonApproveCallbackPayload { + orderID?: string; +} + +export interface PayPalButtonApproveCallbackActions { + order: { + get: () => Promise; + }; +} + +export interface PayPalOrderDetails { + payer: { + name: { + given_name: string; + surname: string; + }; + email_address: string; + address: PayPalOrderAddress; + }; + purchase_units: Array<{ + shipping: { + address: PayPalOrderAddress; + }; + }>; +} + +export interface PayPalOrderAddress { + address_line_1: string; + admin_area_2: string; + admin_area_1?: string; + postal_code: string; + country_code: string; +} + +export enum StyleButtonLabel { + paypal = 'paypal', + checkout = 'checkout', + buynow = 'buynow', + pay = 'pay', + installment = 'installment', +} + +export enum StyleButtonColor { + gold = 'gold', + blue = 'blue', + silver = 'silver', + black = 'black', + white = 'white', +} + +export enum StyleButtonShape { + pill = 'pill', + rect = 'rect', +} + +export interface PayPalButtonStyleOptions { + color?: StyleButtonColor; + shape?: StyleButtonShape; + height?: number; + label?: StyleButtonLabel; +} + +/** + * + * BigCommerce Payments PaymentFields fields + * + */ +export interface BigCommercePaymentsPaymentFields { + render(id: string): void; +} + +export interface BigCommercePaymentsPaymentFieldsOptions { + style?: BigCommercePaymentsFieldsStyleOptions; + fundingSource: string; + fields: { + name?: { + value?: string; + }; + email?: { + value?: string; + }; + }; +} + +export interface BigCommercePaymentsFieldsStyleOptions { + variables?: { + fontFamily?: string; + fontSizeBase?: string; + fontSizeSm?: string; + fontSizeM?: string; + fontSizeLg?: string; + textColor?: string; + colorTextPlaceholder?: string; + colorBackground?: string; + colorInfo?: string; + colorDanger?: string; + borderRadius?: string; + borderColor?: string; + borderWidth?: string; + borderFocusColor?: string; + spacingUnit?: string; + }; + rules?: { + [key: string]: any; + }; +} + +/** + * + * PayLater Messages related types + * doc: https://developer.paypal.com/docs/checkout/pay-later/us/integrate/reference + */ +export interface MessagingRender { + render(container: string): void; +} + +export interface MessagesStyleOptions { + color?: string; // 'blue' | 'black' | 'white' | 'white-no-border' | 'gray' | 'monochrome' | 'grayscale' + layout?: string; // 'text' | 'flex' + logo?: { + type?: string; // 'primary' | 'alternative' | 'inline' | 'none' + position?: string; // 'left' | 'right' | 'top' + }; + ratio?: string; // '1x1' | '1x4' | '8x1' | '20x1' + text?: { + align?: string; // 'left' | 'right' | 'center' + color?: string; // 'black' | 'white' | 'monochrome' | 'grayscale' + size?: number; // from 10 to 16 + }; +} + +export interface MessagingOptions { + amount: number; + placement: string; + style?: MessagesStyleOptions; +} + +export interface PayPalBNPLConfigurationItem { + id: string; + name: string; + status: boolean; + styles: Record; +} + +/** + * + * PayPal Fastlane related types + * + */ +export interface PayPalFastlane { + identity: PayPalFastlaneIdentity; + events: PayPalFastlaneEvents; + profile: PayPalFastlaneProfile; + FastlaneCardComponent( + options: PayPalFastlaneCardComponentOptions, + ): Promise; +} + +export interface PayPalFastlaneOptions { + styles?: PayPalFastlaneStylesOption; +} + +export interface PayPalFastlaneIdentity { + lookupCustomerByEmail(email: string): Promise; + triggerAuthenticationFlow( + customerContextId: string, + ): Promise; +} + +export interface PayPalFastlaneLookupCustomerByEmailResult { + customerContextId?: string; +} + +export interface PayPalFastlaneAuthenticationResult { + authenticationState?: PayPalFastlaneAuthenticationState; + profileData?: PayPalFastlaneProfileData; +} + +export enum PayPalFastlaneAuthenticationState { + SUCCEEDED = 'succeeded', + FAILED = 'failed', + CANCELED = 'cancelled', + UNRECOGNIZED = 'unrecognized', +} + +export interface PayPalFastlaneProfileData { + name: PayPalFastlaneProfileName; + shippingAddress: PayPalFastlaneShippingAddress; + card: PayPalFastlaneProfileCard; +} + +export interface PayPalFastlaneProfileName { + fullName: string; + firstName?: string; + lastName?: string; +} + +export interface PayPalFastlaneProfilePhone { + countryCode: string; + nationalNumber: string; +} + +export interface PayPalFastlaneShippingAddress { + name: PayPalFastlaneProfileName; + phoneNumber: PayPalFastlaneProfilePhone; + address: PayPalFastlaneAddress; +} + +export interface PayPalFastlaneProfileCard { + id: string; // nonce / token + paymentSource: PayPalFastlanePaymentSource; +} + +export interface PayPalFastlanePaymentSource { + card: PayPalFastlaneCardSource; +} + +export interface PayPalFastlaneCardSource { + brand: string; + expiry: string; // "YYYY-MM" + lastDigits: string; // "1111" + name: string; + billingAddress: PayPalFastlaneAddress; +} + +export interface PayPalFastlaneAddress { + company?: string; + addressLine1: string; + addressLine2?: string; + adminArea1: string; // State + adminArea2: string; // City + postalCode: string; + countryCode?: string; +} + +export interface PayPalFastlaneProfileToBcCustomerDataMappingResult { + authenticationState: PayPalFastlaneAuthenticationState; + addresses: CustomerAddress[]; + billingAddress?: CustomerAddress; + shippingAddress?: CustomerAddress; + instruments: CardInstrument[]; +} + +export interface PayPalFastlaneStylesOption { + root?: { + backgroundColorPrimary?: string; + errorColor?: string; + fontFamily?: string; + fontSizeBase?: string; + padding?: string; + primaryColor?: string; + }; + input?: { + borderRadius?: string; + borderColor?: string; + focusBorderColor?: string; + backgroundColor?: string; + borderWidth?: string; + textColorBase?: string; + }; + toggle?: { + colorPrimary?: string; + colorSecondary?: string; + }; + text?: { + body?: { + color?: string; + fontSize?: string; + }; + caption?: { + color?: string; + fontSize?: string; + }; + }; + branding?: string; // 'light' | 'dark' +} + +export interface PayPalFastlaneProfile { + showCardSelector(): Promise; + showShippingAddressSelector(): Promise; +} + +export interface PayPalFastlaneShippingAddressSelectorResponse { + selectionChanged: boolean; + selectedAddress: PayPalFastlaneShippingAddress; +} + +export interface PayPalFastlaneCardSelectorResponse { + selectionChanged: boolean; + selectedCard: PayPalFastlaneProfileCard; +} + +export interface PayPalFastlaneCardComponentMethods { + getPaymentToken( + options: PayPalFastlaneGetPaymentTokenOptions, + ): Promise; + render(element: string): void; +} + +export interface PayPalFastlaneGetPaymentTokenOptions { + name?: PayPalFastlaneProfileName; + billingAddress?: PayPalFastlaneAddress; +} + +export interface PayPalFastlaneCardComponentOptions { + fields?: PayPalFastlaneCardComponentFields; +} + +export interface PayPalFastlaneCardComponentFields { + cardholderName?: { + enabled?: boolean; + prefill?: string; + }; + phoneNumber?: { + placeholder?: string; + prefill?: string; + }; +} + +export interface PayPalFastlaneEvents { + apmSelected: (options: PayPalFastlaneApmSelectedEventOptions) => void; + emailSubmitted: (options: PayPalFastlaneEmailEnteredEventOptions) => void; + orderPlaced: (options: PayPalFastlaneOrderPlacedEventOptions) => void; +} + +export interface PayPalFastlaneEventCommonOptions { + context_type: 'cs_id'; + context_id: string; // checkout session id + page_type: 'checkout_page'; + page_name: string; // title of the checkout initiation page + partner_name: 'bigc'; + user_type: 'store_member' | 'store_guest'; // type of the user on the merchant site + store_id: string; + merchant_name: string; + experiment: string; // stringify JSON object "[{ treatment_group: 'test' | 'control' }]" +} + +export interface PayPalFastlaneApmSelectedEventOptions extends PayPalFastlaneEventCommonOptions { + apm_shown: '0' | '1'; // alternate payment shown on the checkout page + apm_list: string; // list of alternate payment shown on checkout page + apm_selected: string; // alternate payment method selected / methodId + apm_location: 'pre-email section' | 'payment section'; // placement of APM, whether it be above the email entry or in the radio buttons +} + +export interface PayPalFastlaneEmailEnteredEventOptions extends PayPalFastlaneEventCommonOptions { + user_email_saved: boolean; // shows whether checkout was loaded with or without a saved email + apm_shown: '0' | '1'; // alternate payment shown on the checkout page + apm_list: string; // list of alternate payment shown on checkout page 'applepay,googlepay,paypal' +} + +export interface PayPalFastlaneOrderPlacedEventOptions extends PayPalFastlaneEventCommonOptions { + selected_payment_method: string; + currency_code: string; +} + +export interface PayPalFastlanePaymentFormattedPayload { + paypal_connect_token?: { + order_id?: string; + token: string; + }; + paypal_fastlane_token?: { + order_id?: string; + token: string; + }; +} + +export interface FastlaneStylesSettings { + fastlaneRootSettingsBackgroundColor?: string; + fastlaneRootSettingsErrorColor?: string; + fastlaneRootSettingsFontFamily?: string; + fastlaneRootSettingsPadding?: string; + fastlaneRootSettingsPrimaryColor?: string; + fastlaneRootSettingsFontSize?: string; + fastlaneInputSettingsBackgroundColor?: string; + fastlaneInputSettingsBorderRadius?: string; + fastlaneInputSettingsBorderWidth?: string; + fastlaneInputSettingsTextColorBase?: string; + fastlaneInputSettingsBorderColor?: string; + fastlaneInputSettingsFocusBorderBase?: string; + fastlaneToggleSettingsColorPrimary?: string; + fastlaneToggleSettingsColorSecondary?: string; + fastlaneTextBodySettingsColor?: string; + fastlaneTextBodySettingsFontSize?: string; + fastlaneTextCaptionSettingsFontSize?: string; + fastlaneTextCaptionSettingsColor?: string; + fastlaneBrandingSettings?: string; +} diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.spec.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.spec.ts new file mode 100644 index 0000000000..f45681262c --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.spec.ts @@ -0,0 +1,10 @@ +import BigCommercePaymentsFastlaneUtils from './bigcommerce-payments-fastlane-utils'; +import createBigCommercePaymentsFastlaneUtils from './create-bigcommerce-payments-fastlane-utils'; + +describe('createBigCommercePaymentsFastlaneUtils', () => { + it('instantiates BigCommerce Payments Fastlane utils class', () => { + const utilsClass = createBigCommercePaymentsFastlaneUtils(); + + expect(utilsClass).toBeInstanceOf(BigCommercePaymentsFastlaneUtils); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.ts new file mode 100644 index 0000000000..fb27acfe26 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-fastlane-utils.ts @@ -0,0 +1,7 @@ +import { BrowserStorage } from '@bigcommerce/checkout-sdk/storage'; + +import BigCommercePaymentsFastlaneUtils from './bigcommerce-payments-fastlane-utils'; + +export default function createBigCommercePaymentsFastlaneUtils(): BigCommercePaymentsFastlaneUtils { + return new BigCommercePaymentsFastlaneUtils(new BrowserStorage('paypalFastlane')); +} diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts new file mode 100644 index 0000000000..f2df03150d --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts @@ -0,0 +1,10 @@ +import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; +import createBigCommercePaymentsSdk from './create-bigcommerce-payments-sdk'; + +describe('createBigCommercePaymentsSdk', () => { + it('instantiates BigCommerce Payments SDK', () => { + const BigCommercePaymentsSdkInstance = createBigCommercePaymentsSdk(); + + expect(BigCommercePaymentsSdkInstance).toBeInstanceOf(BigCommercePaymentsSdk); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts new file mode 100644 index 0000000000..1b2663c5bd --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts @@ -0,0 +1,7 @@ +import { createScriptLoader } from '@bigcommerce/script-loader'; + +import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; + +export default function createBigCommercePaymentsSdk(): BigCommercePaymentsSdk { + return new BigCommercePaymentsSdk(createScriptLoader()); +} diff --git a/packages/bigcommerce-payments-utils/src/index.ts b/packages/bigcommerce-payments-utils/src/index.ts index e69de29bb2..35754dacb9 100644 --- a/packages/bigcommerce-payments-utils/src/index.ts +++ b/packages/bigcommerce-payments-utils/src/index.ts @@ -0,0 +1,22 @@ +export * from './bigcommerce-payments-types'; +export * from './mocks'; +export * from './utils'; + +// TODO: this export should be moved to ./utils/index.ts file +export { default as isBigCommercePaymentsProviderError } from './utils/is-bigcommerce-payments-provider-error'; + +/** + * + * BigCommerce Payments Sdk exports + * + * */ +export { default as createBigCommercePaymentsSdk } from './create-bigcommerce-payments-sdk'; +export { default as BigCommercePaymentsSdk } from './bigcommerce-payments-sdk'; + +/** + * + * BigCommerce Payments Fastlane utils exports + * + */ +export { default as createBigCommercePaymentsFastlaneUtils } from './create-bigcommerce-payments-fastlane-utils'; +export { default as BigCommercePaymentsFastlaneUtils } from './bigcommerce-payments-fastlane-utils'; diff --git a/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts b/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts new file mode 100644 index 0000000000..18a91151c9 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts @@ -0,0 +1,73 @@ +import { PaymentMethod } from '@bigcommerce/checkout-sdk/payment-integration-api'; + +import { BigCommercePaymentsIntent } from '../bigcommerce-payments-types'; + +export default function getBigCommercePaymentsPaymentMethod(): PaymentMethod { + return { + id: 'bigcommercepayments', + logoUrl: '', + method: 'paypal', + supportedCards: [], + clientToken: 'asdcvY7XFSQasd', + config: { + testMode: true, + merchantId: 'JTS4DY7XFSQZE', + }, + initializationData: { + buttonStyle: { + height: 55, + color: 'black', + label: 'pay', + }, + paymentButtonStyles: { + cartButtonStyles: { + color: 'black', + label: 'checkout', + }, + pdpButtonStyles: { + color: 'black', + label: 'checkout', + }, + checkoutTopButtonStyles: { + color: 'silver', + label: 'checkout', + }, + checkoutPaymentButtonStyles: { + color: 'black', + label: 'pay', + height: 55, + }, + }, + availableAlternativePaymentMethods: [], + clientId: 'abc', + merchantId: 'JTS4DY7XFSQZE', + orderId: '3U4171152W1482642', + attributionId: '1123JLKJASD12', + intent: BigCommercePaymentsIntent.CAPTURE, + isAcceleratedCheckoutEnabled: false, + isBigCommercePaymentsAnalyticsV2Enabled: false, + isPayPalCreditAvailable: false, + isVenmoEnabled: false, + shouldRenderFields: true, + shouldRunAcceleratedCheckout: false, + isHostedCheckoutEnabled: false, + isDeveloperModeApplicable: false, + }, + type: 'PAYMENT_TYPE_API', + }; +} + +export function getBigCommercePaymentsAcceleratedCheckoutPaymentMethod(): PaymentMethod { + const bigCommercePaymentsDefaultPaymentMethod = getBigCommercePaymentsPaymentMethod(); + + return { + ...bigCommercePaymentsDefaultPaymentMethod, + id: 'bigcommercepaymentsacceleratedcheckout', // TODO: check BE team if this is correct + initializationData: { + ...bigCommercePaymentsDefaultPaymentMethod.initializationData, + isAcceleratedCheckoutEnabled: true, + shouldRunAcceleratedCheckout: true, + isBigCommercePaymentsAnalyticsV2Enabled: true, + }, + }; +} diff --git a/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-authentication-result.mock.ts b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-authentication-result.mock.ts new file mode 100644 index 0000000000..27090e7afd --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-authentication-result.mock.ts @@ -0,0 +1,60 @@ +import { PayPalFastlaneAuthenticationState } from '../bigcommerce-payments-types'; + +export default function getPayPalFastlaneAuthenticationResultMock() { + return { + authenticationState: PayPalFastlaneAuthenticationState.SUCCEEDED, + profileData: { + name: { + fullName: 'John Doe', + firstName: 'John', + lastName: 'Doe', + }, + shippingAddress: { + address: { + company: 'BigCommerce', + addressLine1: 'addressLine1', + addressLine2: 'addressLine2', + adminArea1: 'addressState', + adminArea2: 'addressCity', + postalCode: '03004', + countryCode: 'US', + }, + name: { + fullName: 'John Doe', + firstName: 'John', + lastName: 'Doe', + }, + phoneNumber: { + nationalNumber: '5551113344', + countryCode: '1', + }, + }, + card: { + id: 'nonce/token', + paymentSource: { + card: { + brand: 'Visa', + expiry: '2030-12', + lastDigits: '1111', + name: 'John Doe', + billingAddress: { + firstName: 'John', + lastName: 'Doe', + company: 'BigCommerce', + addressLine1: 'addressLine1', + addressLine2: 'addressLine2', + adminArea1: 'addressState', + adminArea2: 'addressCity', + postalCode: '03004', + countryCode: 'US', + phone: { + nationalNumber: '5551113344', + countryCode: '1', + }, + }, + }, + }, + }, + }, + }; +} diff --git a/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-sdk.mock.ts b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-sdk.mock.ts new file mode 100644 index 0000000000..41c853e70c --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane-sdk.mock.ts @@ -0,0 +1,9 @@ +import { PayPalFastlaneSdk } from '../bigcommerce-payments-types'; + +import getPayPalFastlane from './get-paypal-fastlane.mock'; + +export default function getPayPalFastlaneSdk(): PayPalFastlaneSdk { + return { + Fastlane: () => Promise.resolve(getPayPalFastlane()), + }; +} diff --git a/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane.mock.ts b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane.mock.ts new file mode 100644 index 0000000000..85f533303a --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/mocks/get-paypal-fastlane.mock.ts @@ -0,0 +1,52 @@ +import { PayPalFastlane, PayPalFastlaneCardComponentMethods } from '../bigcommerce-payments-types'; + +export default function getPayPalFastlane(): PayPalFastlane { + const paypalFastlaneComponentMethods: PayPalFastlaneCardComponentMethods = { + // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + getPaymentToken: jest.fn(() => ({ + id: 'paypal_fastlane_instrument_id_nonce', + paymentSource: { + card: { + brand: 'Visa', + expiry: '2030-12', + lastDigits: '1111', + name: 'John Doe', + billingAddress: { + firstName: 'John', + lastName: 'Doe', + company: 'BigCommerce', + streetAddress: 'addressLine1', + extendedAddress: 'addressLine2', + locality: 'addressCity', + region: 'addressState', + postalCode: '03004', + countryCodeAlpha2: 'US', + }, + }, + }, + })), + render: jest.fn(), + }; + + return { + identity: { + lookupCustomerByEmail: jest.fn(), + triggerAuthenticationFlow: jest.fn(), + }, + events: { + apmSelected: jest.fn(), + emailSubmitted: jest.fn(), + orderPlaced: jest.fn(), + }, + profile: { + showCardSelector: jest.fn(), + showShippingAddressSelector: jest.fn(), + }, + // TODO: remove ts-ignore and update test with related type (PAYPAL-4383) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + FastlaneCardComponent: jest.fn(() => paypalFastlaneComponentMethods), + }; +} diff --git a/packages/bigcommerce-payments-utils/src/mocks/index.ts b/packages/bigcommerce-payments-utils/src/mocks/index.ts new file mode 100644 index 0000000000..8b41537bc9 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/mocks/index.ts @@ -0,0 +1,7 @@ +export { + default as getBigCommercePaymentsPaymentMethod, + getBigCommercePaymentsAcceleratedCheckoutPaymentMethod, +} from './get-bigcommerce-payments-payment-method.mock'; +export { default as getPayPalFastlaneSdk } from './get-paypal-fastlane-sdk.mock'; +export { default as getPayPalFastlane } from './get-paypal-fastlane.mock'; +export { default as getPayPalFastlaneAuthenticationResultMock } from './get-paypal-fastlane-authentication-result.mock'; diff --git a/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.spec.ts b/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.spec.ts new file mode 100644 index 0000000000..2210439a6a --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.spec.ts @@ -0,0 +1,41 @@ +import getFastlaneStyles from './get-fastlane-styles'; + +describe('#getFastlaneStyles()', () => { + it('returns styles options with provided modifications', () => { + const styles = { + fastlaneRootSettingsBackgroundColor: 'red', + fastlaneInputSettingsBorderColor: 'green', + fastlaneTextBodySettingsFontSize: '12px', + fastlaneTextBodySettingsColor: 'blue', + }; + + const uiStyles = { + root: { + backgroundColorPrimary: 'green', + }, + text: { + caption: { + fontSize: '15px', + }, + }, + }; + + expect(getFastlaneStyles(styles, uiStyles)).toEqual({ + root: { + backgroundColorPrimary: 'red', + }, + input: { + borderColor: 'green', + }, + text: { + body: { + fontSize: '12px', + color: 'blue', + }, + caption: { + fontSize: '15px', + }, + }, + }); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.ts b/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.ts new file mode 100644 index 0000000000..a48d362903 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/get-fastlane-styles.ts @@ -0,0 +1,119 @@ +import { omitBy } from 'lodash'; + +import { FastlaneStylesSettings, PayPalFastlaneStylesOption } from '../index'; + +function isInvalidStyleOption(styleOption: unknown) { + return typeof styleOption !== 'string'; +} + +export default function getFastlaneStyles( + styleSettings?: FastlaneStylesSettings, + uiStyles?: PayPalFastlaneStylesOption, +) { + if (!uiStyles && !styleSettings) { + return undefined; + } + + return cleanUpFastlaneStyles(mergeFastlaneStyles(styleSettings, uiStyles)); +} + +function mergeFastlaneStyles( + styleSettings?: FastlaneStylesSettings, + uiStyles?: PayPalFastlaneStylesOption, +): PayPalFastlaneStylesOption { + return { + root: { + backgroundColorPrimary: + styleSettings?.fastlaneRootSettingsBackgroundColor || + uiStyles?.root?.backgroundColorPrimary, + errorColor: styleSettings?.fastlaneRootSettingsErrorColor || uiStyles?.root?.errorColor, + fontFamily: styleSettings?.fastlaneRootSettingsFontFamily || uiStyles?.root?.fontFamily, + fontSizeBase: + styleSettings?.fastlaneRootSettingsFontSize || uiStyles?.root?.fontSizeBase, + padding: styleSettings?.fastlaneRootSettingsPadding || uiStyles?.root?.padding, + primaryColor: + styleSettings?.fastlaneRootSettingsPrimaryColor || uiStyles?.root?.primaryColor, + }, + input: { + borderRadius: + styleSettings?.fastlaneInputSettingsBorderRadius || uiStyles?.input?.borderRadius, + borderColor: + styleSettings?.fastlaneInputSettingsBorderColor || uiStyles?.input?.borderColor, + focusBorderColor: + styleSettings?.fastlaneInputSettingsFocusBorderBase || + uiStyles?.input?.focusBorderColor, + backgroundColor: + styleSettings?.fastlaneInputSettingsBackgroundColor || + uiStyles?.input?.backgroundColor, + borderWidth: + styleSettings?.fastlaneInputSettingsBorderWidth || uiStyles?.input?.borderWidth, + textColorBase: + styleSettings?.fastlaneInputSettingsTextColorBase || uiStyles?.input?.textColorBase, + }, + toggle: { + colorPrimary: + styleSettings?.fastlaneToggleSettingsColorPrimary || uiStyles?.toggle?.colorPrimary, + colorSecondary: + styleSettings?.fastlaneToggleSettingsColorSecondary || + uiStyles?.toggle?.colorSecondary, + }, + text: { + body: { + color: styleSettings?.fastlaneTextBodySettingsColor || uiStyles?.text?.body?.color, + fontSize: + styleSettings?.fastlaneTextBodySettingsFontSize || + uiStyles?.text?.body?.fontSize, + }, + caption: { + color: + styleSettings?.fastlaneTextCaptionSettingsColor || + uiStyles?.text?.caption?.color, + fontSize: + styleSettings?.fastlaneTextCaptionSettingsFontSize || + uiStyles?.text?.caption?.fontSize, + }, + }, + branding: styleSettings?.fastlaneBrandingSettings || uiStyles?.branding, + }; +} + +function cleanUpFastlaneStyles(styles: PayPalFastlaneStylesOption) { + const fastlaneStyles: PayPalFastlaneStylesOption = {}; + + const root = omitBy(styles.root, isInvalidStyleOption); + const input = omitBy(styles.input, isInvalidStyleOption); + const toggle = omitBy(styles.toggle, isInvalidStyleOption); + const textBody = omitBy(styles.text?.body, isInvalidStyleOption); + const textCaption = omitBy(styles.text?.caption, isInvalidStyleOption); + const branding = styles.branding; + + if (Object.keys(root).length) { + fastlaneStyles.root = root; + } + + if (Object.keys(input).length) { + fastlaneStyles.input = input; + } + + if (Object.keys(toggle).length) { + fastlaneStyles.toggle = toggle; + } + + if (Object.keys(textBody).length) { + fastlaneStyles.text = {}; + fastlaneStyles.text.body = textBody; + } + + if (Object.keys(textCaption).length) { + fastlaneStyles.text = { + ...fastlaneStyles.text, + }; + fastlaneStyles.text.caption = textCaption; + } + + if (branding) { + fastlaneStyles.branding = branding; + } + + return fastlaneStyles; +} diff --git a/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts new file mode 100644 index 0000000000..7f03a442b6 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts @@ -0,0 +1,36 @@ +import getPaypalMessagesStylesFromBNPLConfig from './get-paypal-messages-styles-from-bnpl-config'; + +describe('getPaypalMessagesStylesFromBNPLConfig', () => { + it('returns PayPal Commerce Messages Style Options from BNPL Config', () => { + const input = { + id: 'checkout', + name: 'Checkout page', + status: true, + styles: { + color: 'white-no-border', + layout: 'text', + 'logo-type': 'alternative', + 'logo-position': 'right', + ratio: '8x1', + 'text-color': 'white', + 'text-size': '10', + }, + }; + + const expectedOutput = { + color: 'white-no-border', + layout: 'text', + logo: { + type: 'alternative', + position: 'right', + }, + ratio: '8x1', + text: { + color: 'white', + size: 10, + }, + }; + + expect(getPaypalMessagesStylesFromBNPLConfig(input)).toStrictEqual(expectedOutput); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.ts b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.ts new file mode 100644 index 0000000000..e670a94800 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.ts @@ -0,0 +1,47 @@ +import { MessagesStyleOptions, PayPalBNPLConfigurationItem } from '../bigcommerce-payments-types'; + +function getPaypalMessagesStylesFromBNPLConfig({ + styles, +}: PayPalBNPLConfigurationItem): MessagesStyleOptions { + const messagesStyles: MessagesStyleOptions = {}; + + if (styles.color) { + messagesStyles.color = styles.color; + } + + if (styles.layout) { + messagesStyles.layout = styles.layout; + } + + if (styles['logo-type'] || styles['logo-position']) { + messagesStyles.logo = {}; + + if (styles['logo-type']) { + messagesStyles.logo.type = styles['logo-type']; + } + + if (styles['logo-position']) { + messagesStyles.logo.position = styles['logo-position']; + } + } + + if (styles.ratio) { + messagesStyles.ratio = styles.ratio; + } + + if (styles['text-color'] || styles['text-size']) { + messagesStyles.text = {}; + + if (styles['text-color']) { + messagesStyles.text.color = styles['text-color']; + } + + if (styles['text-size']) { + messagesStyles.text.size = +styles['text-size']; + } + } + + return messagesStyles; +} + +export default getPaypalMessagesStylesFromBNPLConfig; diff --git a/packages/bigcommerce-payments-utils/src/utils/index.ts b/packages/bigcommerce-payments-utils/src/utils/index.ts new file mode 100644 index 0000000000..95c7f1aac6 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/index.ts @@ -0,0 +1,4 @@ +export { default as isBigCommercePaymentsAcceleratedCheckoutCustomer } from './is-bigcommerce-payments-accelerated-checkout-customer'; +export { default as isPayPalFastlaneCustomer } from './is-paypal-fastlane-customer'; +export { default as getFastlaneStyles } from './get-fastlane-styles'; +export { default as getPaypalMessagesStylesFromBNPLConfig } from './get-paypal-messages-styles-from-bnpl-config'; diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts new file mode 100644 index 0000000000..0bc83e7960 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts @@ -0,0 +1,25 @@ +import isBigCommercePaymentsAcceleratedCheckoutCustomer from './is-bigcommerce-payments-accelerated-checkout-customer'; + +describe('isBigCommercePaymentsAcceleratedCheckoutCustomer', () => { + it('returns true if payment provider customer is BigCommercePayments related', () => { + const paymentProviderCustomer = { + authenticationState: 'success', + addresses: [], + instruments: [], + }; + + expect(isBigCommercePaymentsAcceleratedCheckoutCustomer(paymentProviderCustomer)).toBe( + true, + ); + }); + + it('returns false if payment provider customer is not BigCommercePayments related', () => { + const paymentProviderCustomer = { + stripeLinkAuthenticationState: true, + }; + + expect(isBigCommercePaymentsAcceleratedCheckoutCustomer(paymentProviderCustomer)).toBe( + false, + ); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts new file mode 100644 index 0000000000..79a84d4553 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts @@ -0,0 +1,16 @@ +import { + PaymentProviderCustomer, + PayPalConnectCustomer, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; + +export default function isBigCommercePaymentsAcceleratedCheckoutCustomer( + customer?: PaymentProviderCustomer, +): customer is PayPalConnectCustomer { + if (!customer) { + return false; + } + + return ( + 'authenticationState' in customer || 'addresses' in customer || 'instruments' in customer + ); +} diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.spec.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.spec.ts new file mode 100644 index 0000000000..e1bda16d29 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.spec.ts @@ -0,0 +1,45 @@ +import isBigCommercePaymentsProviderError from './is-bigcommerce-payments-provider-error'; + +describe('isBigCommercePaymentsProviderError', () => { + it('returns true if error bigcommerce-payments provider related', () => { + const providerError = { + status: 'error', + three_ds_result: { + acs_url: null, + payer_auth_request: null, + merchant_data: null, + callback_url: null, + }, + errors: [ + { + code: 'invalid_request_error', + message: + 'Were experiencing difficulty processing your transaction. Please contact us or try again later.', + }, + { + code: 'transaction_rejected', + message: 'Payment was declined. Please try again.', + provider_error: { + code: 'INSTRUMENT_DECLINED', + }, + }, + ], + }; + + expect(isBigCommercePaymentsProviderError(providerError)).toBe(true); + }); + + it('returns false if error not bigcommerce-payments provider related', () => { + const notProviderError = { + status: 'error', + three_ds_result: { + acs_url: null, + payer_auth_request: null, + merchant_data: null, + callback_url: null, + }, + }; + + expect(isBigCommercePaymentsProviderError(notProviderError)).toBe(false); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.ts new file mode 100644 index 0000000000..ca26fb50d2 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-provider-error.ts @@ -0,0 +1,22 @@ +export interface ProviderError extends Error { + errors?: ErrorElement[]; + status?: string; + three_ds_result?: { + acs_url: unknown; + payer_auth_request: unknown; + merchant_data: unknown; + callback_url: unknown; + }; +} + +export interface ErrorElement { + code: string; + message: string; + provider_error?: { + code: string; + }; +} + +export default function isBigCommercePaymentsProviderError(error: unknown): error is ProviderError { + return typeof error === 'object' && error !== null && 'errors' in error; +} diff --git a/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.spec.ts b/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.spec.ts new file mode 100644 index 0000000000..64b506d086 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.spec.ts @@ -0,0 +1,21 @@ +import isPayPalFastlaneCustomer from './is-paypal-fastlane-customer'; + +describe('isPayPalFastlaneCustomer', () => { + it('returns true if payment provider customer is PayPal Fastlane related', () => { + const paymentProviderCustomer = { + authenticationState: 'success', + addresses: [], + instruments: [], + }; + + expect(isPayPalFastlaneCustomer(paymentProviderCustomer)).toBe(true); + }); + + it('returns false if payment provider customer is not PayPal Fastlane related', () => { + const paymentProviderCustomer = { + stripeLinkAuthenticationState: true, + }; + + expect(isPayPalFastlaneCustomer(paymentProviderCustomer)).toBe(false); + }); +}); diff --git a/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.ts b/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.ts new file mode 100644 index 0000000000..71215e80d7 --- /dev/null +++ b/packages/bigcommerce-payments-utils/src/utils/is-paypal-fastlane-customer.ts @@ -0,0 +1,17 @@ +import { + PaymentProviderCustomer, + PayPalConnectCustomer, +} from '@bigcommerce/checkout-sdk/payment-integration-api'; + +// TODO: update PayPalConnectCustomer with PayPalFastlaneCustomer +export default function isPayPalFastlaneCustomer( + customer?: PaymentProviderCustomer, +): customer is PayPalConnectCustomer { + if (!customer) { + return false; + } + + return ( + 'authenticationState' in customer || 'addresses' in customer || 'instruments' in customer + ); +} diff --git a/packages/bigcommerce-payments-utils/tsconfig.json b/packages/bigcommerce-payments-utils/tsconfig.json index d81bfb6464..13b93f41d2 100644 --- a/packages/bigcommerce-payments-utils/tsconfig.json +++ b/packages/bigcommerce-payments-utils/tsconfig.json @@ -14,6 +14,10 @@ "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "lib": [ + "es2017", + "dom" + ] } -} +} \ No newline at end of file From bbd159e82a30d895b5ca6c6f71fe24fcf217712d Mon Sep 17 00:00:00 2001 From: SerhiiFilkovskyi Date: Mon, 12 May 2025 12:49:48 +0300 Subject: [PATCH 2/3] feature(payment): refactor naming for BCP utils files --- ...igcommerce-payments-fastlane-utils.spec.ts | 10 +++--- .../bigcommerce-payments-fastlane-utils.ts | 4 +-- .../src/bigcommerce-payments-sdk.spec.ts | 33 +++++++++---------- .../src/bigcommerce-payments-types.ts | 10 +++--- .../create-bigcommerce-payments-sdk.spec.ts | 4 +-- .../src/create-bigcommerce-payments-sdk.ts | 6 ++-- .../bigcommerce-payments-utils/src/index.ts | 2 +- ...igcommerce-payments-payment-method.mock.ts | 4 +-- .../src/mocks/index.ts | 2 +- ...e-payments-sdk.ts => paypal-sdk-helper.ts} | 8 ++--- ...l-messages-styles-from-bnpl-config.spec.ts | 2 +- .../src/utils/index.ts | 2 +- ...mmerce-payments-fastlane-customer.spec.ts} | 12 +++---- ...bigcommerce-payments-fastlane-customer.ts} | 2 +- 14 files changed, 47 insertions(+), 54 deletions(-) rename packages/bigcommerce-payments-utils/src/{bigcommerce-payments-sdk.ts => paypal-sdk-helper.ts} (97%) rename packages/bigcommerce-payments-utils/src/utils/{is-bigcommerce-payments-accelerated-checkout-customer.spec.ts => is-bigcommerce-payments-fastlane-customer.spec.ts} (51%) rename packages/bigcommerce-payments-utils/src/utils/{is-bigcommerce-payments-accelerated-checkout-customer.ts => is-bigcommerce-payments-fastlane-customer.ts} (83%) diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts index 7dab838a40..820f26b24d 100644 --- a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.spec.ts @@ -6,9 +6,9 @@ import { BrowserStorage } from '@bigcommerce/checkout-sdk/storage'; import BigCommercePaymentsFastlaneUtils from './bigcommerce-payments-fastlane-utils'; import { - BigCommercePaymentsHostWindow, PayPalFastlaneAuthenticationState, PayPalFastlaneSdk, + PayPalHostWindow, } from './bigcommerce-payments-types'; import { getPayPalFastlaneAuthenticationResultMock, getPayPalFastlaneSdk } from './mocks'; @@ -17,7 +17,7 @@ describe('BigCommercePaymentsFastlaneUtils', () => { let paypalFastlaneSdk: PayPalFastlaneSdk; let subject: BigCommercePaymentsFastlaneUtils; - const methodIdMock = 'bigcommercepaymentsacceleratedcheckout'; // TODO:double check if this is correct + const methodIdMock = 'bigcommerce_payments_fastlane'; const authenticationResultMock = getPayPalFastlaneAuthenticationResultMock(); const bcAddressMock = { @@ -51,8 +51,8 @@ describe('BigCommercePaymentsFastlaneUtils', () => { expiryYear: '2030', iin: '', last4: '1111', - method: 'bigcommercepaymentsacceleratedcheckout', - provider: 'bigcommercepaymentsacceleratedcheckout', + method: 'bigcommerce_payments_fastlane', + provider: 'bigcommerce_payments_fastlane', trustedShippingAddress: false, untrustedShippingCardVerificationMode: UntrustedShippingCardVerificationType.PAN, type: 'card', @@ -68,7 +68,7 @@ describe('BigCommercePaymentsFastlaneUtils', () => { }); afterEach(() => { - (window as BigCommercePaymentsHostWindow).paypalFastlane = undefined; + (window as PayPalHostWindow).paypalFastlane = undefined; jest.resetAllMocks(); jest.restoreAllMocks(); diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts index cad3d3c2da..0b67947c5d 100644 --- a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-fastlane-utils.ts @@ -10,7 +10,6 @@ import { import { BrowserStorage } from '@bigcommerce/checkout-sdk/storage'; import { - BigCommercePaymentsHostWindow, PayPalFastlane, PayPalFastlaneAddress, PayPalFastlaneAuthenticationResult, @@ -22,10 +21,11 @@ import { PayPalFastlaneProfileToBcCustomerDataMappingResult, PayPalFastlaneSdk, PayPalFastlaneStylesOption, + PayPalHostWindow, } from './bigcommerce-payments-types'; export default class BigCommercePaymentsFastlaneUtils { - private window: BigCommercePaymentsHostWindow; + private window: PayPalHostWindow; constructor(private browserStorage: BrowserStorage) { this.window = window; diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts index 3f77774cfd..01f01598e2 100644 --- a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts @@ -6,23 +6,20 @@ import { PaymentMethodClientUnavailableError, } from '@bigcommerce/checkout-sdk/payment-integration-api'; -import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; import { - BigCommercePaymentsHostWindow, PayPalApmSdk, PayPalFastlaneSdk, + PayPalHostWindow, PayPalMessagesSdk, } from './bigcommerce-payments-types'; -import { - getBigCommercePaymentsAcceleratedCheckoutPaymentMethod, - getPayPalFastlaneSdk, -} from './mocks'; +import { getBigCommercePaymentsFastlanePaymentMethod, getPayPalFastlaneSdk } from './mocks'; +import PayPalSdkHelper from './paypal-sdk-helper'; -describe('BigCommercePaymentsSdk', () => { +describe('PayPalSdkHelper', () => { let loader: ScriptLoader; let paymentMethod: PaymentMethod; let paypalFastlaneSdk: PayPalFastlaneSdk; - let subject: BigCommercePaymentsSdk; + let subject: PayPalSdkHelper; let mockAPMPaymentMethod: PaymentMethod; const paypalMessagesSdk: PayPalMessagesSdk = { @@ -39,7 +36,7 @@ describe('BigCommercePaymentsSdk', () => { beforeEach(() => { loader = createScriptLoader(); - paymentMethod = getBigCommercePaymentsAcceleratedCheckoutPaymentMethod(); + paymentMethod = getBigCommercePaymentsFastlanePaymentMethod(); mockAPMPaymentMethod = { ...paymentMethod, id: 'oxxo', @@ -50,21 +47,21 @@ describe('BigCommercePaymentsSdk', () => { }, }; paypalFastlaneSdk = getPayPalFastlaneSdk(); - subject = new BigCommercePaymentsSdk(loader); + subject = new PayPalSdkHelper(loader); jest.spyOn(loader, 'loadScript').mockImplementation(() => { - (window as BigCommercePaymentsHostWindow).paypalFastlaneSdk = paypalFastlaneSdk; - (window as BigCommercePaymentsHostWindow).paypalMessages = paypalMessagesSdk; - (window as BigCommercePaymentsHostWindow).paypalApms = paypalApmsSdk; + (window as PayPalHostWindow).paypalFastlaneSdk = paypalFastlaneSdk; + (window as PayPalHostWindow).paypalMessages = paypalMessagesSdk; + (window as PayPalHostWindow).paypalApms = paypalApmsSdk; return Promise.resolve(); }); }); afterEach(() => { - (window as BigCommercePaymentsHostWindow).paypalFastlaneSdk = undefined; - (window as BigCommercePaymentsHostWindow).paypalMessages = undefined; - (window as BigCommercePaymentsHostWindow).paypalApms = undefined; + (window as PayPalHostWindow).paypalFastlaneSdk = undefined; + (window as PayPalHostWindow).paypalMessages = undefined; + (window as PayPalHostWindow).paypalApms = undefined; jest.clearAllMocks(); }); @@ -104,10 +101,10 @@ describe('BigCommercePaymentsSdk', () => { }); // TODO: remove this test when A/B testing will be finished - it('loads PayPal Fastlane Sdk script with connectClientToken for bigcommercepaymentstypescreditcards method', async () => { + it('loads PayPal Fastlane Sdk script with connectClientToken for bigcommerce_payments_creditcards method', async () => { const mockPaymentMethod = { ...paymentMethod, - methodId: 'bigcommercepaymentstypescreditcards', // TODO: double check if this is correct + methodId: 'bigcommerce_payments_creditcards', initializationData: { ...paymentMethod.initializationData, clientToken: undefined, diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts index 5d907dd363..c8b48152d7 100644 --- a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts +++ b/packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts @@ -21,7 +21,7 @@ export interface BigCommercePaymentsInitializationData { clientId: string; clientToken?: string; fastlaneStyles?: FastlaneStylesSettings; - connectClientToken?: string; // TODO: remove when PPCP Fastlane A/B test will be finished + connectClientToken?: string; // TODO: remove when BCP Fastlane A/B test will be finished enabledAlternativePaymentMethods: FundingType; isDeveloperModeApplicable?: boolean; intent?: BigCommercePaymentsIntent; @@ -29,25 +29,25 @@ export interface BigCommercePaymentsInitializationData { isFastlaneShippingOptionAutoSelectEnabled?: boolean; // PayPal Fastlane related isFastlaneStylingEnabled?: boolean; isHostedCheckoutEnabled?: boolean; - isBigCommercePaymentsAnalyticsV2Enabled?: boolean; // PayPal Fastlane related //TODO: doublecheck BE Team + isBigCommercePaymentsAnalyticsV2Enabled?: boolean; // PayPal Fastlane related isPayPalCreditAvailable?: boolean; isVenmoEnabled?: boolean; isGooglePayEnabled?: boolean; merchantId?: string; orderId?: string; shouldRenderFields?: boolean; - shouldRunAcceleratedCheckout?: boolean; // TODO: remove when PPCP Fastlane A/B test will be finished + shouldRunAcceleratedCheckout?: boolean; // TODO: remove when BCP Fastlane A/B test will be finished paymentButtonStyles?: Record; paypalBNPLConfiguration?: PayPalBNPLConfigurationItem[]; } /** * - * BigCommercePaymentsHostWindow contains different + * PayPalHostWindow contains different * PayPal Sdk instances for different purposes * */ -export interface BigCommercePaymentsHostWindow extends Window { +export interface PayPalHostWindow extends Window { paypalFastlane?: PayPalFastlane; paypalFastlaneSdk?: PayPalFastlaneSdk; paypalMessages?: PayPalMessagesSdk; diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts index f2df03150d..f2b10c5501 100644 --- a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.spec.ts @@ -1,10 +1,10 @@ -import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; import createBigCommercePaymentsSdk from './create-bigcommerce-payments-sdk'; +import PayPalSdkHelper from './paypal-sdk-helper'; describe('createBigCommercePaymentsSdk', () => { it('instantiates BigCommerce Payments SDK', () => { const BigCommercePaymentsSdkInstance = createBigCommercePaymentsSdk(); - expect(BigCommercePaymentsSdkInstance).toBeInstanceOf(BigCommercePaymentsSdk); + expect(BigCommercePaymentsSdkInstance).toBeInstanceOf(PayPalSdkHelper); }); }); diff --git a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts index 1b2663c5bd..4189328c11 100644 --- a/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts +++ b/packages/bigcommerce-payments-utils/src/create-bigcommerce-payments-sdk.ts @@ -1,7 +1,7 @@ import { createScriptLoader } from '@bigcommerce/script-loader'; -import BigCommercePaymentsSdk from './bigcommerce-payments-sdk'; +import PayPalSdkHelper from './paypal-sdk-helper'; -export default function createBigCommercePaymentsSdk(): BigCommercePaymentsSdk { - return new BigCommercePaymentsSdk(createScriptLoader()); +export default function createBigCommercePaymentsSdk(): PayPalSdkHelper { + return new PayPalSdkHelper(createScriptLoader()); } diff --git a/packages/bigcommerce-payments-utils/src/index.ts b/packages/bigcommerce-payments-utils/src/index.ts index 35754dacb9..62d6bac3c9 100644 --- a/packages/bigcommerce-payments-utils/src/index.ts +++ b/packages/bigcommerce-payments-utils/src/index.ts @@ -11,7 +11,7 @@ export { default as isBigCommercePaymentsProviderError } from './utils/is-bigcom * * */ export { default as createBigCommercePaymentsSdk } from './create-bigcommerce-payments-sdk'; -export { default as BigCommercePaymentsSdk } from './bigcommerce-payments-sdk'; +export { default as PayPalSdkHelper } from './paypal-sdk-helper'; /** * diff --git a/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts b/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts index 18a91151c9..ce9843812c 100644 --- a/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts +++ b/packages/bigcommerce-payments-utils/src/mocks/get-bigcommerce-payments-payment-method.mock.ts @@ -57,12 +57,12 @@ export default function getBigCommercePaymentsPaymentMethod(): PaymentMethod { }; } -export function getBigCommercePaymentsAcceleratedCheckoutPaymentMethod(): PaymentMethod { +export function getBigCommercePaymentsFastlanePaymentMethod(): PaymentMethod { const bigCommercePaymentsDefaultPaymentMethod = getBigCommercePaymentsPaymentMethod(); return { ...bigCommercePaymentsDefaultPaymentMethod, - id: 'bigcommercepaymentsacceleratedcheckout', // TODO: check BE team if this is correct + id: 'bigcommerce_payments_fastlane', initializationData: { ...bigCommercePaymentsDefaultPaymentMethod.initializationData, isAcceleratedCheckoutEnabled: true, diff --git a/packages/bigcommerce-payments-utils/src/mocks/index.ts b/packages/bigcommerce-payments-utils/src/mocks/index.ts index 8b41537bc9..eb3686fc09 100644 --- a/packages/bigcommerce-payments-utils/src/mocks/index.ts +++ b/packages/bigcommerce-payments-utils/src/mocks/index.ts @@ -1,6 +1,6 @@ export { default as getBigCommercePaymentsPaymentMethod, - getBigCommercePaymentsAcceleratedCheckoutPaymentMethod, + getBigCommercePaymentsFastlanePaymentMethod, } from './get-bigcommerce-payments-payment-method.mock'; export { default as getPayPalFastlaneSdk } from './get-paypal-fastlane-sdk.mock'; export { default as getPayPalFastlane } from './get-paypal-fastlane.mock'; diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts b/packages/bigcommerce-payments-utils/src/paypal-sdk-helper.ts similarity index 97% rename from packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts rename to packages/bigcommerce-payments-utils/src/paypal-sdk-helper.ts index d2658764e8..73596c97dc 100644 --- a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.ts +++ b/packages/bigcommerce-payments-utils/src/paypal-sdk-helper.ts @@ -8,15 +8,15 @@ import { } from '@bigcommerce/checkout-sdk/payment-integration-api'; import { - BigCommercePaymentsHostWindow, BigCommercePaymentsInitializationData, PayPalFastlaneSdk, + PayPalHostWindow, PayPalMessagesSdk, PayPalSdkConfig, } from './bigcommerce-payments-types'; -export default class BigCommercePaymentsSdk { - private window: BigCommercePaymentsHostWindow; +export default class PayPalSdkHelper { + private window: PayPalHostWindow; constructor(private scriptLoader: ScriptLoader) { this.window = window; @@ -122,7 +122,7 @@ export default class BigCommercePaymentsSdk { clientId, merchantId, attributionId, - connectClientToken, // TODO: remove when PPCP Fastlane A/B testing will be finished + connectClientToken, // TODO: remove when BCP Fastlane A/B testing will be finished } = initializationData; return { diff --git a/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts index 7f03a442b6..9816202d8f 100644 --- a/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts +++ b/packages/bigcommerce-payments-utils/src/utils/get-paypal-messages-styles-from-bnpl-config.spec.ts @@ -1,7 +1,7 @@ import getPaypalMessagesStylesFromBNPLConfig from './get-paypal-messages-styles-from-bnpl-config'; describe('getPaypalMessagesStylesFromBNPLConfig', () => { - it('returns PayPal Commerce Messages Style Options from BNPL Config', () => { + it('returns BigCommerce Messages Style Options from BNPL Config', () => { const input = { id: 'checkout', name: 'Checkout page', diff --git a/packages/bigcommerce-payments-utils/src/utils/index.ts b/packages/bigcommerce-payments-utils/src/utils/index.ts index 95c7f1aac6..13eabee758 100644 --- a/packages/bigcommerce-payments-utils/src/utils/index.ts +++ b/packages/bigcommerce-payments-utils/src/utils/index.ts @@ -1,4 +1,4 @@ -export { default as isBigCommercePaymentsAcceleratedCheckoutCustomer } from './is-bigcommerce-payments-accelerated-checkout-customer'; +export { default as isBigCommercePaymentsFastlaneCustomer } from './is-bigcommerce-payments-fastlane-customer'; export { default as isPayPalFastlaneCustomer } from './is-paypal-fastlane-customer'; export { default as getFastlaneStyles } from './get-fastlane-styles'; export { default as getPaypalMessagesStylesFromBNPLConfig } from './get-paypal-messages-styles-from-bnpl-config'; diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.spec.ts similarity index 51% rename from packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts rename to packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.spec.ts index 0bc83e7960..11afb0e3d2 100644 --- a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.spec.ts +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.spec.ts @@ -1,6 +1,6 @@ -import isBigCommercePaymentsAcceleratedCheckoutCustomer from './is-bigcommerce-payments-accelerated-checkout-customer'; +import isBigCommercePaymentsFastlaneCustomer from './is-bigcommerce-payments-fastlane-customer'; -describe('isBigCommercePaymentsAcceleratedCheckoutCustomer', () => { +describe('isBigCommercePaymentsFastlaneCustomer', () => { it('returns true if payment provider customer is BigCommercePayments related', () => { const paymentProviderCustomer = { authenticationState: 'success', @@ -8,9 +8,7 @@ describe('isBigCommercePaymentsAcceleratedCheckoutCustomer', () => { instruments: [], }; - expect(isBigCommercePaymentsAcceleratedCheckoutCustomer(paymentProviderCustomer)).toBe( - true, - ); + expect(isBigCommercePaymentsFastlaneCustomer(paymentProviderCustomer)).toBe(true); }); it('returns false if payment provider customer is not BigCommercePayments related', () => { @@ -18,8 +16,6 @@ describe('isBigCommercePaymentsAcceleratedCheckoutCustomer', () => { stripeLinkAuthenticationState: true, }; - expect(isBigCommercePaymentsAcceleratedCheckoutCustomer(paymentProviderCustomer)).toBe( - false, - ); + expect(isBigCommercePaymentsFastlaneCustomer(paymentProviderCustomer)).toBe(false); }); }); diff --git a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.ts similarity index 83% rename from packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts rename to packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.ts index 79a84d4553..7c3d0b6cbe 100644 --- a/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-accelerated-checkout-customer.ts +++ b/packages/bigcommerce-payments-utils/src/utils/is-bigcommerce-payments-fastlane-customer.ts @@ -3,7 +3,7 @@ import { PayPalConnectCustomer, } from '@bigcommerce/checkout-sdk/payment-integration-api'; -export default function isBigCommercePaymentsAcceleratedCheckoutCustomer( +export default function isBigCommercePaymentsFastlaneCustomer( customer?: PaymentProviderCustomer, ): customer is PayPalConnectCustomer { if (!customer) { From 56f84edae304024530beb2ceb30fbf5f2bac8999 Mon Sep 17 00:00:00 2001 From: SerhiiFilkovskyi Date: Tue, 13 May 2025 11:12:57 +0300 Subject: [PATCH 3/3] feature(payment): rename files --- ...bigcommerce-payments-sdk.spec.ts => paypal-sdk-helper.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/bigcommerce-payments-utils/src/{bigcommerce-payments-sdk.spec.ts => paypal-sdk-helper.spec.ts} (100%) diff --git a/packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts b/packages/bigcommerce-payments-utils/src/paypal-sdk-helper.spec.ts similarity index 100% rename from packages/bigcommerce-payments-utils/src/bigcommerce-payments-sdk.spec.ts rename to packages/bigcommerce-payments-utils/src/paypal-sdk-helper.spec.ts