Skip to content

Commit d2c0b16

Browse files
authored
Merge pull request #1534 from Adyen/terminal-api-regions
Terminal API: support Regions
2 parents ccfa1d1 + b55677a commit d2c0b16

File tree

10 files changed

+449
-111
lines changed

10 files changed

+449
-111
lines changed

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ Check for breaking changes on the [releases page](https://github.com/Adyen/adyen
8989

9090
``` javascript
9191
// Step 1: Require the parts of the module you want to use
92-
const { Client, CheckoutAPI} = require('@adyen/api-library');
92+
const { Client, CheckoutAPI, EnvironmentEnum} = require('@adyen/api-library');
9393

9494
// Step 2: Initialize the client object
95-
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
95+
const client = new Client({apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.TEST});
9696

9797
// Step 3: Initialize the API object
9898
const checkoutApi = new CheckoutAPI(client);
@@ -143,7 +143,7 @@ Use the Node.js `require` function to load the `Client` and API objects from the
143143
For example, to use the [Checkout API](https://docs.adyen.com/api-explorer/Checkout/70/overview):
144144

145145
``` javascript
146-
const { Client, CheckoutAPI} = require('@adyen/api-library');
146+
const { Client, CheckoutAPI, EnvironmentEnum} = require('@adyen/api-library');
147147
```
148148

149149
### Step 2: Initialize the client object
@@ -155,7 +155,7 @@ Initialize the client object, passing the following:
155155
For example:
156156

157157
``` javascript
158-
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
158+
const client = new Client({apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.TEST});
159159
```
160160

161161
### Step 3: Initialize the API object
@@ -208,18 +208,18 @@ checkoutApi.PaymentsApi.payments(paymentRequest)
208208

209209
For APIS that require your [Live URL Prefix](https://docs.adyen.com/development-resources/live-endpoints#live-url-prefix) (Binlookup, BalanceControl, Checkout, Payout and Recurring) the client is set up as follows in order to start processing live payments:
210210
``` typescript
211-
const { Client } = require('@adyen/api-library');
211+
const { Client, EnvironmentEnum } = require('@adyen/api-library');
212212

213-
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST", liveEndpointUrlPrefix: "YOUR_LIVE_URL_PREFIX"});
213+
const client = new Client({apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.LIVE, liveEndpointUrlPrefix: "YOUR_LIVE_URL_PREFIX"});
214214
```
215215

216216
### Usage in TypeScript
217217

218218
Alternatively, you can use the `Types` included in this module for Typescript and `async` syntax.
219219

220220
``` typescript
221-
const { Client, CheckoutAPI, Types } = require('@adyen/api-library');
222-
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
221+
const { Client, EnvironmentEnum, CheckoutAPI, Types } = require('@adyen/api-library');
222+
const client = new Client({apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.LIVE, liveEndpointUrlPrefix: "YOUR_LIVE_URL_PREFIX"});
223223

224224
const makePaymentsRequest = async () => {
225225
const paymentsRequest : Types.checkout.PaymentRequest = {
@@ -265,6 +265,8 @@ const paymentRequest: checkout.PaymentRequest = await checkout.ObjectSerializer.
265265

266266
By default, [Node.js https](https://nodejs.org/api/https.html) is used to make API requests. Alternatively, you can set a custom `HttpClient` for your `Client` object.
267267

268+
**Note**: when using your custom `HttpClient`, you must define all required properties (API key, content-type, timeouts, etc..)
269+
268270
For example, to set `axios` as your HTTP client:
269271

270272
``` javascript
@@ -330,20 +332,26 @@ const client = new Client({ config });
330332
const httpClient = new HttpURLConnectionClient();
331333
httpClient.proxy = { host: "http://google.com", port: 8888, };
332334

333-
client.setEnvironment('TEST');
335+
client.setEnvironment(EnvironmentEnum.TEST);
334336
client.httpClient = httpClient;
335337

336338
// ... more code
337339
```
338340

339-
### Using the Cloud Terminal API Integration
340-
In order to submit In-Person requests with [Terminal API over Cloud](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) you need to initialize the client in a similar way as the steps listed above for Ecommerce transactions, but make sure to include `TerminalCloudAPI`:
341+
### Using the Cloud Terminal API
342+
For In-Person Payments integrations with the [Cloud Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/), you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud):
341343
``` javascript
342344
// Step 1: Require the parts of the module you want to use
343345
const {Client, TerminalCloudAPI} from "@adyen/api-library";
346+
const { Config, EnvironmentEnum, RegionEnum } = require("@adyen/api-library");
344347

345348
// Step 2: Initialize the client object
346-
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
349+
const config = new Config({
350+
apiKey: "YOUR_API_KEY",
351+
environment: EnvironmentEnum.LIVE,
352+
region: RegionEnum.US
353+
});
354+
const client = new Client(config);
347355

348356
// Step 3: Initialize the API object
349357
const terminalCloudAPI = new TerminalCloudAPI(client);

src/__mocks__/base.ts

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

2020
import Client from "../client";
21-
import Config from "../config";
21+
import Config, { EnvironmentEnum } from "../config";
2222
import {
2323
AmountsReq,
2424
MessageCategoryType,
@@ -37,18 +37,19 @@ import {
3737

3838
export const createClient = (apiKey = process.env.ADYEN_API_KEY): Client => {
3939
const config: Config = new Config();
40+
config.environment = EnvironmentEnum.TEST;
4041
config.terminalApiCloudEndpoint = Client.TERMINAL_API_ENDPOINT_TEST;
4142
config.terminalApiLocalEndpoint = "https://mocked_local_endpoint.com";
4243
config.marketPayEndpoint = Client.MARKETPAY_ENDPOINT_TEST;
4344
config.apiKey = apiKey == null ? "apiKey" : apiKey;
44-
return new Client({ config });
45+
return new Client(config);
4546
};
4647

4748
export const createBasicAuthClient = (): Client => {
4849
return new Client({
4950
username: process.env.ADYEN_USER!,
5051
password: process.env.ADYEN_PASSWORD!,
51-
environment: "TEST",
52+
environment: EnvironmentEnum.TEST,
5253
applicationName: "adyen-node-api-library"
5354
});
5455
};

src/__tests__/checkout.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { checkout } from "../typings";
1616
import { IRequest } from "../typings/requestOptions";
1717
import { SessionResultResponse } from "../typings/checkout/sessionResultResponse";
1818
import { payments3DS2NativeAction } from "../__mocks__/checkout/payments3DS2NativeAction";
19+
import { EnvironmentEnum } from "../config";
1920

2021
const merchantAccount = process.env.ADYEN_MERCHANT!;
2122
const reference = "Your order number";
@@ -384,14 +385,14 @@ describe("Checkout", (): void => {
384385
// });
385386

386387
test("should have missing identifier on live", async (): Promise<void> => {
387-
client.setEnvironment("LIVE");
388+
client.config.environment = EnvironmentEnum.LIVE;
388389
try {
389390
const liveCheckout = new CheckoutAPI(client);
390391
await liveCheckout.PaymentsApi.payments(createPaymentsCheckoutRequest());
391392
fail();
392393
} catch (e) {
393394
if(e instanceof Error) {
394-
expect(e.message).toEqual("Please provide your unique live url prefix on the setEnvironment() call on the Client.");
395+
expect(e.message).toEqual("Live endpoint URL prefix must be provided for LIVE environment.");
395396

396397
} else {
397398
fail();

src/__tests__/client.spec.ts

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import Client from "../client";
2+
import Config, { EnvironmentEnum, RegionEnum } from "../config";
23

34
describe("API Client", function (): void {
45
test("should be able to make a request using basic auth", async function (): Promise<void> {
56
new Client({
67
username: process.env.ADYEN_USER!,
78
password: process.env.ADYEN_PASSWORD!,
8-
environment: "TEST"
9+
environment: EnvironmentEnum.TEST
910
});
1011
});
1112

1213
test("should create client with API key", () => {
1314
const client = new Client({
1415
apiKey: "ADYEN_API_KEY",
15-
environment: "TEST"
16+
environment: EnvironmentEnum.TEST
1617
});
1718

1819
expect(client.config.apiKey).toBe("ADYEN_API_KEY");
20+
expect(client.config.environment).toBe(EnvironmentEnum.TEST);
1921
expect(client.config.environment).toBe("TEST");
2022
expect(client.config.marketPayEndpoint).toBe(Client.MARKETPAY_ENDPOINT_TEST);
2123
});
@@ -24,31 +26,134 @@ describe("API Client", function (): void {
2426
const client = new Client({
2527
username: "username",
2628
password: "password",
27-
environment: "TEST"
29+
environment: EnvironmentEnum.TEST
2830
});
2931

3032
expect(client.config.username).toBe("username");
3133
expect(client.config.password).toBe("password");
32-
expect(client.config.environment).toBe("TEST");
34+
expect(client.config.environment).toBe(EnvironmentEnum.TEST);
3335
});
3436

3537
test("should set application name", () => {
3638
const client = new Client({
3739
apiKey: "ADYEN_API_KEY",
38-
environment: "TEST",
40+
environment: EnvironmentEnum.TEST,
3941
applicationName: "my_application_name"
4042
});
4143

4244
expect(client.config.applicationName).toBe("my_application_name");
4345
});
4446

47+
test("should define timeout in Config", () => {
48+
const client = new Client({
49+
apiKey: "ADYEN_API_KEY",
50+
environment: EnvironmentEnum.TEST,
51+
connectionTimeoutMillis: 30000
52+
});
53+
54+
expect(client.config.connectionTimeoutMillis).toBe(30000);
55+
});
56+
4557
test("should set timeout", () => {
4658
const client = new Client({
4759
apiKey: "ADYEN_API_KEY",
48-
environment: "TEST"
60+
environment: EnvironmentEnum.TEST
4961
});
5062

5163
client.setTimeouts(30000);
5264
expect(client.config.connectionTimeoutMillis).toBe(30000);
5365
});
66+
67+
test("should throw error if environment is not defined", () => {
68+
expect(() => {
69+
new Client({ apiKey: "ADYEN_API_KEY" } as any);
70+
}).toThrow("Environment must be defined");
71+
});
72+
73+
test("should throw error if environment is LIVE and region is invalid", () => {
74+
const config = new Config({
75+
apiKey: "ADYEN_API_KEY",
76+
environment: EnvironmentEnum.LIVE,
77+
region: "INVALID" as RegionEnum,
78+
liveEndpointUrlPrefix: "prefix"
79+
});
80+
expect(() => {
81+
new Client(config);
82+
}).toThrow("Invalid region provided: INVALID");
83+
});
84+
85+
test("should set terminalApiCloudEndpoint for TEST region", () => {
86+
const config = new Config({
87+
apiKey: "ADYEN_API_KEY",
88+
environment: EnvironmentEnum.TEST
89+
});
90+
const client = new Client(config);
91+
expect(client.config.terminalApiCloudEndpoint).toBeDefined();
92+
expect(client.config.terminalApiCloudEndpoint).toBe("https://terminal-api-test.adyen.com");
93+
});
94+
95+
test("should set terminalApiCloudEndpoint for LIVE region", () => {
96+
const config = new Config({
97+
apiKey: "ADYEN_API_KEY",
98+
environment: EnvironmentEnum.LIVE,
99+
region: RegionEnum.US,
100+
liveEndpointUrlPrefix: "prefix"
101+
});
102+
const client = new Client(config);
103+
expect(client.config.terminalApiCloudEndpoint).toBeDefined();
104+
expect(client.config.terminalApiCloudEndpoint).toBe("https://terminal-api-us.adyen.com");
105+
});
106+
107+
test("should set and get custom http client", () => {
108+
const config = new Config({
109+
apiKey: "ADYEN_API_KEY",
110+
environment: EnvironmentEnum.TEST
111+
});
112+
const client = new Client(config);
113+
const mockHttpClient = { request: jest.fn() };
114+
client.httpClient = mockHttpClient as any;
115+
expect(client.httpClient).toBe(mockHttpClient);
116+
});
117+
118+
test("should set application name via setApplicationName", () => {
119+
const config = new Config({
120+
apiKey: "ADYEN_API_KEY",
121+
environment: EnvironmentEnum.TEST
122+
});
123+
const client = new Client(config);
124+
client.setApplicationName("test_app");
125+
expect(client.config.applicationName).toBe("test_app");
126+
});
127+
128+
test("should return true for valid environments", () => {
129+
expect(Config.isEnvironmentValid(EnvironmentEnum.LIVE)).toBe(true);
130+
expect(Config.isEnvironmentValid(EnvironmentEnum.TEST)).toBe(true);
131+
});
132+
133+
test("should return false for invalid environments", () => {
134+
// @ts-expect-error purposely passing invalid value
135+
expect(Config.isEnvironmentValid("INVALID")).toBe(false);
136+
// @ts-expect-error purposely passing undefined
137+
expect(Config.isEnvironmentValid(undefined)).toBe(false);
138+
// @ts-expect-error purposely passing null
139+
expect(Config.isEnvironmentValid(null)).toBe(false);
140+
});
141+
142+
test("should return true for valid regions", () => {
143+
expect(Config.isRegionValid(RegionEnum.EU)).toBe(true);
144+
expect(Config.isRegionValid(RegionEnum.AU)).toBe(true);
145+
expect(Config.isRegionValid(RegionEnum.US)).toBe(true);
146+
expect(Config.isRegionValid(RegionEnum.APSE)).toBe(true);
147+
});
148+
149+
test("should return false for invalid regions", () => {
150+
// @ts-expect-error purposely passing invalid value
151+
expect(Config.isRegionValid("INVALID")).toBe(false);
152+
// @ts-expect-error purposely passing undefined
153+
expect(Config.isRegionValid(undefined)).toBe(false);
154+
// @ts-expect-error purposely passing null
155+
expect(Config.isRegionValid(null)).toBe(false);
156+
});
157+
54158
});
159+

src/__tests__/httpClient.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import HttpClientException from "../httpClient/httpClientException";
77
import { binlookup } from "../typings";
88
import { ApiConstants } from "../constants/apiConstants";
99
import { paymentMethodsSuccess } from "../__mocks__/checkout/paymentMethodsSuccess";
10-
import Config from "../config";
10+
import Config, { EnvironmentEnum } from "../config";
1111
import LibraryConstants from "../constants/libraryConstants";
1212

1313

@@ -28,7 +28,7 @@ const threeDSAvailabilitySuccess = {
2828
type errorType = "HttpClientException" | "ApiException";
2929
type testOptions = { errorType: errorType; errorMessageContains?: string; errorMessageEquals?: string };
3030

31-
const getResponse = async ({ apiKey, environment }: { apiKey: string; environment: Environment }, cb: (scope: Interceptor) => testOptions): Promise<void> => {
31+
const getResponse = async ({ apiKey, environment }: { apiKey: string; environment: EnvironmentEnum }, cb: (scope: Interceptor) => testOptions): Promise<void> => {
3232
const client = new Client({ apiKey, environment });
3333
const binLookup = new BinLookupAPI(client);
3434

@@ -55,7 +55,7 @@ describe("HTTP Client", function (): void {
5555

5656
test("Should return ApiException with message containing 'mocked_error_response'", async () => {
5757
await getResponse(
58-
{ apiKey: "", environment: "TEST" },
58+
{ apiKey: "", environment: EnvironmentEnum.TEST },
5959
(scope) => {
6060
scope.replyWithError("mocked_error_response");
6161
return {
@@ -69,7 +69,7 @@ describe("HTTP Client", function (): void {
6969

7070
test("Should return ApiException with message equal to 'some_error'", async () => {
7171
await getResponse(
72-
{ apiKey: "MOCKED_API_KEY", environment: "TEST" },
72+
{ apiKey: "MOCKED_API_KEY", environment: EnvironmentEnum.TEST },
7373
(scope) => {
7474
scope.replyWithError("some_error");
7575
return {
@@ -83,7 +83,7 @@ describe("HTTP Client", function (): void {
8383

8484
test("Should return HttpClientException with message equal to 'HTTP Exception: 401. null: Invalid Request'", async () => {
8585
await getResponse(
86-
{ apiKey: "API_KEY", environment: "TEST" },
86+
{ apiKey: "API_KEY", environment: EnvironmentEnum.TEST },
8787
(scope) => {
8888
scope.reply(401, { status: 401, message: "Invalid Request", errorCode: "171", errorType: "validationError" });
8989
return {
@@ -97,7 +97,7 @@ describe("HTTP Client", function (): void {
9797

9898
test("Should return HttpClientException with message equal to 'HTTP Exception: 401. null'", async () => {
9999
await getResponse(
100-
{ apiKey: "API_KEY", environment: "TEST" },
100+
{ apiKey: "API_KEY", environment: EnvironmentEnum.TEST },
101101
(scope) => {
102102
scope.reply(401, {});
103103
return {
@@ -111,7 +111,7 @@ describe("HTTP Client", function (): void {
111111

112112
test("Should return HttpClientException with message starting with 'HTTP Exception: 401'", async () => {
113113
await getResponse(
114-
{ apiKey: "API_KEY", environment: "TEST" },
114+
{ apiKey: "API_KEY", environment: EnvironmentEnum.TEST },
115115
(scope) => {
116116
scope.reply(401, "fail");
117117
return {

0 commit comments

Comments
 (0)