Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pi 2428 test #2591

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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 @@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartButtonStrategy: CheckoutButtonStrategyFactory<
paymentIntegrationService,
new GooglePayPaymentProcessor(
createGooglePayScriptLoader(),
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
createRequestSender(),
createFormPoster(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartCustomerStrategy: CustomerStrategyFactory<
paymentIntegrationService,
new GooglePayPaymentProcessor(
createGooglePayScriptLoader(),
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
createRequestSender(),
createFormPoster(),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartPaymentStrategy: PaymentStrategyFactory<
paymentIntegrationService,
new GooglePayPaymentProcessor(
createGooglePayScriptLoader(),
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
createRequestSender(),
createFormPoster(),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,161 @@
import { PaymentIntegrationService } from '@bigcommerce/checkout-sdk/payment-integration-api';
import { FormPoster } from '@bigcommerce/form-poster';
import { createRequestSender } from '@bigcommerce/request-sender';

import {
OrderRequestBody,
PaymentArgumentInvalidError,
PaymentIntegrationService,
} from '@bigcommerce/checkout-sdk/payment-integration-api';
import { PaymentIntegrationServiceMock } from '@bigcommerce/checkout-sdk/payment-integrations-test-utils';

import createGooglePayScriptLoader from '../factories/create-google-pay-script-loader';
import GooglePayPaymentProcessor from '../google-pay-payment-processor';
import GooglePayPaymentStrategy from '../google-pay-payment-strategy';
import * as TdOnlineMartAdditionalAction from '../guards/is-google-pay-td-online-mart-additional-action';
import { getGeneric } from '../mocks/google-pay-payment-method.mock';

import GooglePayGateway from './google-pay-gateway';
import GooglePayTdOnlineMartGateway from './google-pay-tdonlinemart-gateway';

describe('GooglePayTdOnlineMartGateway', () => {
let gateway: GooglePayTdOnlineMartGateway;
let paymentIntegrationService: PaymentIntegrationService;
let processor: GooglePayPaymentProcessor;
let strategy: GooglePayPaymentStrategy;
let payload: OrderRequestBody;
let formPoster: FormPoster;

beforeEach(() => {
formPoster = {
postForm: jest.fn(),
} as unknown as FormPoster;

paymentIntegrationService = new PaymentIntegrationServiceMock();

gateway = new GooglePayTdOnlineMartGateway(paymentIntegrationService);
gateway = new GooglePayTdOnlineMartGateway(paymentIntegrationService, formPoster);

processor = new GooglePayPaymentProcessor(
createGooglePayScriptLoader(),
new GooglePayTdOnlineMartGateway(paymentIntegrationService, formPoster),
createRequestSender(),
formPoster,
);

strategy = new GooglePayPaymentStrategy(paymentIntegrationService, processor);

payload = {
payment: {
methodId: 'worldlinena',
},
};

jest.spyOn(paymentIntegrationService.getState(), 'getPaymentMethodOrThrow').mockReturnValue(
getGeneric(),
);

jest.spyOn(processor, 'initialize').mockResolvedValue(undefined);
jest.spyOn(processor, 'getNonce').mockResolvedValue('nonceValue');
});

afterEach(() => {
jest.clearAllMocks();
});

it('is a special type of GooglePayGateway', () => {
jest.spyOn(processor, 'processAdditionalAction').mockResolvedValue(undefined);

expect(gateway).toBeInstanceOf(GooglePayGateway);
});

it('should process additional action', async () => {
jest.spyOn(processor, 'processAdditionalAction').mockResolvedValue(undefined);

jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue('error');

await strategy.execute(payload);

expect(processor.processAdditionalAction).toHaveBeenCalledWith('error');
});

it('throw not additional action error', async () => {
let submitPaymentError;

jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
false,
);

jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
message: 'any_error',
});

try {
await strategy.execute(payload);
} catch (error) {
submitPaymentError = error;
} finally {
expect(submitPaymentError).toEqual({ message: 'any_error' });
}
});

it('throw error when not enough 3DS data', async () => {
let submitPaymentError;

jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
true,
);

jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
body: {
errors: [
{
code: 'three_ds_result',
},
],
three_ds_result: {},
},
});

try {
await strategy.execute(payload);
} catch (error) {
submitPaymentError = error;
} finally {
expect(submitPaymentError).toBeInstanceOf(PaymentArgumentInvalidError);
}
});

it('execute 3DS challenge', async () => {
const postFormMock = jest.fn((_url, _options, resolveFn) => Promise.resolve(resolveFn()));

jest.spyOn(formPoster, 'postForm').mockImplementation(postFormMock);
jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
true,
);
jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
body: {
errors: [
{
code: 'three_ds_result',
},
],
three_ds_result: {
acs_url: 'https://example.com',
payer_auth_request: '3ds_session_data',
merchant_data: 'creq_data',
},
},
});

await strategy.execute(payload);

