Skip to content

Commit 454574d

Browse files
committed
Handling 308 redirect
1 parent fe65d2a commit 454574d

File tree

2 files changed

+76
-29
lines changed

2 files changed

+76
-29
lines changed

src/__tests__/terminalCloudAPI.spec.ts

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Client from "../client";
66
import TerminalCloudAPI from "../services/terminalCloudAPI";
77
import { terminal } from "../typings";
88
import { EnvironmentEnum } from "../config";
9+
import HttpClientException from "../httpClient/httpClientException";
910

1011
let client: Client;
1112
let terminalCloudAPI: TerminalCloudAPI;
@@ -31,29 +32,29 @@ describe("Terminal Cloud API", (): void => {
3132

3233
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
3334

34-
const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest);
35+
const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest);
3536

36-
expect(typeof requestResponse).toBe("string");
37-
expect(requestResponse).toEqual("ok");
38-
});
37+
expect(typeof requestResponse).toBe("string");
38+
expect(requestResponse).toEqual("ok");
39+
});
3940

40-
test("should get an error after async payment request", async (): Promise<void> => {
41-
scope.post("/async").reply(200, asyncErrorRes);
41+
test("should get an error after async payment request", async (): Promise<void> => {
42+
scope.post("/async").reply(200, asyncErrorRes);
4243

43-
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
44+
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
4445

45-
const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest);
46+
const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest);
4647

47-
if (typeof requestResponse === "object") {
48-
expect(requestResponse.SaleToPOIRequest?.EventNotification).toBeDefined();
49-
expect(requestResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("Reject");
50-
} else {
51-
throw new Error("Expected structured response, but got raw string");
52-
}
53-
});
48+
if (typeof requestResponse === "object") {
49+
expect(requestResponse.SaleToPOIRequest?.EventNotification).toBeDefined();
50+
expect(requestResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("Reject");
51+
} else {
52+
throw new Error("Expected structured response, but got raw string");
53+
}
54+
});
5455

55-
test("should make a sync payment request", async (): Promise<void> => {
56-
scope.post("/sync").reply(200, syncRes);
56+
test("should make a sync payment request", async (): Promise<void> => {
57+
scope.post("/sync").reply(200, syncRes);
5758

5859
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
5960
const terminalAPIResponse: terminal.TerminalApiResponse = await terminalCloudAPI.sync(terminalAPIPaymentRequest);
@@ -168,6 +169,7 @@ describe("Terminal Cloud API", (): void => {
168169
const terminalApiHost = "https://terminal-api-test.adyen.com";
169170

170171
const client = new Client({ apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.TEST });
172+
171173
const terminalCloudAPI = new TerminalCloudAPI(client);
172174

173175
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
@@ -191,14 +193,45 @@ describe("Terminal Cloud API", (): void => {
191193
},
192194
});
193195

194-
try {
195-
await terminalCloudAPI.sync(terminalAPIPaymentRequest);
196-
fail("No exception was thrown");
196+
try {
197+
await terminalCloudAPI.sync(terminalAPIPaymentRequest);
198+
fail("No exception was thrown");
197199
} catch (e) {
198-
expect(e).toBeInstanceOf(Error);
200+
expect(e).toBeInstanceOf(Error);
199201
}
200202

201-
});
203+
});
204+
205+
test("async should skip 308 redirect", async (): Promise<void> => {
206+
207+
const terminalApiHost = "https://terminal-api-test.adyen.com";
208+
209+
const client = new Client({ apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.TEST, enable308Redirect: false });
210+
const terminalCloudAPI = new TerminalCloudAPI(client);
211+
212+
const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest();
213+
// custom value to trigger mock 308 response
214+
terminalAPIPaymentRequest.SaleToPOIRequest.MessageHeader.SaleID = "response-with-redirect";
215+
216+
// Mock first request: returns a 308 redirect with Location header
217+
nock(terminalApiHost)
218+
.post("/async", (body) => {
219+
return body?.SaleToPOIRequest?.MessageHeader?.SaleID === "response-with-redirect";
220+
})
221+
.reply(308, "", { Location: `${terminalApiHost}/async?redirect=false` });
222+
223+
224+
// Must throw an error
225+
try {
226+
await terminalCloudAPI.async(terminalAPIPaymentRequest);
227+
fail("No exception was thrown");
228+
} catch (e: unknown) {
229+
expect(e).toBeInstanceOf(HttpClientException);
230+
if (e instanceof HttpClientException) {
231+
expect(e.statusCode).toBe(308);
232+
}
233+
}
234+
});
202235

203236
});
204237

src/httpClient/httpURLConnectionClient.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,13 @@ class HttpURLConnectionClient implements ClientInterface {
8686
requestOptions.headers[ApiConstants.CONTENT_TYPE] = ApiConstants.APPLICATION_JSON_TYPE;
8787

8888
const httpConnection: ClientRequest = this.createRequest(endpoint, requestOptions, config.applicationName);
89-
return this.doRequest(httpConnection, json);
89+
90+
if (config.enable308Redirect === undefined) {
91+
// 308 redirect enabled by default
92+
config.enable308Redirect = true;
93+
}
94+
95+
return this.doRequest(httpConnection, json, config.enable308Redirect as boolean);
9096
}
9197

9298
// create Request object
@@ -142,8 +148,15 @@ class HttpURLConnectionClient implements ClientInterface {
142148
return req;
143149
}
144150

145-
// invoke request
146-
private doRequest(connectionRequest: ClientRequest, json: string): Promise<string> {
151+
/**
152+
* Invoke the request
153+
* @param connectionRequest The request
154+
* @param json The payload
155+
* @param allowRedirect Whether to allow redirect upon 308 response status code
156+
* @returns Promise with the API response
157+
*/
158+
private doRequest(connectionRequest: ClientRequest, json: string, allowRedirect: boolean): Promise<string> {
159+
147160
return new Promise((resolve, reject): void => {
148161
connectionRequest.flushHeaders();
149162

@@ -173,8 +186,8 @@ class HttpURLConnectionClient implements ClientInterface {
173186
reject(new Error("The connection was terminated while the message was still being sent"));
174187
}
175188

176-
// Handle 308 redirect
177-
if (res.statusCode && res.statusCode === 308) {
189+
// Handle 308 redirect (when enabled)
190+
if (allowRedirect && res.statusCode && res.statusCode === 308) {
178191
const location = res.headers["location"];
179192
if (location) {
180193
// follow the redirect
@@ -195,7 +208,8 @@ class HttpURLConnectionClient implements ClientInterface {
195208
};
196209
const clientRequestFn = url.protocol === "https:" ? httpsRequest : httpRequest;
197210
const redirectedRequest: ClientRequest = clientRequestFn(newRequestOptions);
198-
const redirectResponse = this.doRequest(redirectedRequest, json);
211+
// To prevent potential redirect loops, disable further redirects for this new request.
212+
const redirectResponse = this.doRequest(redirectedRequest, json, false as boolean);
199213
return resolve(redirectResponse);
200214
} catch (err) {
201215
return reject(err);
@@ -230,7 +244,7 @@ class HttpURLConnectionClient implements ClientInterface {
230244
} catch (e) {
231245
// parsing error
232246
exception = new HttpClientException({
233-
message: `HTTP Exception: ${response.statusCode}. Error parsing response: ${(e as Error).message}`,
247+
message: `HTTP Exception: ${response.statusCode}. Error ${(e as Error).message} while parsing response: ${response.body}`,
234248
statusCode: response.statusCode,
235249
responseHeaders: response.headers,
236250
responseBody: response.body,

0 commit comments

Comments
 (0)