Skip to content

Commit fb18a22

Browse files
feat(payment): PI-2428 Google Pay on TD Online Mart - check if FE is working correctly, adjust communication FE -> BE
1 parent 62f982a commit fb18a22

8 files changed

+290
-8
lines changed

packages/google-pay-integration/src/factories/button/create-google-pay-tdonlinemart-button-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartButtonStrategy: CheckoutButtonStrategyFactory<
1818
paymentIntegrationService,
1919
new GooglePayPaymentProcessor(
2020
createGooglePayScriptLoader(),
21-
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
21+
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
2222
createRequestSender(),
2323
createFormPoster(),
2424
),

packages/google-pay-integration/src/factories/customer/create-google-pay-tdonlinemart-customer-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartCustomerStrategy: CustomerStrategyFactory<
1818
paymentIntegrationService,
1919
new GooglePayPaymentProcessor(
2020
createGooglePayScriptLoader(),
21-
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
21+
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
2222
createRequestSender(),
2323
createFormPoster(),
2424
),

packages/google-pay-integration/src/factories/payment/create-google-pay-tdonlinemart-payment-strategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const createGooglePayTdOnlineMartPaymentStrategy: PaymentStrategyFactory<
1818
paymentIntegrationService,
1919
new GooglePayPaymentProcessor(
2020
createGooglePayScriptLoader(),
21-
new GooglePayTdOnlineMartGateway(paymentIntegrationService),
21+
new GooglePayTdOnlineMartGateway(paymentIntegrationService, createFormPoster()),
2222
createRequestSender(),
2323
createFormPoster(),
2424
),
Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,161 @@
1-
import { PaymentIntegrationService } from '@bigcommerce/checkout-sdk/payment-integration-api';
1+
import { FormPoster } from '@bigcommerce/form-poster';
2+
import { createRequestSender } from '@bigcommerce/request-sender';
3+
4+
import {
5+
OrderRequestBody,
6+
PaymentArgumentInvalidError,
7+
PaymentIntegrationService,
8+
} from '@bigcommerce/checkout-sdk/payment-integration-api';
29
import { PaymentIntegrationServiceMock } from '@bigcommerce/checkout-sdk/payment-integrations-test-utils';
310

11+
import createGooglePayScriptLoader from '../factories/create-google-pay-script-loader';
12+
import GooglePayPaymentProcessor from '../google-pay-payment-processor';
13+
import GooglePayPaymentStrategy from '../google-pay-payment-strategy';
14+
import * as TdOnlineMartAdditionalAction from '../guards/is-google-pay-td-online-mart-additional-action';
15+
import { getGeneric } from '../mocks/google-pay-payment-method.mock';
16+
417
import GooglePayGateway from './google-pay-gateway';
518
import GooglePayTdOnlineMartGateway from './google-pay-tdonlinemart-gateway';
619

