diff --git a/README.md b/README.md index f1e1e0a0e..5b8070598 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,75 @@ const paymentRequest: SaleToPOIRequest = { // Step 5: Make the request const terminalApiResponse: terminal.TerminalApiResponse = await terminalLocalAPI.request(paymentRequest); ``` +### Using the Cloud Terminal API Integration (async) +If you choose to integrate [Terminal API over Cloud](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) **asynchronously**, you need to follow similar steps to initialize the client and prepare the request object. However the response will be asynchronous: +* a successful request will return `200` status code and `ok` as response body. Make sure to setup the [event notifications](https://docs.adyen.com/point-of-sale/design-your-integration/notifications/event-notifications/) +* a request that fails will return `200` status code and the `TerminalApiResponse` as response body +``` typescript +// Step 1: Require the parts of the module you want to use +const {Client, TerminalCloudAPI} from "@adyen/api-library"; + +// Step 2: Initialize the client object +const client = new Client({apiKey: "YOUR_API_KEY", environment: "TEST"}); + +// Step 3: Initialize the API object +const terminalCloudAPI = new TerminalCloudAPI(client); + +// Step 4: Create the request object +const serviceID = "123456789"; +const saleID = "POS-SystemID12345"; +const POIID = "Your Device Name(eg V400m-123456789)"; + +// Use a unique transaction for every transaction you perform +const transactionID = "TransactionID"; +const paymentRequest: SaleToPOIRequest = { + MessageHeader: { + MessageClass: MessageClassType.Service, + MessageCategory: MessageCategoryType.Payment, + MessageType: MessageType.Request, + ProtocolVersion: "3.0", + ServiceID: serviceID, + SaleID: saleID, + POIID: POIID + }, + PaymentRequest: { + SaleData: { + SaleTransactionID: { + TransactionID: transactionID, + TimeStamp: new Date().toISOString() + }, + + SaleToAcquirerData: { + applicationInfo: { + merchantApplication: { + version: "1", + name: "test", + } + } + } + }, + PaymentTransaction: { + AmountsReq: { + Currency: "EUR", + RequestedAmount: 1000 + } + } + } +}; + +// Step 5: Make the request +const response = await terminalCloudAPI.async(paymentRequest); +// handle both `string` and `TerminalApiResponse` +if (typeof response === "string") { + // request was successful + console.log("response:", response); // should be 'ok' +} else { + // request failed: see details in the EventNotification object + console.log("EventToNotify:", response.SaleToPOIRequest?.EventNotification?.EventToNotify); + console.log("EventDetails:", response.SaleToPOIRequest?.EventNotification?.EventDetails); +} +``` + ## Feedback We value your input! Help us enhance our API Libraries and improve the integration experience by providing your feedback. Please take a moment to fill out [our feedback form](https://forms.gle/A4EERrR6CWgKWe5r9) to share your thoughts, suggestions or ideas. diff --git a/src/__mocks__/terminalApi/async.ts b/src/__mocks__/terminalApi/async.ts index f8d897a14..fcc8e92bc 100644 --- a/src/__mocks__/terminalApi/async.ts +++ b/src/__mocks__/terminalApi/async.ts @@ -18,3 +18,25 @@ */ export const asyncRes = "ok"; + +export const asyncErrorRes = { + SaleToPOIRequest: { + EventNotification: { + EventToNotify: "Reject", + EventDetails: "message=Did+not+receive+a+response+from+the+POI.", + RejectedMessage: "ewoi...0KfQo=", + TimeStamp: "2020-03-31T10:28:39.515Z" + }, + MessageHeader: { + DeviceID: "666568147", + MessageCategory: "Event", + MessageClass: "Event", + MessageType: "Notification", + POIID: "P400Plus-123456789", + ProtocolVersion: "3.0", + SaleID: "saleid-4c32759faaa7", + ServiceID: "31122609" + } + } +}; + diff --git a/src/__tests__/terminalCloudAPI.spec.ts b/src/__tests__/terminalCloudAPI.spec.ts index 4a538b55a..9fcc532e9 100644 --- a/src/__tests__/terminalCloudAPI.spec.ts +++ b/src/__tests__/terminalCloudAPI.spec.ts @@ -1,6 +1,6 @@ import nock from "nock"; import { createClient, createTerminalAPIPaymentRequest, createTerminalAPIRefundRequest } from "../__mocks__/base"; -import { asyncRes } from "../__mocks__/terminalApi/async"; +import { asyncRes, asyncErrorRes } from "../__mocks__/terminalApi/async"; import { syncRefund, syncRes, syncResEventNotification, syncResEventNotificationWithAdditionalAttributes, syncResEventNotificationWithUnknownEnum } from "../__mocks__/terminalApi/sync"; import Client from "../client"; import TerminalCloudAPI from "../services/terminalCloudAPI"; @@ -30,11 +30,27 @@ describe("Terminal Cloud API", (): void => { const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); - const requestResponse: string = await terminalCloudAPI.async(terminalAPIPaymentRequest); + const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest); + expect(typeof requestResponse).toBe("string"); expect(requestResponse).toEqual("ok"); }); + test("should get an error after async payment request", async (): Promise => { + scope.post("/async").reply(200, asyncErrorRes); + + const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); + + const requestResponse = await terminalCloudAPI.async(terminalAPIPaymentRequest); + + if (typeof requestResponse === "object") { + expect(requestResponse.SaleToPOIRequest?.EventNotification).toBeDefined(); + expect(requestResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("Reject"); + } else { + throw new Error("Expected structured response, but got raw string"); + } + }); + test("should make a sync payment request", async (): Promise => { scope.post("/sync").reply(200, syncRes); @@ -453,4 +469,3 @@ export const syncTerminalPaymentResponse = { } } }; - diff --git a/src/helpers/getJsonResponse.ts b/src/helpers/getJsonResponse.ts index 6693b2f1f..1325db0f8 100644 --- a/src/helpers/getJsonResponse.ts +++ b/src/helpers/getJsonResponse.ts @@ -19,8 +19,36 @@ import Resource from "../services/resource"; import { IRequest } from "../typings/requestOptions"; +import { TerminalApiResponse } from "../typings/terminal/models"; -async function getJsonResponse(resource: Resource, jsonRequest: T | string, requestOptions?: IRequest.Options): Promise; +/** + * Sends a JSON request to the given resource and returns a string or a deserialized `TerminalApiResponse`. + * Used by Terminal API /async method + * + * @template T The request type (usually a model or plain object). + * @param {Resource} resource - The API resource to which the request is sent. + * @param {T | string} jsonRequest - The request payload, either as an object or raw JSON string. + * @param {IRequest.Options} [requestOptions] - Optional HTTP request options. + * @returns {Promise} A promise resolving to either a raw response string or a `TerminalApiResponse` object. + * + * @example + * const response = await getJsonResponse(terminalApiResource, request); + */ +async function getJsonResponse(resource: Resource, jsonRequest: T | string, requestOptions?: IRequest.Options): Promise; +/** + * Sends a JSON request to the given resource and returns a deserialized response of the expected type. + * Used by all APIs and Terminal API sync method + * + * @template T The request type. + * @template R The expected deserialized response type. + * @param {Resource} resource - The API resource to which the request is sent. + * @param {T | string} jsonRequest - The request payload, either as an object or raw JSON string. + * @param {IRequest.Options} [requestOptions] - Optional HTTP request options. + * @returns {Promise} A promise resolving to the deserialized response object. + * + * @example + * const response = await getJsonResponse(resource, request); + */ async function getJsonResponse(resource: Resource, jsonRequest: T | string, requestOptions?: IRequest.Options): Promise; /** diff --git a/src/services/terminalCloudAPI.ts b/src/services/terminalCloudAPI.ts index a505f99bb..5eeaf5111 100644 --- a/src/services/terminalCloudAPI.ts +++ b/src/services/terminalCloudAPI.ts @@ -40,11 +40,11 @@ class TerminalCloudAPI extends Service { private static setApplicationInfo(request: TerminalApiRequest): TerminalApiRequest { if (request.SaleToPOIRequest.PaymentRequest) { const applicationInfo = new ApplicationInfo(); - const saleToAcquirerData = {applicationInfo}; - const saleData = {saleToAcquirerData}; - const paymentRequest = {saleData}; - const saleToPOIRequest = {paymentRequest}; - const reqWithAppInfo = {saleToPOIRequest}; + const saleToAcquirerData = { applicationInfo }; + const saleData = { saleToAcquirerData }; + const paymentRequest = { saleData }; + const saleToPOIRequest = { paymentRequest }; + const reqWithAppInfo = { saleToPOIRequest }; mergeDeep(request, reqWithAppInfo); } @@ -52,11 +52,22 @@ class TerminalCloudAPI extends Service { return ObjectSerializer.serialize(request, "TerminalApiRequest"); } - public async(terminalApiRequest: TerminalApiRequest): Promise { + /** + * Send an asynchronous payment request to the Terminal API. + * + * @param terminalApiRequest - The request to send. + * @returns A promise that resolves to "ok" if the request was successful, or a TerminalApiResponse if there is an error. + */ + public async(terminalApiRequest: TerminalApiRequest): Promise { const request = TerminalCloudAPI.setApplicationInfo(terminalApiRequest); return getJsonResponse(this.terminalApiAsync, request); } + /** + * Send a synchronous payment request to the Terminal API. + * @param terminalApiRequest - The request to send. + * @returns A promise that resolves to a TerminalApiResponse. + */ public async sync(terminalApiRequest: TerminalApiRequest): Promise { const request = TerminalCloudAPI.setApplicationInfo(terminalApiRequest); const response = await getJsonResponse(