@adyen/api-library
Version:
The Adyen API Library for NodeJS enables you to work with Adyen APIs.
190 lines • 10.5 kB
JavaScript
;
/*
* ######
* ######
* ############ ####( ###### #####. ###### ############ ############
* ############# #####( ###### #####. ###### ############# #############
* ###### #####( ###### #####. ###### ##### ###### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ##### ######
* ###### ###### #####( ###### #####. ###### ##### ##### ######
* ############# ############# ############# ############# ##### ######
* ############ ############ ############# ############ ##### ######
* ######
* #############
* ############
* Adyen NodeJS API Library
* Copyright (c) 2026 Adyen B.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EncryptedCloudDeviceApi = void 0;
const getJsonResponse_1 = __importDefault(require("../../helpers/getJsonResponse"));
const service_1 = __importDefault(require("../../service"));
const resource_1 = __importDefault(require("../resource"));
const cloudDeviceApiAsyncResponse_1 = require("../../typings/clouddevice/cloudDeviceApiAsyncResponse");
const nexoSecurityManager_1 = require("../../security/clouddevice/nexoSecurityManager");
const nexoSecurityException_1 = require("../../security/clouddevice/nexoSecurityException");
/**
* Cloud Device API service with encrypted payloads.
*/
class EncryptedCloudDeviceApi extends service_1.default {
constructor(client, encryptionCredentialDetails) {
super(client);
this.API_BASEPATH = "https://device-api-test.adyen.com/v1";
this.baseUrl = this.createBaseUrl(this.API_BASEPATH);
this.nexoSecurityManager = new nexoSecurityManager_1.NexoSecurityManager(encryptionCredentialDetails);
}
/**
* @summary Send a Terminal API request with encryption and receive a synchronous decrypted response
* @param merchantAccount {@link string } The unique identifier of the merchant account. (required)
* @param deviceId {@link string } The unique identifier of the payment device. Must match POIID in the MessageHeader. (required)
* @param cloudDeviceApiRequest {@link CloudDeviceApiRequest }
* @param requestOptions {@link IRequest.Options }
* @return {@link CloudDeviceApiResponse }
*/
async sync(merchantAccount, deviceId, cloudDeviceApiRequest, requestOptions) {
var _a;
if (!merchantAccount) {
throw new Error("Please provide the merchantAccount path parameter");
}
if (!deviceId) {
throw new Error("Please provide the deviceId path parameter");
}
if (!((_a = cloudDeviceApiRequest.SaleToPOIRequest) === null || _a === void 0 ? void 0 : _a.MessageHeader)) {
throw new Error("cloudDeviceApiRequest must contain a SaleToPOIRequest with a MessageHeader");
}
cloudDeviceApiRequest.SaleToPOIRequest.MessageHeader.POIID = deviceId;
const secured = this.nexoSecurityManager.encrypt(JSON.stringify(cloudDeviceApiRequest), cloudDeviceApiRequest.SaleToPOIRequest.MessageHeader);
const securedRequest = { SaleToPOIRequest: secured };
const endpoint = `${this.baseUrl}/merchants/{merchantAccount}/devices/{deviceId}/sync`
.replace("{merchantAccount}", encodeURIComponent(merchantAccount))
.replace("{deviceId}", encodeURIComponent(deviceId));
const resource = new resource_1.default(this, endpoint);
const response = await (0, getJsonResponse_1.default)(resource, securedRequest, { ...requestOptions, method: "POST" });
const encryptedResponse = response.SaleToPOIResponse;
if (!(encryptedResponse === null || encryptedResponse === void 0 ? void 0 : encryptedResponse.NexoBlob)) {
// Terminal returned an unencrypted error response (e.g. terminal unreachable).
// A genuine terminal outcome is always encrypted, so an unencrypted success is forged.
rejectUnencryptedSuccess(response);
return response;
}
return JSON.parse(this.nexoSecurityManager.decrypt(encryptedResponse));
}
/**
* @summary Send a Terminal API request with encryption and receive an asynchronous response
* @param merchantAccount {@link string } The unique identifier of the merchant account. (required)
* @param deviceId {@link string } The unique identifier of the payment device. Must match POIID in the MessageHeader. (required)
* @param cloudDeviceApiRequest {@link CloudDeviceApiRequest }
* @param requestOptions {@link IRequest.Options }
* @return {@link CloudDeviceApiAsyncResponse }
*/
async async(merchantAccount, deviceId, cloudDeviceApiRequest, requestOptions) {
var _a;
if (!merchantAccount) {
throw new Error("Please provide the merchantAccount path parameter");
}
if (!deviceId) {
throw new Error("Please provide the deviceId path parameter");
}
if (!((_a = cloudDeviceApiRequest.SaleToPOIRequest) === null || _a === void 0 ? void 0 : _a.MessageHeader)) {
throw new Error("cloudDeviceApiRequest must contain a SaleToPOIRequest with a MessageHeader");
}
cloudDeviceApiRequest.SaleToPOIRequest.MessageHeader.POIID = deviceId;
const secured = this.nexoSecurityManager.encrypt(JSON.stringify(cloudDeviceApiRequest), cloudDeviceApiRequest.SaleToPOIRequest.MessageHeader);
const securedRequest = { SaleToPOIRequest: secured };
const endpoint = `${this.baseUrl}/merchants/{merchantAccount}/devices/{deviceId}/async`
.replace("{merchantAccount}", encodeURIComponent(merchantAccount))
.replace("{deviceId}", encodeURIComponent(deviceId));
const resource = new resource_1.default(this, endpoint);
const response = await (0, getJsonResponse_1.default)(resource, securedRequest, { ...requestOptions, method: "POST" });
const result = new cloudDeviceApiAsyncResponse_1.CloudDeviceApiAsyncResponse();
if (typeof response === "string" && response.toLowerCase().trim() === "ok") {
result.Result = response.trim();
return result;
}
// Error response: an encrypted EventNotification wrapped in a SaleToPOIRequest envelope.
const encryptedError = response;
const encryptedErrorRequest = encryptedError === null || encryptedError === void 0 ? void 0 : encryptedError.SaleToPOIRequest;
if (!(encryptedErrorRequest === null || encryptedErrorRequest === void 0 ? void 0 : encryptedErrorRequest.NexoBlob)) {
// Unencrypted error response (e.g. terminal unreachable).
// A genuine terminal outcome is always encrypted, so an unencrypted success is forged.
rejectUnencryptedSuccess(response);
return response;
}
const decryptedJson = this.nexoSecurityManager.decrypt(encryptedErrorRequest);
const errorResponse = JSON.parse(decryptedJson);
result.SaleToPOIRequest = errorResponse.SaleToPOIRequest;
return result;
}
/**
* Decrypt an event notification payload.
*
* @param payload JSON string containing either a SaleToPOIResponse or SaleToPOIRequest envelope.
* @return the decrypted payload as a JSON string
* @throws NexoSecurityException when decryption fails or the payload is invalid
*/
decryptNotification(payload) {
let parsed;
try {
parsed = JSON.parse(payload);
}
catch (_a) {
throw new nexoSecurityException_1.NexoSecurityException("Invalid payload");
}
if (!parsed || typeof parsed !== "object") {
throw new nexoSecurityException_1.NexoSecurityException("Unexpected payload: must be a JSON object");
}
if ("SaleToPOIResponse" in parsed) {
const securedResponse = parsed;
if (!securedResponse.SaleToPOIResponse) {
throw new nexoSecurityException_1.NexoSecurityException("Unexpected payload without SaleToPOIResponse or SaleToPOIRequest");
}
return this.nexoSecurityManager.decrypt(securedResponse.SaleToPOIResponse);
}
if ("SaleToPOIRequest" in parsed) {
const securedRequest = parsed;
return this.nexoSecurityManager.decrypt(securedRequest.SaleToPOIRequest);
}
throw new nexoSecurityException_1.NexoSecurityException("Unexpected payload without SaleToPOIResponse or SaleToPOIRequest");
}
}
exports.EncryptedCloudDeviceApi = EncryptedCloudDeviceApi;
/**
* Rejects an unencrypted response that claims a non-failure outcome.
*
* A genuine terminal outcome is always encrypted (delivered inside a NexoBlob), so an
* unencrypted response carrying a "Success" or "Partial" Result cannot come from the terminal
* and must be treated as forged. Legitimate unencrypted responses are gateway-generated errors
* (e.g. terminal unreachable, reject event notifications), which always carry "Failure" or no Result.
*
* @throws NexoSecurityException when a non-failure Result is found in an unencrypted response.
*/
function rejectUnencryptedSuccess(response) {
if (!response || typeof response !== "object") {
return;
}
const envelope = response;
for (const message of Object.values(envelope)) {
if (!message || typeof message !== "object") {
continue;
}
for (const body of Object.values(message)) {
if (!body || typeof body !== "object") {
continue;
}
const inner = body.Response;
if (inner && typeof inner === "object") {
const result = inner.Result;
if (result === "Success" || result === "Partial") {
throw new nexoSecurityException_1.NexoSecurityException("Received an unencrypted response with a non-failure result. " +
"A genuine terminal outcome must be encrypted.");
}
}
}
}
}
//# sourceMappingURL=encryptedCloudDeviceApi.js.map