Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getBigCommercePaymentsFastlanePaymentMethod,
getPayPalFastlaneAuthenticationResultMock,
getPayPalFastlaneSdk,
LiabilityShiftEnum,
PayPalFastlane,
PayPalFastlaneAuthenticationState,
PayPalFastlaneSdk,
Expand Down Expand Up @@ -551,7 +552,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
...threeDomainSecureComponentMock,
isEligible: jest.fn().mockReturnValue(Promise.resolve(true)),
show: jest.fn().mockResolvedValue({
liabilityShift: 'YES',
liabilityShift: LiabilityShiftEnum.Yes,
}),
},
};
Expand Down Expand Up @@ -580,7 +581,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
...threeDomainSecureComponentMock,
isEligible: jest.fn().mockReturnValue(Promise.resolve(true)),
show: jest.fn().mockResolvedValue({
liabilityShift: 'UNKNOWN',
liabilityShift: LiabilityShiftEnum.Unknown,
}),
},
};
Expand Down Expand Up @@ -656,7 +657,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'possible',
liabilityShift: LiabilityShiftEnum.Possible,
authenticationState: 'succeeded',
nonce: 'bigcommerce_payments_fastlane_instrument_id_nonce',
}),
Expand Down Expand Up @@ -695,7 +696,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'NO',
liabilityShift: LiabilityShiftEnum.No,
authenticationState: 'success',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand All @@ -721,7 +722,7 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'possible',
liabilityShift: LiabilityShiftEnum.Possible,
authenticationState: 'errored',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand All @@ -739,6 +740,32 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
expect(error).toBeInstanceOf(Error);
}
});

it('creates order with payment token when 3ds is on and isEligible false', async () => {
const bigCommerceFastlaneSdkMock = {
...paypalFastlaneSdk,
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
isEligible: jest.fn().mockReturnValue(Promise.resolve(false)),
},
};

jest.spyOn(bigCommercePaymentsSdk, 'getPayPalFastlaneSdk').mockImplementation(() =>
Promise.resolve(bigCommerceFastlaneSdkMock),
);

await strategy.initialize(initializationOptions);

await strategy.execute(executeOptions);

expect(bigCommercePaymentsRequestSender.createOrder).toHaveBeenCalledWith(
methodId,
{
cartId: cart.id,
fastlaneToken: 'paypal_fastlane_instrument_id_nonce',
},
);
});
});

