Skip to content

Commit 60dab31

Browse files
authored
Merge pull request #1509 from Adyen/error-handling-apierror
ApiError: Improving error handling
2 parents 1702cda + 1a15fc4 commit 60dab31

File tree

11 files changed

+73
-30
lines changed

11 files changed

+73
-30
lines changed

src/__tests__/balanceControl.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ describe("Balance Control", (): void => {
6565
fail("No exception was thrown");
6666
} catch (error) {
6767
expect(error instanceof HttpClientException).toBeTruthy();
68-
if (error instanceof HttpClientException && error.responseBody && error.stack) {
69-
expect(JSON.parse(error.responseBody).errorType).toBe("validation");
68+
if (error instanceof HttpClientException) {
69+
expect(error.statusCode).toBe(422);
70+
expect(error.responseBody).toBeTruthy();
71+
expect(error.errorCode).toBe("30_004");
72+
// check apiError
73+
expect(error.apiError).toBeTruthy();
74+
expect(error.apiError?.errorCode).toBe("30_004");
75+
expect(error.apiError?.message).toBe("Merchant account code is invalid or missing");
76+
expect(error.apiError?.errorType).toBe("validation")
7077
} else {
7178
fail("Error did not contain the expected data");
7279
}

src/__tests__/checkout.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,14 @@ describe("Checkout", (): void => {
273273
} catch (error) {
274274
expect(error instanceof HttpClientException).toBeTruthy();
275275
if(error instanceof HttpClientException && error.responseBody && error.stack) {
276-
expect(JSON.parse(error.responseBody).errorType).toBe("validation");
276+
expect(error.statusCode).toBe(422);
277+
expect(error.responseBody).toBeTruthy();
278+
expect(error.errorCode).toBe("200");
279+
// check apiError
280+
expect(error.apiError).toBeTruthy();
281+
expect(error.apiError?.pspReference).toBe("DMB552CV6JHKGK82");
282+
expect(error.apiError?.message).toBe("Field 'countryCode' is not valid.");
283+
expect(error.apiError?.errorType).toBe("validation");
277284
} else {
278285
fail("Error did not contain the expected data");
279286
}

src/__tests__/management.spec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,9 +576,17 @@ describe("Management", (): void => {
576576
await managementService.UsersMerchantLevelApi.createNewUser(merchantId, requests.createMerchantUserRequestInvalidEmail);
577577
} catch (error) {
578578
expect(error instanceof HttpClientException).toBeTruthy();
579-
if(error instanceof HttpClientException && error.responseBody && error.stack) {
580-
expect(JSON.parse(error.responseBody).status).toBe(422);
581-
expect(JSON.parse(error.responseBody).invalidFields).toBeTruthy();
579+
if(error instanceof HttpClientException) {
580+
expect(error.statusCode).toBe(422);
581+
expect(error.responseBody).toBeTruthy();
582+
expect(error.errorCode).toBe("31_007");
583+
// check apiError
584+
expect(error.apiError).toBeTruthy();
585+
expect(error.apiError?.errorCode).toBe("31_007");
586+
expect(error.apiError?.title).toBe("Invalid user information provided.");
587+
expect(error.apiError?.type).toBe("https://docs.adyen.com/errors/validation")
588+
expect(error.apiError?.invalidFields).toBeTruthy();
589+
expect(error.apiError?.invalidFields?.length).toBe(1);
582590
} else {
583591
fail("Error did not contain the expected data");
584592
}

src/__tests__/modification.spec.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ describe("Modification", (): void => {
175175
});
176176

177177
test("should fail to perform an amount update request", async (): Promise<void> => {
178-
expect.assertions(2);
178+
expect.assertions(4);
179179
const request = createAmountUpdateRequest();
180180
scope.post(`/payments/${invalidPaymentPspReference}/amountUpdates`)
181181
.reply(422, invalidModificationResult);
@@ -184,8 +184,11 @@ describe("Modification", (): void => {
184184
await checkoutAPI.ModificationsApi.updateAuthorisedAmount(invalidPaymentPspReference, request);
185185
} catch (e) {
186186
if(e instanceof HttpClientException) {
187-
if(e.statusCode) expect(e.statusCode).toBe(422);
187+
expect(e.statusCode).toBe(422);
188188
expect(e.message).toContain("Original pspReference required for this operation");
189+
// check API apiError
190+
expect(e.apiError).toBeTruthy();
191+
expect(e.apiError?.status).toBe(422);
189192
} else {
190193
fail();
191194
}
@@ -209,16 +212,20 @@ describe("Modification", (): void => {
209212
});
210213

211214
test("should fail to perform a cancels request", async (): Promise<void> => {
212-
expect.assertions(2);
215+
expect.assertions(4);
213216
const request = createCancelsRequest();
214217
scope.post(`/payments/${invalidPaymentPspReference}/cancels`)
215218
.reply(422, invalidModificationResult);
216219
try {
217220
await checkoutAPI.ModificationsApi.cancelAuthorisedPaymentByPspReference(invalidPaymentPspReference, request);
218221
} catch (e) {
219222
if(e instanceof HttpClientException) {
220-
if(e.statusCode) expect(e.statusCode).toBe(422);
223+
expect(e.statusCode).toBe(422);
221224
expect(e.message).toContain("Original pspReference required for this operation");
225+
// check apiError
226+
expect(e.apiError).toBeTruthy();
227+
expect(e.apiError?.errorCode).toBe("167");
228+
222229
} else {
223230
fail();
224231
}
@@ -258,16 +265,19 @@ describe("Modification", (): void => {
258265
});
259266

260267
test("should fail to perform a captures request", async (): Promise<void> => {
261-
expect.assertions(2);
268+
expect.assertions(4);
262269
const request = createCapturesRequest();
263270
scope.post(`/payments/${invalidPaymentPspReference}/captures`)
264271
.reply(422, invalidModificationResult);
265272
try {
266273
await checkoutAPI.ModificationsApi.captureAuthorisedPayment(invalidPaymentPspReference, request);
267274
} catch (e) {
268275
if(e instanceof HttpClientException) {
269-
if(e.statusCode) expect(e.statusCode).toBe(422);
276+
expect(e.statusCode).toBe(422);
270277
expect(e.message).toContain("Original pspReference required for this operation");
278+
// check apiError
279+
expect(e.apiError).toBeTruthy();
280+
expect(e.apiError?.errorCode).toBe("167");
271281
} else {
272282
fail();
273283
}
@@ -324,16 +334,19 @@ describe("Modification", (): void => {
324334
});
325335

326336
test("should fail to perform a reversals request", async (): Promise<void> => {
327-
expect.assertions(2);
337+
expect.assertions(4);
328338
const request = createReversalsRequest();
329339
scope.post(`/payments/${invalidPaymentPspReference}/reversals`)
330340
.reply(422, invalidModificationResult);
331341
try {
332342
await checkoutAPI.ModificationsApi.refundOrCancelPayment(invalidPaymentPspReference, request);
333343
} catch (e) {
334344
if(e instanceof HttpClientException) {
335-
if(e.statusCode) expect(e.statusCode).toBe(422);
345+
expect(e.statusCode).toBe(422);
336346
expect(e.message).toContain("Original pspReference required for this operation");
347+
// check apiError
348+
expect(e.apiError).toBeTruthy();
349+
expect(e.apiError?.errorCode).toBe("167");
337350
} else {
338351
fail();
339352
}

src/__tests__/notification.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe("Notification Test", function (): void {
135135

136136
it("should verify the banking hmac", function (): void {
137137
const jsonString = "{\"data\":{\"balancePlatform\":\"Integration_tools_test\",\"accountId\":\"BA32272223222H5HVKTBK4MLB\",\"sweep\":{\"id\":\"SWPC42272223222H5HVKV6H8C64DP5\",\"schedule\":{\"type\":\"balance\"},\"status\":\"active\",\"targetAmount\":{\"currency\":\"EUR\",\"value\":0},\"triggerAmount\":{\"currency\":\"EUR\",\"value\":0},\"type\":\"pull\",\"counterparty\":{\"balanceAccountId\":\"BA3227C223222H5HVKT3H9WLC\"},\"currency\":\"EUR\"}},\"environment\":\"test\",\"type\":\"balancePlatform.balanceAccountSweep.updated\"}";
138-
const isValid = hmacValidator.validateBankingHMAC("9Qz9S/0xpar1klkniKdshxpAhRKbiSAewPpWoxKefQA=", "D7DD5BA6146493707BF0BE7496F6404EC7A63616B7158EC927B9F54BB436765F", jsonString);
138+
const isValid = hmacValidator.validateHMACSignature("D7DD5BA6146493707BF0BE7496F6404EC7A63616B7158EC927B9F54BB436765F", "9Qz9S/0xpar1klkniKdshxpAhRKbiSAewPpWoxKefQA=", jsonString);
139139
expect(isValid).toBe(true);
140140
});
141141

src/helpers/getJsonResponse.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919

2020
import Resource from "../services/resource";
21-
import HttpClientException from "../httpClient/httpClientException";
22-
import ApiException from "../services/exception/apiException";
2321
import { IRequest } from "../typings/requestOptions";
2422

2523
async function getJsonResponse<T>(resource: Resource, jsonRequest: T | string, requestOptions?: IRequest.Options): Promise<string>;
@@ -29,7 +27,7 @@ async function getJsonResponse<T, R>(
2927
resource: Resource,
3028
jsonRequest: T | string,
3129
requestOptions: IRequest.Options = {},
32-
): Promise<R | string | HttpClientException | ApiException> {
30+
): Promise<R | string> {
3331
const request = typeof jsonRequest === "string" ? jsonRequest : JSON.stringify(jsonRequest);
3432
const response = await resource.request(request, requestOptions);
3533
try {

src/httpClient/clientInterface.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@
1717
* See the LICENSE file for more info.
1818
*/
1919
import { AgentOptions } from "https";
20-
import HttpClientException from "./httpClientException";
21-
import ApiException from "../services/exception/apiException";
2220
import { Config } from "../index";
2321
import { IRequest } from "../typings/requestOptions";
2422

2523
interface ClientInterface {
2624
request(
2725
endpoint: string, json: string, config: Config, isApiKeyRequired: boolean, requestOptions?: IRequest.Options,
28-
): Promise<string | HttpClientException | ApiException>;
29-
post(endpoint: string, postParameters: [string, string][], config: Config): Promise<HttpClientException | string>;
26+
): Promise<string>;
27+
post(endpoint: string, postParameters: [string, string][], config: Config): Promise<string>;
3028
proxy?: AgentOptions;
3129
}
3230

src/httpClient/httpClientException.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
*/
1919

2020
import { IncomingHttpHeaders } from "http";
21+
import { ApiError } from "../typings/apiError";
2122

2223
interface ExceptionInterface {
2324
message: string;
2425
statusCode?: number;
2526
errorCode?: string;
2627
responseHeaders?: IncomingHttpHeaders;
2728
responseBody?: string;
29+
apiError?: ApiError; // model of the error returned by the API
2830
}
2931

3032
class HttpClientException extends Error {
@@ -33,6 +35,7 @@ class HttpClientException extends Error {
3335
public responseHeaders?: IncomingHttpHeaders;
3436
public readonly name: string;
3537
public responseBody?: string;
38+
public apiError?: ApiError;
3639

3740
public constructor(props: ExceptionInterface) {
3841
super(props.message);
@@ -44,6 +47,7 @@ class HttpClientException extends Error {
4447
if (props.responseBody) this.responseBody = props.responseBody;
4548
if (props.errorCode) this.errorCode = props.errorCode;
4649
if (props.statusCode) this.statusCode = props.statusCode;
50+
if (props.apiError) this.apiError = props.apiError;
4751
}
4852
}
4953

src/httpClient/httpURLConnectionClient.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class HttpURLConnectionClient implements ClientInterface {
4242
public request(
4343
endpoint: string, json: string, config: Config, isApiRequired: boolean,
4444
requestOptions: IRequest.Options,
45-
): Promise<string | HttpClientException | ApiException> {
45+
): Promise<string> {
4646
requestOptions.headers ??= {};
4747
requestOptions.timeout = config.connectionTimeoutMillis;
4848

@@ -71,7 +71,7 @@ class HttpURLConnectionClient implements ClientInterface {
7171
return this.doPostRequest(httpConnection, json);
7272
}
7373

74-
public post(endpoint: string, postParameters: [string, string][], config: Config): Promise<HttpClientException | string> {
74+
public post(endpoint: string, postParameters: [string, string][], config: Config): Promise<string> {
7575
const postQuery: string = this.getQuery(postParameters);
7676
const connectionRequest: ClientRequest = this.createRequest(endpoint, {}, config.applicationName);
7777
return this.doPostRequest(connectionRequest, postQuery);
@@ -133,7 +133,7 @@ class HttpURLConnectionClient implements ClientInterface {
133133
return params.map(([key, value]): string => `${key}=${value}`).join("&");
134134
}
135135

136-
private doPostRequest(connectionRequest: ClientRequest, json: string): Promise<HttpClientException | string> {
136+
private doPostRequest(connectionRequest: ClientRequest, json: string): Promise<string> {
137137
return new Promise((resolve, reject): void => {
138138
connectionRequest.flushHeaders();
139139

@@ -176,6 +176,7 @@ class HttpURLConnectionClient implements ClientInterface {
176176
errorCode: formattedData.errorCode,
177177
responseHeaders: res.headers,
178178
responseBody: response.body,
179+
apiError: formattedData,
179180
});
180181
} else if (isRequestError) {
181182
exception = new Error(response.body);

src/services/resource.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
import Config from "../config";
2121
import Service from "../service";
22-
import HttpClientException from "../httpClient/httpClientException";
23-
import ApiException from "./exception/apiException";
2422
import ClientInterface from "../httpClient/clientInterface";
2523
import { IRequest } from "../typings/requestOptions";
2624

@@ -33,7 +31,7 @@ class Resource {
3331
this.endpoint = endpoint;
3432
}
3533

36-
public request(json: string, requestOptions?: IRequest.Options): Promise<string | HttpClientException | ApiException> {
34+
public request(json: string, requestOptions?: IRequest.Options): Promise<string> {
3735
const clientInterface: ClientInterface = this.service.client.httpClient;
3836
const config: Config = this.service.client.config;
3937

0 commit comments

Comments
 (0)