720
describe('GooglePayTdOnlineMartGateway', () => {
821
let gateway: GooglePayTdOnlineMartGateway;
922
let paymentIntegrationService: PaymentIntegrationService;
23+
let processor: GooglePayPaymentProcessor;
24+
let strategy: GooglePayPaymentStrategy;
25+
let payload: OrderRequestBody;
26+
let formPoster: FormPoster;
1027

1128
beforeEach(() => {
29+
formPoster = {
30+
postForm: jest.fn(),
31+
} as unknown as FormPoster;
32+
1233
paymentIntegrationService = new PaymentIntegrationServiceMock();
1334

14-
gateway = new GooglePayTdOnlineMartGateway(paymentIntegrationService);
35+
gateway = new GooglePayTdOnlineMartGateway(paymentIntegrationService, formPoster);
36+
37+
processor = new GooglePayPaymentProcessor(
38+
createGooglePayScriptLoader(),
39+
new GooglePayTdOnlineMartGateway(paymentIntegrationService, formPoster),
40+
createRequestSender(),
41+
formPoster,
42+
);
43+
44+
strategy = new GooglePayPaymentStrategy(paymentIntegrationService, processor);
45+
46+
payload = {
47+
payment: {
48+
methodId: 'worldlinena',
49+
},
50+
};
51+
52+
jest.spyOn(paymentIntegrationService.getState(), 'getPaymentMethodOrThrow').mockReturnValue(
53+
getGeneric(),
54+
);
55+
56+
jest.spyOn(processor, 'initialize').mockResolvedValue(undefined);
57+
jest.spyOn(processor, 'getNonce').mockResolvedValue('nonceValue');
58+
});
59+
60+
afterEach(() => {
61+
jest.clearAllMocks();
1562
});
1663

1764
it('is a special type of GooglePayGateway', () => {
65+
jest.spyOn(processor, 'processAdditionalAction').mockResolvedValue(undefined);
66+
1867
expect(gateway).toBeInstanceOf(GooglePayGateway);
1968
});
69+
70+
it('should process additional action', async () => {
71+
jest.spyOn(processor, 'processAdditionalAction').mockResolvedValue(undefined);
72+
73+
jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue('error');
74+
75+
await strategy.execute(payload);
76+
77+
expect(processor.processAdditionalAction).toHaveBeenCalledWith('error');
78+
});
79+
80+
it('throw not additional action error', async () => {
81+
let submitPaymentError;
82+
83+
jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
84+
false,
85+
);
86+
87+
jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
88+
message: 'any_error',
89+
});
90+
91+
try {
92+
await strategy.execute(payload);
93+
} catch (error) {
94+
submitPaymentError = error;
95+
} finally {
96+
expect(submitPaymentError).toEqual({ message: 'any_error' });
97+
}
98+
});
99+
100+
it('throw error when not enough 3DS data', async () => {
101+
let submitPaymentError;
102+
103+
jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
104+
true,
105+
);
106+
107+
jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
108+
body: {
109+
errors: [
110+
{
111+
code: 'three_ds_result',
112+
},
113+
],
114+
three_ds_result: {},
115+
},
116+
});
117+
118+
try {
119+
await strategy.execute(payload);
120+
} catch (error) {
121+
submitPaymentError = error;
122+
} finally {
123+
expect(submitPaymentError).toBeInstanceOf(PaymentArgumentInvalidError);
124+
}
125+
});
126+
127+
it('execute 3DS challenge', async () => {
128+
const postFormMock = jest.fn((_url, _options, resolveFn) => Promise.resolve(resolveFn()));
129+
130+
jest.spyOn(formPoster, 'postForm').mockImplementation(postFormMock);
131+
jest.spyOn(TdOnlineMartAdditionalAction, 'isTdOnlineMartAdditionalAction').mockReturnValue(
132+
true,
133+
);
134+
jest.spyOn(paymentIntegrationService, 'submitPayment').mockRejectedValue({
135+
body: {
136+
errors: [
137+
{
138+
code: 'three_ds_result',
139+
},
140+
],
141+
three_ds_result: {
142+
acs_url: 'https://example.com',
143+
payer_auth_request: '3ds_session_data',
144+
merchant_data: 'creq_data',
145+
},
146+
},
147+
});
148+
149+
await strategy.execute(payload);
150+
151+
expect(postFormMock).toHaveBeenCalledWith(
152+
'https://example.com',
153+
{
154+
threeDSSessionData: '3ds_session_data',
155+
creq: 'creq_data',
156+
},
157+
expect.any(Function),
158+
'_top',
159+
);
160+
});
20161
});
Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,51 @@
1-
import { PaymentIntegrationService } from '@bigcommerce/checkout-sdk/payment-integration-api';
1+
import { FormPoster } from '@bigcommerce/form-poster';
2+
3+
import {
4+
getBrowserInfo,
5+
PaymentArgumentInvalidError,
6+
PaymentIntegrationService,
7+
} from '@bigcommerce/checkout-sdk/payment-integration-api';
8+
9+
import { isTdOnlineMartAdditionalAction } from '../guards/is-google-pay-td-online-mart-additional-action';
10+
import { ExtraPaymentData, TdOnlineMartThreeDSErrorBody } from '../types';
211

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