expect(postFormMock).toHaveBeenCalledWith(
'https://example.com',
{
threeDSSessionData: '3ds_session_data',
creq: 'creq_data',
},
expect.any(Function),
'_top',
);
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
import { PaymentIntegrationService } from '@bigcommerce/checkout-sdk/payment-integration-api';
import { FormPoster } from '@bigcommerce/form-poster';

import {
getBrowserInfo,
PaymentArgumentInvalidError,
PaymentIntegrationService,
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { isTdOnlineMartAdditionalAction } from '../guards/is-google-pay-td-online-mart-additional-action';
import { ExtraPaymentData, TdOnlineMartThreeDSErrorBody } from '../types';

import GooglePayGateway from './google-pay-gateway';

export default class GooglePayTdOnlineMartGateway extends GooglePayGateway {
constructor(service: PaymentIntegrationService) {
constructor(service: PaymentIntegrationService, private formPoster: FormPoster) {
super('worldlinena', service);
}

async extraPaymentData(): Promise<undefined | ExtraPaymentData> {
return Promise.resolve({ browser_info: getBrowserInfo() });
}

async processAdditionalAction(error: unknown): Promise<void> {
if (!isTdOnlineMartAdditionalAction(error)) {
throw error;
}

const { three_ds_result: threeDSResult }: TdOnlineMartThreeDSErrorBody = error.body;
const {
acs_url: formUrl,
payer_auth_request: threeDSSessionData,
merchant_data: creq,
} = threeDSResult || {};

if (!formUrl || !threeDSSessionData || !creq) {
throw new PaymentArgumentInvalidError(['formUrl', 'threeDSSessionData', 'creq']);
}

return new Promise((resolve) => {
this.formPoster.postForm(
formUrl,
{
threeDSSessionData,
creq,
},
resolve,
'_top',
);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { isTdOnlineMartAdditionalAction } from './isTdOnlineMartAdditionalAction';

describe('isTdOnlineMartAdditionalAction', () => {
it('receive not request error', () => {
expect(isTdOnlineMartAdditionalAction({})).toBe(false);
});

it('error does not contain 3DS error code', () => {
expect(
isTdOnlineMartAdditionalAction({
body: {
errors: [
{
code: 'any_code',
},
],
// eslint-disable-next-line @typescript-eslint/naming-convention
three_ds_result: {},
},
}),
).toBe(false);
});

it('error does not contain 3DS results', () => {
expect(
isTdOnlineMartAdditionalAction({
body: {
errors: [
{
code: 'three_d_secure_required',
},
],
},
}),
).toBe(false);
});

it('error is a TD bank additional action error', () => {
expect(
isTdOnlineMartAdditionalAction({
body: {
errors: [
{
code: 'any_code',
},
{
code: 'three_d_secure_required',
},
],
// eslint-disable-next-line @typescript-eslint/naming-convention
three_ds_result: {},
},
}),
).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { isArray, isObject, some } from 'lodash';

import { isRequestError } from '@bigcommerce/checkout-sdk/payment-integration-api';

import { TdOnlineMartAdditionalAction, TdOnlineMartThreeDSErrorBody } from '../types';

function isTdOnlineMartThreeDSErrorBody(
errorBody: unknown,
): errorBody is TdOnlineMartThreeDSErrorBody {
return (
isObject(errorBody) &&
typeof errorBody === 'object' &&
'errors' in errorBody &&
'three_ds_result' in errorBody &&
isArray((errorBody as TdOnlineMartThreeDSErrorBody).errors) &&
some((errorBody as TdOnlineMartThreeDSErrorBody).errors, {
code: 'three_d_secure_required',
})
);
}
/* eslint-enable @typescript-eslint/consistent-type-assertions */

export function isTdOnlineMartAdditionalAction(
error: unknown,
): error is TdOnlineMartAdditionalAction {
return isRequestError(error) && isTdOnlineMartThreeDSErrorBody(error.body);
}
18 changes: 17 additions & 1 deletion packages/google-pay-integration/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { BuyNowCartRequestBody } from '@bigcommerce/checkout-sdk/payment-integration-api';
import {
BrowserInfo,
BuyNowCartRequestBody,
RequestError,
} from '@bigcommerce/checkout-sdk/payment-integration-api';

import { FundingType } from './google-pay-paypal-commerce/types';

Expand Down Expand Up @@ -411,6 +415,7 @@ export interface GooglePayBuyNowInitializeOptions {

export interface ExtraPaymentData {
deviceSessionId?: string;
browser_info?: BrowserInfo;
}

export type GooglePayButtonColor = 'default' | 'black' | 'white';
Expand All @@ -425,3 +430,14 @@ export type GooglePayButtonType =
| 'subscribe'
| 'long'
| 'short';

export interface TdOnlineMartThreeDSErrorBody {
errors?: Array<{ code: string }>;
three_ds_result?: {
acs_url: string;
payer_auth_request: string;
merchant_data: string;
};
}

export type TdOnlineMartAdditionalAction = RequestError<TdOnlineMartThreeDSErrorBody>;