Skip to content

Terminal API: support Regions #1534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ Check for breaking changes on the [releases page](https://github.com/Adyen/adyen

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

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

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

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

### Step 2: Initialize the client object
Expand All @@ -155,7 +155,7 @@ Initialize the client object, passing the following:
For example:

``` javascript
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
const client = new Client({apiKey: "YOUR_API_KEY", environment: EnvironmentEnum.TEST});
```

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

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:
``` typescript
const { Client } = require('@adyen/api-library');
const { Client, EnvironmentEnum } = require('@adyen/api-library');

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

### Usage in TypeScript

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

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

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

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.

**Note**: when using your custom `HttpClient`, you must define all required properties (API key, content-type, timeouts, etc..)

For example, to set `axios` as your HTTP client:

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

client.setEnvironment('TEST');
client.setEnvironment(EnvironmentEnum.TEST);
client.httpClient = httpClient;

// ... more code
```

### Using the Cloud Terminal API Integration
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`:
### Using the Cloud Terminal API
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):
``` javascript
// Step 1: Require the parts of the module you want to use
const {Client, TerminalCloudAPI} from "@adyen/api-library";
const { Config, EnvironmentEnum, RegionEnum } = require("@adyen/api-library");

// Step 2: Initialize the client object
const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"});
const config = new Config({
apiKey: "YOUR_API_KEY",
environment: EnvironmentEnum.LIVE,
region: RegionEnum.US
});
const client = new Client(config);