514
export default class GooglePayTdOnlineMartGateway extends GooglePayGateway {
6-
constructor(service: PaymentIntegrationService) {
15+
constructor(service: PaymentIntegrationService, private formPoster: FormPoster) {
716
super('worldlinena', service);
817
}
18+
19+
async extraPaymentData(): Promise<undefined | ExtraPaymentData> {
20+
return Promise.resolve({ browser_info: getBrowserInfo() });
21+
}
22+
23+
async processAdditionalAction(error: unknown): Promise<void> {
24+
if (!isTdOnlineMartAdditionalAction(error)) {
25+
throw error;
26+
}
27+
28+
const { three_ds_result: threeDSResult }: TdOnlineMartThreeDSErrorBody = error.body;
29+
const {
30+
acs_url: formUrl,
31+
payer_auth_request: threeDSSessionData,
32+
merchant_data: creq,
33+
} = threeDSResult || {};
34+
35+
if (!formUrl || !threeDSSessionData || !creq) {
36+
throw new PaymentArgumentInvalidError(['formUrl', 'threeDSSessionData', 'creq']);
37+
}
38+
39+
return new Promise((resolve) => {
40+
this.formPoster.postForm(
41+
formUrl,
42+
{
43+
threeDSSessionData,
44+
creq,
45+
},
46+
resolve,
47+
'_top',
48+
);
49+
});
50+
}
951
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { isTdOnlineMartAdditionalAction } from './isTdOnlineMartAdditionalAction';
2+
3+
describe('isTdOnlineMartAdditionalAction', () => {
4+
it('receive not request error', () => {
5+
expect(isTdOnlineMartAdditionalAction({})).toBe(false);
6+
});
7+
8+
it('error does not contain 3DS error code', () => {
9+
expect(
10+
isTdOnlineMartAdditionalAction({
11+
body: {
12+
errors: [
13+
{
14+
code: 'any_code',
15+
},
16+
],
17+
// eslint-disable-next-line @typescript-eslint/naming-convention
18+
three_ds_result: {},
19+
},
20+
}),
21+
).toBe(false);
22+
});
23+
24+
it('error does not contain 3DS results', () => {
25+
expect(
26+
isTdOnlineMartAdditionalAction({
27+
body: {
28+
errors: [
29+
{
30+
code: 'three_d_secure_required',
31+
},
32+
],
33+
},
34+
}),
35+
).toBe(false);
36+
});
37+
38+
it('error is a TD bank additional action error', () => {
39+
expect(
40+
isTdOnlineMartAdditionalAction({
41+
body: {
42+
errors: [
43+
{
44+
code: 'any_code',
45+
},
46+
{
47+
code: 'three_d_secure_required',
48+
},
49+
],
50+
// eslint-disable-next-line @typescript-eslint/naming-convention
51+
three_ds_result: {},
52+
},
53+
}),
54+
).toBe(true);
55+
});
56+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { isArray, isObject, some } from 'lodash';
2+
3+
import { isRequestError } from '@bigcommerce/checkout-sdk/payment-integration-api';
4+
5+
import { TdOnlineMartAdditionalAction, TdOnlineMartThreeDSErrorBody } from '../types';
6+
7+
function isTdOnlineMartThreeDSErrorBody(
8+
errorBody: unknown,
9+
): errorBody is TdOnlineMartThreeDSErrorBody {
10+
return (
11+
isObject(errorBody) &&
12+
typeof errorBody === 'object' &&
13+
'errors' in errorBody &&
14+
'three_ds_result' in errorBody &&
15+
isArray((errorBody as TdOnlineMartThreeDSErrorBody).errors) &&
16+
some((errorBody as TdOnlineMartThreeDSErrorBody).errors, {
17+
code: 'three_d_secure_required',
18+
})
19+
);
20+
}
21+
/* eslint-enable @typescript-eslint/consistent-type-assertions */
22+
23+
export function isTdOnlineMartAdditionalAction(
24+
error: unknown,
25+
): error is TdOnlineMartAdditionalAction {
26+
return isRequestError(error) && isTdOnlineMartThreeDSErrorBody(error.body);
27+
}

packages/google-pay-integration/src/types.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { BuyNowCartRequestBody } from '@bigcommerce/checkout-sdk/payment-integration-api';
1+
import {
2+
BrowserInfo,
3+
BuyNowCartRequestBody,
4+
RequestError,
5+
} from '@bigcommerce/checkout-sdk/payment-integration-api';
26

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

@@ -411,6 +415,7 @@ export interface GooglePayBuyNowInitializeOptions {
411415

412416
export interface ExtraPaymentData {
413417
deviceSessionId?: string;
418+
browser_info?: BrowserInfo;
414419
}
415420

416421
export type GooglePayButtonColor = 'default' | 'black' | 'white';
@@ -425,3 +430,14 @@ export type GooglePayButtonType =
425430
| 'subscribe'
426431
| 'long'
427432
| 'short';
433+
434+
export interface TdOnlineMartThreeDSErrorBody {
435+
errors?: Array<{ code: string }>;
436+
three_ds_result?: {
437+
acs_url: string;
438+
payer_auth_request: string;
439+
merchant_data: string;
440+
};
441+
}
442+
443+
export type TdOnlineMartAdditionalAction = RequestError<TdOnlineMartThreeDSErrorBody>;

0 commit comments

Comments
 (0)