Skip to content

Commit 145f5e6

Browse files
chore: test CTBCPayments.handleCallbackTextBodyByURLPath
1 parent d9ad089 commit 145f5e6

2 files changed

Lines changed: 334 additions & 0 deletions

File tree

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
import { CardType, CTBCBindCardRequest, CTBCOrder, CTBCPayment } from '../src';
2+
import * as iconv from 'iconv-lite';
3+
import * as ctbcCrypto from '../src/ctbc-crypto-core';
4+
5+
jest.mock('iconv-lite', () => ({
6+
__esModule: true,
7+
decode: jest.fn(),
8+
}));
9+
10+
const rspjsonpwdToBody = (response: {}): string => {
11+
const rspjsonpwd = encodeURIComponent(JSON.stringify(response));
12+
13+
return `rspjsonpwd=${rspjsonpwd}`;
14+
};
15+
16+
const jsonToBody = (response: {}): string => {
17+
return encodeURIComponent(JSON.stringify(response));
18+
};
19+
20+
describe('CTBCPayments Mock Tests', () => {
21+
let payment: CTBCPayment;
22+
let order: CTBCOrder;
23+
let bindCardRequest: CTBCBindCardRequest;
24+
25+
const mockFailedResponse = {
26+
Response: {
27+
ReturnCode: 'I0001',
28+
ReturnMsg: 'FAILED',
29+
Data: {
30+
TXN: 'aZ7xQpL9vRt3HsMnK2wJf4Dy',
31+
},
32+
},
33+
};
34+
35+
const mockSuccessResponse = {
36+
Response: {
37+
ReturnCode: 'I0000',
38+
ReturnMsg: 'SUCCESS',
39+
Data: {
40+
TXN: 'aZ7xQpL9vRt3HsMnK2wJf4Dy',
41+
},
42+
},
43+
};
44+
45+
beforeEach(() => {
46+
jest.clearAllMocks();
47+
48+
// setup order
49+
order = new CTBCOrder({
50+
id: '123',
51+
items: [
52+
{
53+
name: 'Test Item',
54+
quantity: 1,
55+
unitPrice: 1000,
56+
},
57+
],
58+
gateway: payment,
59+
createdAt: new Date(),
60+
});
61+
62+
// setup payment instance with same txnKey
63+
payment = new CTBCPayment({
64+
merchantId: 'TEST_MERCHANT',
65+
merId: 'TEST_MERID',
66+
txnKey: 'h3Qx7uZLmYpWv8CtEr2KdBfj',
67+
terminalId: 'TEST_TERMINAL',
68+
baseUrl: 'https://testepos.ctbcbank.com',
69+
isAmex: false,
70+
});
71+
72+
Reflect.set(order, '_gateway', payment);
73+
74+
// setup bind card request
75+
bindCardRequest = new CTBCBindCardRequest(
76+
{
77+
MerID: 'TEST_MERID',
78+
MemberID: 'TEST_MEMBERID',
79+
RequestNo: 'xyz987',
80+
TokenURL: 'https://tokenurl.ctbcbank.com',
81+
},
82+
payment,
83+
);
84+
});
85+
86+
describe('CTBCPayments.handleCallbackTextBodyByURLPath', () => {
87+
describe('callbackPath', () => {
88+
const url = '/payments/ctbc/callback';
89+
90+
it('should throw error if URLResEnc is not provided', async () => {
91+
await expect(payment.handleCallbackTextBodyByURLPath(url, '')).rejects.toThrow(
92+
'Missing URLResEnc parameter in callback',
93+
);
94+
});
95+
96+
it('should throw error if requestId is not in parameter', async () => {
97+
jest.spyOn(iconv, 'decode').mockReturnValue('status=SUCCESS');
98+
99+
await expect(
100+
payment.handleCallbackTextBodyByURLPath(url, 'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj'),
101+
).rejects.toThrow('Missing lidm parameter in callback');
102+
});
103+
104+
describe('Checkout Failed', () => {
105+
it('VMJ', async () => {
106+
jest.spyOn(iconv, 'decode').mockReturnValue('status=SUCCESS&lidm=123&errcode=01&errDesc=Transaction Failed');
107+
108+
const orderCache = Reflect.get(payment, 'orderCache');
109+
110+
orderCache.set('123', order);
111+
112+
await expect(
113+
payment.handleCallbackTextBodyByURLPath(url, 'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj&lidm=123'),
114+
).rejects.toThrow('CTBC Card Checkout Failed: 01 - Transaction Failed');
115+
});
116+
117+
it('AE', async () => {
118+
Reflect.set(order, '_cardType', CardType.AE);
119+
Reflect.set(payment, 'isAmex', true);
120+
jest
121+
.spyOn(iconv, 'decode')
122+
.mockReturnValue('status=SUCCESS&lidm=123&errcode=A001&errDesc=Transaction Failed');
123+
124+
const orderCache = Reflect.get(payment, 'orderCache');
125+
126+
orderCache.set('123', order);
127+
128+
await expect(
129+
payment.handleCallbackTextBodyByURLPath(url, 'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj&lidm=123'),
130+
).rejects.toThrow('CTBC Amex Checkout Failed: A001 - Transaction Failed');
131+
});
132+
133+
it('Unknown', async () => {
134+
Reflect.set(order, '_cardType', null);
135+
jest.spyOn(iconv, 'decode').mockReturnValue('status=SUCCESS&lidm=123&');
136+
137+
const orderCache = Reflect.get(payment, 'orderCache');
138+
139+
orderCache.set('123', order);
140+
141+
await expect(
142+
payment.handleCallbackTextBodyByURLPath(url, 'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj&lidm=123'),
143+
).rejects.toThrow('CTBC Unknown Checkout Failed: null - null');
144+
});
145+
});
146+
147+
describe('Checkout Success', () => {
148+
it('should return 302 if clientBackUrl is provided', async () => {
149+
Reflect.set(order, '_clientBackUrl', 'https://example.com');
150+
jest.spyOn(iconv, 'decode').mockReturnValue('status=SUCCESS&lidm=123&errcode=00&xid=xid_123');
151+
152+
const orderCache = Reflect.get(payment, 'orderCache');
153+
154+
orderCache.set('123', order);
155+
156+
const result = await payment.handleCallbackTextBodyByURLPath(
157+
url,
158+
'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj&lidm=123',
159+
);
160+
161+
expect(result).toEqual({
162+
status: 302,
163+
headers: {
164+
Location: 'https://example.com',
165+
},
166+
});
167+
});
168+
169+
it('should return 200 if clientBackUrl is not provided', async () => {
170+
jest.spyOn(iconv, 'decode').mockReturnValue('status=SUCCESS&lidm=123&errcode=00&xid=xid_123');
171+
172+
const orderCache = Reflect.get(payment, 'orderCache');
173+
174+
orderCache.set('123', order);
175+
176+
const result = await payment.handleCallbackTextBodyByURLPath(
177+
url,
178+
'URLResEnc=h3Qx7uZLmYpWv8CtEr2KdBfj&lidm=123',
179+
);
180+
181+
expect(result).toEqual({
182+
status: 200,
183+
headers: {
184+
'Content-Type': 'text/plain',
185+
},
186+
body: '1|OK',
187+
});
188+
});
189+
});
190+
});
191+
192+
describe('boundCardPath', () => {
193+
const url = '/payments/ctbc/bound-card';
194+
195+
describe('status code not I0000', () => {
196+
it('should throw Bind Card Failed if there is request', async () => {
197+
jest.spyOn(ctbcCrypto, 'decrypt3DES').mockReturnValue('StatusCode=I0001&StatusDesc=FAILED&RequestNo=abc123');
198+
199+
const body = rspjsonpwdToBody(mockFailedResponse);
200+
201+
await expect(payment.handleCallbackTextBodyByURLPath(url, body)).rejects.toThrow(
202+
'CTBC Bind Card Failed: I0001 - FAILED',
203+
);
204+
});
205+
});
206+
207+
describe('status code I0000', () => {
208+
it('should throw error if request is undefined', async () => {
209+
jest.spyOn(ctbcCrypto, 'decrypt3DES').mockReturnValue('StatusCode=I0000&StatusDesc=SUCCESS&RequestNo=abc123');
210+
211+
const body = rspjsonpwdToBody(mockSuccessResponse);
212+
213+
await expect(payment.handleCallbackTextBodyByURLPath(url, body)).rejects.toThrow(
214+
'Unknown bind card request: abc123',
215+
);
216+
});
217+
218+
it('should return status 200 - OK', async () => {
219+
jest
220+
.spyOn(ctbcCrypto, 'decrypt3DES')
221+
.mockReturnValue(
222+
'StatusCode=I0000&StatusDesc=SUCCESS&RequestNo=abc123&CardToken=token_987&CardNoMask=CardMask=1234-5678-9876-5432',
223+
);
224+
225+
const body = rspjsonpwdToBody(mockSuccessResponse);
226+
227+
const bindCardRequestsCache = Reflect.get(payment, 'bindCardRequestsCache');
228+
229+
bindCardRequestsCache.set('abc123', bindCardRequest);
230+
231+
const result = await payment.handleCallbackTextBodyByURLPath(url, body);
232+
233+
expect(result).toEqual({
234+
status: 200,
235+
headers: {
236+
'Content-Type': 'text/plain',
237+
},
238+
body: '1|OK',
239+
});
240+
});
241+
});
242+
});
243+
244+
describe('boundCardCheckoutResultPath', () => {
245+
const url = '/payments/ctbc/bound-card/checkout-result';
246+
247+
describe('status code not I0000', () => {
248+
it('should throw Bound Card Checkout Failed if code is not I0000', async () => {
249+
jest.spyOn(ctbcCrypto, 'decrypt3DES').mockReturnValue('StatusCode=I0001&StatusDesc=FAILED&RequestNo=abc123');
250+
251+
const body = jsonToBody(mockFailedResponse);
252+
253+
await expect(payment.handleCallbackTextBodyByURLPath(url, body)).rejects.toThrow(
254+
'CTBC Bound Card Checkout Failed: I0001 - FAILED',
255+
);
256+
});
257+
});
258+
259+
describe('status code I0000', () => {
260+
it('should throw error if order is undefined', async () => {
261+
jest.spyOn(ctbcCrypto, 'decrypt3DES').mockReturnValue('StatusCode=I0000&StatusDesc=SUCCESS&RequestNo=abc123');
262+
263+
const body = jsonToBody(mockSuccessResponse);
264+
265+
await expect(payment.handleCallbackTextBodyByURLPath(url, body)).rejects.toThrow(
266+
'Unknown bound card checkout order: abc123',
267+
);
268+
});
269+
270+
describe('Checkout Success', () => {
271+
it('should return 302 if clientBackUrl is provided', async () => {
272+
Reflect.set(order, '_clientBackUrl', 'https://example.com');
273+
jest
274+
.spyOn(ctbcCrypto, 'decrypt3DES')
275+
.mockReturnValue('StatusCode=I0000&StatusDesc=SUCCESS&RequestNo=abc123');
276+
277+
const body = jsonToBody(mockSuccessResponse);
278+
279+
const orderCache = Reflect.get(payment, 'orderCache');
280+
281+
orderCache.set('abc123', order);
282+
283+
const result = await payment.handleCallbackTextBodyByURLPath(url, body);
284+
285+
expect(result).toEqual({
286+
status: 302,
287+
headers: {
288+
Location: 'https://example.com',
289+
},
290+
});
291+
});
292+
293+
it('should return 200 - OK if clientBackUrl is not provided', async () => {
294+
jest
295+
.spyOn(ctbcCrypto, 'decrypt3DES')
296+
.mockReturnValue('StatusCode=I0000&StatusDesc=SUCCESS&RequestNo=abc123');
297+
298+
const body = jsonToBody(mockSuccessResponse);
299+
300+
const orderCache = Reflect.get(payment, 'orderCache');
301+
302+
orderCache.set('abc123', order);
303+
304+
const result = await payment.handleCallbackTextBodyByURLPath(url, body);
305+
306+
expect(result).toEqual({
307+
status: 200,
308+
headers: {
309+
'Content-Type': 'text/plain',
310+
},
311+
body: '1|OK',
312+
});
313+
});
314+
});
315+
});
316+
});
317+
318+
it('not found path', async () => {
319+
const result = await payment.handleCallbackTextBodyByURLPath('', '');
320+
321+
expect(result).toEqual({
322+
status: 404,
323+
headers: {
324+
'Content-Type': 'text/plain',
325+
},
326+
body: '0|Not Found',
327+
});
328+
});
329+
});
330+
});

packages/payments-adapter-ctbc-micro-fast-pay/src/ctbc-payment.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export class CTBCPayment<CM extends CTBCOrderCommitMessage = CTBCOrderCommitMess
158158
const plain = iconv.decode(decrypted, 'big5');
159159

160160
const payload = new URLSearchParams(plain);
161+
161162
const requestId = payload.get('lidm');
162163

163164
if (!requestId) {
@@ -255,9 +256,12 @@ export class CTBCPayment<CM extends CTBCOrderCommitMessage = CTBCOrderCommitMess
255256
const statusCode = decryptedParams.get('StatusCode');
256257
const statusDesc = decryptedParams.get('StatusDesc');
257258
const requestId = decryptedParams.get('RequestNo') ?? '';
259+
258260
const request = await this.bindCardRequestsCache.get(requestId);
259261

260262
if (statusCode !== 'I0000') {
263+
console.log(request);
264+
261265
if (request) {
262266
request.fail(statusCode ?? 'x9999', statusDesc ?? '-');
263267
}

0 commit comments

Comments
 (0)