Skip to content

Commit

Permalink
Merge pull request #2043 from PavlenkoM/PAYPAL-2502
Browse files Browse the repository at this point in the history
fix(payment): PAYPAL-2502 bump braintree sdk version
  • Loading branch information
PavlenkoM authored Jul 13, 2023
2 parents 3876684 + 33970f0 commit 3192f93
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
getPaypalCheckoutMock,
} from './braintree.mock';

const VERSION = '3.81.0';
const VERSION = '3.95.0';

describe('BraintreeScriptLoader', () => {
let scriptLoader: ScriptLoader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
BraintreePaypalCheckoutCreator,
} from './braintree';

const VERSION = '3.81.0';
const VERSION = '3.95.0';

export default class BraintreeScriptLoader {
constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import PaymentRequestTransformer from '../../payment-request-transformer';
import BraintreeCreditCardPaymentStrategy from './braintree-credit-card-payment-strategy';
import { BraintreePaymentInitializeOptions } from './braintree-payment-options';
import BraintreePaymentProcessor from './braintree-payment-processor';
import { getTokenizeResponseBody } from './braintree.mock';

describe('BraintreeCreditCardPaymentStrategy', () => {
let order: Order;
Expand Down Expand Up @@ -587,14 +588,19 @@ describe('BraintreeCreditCardPaymentStrategy', () => {
},
};

jest.spyOn(
store.getState().instruments,
'getCardInstrumentOrThrow',
).mockImplementation(jest.fn(() => getTokenizeResponseBody()));

await braintreeCreditCardPaymentStrategy.initialize({
methodId: paymentMethodMock.id,
});
await braintreeCreditCardPaymentStrategy.execute(payload, options);

expect(paymentActionCreator.submitPayment).toHaveBeenCalledTimes(2);
expect(braintreePaymentProcessorMock.challenge3DSVerification).toHaveBeenCalledWith(
threeDSecureNonce,
{ nonce: threeDSecureNonce },
getOrder().orderAmount,
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,22 @@ export default class BraintreeCreditCardPaymentStrategy implements PaymentStrate
}

try {
const {
instruments: { getCardInstrumentOrThrow },
} = this._store.getState();
const { payer_auth_request: storedCreditCardNonce } = error.body.three_ds_result || {};
const { paymentData } = payment;

if (!paymentData || !isVaultedInstrument(paymentData)) {
throw new PaymentArgumentInvalidError(['instrumentId']);
}

const instrument = getCardInstrumentOrThrow(paymentData.instrumentId);
const { nonce } = await this._braintreePaymentProcessor.challenge3DSVerification(
storedCreditCardNonce,
{
nonce: storedCreditCardNonce,
bin: instrument.iin,
},
orderAmount,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Dictionary, isEmpty, isNil, omitBy } from 'lodash';
import { Address } from '../../../address';
import { NotInitializedError, NotInitializedErrorType } from '../../../common/error/errors';
import { PaymentInvalidFormError, PaymentInvalidFormErrorDetails } from '../../errors';
import { NonceInstrument } from '../../payment';

import {
BraintreeBillingAddressRequestData,
Expand All @@ -13,6 +12,7 @@ import {
BraintreeHostedFieldsCreatorConfig,
BraintreeHostedFieldsState,
BraintreeHostedFormError,
TokenizationPayload,
} from './braintree';
import {
BraintreeFormFieldsMap,
Expand Down Expand Up @@ -92,13 +92,13 @@ export default class BraintreeHostedForm {
this._cardNameField?.detach();
}

async tokenize(billingAddress: Address): Promise<NonceInstrument> {
async tokenize(billingAddress: Address): Promise<TokenizationPayload> {
if (!this._cardFields) {
throw new NotInitializedError(NotInitializedErrorType.PaymentNotInitialized);
}

try {
const { nonce } = await this._cardFields.tokenize(
const tokenizationPayload = await this._cardFields.tokenize(
omitBy(
{
billingAddress: billingAddress && this._mapBillingAddress(billingAddress),
Expand All @@ -113,7 +113,10 @@ export default class BraintreeHostedForm {
errors: {},
});

return { nonce };
return {
nonce: tokenizationPayload.nonce,
bin: tokenizationPayload.details?.bin,
};
} catch (error) {
const errors = this._mapTokenizeError(error);

Expand All @@ -130,13 +133,13 @@ export default class BraintreeHostedForm {
}
}

async tokenizeForStoredCardVerification(): Promise<NonceInstrument> {
async tokenizeForStoredCardVerification(): Promise<TokenizationPayload> {
if (!this._cardFields) {
throw new NotInitializedError(NotInitializedErrorType.PaymentNotInitialized);
}

try {
const { nonce } = await this._cardFields.tokenize(
const tokenizationPayload = await this._cardFields.tokenize(
omitBy(
{
cardholderName: this._cardNameField?.getValue(),
Expand All @@ -150,7 +153,10 @@ export default class BraintreeHostedForm {
errors: {},
});

return { nonce };
return {
nonce: tokenizationPayload.nonce,
bin: tokenizationPayload.details?.bin,
};
} catch (error) {
const errors = this._mapTokenizeError(error, true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ describe('BraintreePaymentProcessor', () => {
getBillingAddress(),
);

expect(tokenizedCard).toEqual({ nonce: 'demo_nonce' });
expect(tokenizedCard).toEqual({
nonce: 'demo_nonce',
bin: 'demo_bin',
});
});

it('calls the braintree client request with the correct information', async () => {
Expand Down Expand Up @@ -548,31 +551,49 @@ describe('BraintreePaymentProcessor', () => {
braintreePaymentProcessor.initialize('clientToken', {});

return expect(
braintreePaymentProcessor.challenge3DSVerification('tokenization_nonce', 122),
braintreePaymentProcessor.challenge3DSVerification(
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
),
).rejects.toThrow(NotInitializedError);
});

it('challenges 3DS verifies the card using 3DS', async () => {
jest.spyOn(threeDSecureMock, 'verifyCard').mockReturnValue(
Promise.resolve({ nonce: 'three_ds_nonce' }),
Promise.resolve({
nonce: 'three_ds_nonce',
}),
);

const verifiedCard = await braintreePaymentProcessor.challenge3DSVerification(
'tokenization_nonce',
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
);

expect(verifiedCard).toEqual({ nonce: 'three_ds_nonce' });
});

it('calls the verification service with the right values', async () => {
await braintreePaymentProcessor.challenge3DSVerification('tokenization_nonce', 122);
await braintreePaymentProcessor.challenge3DSVerification(
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
);

expect(threeDSecureMock.verifyCard).toHaveBeenCalledWith({
addFrame: expect.any(Function),
removeFrame: expect.any(Function),
challengeRequested: true,
amount: 122,
bin: '123456',
nonce: 'tokenization_nonce',
onLookupComplete: expect.any(Function),
});
Expand All @@ -593,7 +614,13 @@ describe('BraintreePaymentProcessor', () => {

it('cancels card verification', async () => {
braintreePaymentProcessor
.challenge3DSVerification('tokenization_nonce', 122)
.challenge3DSVerification(
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
)
.catch(noop);

await new Promise((resolve) => process.nextTick(resolve));
Expand All @@ -604,7 +631,10 @@ describe('BraintreePaymentProcessor', () => {

it('rejects the return promise', async () => {
const promise = braintreePaymentProcessor.challenge3DSVerification(
'tokenization_nonce',
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
);

Expand All @@ -615,7 +645,13 @@ describe('BraintreePaymentProcessor', () => {
});

it('resolves with verify payload', async () => {
braintreePaymentProcessor.challenge3DSVerification('tokenization_nonce', 122);
braintreePaymentProcessor.challenge3DSVerification(
{
nonce: 'tokenization_nonce',
bin: '123456',
},
122,
);

await new Promise((resolve) => process.nextTick(resolve));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
BraintreeTokenizePayload,
BraintreeVenmoCheckout,
BraintreeVerifyPayload,
TokenizationPayload,
} from './braintree';
import BraintreeHostedForm from './braintree-hosted-form';
import {
Expand Down Expand Up @@ -68,7 +69,7 @@ export default class BraintreePaymentProcessor {
async tokenizeCard(
payment: OrderPaymentRequestBody,
billingAddress: Address,
): Promise<NonceInstrument> {
): Promise<TokenizationPayload> {
const { paymentData } = payment;

if (!isCreditCardInstrumentLike(paymentData)) {
Expand All @@ -85,17 +86,20 @@ export default class BraintreePaymentProcessor {
const client = await this._braintreeSDKCreator.getClient();
const { creditCards } = await client.request(requestData);

return { nonce: creditCards[0].nonce };
return {
nonce: creditCards[0].nonce,
bin: creditCards[0].details?.bin,
};
}

async verifyCard(
payment: OrderPaymentRequestBody,
billingAddress: Address,
amount: number,
): Promise<NonceInstrument> {
const { nonce } = await this.tokenizeCard(payment, billingAddress);
const tokenizationPayload = await this.tokenizeCard(payment, billingAddress);

return this.challenge3DSVerification(nonce, amount);
return this.challenge3DSVerification(tokenizationPayload, amount);
}

paypal({ shouldSaveInstrument, ...config }: PaypalConfig): Promise<BraintreeTokenizePayload> {
Expand Down Expand Up @@ -171,15 +175,18 @@ export default class BraintreePaymentProcessor {
billingAddress: Address,
amount: number,
): Promise<NonceInstrument> {
const { nonce } = await this._braintreeHostedForm.tokenize(billingAddress);
const tokenizationPayload = await this._braintreeHostedForm.tokenize(billingAddress);

return this.challenge3DSVerification(nonce, amount);
return this.challenge3DSVerification(tokenizationPayload, amount);
}

async challenge3DSVerification(nonce: string, amount: number): Promise<NonceInstrument> {
async challenge3DSVerification(
tokenizationPayload: TokenizationPayload,
amount: number,
): Promise<NonceInstrument> {
const threeDSecure = await this._braintreeSDKCreator.get3DS();

return this._present3DSChallenge(threeDSecure, amount, nonce);
return this._present3DSChallenge(threeDSecure, amount, tokenizationPayload);
}

async getVenmoCheckout(): Promise<BraintreeVenmoCheckout> {
Expand Down Expand Up @@ -218,8 +225,10 @@ export default class BraintreePaymentProcessor {
private _present3DSChallenge(
threeDSecure: BraintreeThreeDSecure,
amount: number,
nonce: string,
tokenizationPayload: TokenizationPayload,
): Promise<BraintreeVerifyPayload> {
const { nonce, bin } = tokenizationPayload;

if (!this._threeDSecureOptions || !nonce) {
throw new NotInitializedError(NotInitializedErrorType.PaymentNotInitialized);
}
Expand All @@ -241,6 +250,7 @@ export default class BraintreePaymentProcessor {
addFrame(error, iframe, cancelVerifyCard);
},
amount: Number(roundedAmount),
bin,
challengeRequested: true,
nonce,
removeFrame,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
getVisaCheckoutMock,
} from './braintree.mock';

const version = '3.81.0';
const version = '3.95.0';

describe('BraintreeScriptLoader', () => {
let braintreeScriptLoader: BraintreeScriptLoader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
BraintreeVisaCheckoutCreator,
} from './braintree';

const version = '3.81.0';
const version = '3.95.0';

export default class BraintreeScriptLoader {
constructor(
Expand Down
27 changes: 26 additions & 1 deletion packages/core/src/payment/strategies/braintree/braintree.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,32 @@ export function getModuleCreatorNewMock<T>(

export function getTokenizeResponseBody(): BraintreeTokenizeResponse {
return {
creditCards: [{ nonce: 'demo_nonce' }],
creditCards: [
{
nonce: 'demo_nonce',
details: {
bin: 'demo_bin',
cardType: 'Visa',
expirationMonth: '01',
expirationYear: '2025',
lastFour: '0001',
lastTwo: '01',
},
description: 'ending in 01',
type: 'CreditCard',
binData: {
commercial: 'bin_data_commercial',
countryOfIssuance: 'bin_data_country_of_issuance',
debit: 'bin_data_debit',
durbinRegulated: 'bin_data_durbin_regulated',
healthcare: 'bin_data_healthcare',
issuingBank: 'bin_data_issuing_bank',
payroll: 'bin_data_payroll',
prepaid: 'bin_data_prepaid',
productId: 'bin_data_product_id',
},
},
],
};
}

Expand Down
Loading

0 comments on commit 3192f93

Please sign in to comment.