it('throws specific error when get 422 error on payment request', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
PayPalFastlaneSdk,
PayPalSdkHelper,
TDSecureAuthenticationState,
TDSecureVerificationMethod,
} from '@bigcommerce/checkout-sdk/bigcommerce-payments-utils';
import {
CardInstrument,
Expand Down Expand Up @@ -337,19 +338,17 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
this.isBigcommercePaymentsFastlaneThreeDSAvailable() &&
paymentMethod.config.is3dsEnabled;

if (!is3DSEnabled) {
await this.createOrder(instrumentId);
}
const nonce = is3DSEnabled ? await this.get3DSNonce(instrumentId) : instrumentId;

const fastlaneToken = is3DSEnabled ? await this.get3DSNonce(instrumentId) : instrumentId;
await this.createOrder(nonce);

return {
methodId,
paymentData: {
formattedPayload: {
paypal_fastlane_token: {
order_id: this.orderId,
token: fastlaneToken,
token: nonce,
},
},
},
Expand Down Expand Up @@ -379,14 +378,12 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
this.isBigcommercePaymentsFastlaneThreeDSAvailable() &&
paymentMethod.config.is3dsEnabled;

if (!is3DSEnabled) {
await this.createOrder(id);
}

const { shouldSaveInstrument = false, shouldSetAsDefaultInstrument = false } =
isHostedInstrumentLike(paymentData) ? paymentData : {};

const fastlaneToken = is3DSEnabled ? await this.get3DSNonce(id) : id;
const nonce = is3DSEnabled ? await this.get3DSNonce(id) : id;

await this.createOrder(nonce);

return {
methodId,
Expand All @@ -397,7 +394,7 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
formattedPayload: {
paypal_fastlane_token: {
order_id: this.orderId,
token: fastlaneToken,
token: nonce,
},
},
},
Expand Down Expand Up @@ -426,7 +423,7 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
* 3DSecure methods
*
* */
private async get3DSNonce(paypalNonce: string): Promise<string> {
private async get3DSNonce(nonce: string): Promise<string> {
const state = this.paymentIntegrationService.getState();
const cart = state.getCartOrThrow();
const order = state.getOrderOrThrow();
Expand All @@ -441,8 +438,8 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
const threeDomainSecureParameters = {
amount: order.orderAmount.toFixed(2),
currency: cart.currency.code,
nonce: paypalNonce,
threeDSRequested: this.threeDSVerificationMethod === 'SCA_ALWAYS',
nonce,
threeDSRequested: this.threeDSVerificationMethod === TDSecureVerificationMethod.Always,
transactionContext: {
experience_context: {
locale: 'en-US',
Expand All @@ -456,35 +453,38 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
threeDomainSecureParameters,
);

if (
this.threeDSVerificationMethod === TDSecureVerificationMethod.Always &&
!isThreeDomainSecureEligible
) {
throw new PaymentMethodInvalidError();
}

if (isThreeDomainSecureEligible) {
const { liabilityShift, authenticationState, nonce } =
await threeDomainSecureComponent.show();
const {
liabilityShift,
authenticationState,
nonce: threeDSNonce,
} = await threeDomainSecureComponent.show();

if (
liabilityShift === LiabilityShiftEnum.No ||
liabilityShift === LiabilityShiftEnum.Unknown
liabilityShift === LiabilityShiftEnum.Unknown ||
authenticationState === TDSecureAuthenticationState.Errored ||
authenticationState === TDSecureAuthenticationState.Cancelled
) {
throw new PaymentMethodInvalidError();
}

await this.createOrder(paypalNonce);

if (authenticationState === TDSecureAuthenticationState.Succeeded) {
return nonce;
}

// Cancelled or errored, merchant can choose to send the customer back to 3D Secure or submit a payment and or vault the payment token.
if (authenticationState === TDSecureAuthenticationState.Errored) {
throw new PaymentMethodInvalidError();
}

if (authenticationState === TDSecureAuthenticationState.Cancelled) {
console.error('3DS check was canceled');
throw new PaymentMethodInvalidError();
if (
authenticationState === TDSecureAuthenticationState.Succeeded &&
[LiabilityShiftEnum.Yes, LiabilityShiftEnum.Possible].includes(liabilityShift)
) {
return threeDSNonce;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask you to verify do we cover this case in test file? I may assume we can not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cover it with Possible value line 660 in test file

}
}

return paypalNonce;
return nonce;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ export interface PayPalGooglePaySdk {
Googlepay(): GooglePay;
}

export enum TDSecureVerificationMethod {
Always = 'SCA_ALWAYS',
}

export enum TDSecureAuthenticationState {
Succeeded = 'succeeded',
Cancelled = 'cancelled',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from '@bigcommerce/checkout-sdk/paypal-utils';

import PayPalCommerceRequestSender from '../paypal-commerce-request-sender';
import { LiabilityShiftEnum } from '../paypal-commerce-types';

import PayPalCommerceFastlanePaymentStrategy from './paypal-commerce-fastlane-payment-strategy';

Expand Down Expand Up @@ -565,7 +566,7 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'YES',
liabilityShift: LiabilityShiftEnum.Yes,
authenticationState: 'succeeded',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand All @@ -591,7 +592,7 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'UNKNOWN',
liabilityShift: LiabilityShiftEnum.Unknown,
authenticationState: 'succeeded',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand Down Expand Up @@ -637,7 +638,7 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'POSSIBLE',
liabilityShift: LiabilityShiftEnum.Possible,
authenticationState: 'succeeded',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand Down Expand Up @@ -676,7 +677,7 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'NO',
liabilityShift: LiabilityShiftEnum.No,
authenticationState: 'success',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand All @@ -702,7 +703,7 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
show: jest.fn().mockReturnValue({
liabilityShift: 'POSSIBLE',
liabilityShift: LiabilityShiftEnum.Possible,
authenticationState: 'errored',
nonce: 'paypal_fastlane_instrument_id_nonce_3ds',
}),
Expand All @@ -720,6 +721,28 @@ describe('PayPalCommerceFastlanePaymentStrategy', () => {
expect(error).toBeInstanceOf(Error);
}
});

it('creates order with payment token when 3ds is on and isEligible false', async () => {
const paypalFastlaneSdkMock = {
...paypalFastlaneSdk,
ThreeDomainSecureClient: {
...threeDomainSecureComponentMock,
isEligible: jest.fn().mockReturnValue(Promise.resolve(false)),
},
};

jest.spyOn(paypalSdkScriptLoader, 'getPayPalFastlaneSdk').mockImplementation(() =>
Promise.resolve(paypalFastlaneSdkMock),
);
await strategy.initialize(initializationOptions);

await strategy.execute(executeOptions);

expect(paypalCommerceRequestSender.createOrder).toHaveBeenCalledWith(methodId, {
cartId: cart.id,
fastlaneToken: 'paypal_fastlane_instrument_id_nonce',
});
});
});
});

Expand Down
Loading