Skip to content

Commit

Permalink
feat(payment): PAYPAL-2725 added an ability to switch braintree sdk v…
Browse files Browse the repository at this point in the history
…ersion to alpha due to the settings in cp (#2063)
  • Loading branch information
serhii-tkachenko authored Jul 20, 2023
1 parent 9bea6d3 commit 94a4ce3
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ describe('BraintreeIntegrationService', () => {
let braintreeHostWindowMock: BraintreeHostWindow;
let braintreeIntegrationService: BraintreeIntegrationService;

const clientToken = 'clientToken';
const initializationData = {
isAcceleratedCheckoutEnabled: false,
};

beforeEach(() => {
clientMock = getClientMock();
braintreeScriptLoader = {} as BraintreeScriptLoader;
Expand All @@ -39,6 +44,16 @@ describe('BraintreeIntegrationService', () => {
braintreeScriptLoader,
braintreeHostWindowMock,
);

braintreeScriptLoader.initialize = jest.fn();
});

describe('#initialize()', () => {
it('initializes braintree script loader with provided initialization data', () => {
braintreeIntegrationService.initialize(clientToken, initializationData);

expect(braintreeScriptLoader.initialize).toHaveBeenCalledWith(initializationData);
});
});

describe('#getClient()', () => {
Expand All @@ -50,7 +65,7 @@ describe('BraintreeIntegrationService', () => {
});

it('uses the right arguments to create the client', async () => {
braintreeIntegrationService.initialize('clientToken');
braintreeIntegrationService.initialize(clientToken, initializationData);

const client = await braintreeIntegrationService.getClient();

Expand All @@ -59,7 +74,7 @@ describe('BraintreeIntegrationService', () => {
});

it('always returns the same instance of the client', async () => {
braintreeIntegrationService.initialize('clientToken');
braintreeIntegrationService.initialize(clientToken, initializationData);

const client1 = await braintreeIntegrationService.getClient();
const client2 = await braintreeIntegrationService.getClient();
Expand Down Expand Up @@ -299,7 +314,7 @@ describe('BraintreeIntegrationService', () => {
.fn()
.mockReturnValue({ create: jest.fn() });
braintreeScriptLoader.loadClient = jest.fn().mockReturnValue({ create: jest.fn() });
braintreeIntegrationService.initialize('client-token');
braintreeIntegrationService.initialize(clientToken, initializationData);
await braintreeIntegrationService.loadBraintreeLocalMethods(jest.fn(), '');

expect(braintreeScriptLoader.loadBraintreeLocalMethods).toHaveBeenCalled();
Expand Down Expand Up @@ -415,7 +430,7 @@ describe('BraintreeIntegrationService', () => {
.fn()
.mockReturnValue(Promise.resolve(getModuleCreatorMock(clientMock)));

braintreeIntegrationService.initialize('clientToken');
braintreeIntegrationService.initialize(clientToken, initializationData);
});

it('calls teardown in all the dependencies', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ import {
BraintreeEnv,
BraintreeError,
BraintreeHostWindow,
BraintreeInitializationData,
BraintreeModule,
BraintreePaypalCheckout,
BraintreePaypalSdkCreatorConfig,
BraintreeShippingAddressOverride,
} from './braintree';
import BraintreeScriptLoader from './braintree-script-loader';
import isBraintreeError from './is-braintree-error';
import { PAYPAL_COMPONENTS } from './paypal';
import {
BraintreeLocalMethods,
GetLocalPaymentInstance,
LocalPaymentInstance,
} from './braintree-local-payment-methods/braintree-local-methods-options';
import BraintreeScriptLoader from './braintree-script-loader';
import isBraintreeError from './is-braintree-error';
import { PAYPAL_COMPONENTS } from './paypal';

export default class BraintreeIntegrationService {
private client?: Promise<BraintreeClient>;
Expand All @@ -43,8 +44,9 @@ export default class BraintreeIntegrationService {
private braintreeHostWindow: BraintreeHostWindow,
) {}

initialize(clientToken: string) {
initialize(clientToken: string, initializationData: BraintreeInitializationData) {
this.clientToken = clientToken;
this.braintreeScriptLoader.initialize(initializationData);
}

async getClient(): Promise<BraintreeClient> {
Expand Down Expand Up @@ -118,6 +120,7 @@ export default class BraintreeIntegrationService {
if (localPaymentErr) {
throw new Error(localPaymentErr);
}

getLocalPaymentInstance(localPaymentInstance);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,17 @@ describe('BraintreeLocalMethodsPaymentStrategy', () => {
jest.spyOn(paymentIntegrationService.getState(), 'getPaymentMethodOrThrow').mockReturnValue(
paymentMethodMock,
);
jest.spyOn(paymentIntegrationService.getState(), 'getCheckoutOrThrow').mockReturnValue(
getCheckout(),
);

jest.spyOn(braintreeIntegrationService, 'initialize');
jest.spyOn(braintreeIntegrationService, 'getClient').mockReturnValue(
paymentMethodMock.clientToken,
);

jest.spyOn(braintreeIntegrationService, 'getSessionId').mockReturnValue(
paymentMethodMock.clientToken,
);

jest.spyOn(paymentIntegrationService.getState(), 'getCheckoutOrThrow').mockReturnValue(
getCheckout(),
);

jest.spyOn(braintreeIntegrationService, 'loadBraintreeLocalMethods').mockReturnValue(
localPaymentInstanceMock,
);
Expand Down Expand Up @@ -124,6 +122,20 @@ describe('BraintreeLocalMethodsPaymentStrategy', () => {
}
});

it('throws error if initializationData is not provided', async () => {
paymentMethodMock.initializationData = null;
jest.spyOn(
paymentIntegrationService.getState(),
'getPaymentMethodOrThrow',
).mockReturnValue(paymentMethodMock);

try {
await strategy.initialize(initializationOptions);
} catch (error) {
expect(error).toBeInstanceOf(MissingDataError);
}
});

it('throws error if gatewayId is not provided', async () => {
const options = {
methodId: 'giropay',
Expand All @@ -149,6 +161,15 @@ describe('BraintreeLocalMethodsPaymentStrategy', () => {
}
});

it('initializes braintree integration service', async () => {
await strategy.initialize(initializationOptions);

expect(braintreeIntegrationService.initialize).toHaveBeenCalledWith(
paymentMethodMock.clientToken,
paymentMethodMock.initializationData,
);
});

it('loads Braintree Local Methods', async () => {
await strategy.initialize(initializationOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
PaymentRequestOptions,
PaymentStrategy,
} from '@bigcommerce/checkout-sdk/payment-integration-api';
import { LoadingIndicator } from '@bigcommerce/checkout-sdk/ui';

import { BraintreeInitializationData } from '../braintree';
import BraintreeIntegrationService from '../braintree-integration-service';

import {
BraintreeLocalMethods,
LocalPaymentInstance,
Expand All @@ -20,7 +24,6 @@ import {
StartPaymentError,
WithBraintreeLocalMethodsPaymentInitializeOptions,
} from './braintree-local-methods-options';
import { LoadingIndicator } from '@bigcommerce/checkout-sdk/ui';

export default class BraintreeLocalMethodsPaymentStrategy implements PaymentStrategy {
private orderId?: string;
Expand All @@ -38,6 +41,7 @@ export default class BraintreeLocalMethodsPaymentStrategy implements PaymentStra
options: PaymentInitializeOptions & WithBraintreeLocalMethodsPaymentInitializeOptions,
): Promise<void> {
const { gatewayId, methodId, braintreelocalmethods } = options;

if (!methodId) {
throw new InvalidArgumentError(
'Unable to initialize payment because "options.methodId" argument is not provided.',
Expand All @@ -63,18 +67,18 @@ export default class BraintreeLocalMethodsPaymentStrategy implements PaymentStra
await this.paymentIntegrationService.loadPaymentMethod(gatewayId);

const state = this.paymentIntegrationService.getState();
const paymentMethod = state.getPaymentMethodOrThrow(gatewayId);
const { merchantId } = paymentMethod.config;
const paymentMethod = state.getPaymentMethodOrThrow<BraintreeInitializationData>(gatewayId);
const { clientToken, config, initializationData } = paymentMethod;

if (!paymentMethod.clientToken) {
if (!clientToken || !initializationData) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
}

try {
this.braintreeIntegrationService.initialize(paymentMethod.clientToken);
this.braintreeIntegrationService.initialize(clientToken, initializationData);
await this.braintreeIntegrationService.loadBraintreeLocalMethods(
this.getLocalPaymentInstance.bind(this),
merchantId || '',
config.merchantId || '',
);
} catch (error: unknown) {
this.handleError(error);
Expand Down Expand Up @@ -143,6 +147,7 @@ export default class BraintreeLocalMethodsPaymentStrategy implements PaymentStra
if (startPaymentError.code !== 'LOCAL_PAYMENT_WINDOW_CLOSED') {
reject(() => this.handleError(startPaymentError));
}

this.toggleLoadingIndicator(false);
reject();
} else {
Expand Down Expand Up @@ -201,6 +206,7 @@ export default class BraintreeLocalMethodsPaymentStrategy implements PaymentStra

private handleError(error: unknown) {
const { onError } = this.braintreeLocalMethods || {};

this.toggleLoadingIndicator(false);

if (onError && typeof onError === 'function') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { getScriptLoader } from '@bigcommerce/script-loader';

import {
PaymentStrategyFactory,
toResolvableModule,
} from '@bigcommerce/checkout-sdk/payment-integration-api';
import { LoadingIndicator } from '@bigcommerce/checkout-sdk/ui';

import BraintreeLocalMethodsPaymentStrategy from './braintree-local-methods-payment-strategy';
import { BraintreeHostWindow } from '../braintree';
import BraintreeIntegrationService from '../braintree-integration-service';
import BraintreeScriptLoader from '../braintree-script-loader';
import { getScriptLoader } from '@bigcommerce/script-loader';
import { LoadingIndicator } from '@bigcommerce/checkout-sdk/ui';

import BraintreeLocalMethodsPaymentStrategy from './braintree-local-methods-payment-strategy';

const createBraintreeLocalMethodsPaymentStrategy: PaymentStrategyFactory<
BraintreeLocalMethodsPaymentStrategy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,23 @@ describe('BraintreePaypalAchPaymentStrategy', () => {
}
});

it('throws error if initialization data is missing', async () => {
paymentMethodMock.initializationData = undefined;

try {
await strategy.initialize(mockOptions);
} catch (error) {
expect(error).toBeInstanceOf(MissingDataError);
}
});

it('successfully initialization payment strategy', async () => {
await strategy.initialize(mockOptions);

expect(paymentIntegrationService.loadPaymentMethod).toHaveBeenCalledWith(methodId);
expect(braintreeIntegrationService.initialize).toHaveBeenCalledWith(
paymentMethodMock.clientToken,
paymentMethodMock.initializationData,
);
expect(braintreeIntegrationService.getUsBankAccount).toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import {
WithBankAccountInstrument,
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { BankAccountSuccessPayload, BraintreeBankAccount } from '../braintree';
import {
BankAccountSuccessPayload,
BraintreeBankAccount,
BraintreeInitializationData,
} from '../braintree';
import BraintreeIntegrationService from '../braintree-integration-service';
import isBraintreeError from '../is-braintree-error';
import isUsBankAccountInstrumentLike from '../is-us-bank-account-instrument-like';
Expand Down Expand Up @@ -52,14 +56,17 @@ export default class BraintreePaypalAchPaymentStrategy implements PaymentStrateg

const state = this.paymentIntegrationService.getState();

const paymentMethod = state.getPaymentMethodOrThrow(options.methodId);
const paymentMethod = state.getPaymentMethodOrThrow<BraintreeInitializationData>(
options.methodId,
);
const { clientToken, initializationData } = paymentMethod;

if (!paymentMethod.clientToken) {
if (!clientToken || !initializationData) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
}

try {
this.braintreeIntegrationService.initialize(paymentMethod.clientToken);
this.braintreeIntegrationService.initialize(clientToken, initializationData);
this.usBankAccount = await this.braintreeIntegrationService.getUsBankAccount();
} catch (error) {
this.handleError(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ describe('BraintreePaypalCustomerStrategy', () => {
}
});

it('throws error if initialization data is missing', async () => {
paymentMethodMock.initializationData = undefined;

try {
await strategy.initialize(initializationOptions);
} catch (error) {
expect(error).toBeInstanceOf(MissingDataError);
}
});

it('initializes braintree paypal checkout', async () => {
braintreeIntegrationService.initialize = jest.fn();
braintreeIntegrationService.getPaypalCheckout = jest.fn();
Expand All @@ -219,6 +229,7 @@ describe('BraintreePaypalCustomerStrategy', () => {

expect(braintreeIntegrationService.initialize).toHaveBeenCalledWith(
paymentMethodMock.clientToken,
paymentMethodMock.initializationData,
);
expect(braintreeIntegrationService.getPaypalCheckout).toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,17 @@ export default class BraintreePaypalCustomerStrategy implements CustomerStrategy
const paymentMethod: PaymentMethod<BraintreeInitializationData> =
state.getPaymentMethodOrThrow(methodId);

if (!paymentMethod.clientToken) {
const { clientToken, config, initializationData } = paymentMethod;

if (!clientToken || !initializationData) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
}

const currencyCode = state.getCartOrThrow().currency.code;
const paypalCheckoutOptions: Partial<BraintreePaypalSdkCreatorConfig> = {
currency: currencyCode,
intent: paymentMethod.initializationData?.intent,
isCreditEnabled: paymentMethod.initializationData?.isCreditEnabled,
intent: initializationData.intent,
isCreditEnabled: initializationData.isCreditEnabled,
};

const paypalCheckoutSuccessCallback = (
Expand All @@ -87,14 +89,14 @@ export default class BraintreePaypalCustomerStrategy implements CustomerStrategy
braintreepaypal,
container,
methodId,
Boolean(paymentMethod.config.testMode),
Boolean(config.testMode),
buttonHeight,
);
};
const paypalCheckoutErrorCallback = (error: BraintreeError) =>
this.handleError(error, container, onError);

this.braintreeIntegrationService.initialize(paymentMethod.clientToken);
this.braintreeIntegrationService.initialize(clientToken, initializationData);
await this.braintreeIntegrationService.getPaypalCheckout(
paypalCheckoutOptions,
paypalCheckoutSuccessCallback,
Expand Down
Loading

0 comments on commit 94a4ce3

Please sign in to comment.