// Step 3: Initialize the API object
const terminalCloudAPI = new TerminalCloudAPI(client);
Expand Down
7 changes: 4 additions & 3 deletions src/__mocks__/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import Client from "../client";
import Config from "../config";
import Config, { EnvironmentEnum } from "../config";
import {
AmountsReq,
MessageCategoryType,
Expand All @@ -37,18 +37,19 @@ import {

export const createClient = (apiKey = process.env.ADYEN_API_KEY): Client => {
const config: Config = new Config();
config.environment = EnvironmentEnum.TEST;
config.terminalApiCloudEndpoint = Client.TERMINAL_API_ENDPOINT_TEST;
config.terminalApiLocalEndpoint = "https://mocked_local_endpoint.com";
config.marketPayEndpoint = Client.MARKETPAY_ENDPOINT_TEST;
config.apiKey = apiKey == null ? "apiKey" : apiKey;
return new Client({ config });
return new Client(config);
};

export const createBasicAuthClient = (): Client => {
return new Client({
username: process.env.ADYEN_USER!,
password: process.env.ADYEN_PASSWORD!,
environment: "TEST",
environment: EnvironmentEnum.TEST,
applicationName: "adyen-node-api-library"
});
};
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/checkout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { checkout } from "../typings";
import { IRequest } from "../typings/requestOptions";
import { SessionResultResponse } from "../typings/checkout/sessionResultResponse";
import { payments3DS2NativeAction } from "../__mocks__/checkout/payments3DS2NativeAction";
import { EnvironmentEnum } from "../config";

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

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

} else {
fail();
Expand Down
117 changes: 111 additions & 6 deletions src/__tests__/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import Client from "../client";
import Config, { EnvironmentEnum, RegionEnum } from "../config";

describe("API Client", function (): void {
test("should be able to make a request using basic auth", async function (): Promise<void> {
new Client({
username: process.env.ADYEN_USER!,
password: process.env.ADYEN_PASSWORD!,
environment: "TEST"
environment: EnvironmentEnum.TEST
});
});

test("should create client with API key", () => {
const client = new Client({
apiKey: "ADYEN_API_KEY",
environment: "TEST"
environment: EnvironmentEnum.TEST
});

expect(client.config.apiKey).toBe("ADYEN_API_KEY");
expect(client.config.environment).toBe(EnvironmentEnum.TEST);
expect(client.config.environment).toBe("TEST");
expect(client.config.marketPayEndpoint).toBe(Client.MARKETPAY_ENDPOINT_TEST);
});
Expand All @@ -24,31 +26,134 @@ describe("API Client", function (): void {
const client = new Client({
username: "username",
password: "password",
environment: "TEST"
environment: EnvironmentEnum.TEST
});

expect(client.config.username).toBe("username");
expect(client.config.password).toBe("password");
expect(client.config.environment).toBe("TEST");
expect(client.config.environment).toBe(EnvironmentEnum.TEST);
});

test("should set application name", () => {
const client = new Client({
apiKey: "ADYEN_API_KEY",
environment: "TEST",
environment: EnvironmentEnum.TEST,
applicationName: "my_application_name"
});

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

test("should define timeout in Config", () => {
const client = new Client({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.TEST,
connectionTimeoutMillis: 30000
});

expect(client.config.connectionTimeoutMillis).toBe(30000);
});

test("should set timeout", () => {
const client = new Client({
apiKey: "ADYEN_API_KEY",
environment: "TEST"
environment: EnvironmentEnum.TEST
});

client.setTimeouts(30000);
expect(client.config.connectionTimeoutMillis).toBe(30000);
});

test("should throw error if environment is not defined", () => {
expect(() => {
new Client({ apiKey: "ADYEN_API_KEY" } as any);
}).toThrow("Environment must be defined");
});

test("should throw error if environment is LIVE and region is invalid", () => {
const config = new Config({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.LIVE,
region: "INVALID" as RegionEnum,
liveEndpointUrlPrefix: "prefix"
});
expect(() => {
new Client(config);
}).toThrow("Invalid region provided: INVALID");
});

test("should set terminalApiCloudEndpoint for TEST region", () => {
const config = new Config({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.TEST
});
const client = new Client(config);
expect(client.config.terminalApiCloudEndpoint).toBeDefined();
expect(client.config.terminalApiCloudEndpoint).toBe("https://terminal-api-test.adyen.com");
});

test("should set terminalApiCloudEndpoint for LIVE region", () => {
const config = new Config({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.LIVE,
region: RegionEnum.US,
liveEndpointUrlPrefix: "prefix"
});
const client = new Client(config);
expect(client.config.terminalApiCloudEndpoint).toBeDefined();
expect(client.config.terminalApiCloudEndpoint).toBe("https://terminal-api-us.adyen.com");
});

test("should set and get custom http client", () => {
const config = new Config({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.TEST
});
const client = new Client(config);
const mockHttpClient = { request: jest.fn() };
client.httpClient = mockHttpClient as any;
expect(client.httpClient).toBe(mockHttpClient);
});

test("should set application name via setApplicationName", () => {
const config = new Config({
apiKey: "ADYEN_API_KEY",
environment: EnvironmentEnum.TEST
});
const client = new Client(config);
client.setApplicationName("test_app");
expect(client.config.applicationName).toBe("test_app");
});

test("should return true for valid environments", () => {
expect(Config.isEnvironmentValid(EnvironmentEnum.LIVE)).toBe(true);
expect(Config.isEnvironmentValid(EnvironmentEnum.TEST)).toBe(true);
});

test("should return false for invalid environments", () => {
// @ts-expect-error purposely passing invalid value
expect(Config.isEnvironmentValid("INVALID")).toBe(false);
// @ts-expect-error purposely passing undefined
expect(Config.isEnvironmentValid(undefined)).toBe(false);
// @ts-expect-error purposely passing null
expect(Config.isEnvironmentValid(null)).toBe(false);
});

test("should return true for valid regions", () => {
expect(Config.isRegionValid(RegionEnum.EU)).toBe(true);
expect(Config.isRegionValid(RegionEnum.AU)).toBe(true);
expect(Config.isRegionValid(RegionEnum.US)).toBe(true);
expect(Config.isRegionValid(RegionEnum.APSE)).toBe(true);
});

test("should return false for invalid regions", () => {
// @ts-expect-error purposely passing invalid value
expect(Config.isRegionValid("INVALID")).toBe(false);
// @ts-expect-error purposely passing undefined
expect(Config.isRegionValid(undefined)).toBe(false);
// @ts-expect-error purposely passing null
expect(Config.isRegionValid(null)).toBe(false);
});

});

14 changes: 7 additions & 7 deletions src/__tests__/httpClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import HttpClientException from "../httpClient/httpClientException";
import { binlookup } from "../typings";
import { ApiConstants } from "../constants/apiConstants";
import { paymentMethodsSuccess } from "../__mocks__/checkout/paymentMethodsSuccess";
import Config from "../config";
import Config, { EnvironmentEnum } from "../config";
import LibraryConstants from "../constants/libraryConstants";


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

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

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

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

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

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

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

test("Should return HttpClientException with message starting with 'HTTP Exception: 401'", async () => {
await getResponse(
{ apiKey: "API_KEY", environment: "TEST" },
{ apiKey: "API_KEY", environment: EnvironmentEnum.TEST },
(scope) => {
scope.reply(401, "fail");
return {
Expand Down
Loading