From b3f456d32a0314397103c961a953213308bf12e7 Mon Sep 17 00:00:00 2001 From: Beppe Catanese Date: Fri, 11 Jul 2025 09:32:49 +0200 Subject: [PATCH 1/3] Add PredefinedContentHelper --- ...terminalAPIPredefinedContentHelper.spec.ts | 46 ++++++++ src/typings/terminal/models.ts | 2 + .../terminal/predefinedContentHelper.ts | 103 ++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 src/__tests__/terminalAPIPredefinedContentHelper.spec.ts create mode 100644 src/typings/terminal/predefinedContentHelper.ts diff --git a/src/__tests__/terminalAPIPredefinedContentHelper.spec.ts b/src/__tests__/terminalAPIPredefinedContentHelper.spec.ts new file mode 100644 index 000000000..faa0f6915 --- /dev/null +++ b/src/__tests__/terminalAPIPredefinedContentHelper.spec.ts @@ -0,0 +1,46 @@ +import { PredefinedContentHelper, DisplayNotificationEvent } from "../typings/terminal/predefinedContentHelper"; + +describe("PredefinedContentHelper", () => { + it("should extract a valid event", () => { + + const ReferenceID = "TransactionID=oLkO001517998574000&TimeStamp=2018-02-07T10%3a16%3a14.000Z&event=PIN_ENTERED"; + + const helper = new PredefinedContentHelper(ReferenceID); + expect(helper.getEvent()).toBe(DisplayNotificationEvent.PIN_ENTERED); + }); + + it("should return null for an invalid event", () => { + const helper = new PredefinedContentHelper("event=INVALID_EVENT"); + expect(helper.getEvent()).toBeNull(); + }); + + it("should extract TransactionID", () => { + const helper = new PredefinedContentHelper("TransactionID=12345&TimeStamp=2018-02-07T10%3a16%3a14.000Z&event=PIN_ENTERED"); + expect(helper.getTransactionId()).toBe("12345"); + }); + + it("should extract TimeStamp", () => { + const helper = new PredefinedContentHelper("TimeStamp=2024-07-11T12:00:00Z"); + expect(helper.getTimeStamp()).toBe("2024-07-11T12:00:00Z"); + }); + + it("should extract arbitrary key", () => { + const helper = new PredefinedContentHelper("foo=bar&baz=qux"); + expect(helper.get("foo")).toBe("bar"); + expect(helper.get("baz")).toBe("qux"); + expect(helper.get("missing")).toBeNull(); + }); + + it("should convert params to object", () => { + const helper = new PredefinedContentHelper("a=1&b=2&event=WAIT_FOR_PIN"); + expect(helper.toObject()).toEqual({ a: "1", b: "2", event: "WAIT_FOR_PIN" }); + }); + + it("should handle empty referenceId", () => { + const helper = new PredefinedContentHelper(""); + expect(helper.getEvent()).toBeNull(); + expect(helper.getTransactionId()).toBeNull(); + expect(helper.getTimeStamp()).toBeNull(); + expect(helper.toObject()).toEqual({}); + }); +}); \ No newline at end of file diff --git a/src/typings/terminal/models.ts b/src/typings/terminal/models.ts index 99145d68f..38e749887 100644 --- a/src/typings/terminal/models.ts +++ b/src/typings/terminal/models.ts @@ -265,6 +265,8 @@ export * from "./transmitResponse"; export * from "./uTMCoordinates"; export * from "./unitOfMeasureType"; export * from "./versionType"; +// helpers +export * from "./predefinedContentHelper"; import { AbortRequest } from "./abortRequest"; import { AccountType } from "./accountType"; diff --git a/src/typings/terminal/predefinedContentHelper.ts b/src/typings/terminal/predefinedContentHelper.ts new file mode 100644 index 000000000..38cefa3f0 --- /dev/null +++ b/src/typings/terminal/predefinedContentHelper.ts @@ -0,0 +1,103 @@ +/** + * PredefinedContentHelper class to parse and manage predefined content reference IDs. + */ +export class PredefinedContentHelper { + private params: URLSearchParams; + + constructor(referenceId: string) { + this.params = new URLSearchParams(referenceId); + } + + /** + * Extracts and validates the `event` value from the ReferenceID. + * + * @returns A valid `DisplayNotificationEvent`, otherwise `null`. + * + * @example + * const helper = new PredefinedContentHelper("...&event=PIN_ENTERED"); + * const event = helper.getEvent(); // DisplayNotificationEvent.PIN_ENTERED or null + */ + getEvent(): DisplayNotificationEvent | null { + const event = this.params.get("event"); + + switch (event) { + case "TENDER_CREATED": + case "CARD_INSERTED": + case "CARD_PRESENTED": + case "CARD_SWIPED": + case "WAIT_FOR_APP_SELECTION": + case "APPLICATION_SELECTED": + case "ASK_SIGNATURE": + case "CHECK_SIGNATURE": + case "SIGNATURE_CHECKED": + case "WAIT_FOR_PIN": + case "PIN_ENTERED": + case "PRINT_RECEIPT": + case "RECEIPT_PRINTED": + case "CARD_REMOVED": + case "TENDER_FINAL": + case "ASK_DCC": + case "DCC_ACCEPTED": + case "DCC_REJECTED": + case "ASK_GRATUITY": + case "GRATUITY_ENTERED": + case "BALANCE_QUERY_STARTED": + case "BALANCE_QUERY_COMPLETED": + case "LOAD_STARTED": + case "LOAD_COMPLETED": + case "PROVIDE_CARD_DETAILS": + case "CARD_DETAILS_PROVIDED": + return event as DisplayNotificationEvent; + default: + return null; + } + } + getTransactionId(): string | null { + return this.params.get("TransactionID"); + } + + getTimeStamp(): string | null { + return this.params.get("TimeStamp"); + } + + get(key: string): string | null { + return this.params.get(key); + } + + toObject(): Record { + const result: Record = {}; + for (const [key, value] of this.params.entries()) { + result[key] = value; + } + return result; + } +} + +export enum DisplayNotificationEvent { + TENDER_CREATED = "TENDER_CREATED", + CARD_INSERTED = "CARD_INSERTED", + CARD_PRESENTED = "CARD_PRESENTED", + CARD_SWIPED = "CARD_SWIPED", + WAIT_FOR_APP_SELECTION = "WAIT_FOR_APP_SELECTION", + APPLICATION_SELECTED = "APPLICATION_SELECTED", + ASK_SIGNATURE = "ASK_SIGNATURE", + CHECK_SIGNATURE = "CHECK_SIGNATURE", + SIGNATURE_CHECKED = "SIGNATURE_CHECKED", + WAIT_FOR_PIN = "WAIT_FOR_PIN", + PIN_ENTERED = "PIN_ENTERED", + PRINT_RECEIPT = "PRINT_RECEIPT", + RECEIPT_PRINTED = "RECEIPT_PRINTED", + CARD_REMOVED = "CARD_REMOVED", + TENDER_FINAL = "TENDER_FINAL", + ASK_DCC = "ASK_DCC", + DCC_ACCEPTED = "DCC_ACCEPTED", + DCC_REJECTED = "DCC_REJECTED", + ASK_GRATUITY = "ASK_GRATUITY", + GRATUITY_ENTERED = "GRATUITY_ENTERED", + BALANCE_QUERY_STARTED = "BALANCE_QUERY_STARTED", + BALANCE_QUERY_COMPLETED = "BALANCE_QUERY_COMPLETED", + LOAD_STARTED = "LOAD_STARTED", + LOAD_COMPLETED = "LOAD_COMPLETED", + PROVIDE_CARD_DETAILS = "PROVIDE_CARD_DETAILS", + CARD_DETAILS_PROVIDED = "CARD_DETAILS_PROVIDED", +} From 4c0d5d69d1a67c8f4dbe2901c3d0b2eff489aea7 Mon Sep 17 00:00:00 2001 From: Beppe Catanese Date: Fri, 11 Jul 2025 09:33:02 +0200 Subject: [PATCH 2/3] Minor edit --- src/__tests__/terminalCloudAPI.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/terminalCloudAPI.spec.ts b/src/__tests__/terminalCloudAPI.spec.ts index 9ea15245d..4a538b55a 100644 --- a/src/__tests__/terminalCloudAPI.spec.ts +++ b/src/__tests__/terminalCloudAPI.spec.ts @@ -4,7 +4,7 @@ import { asyncRes } from "../__mocks__/terminalApi/async"; import { syncRefund, syncRes, syncResEventNotification, syncResEventNotificationWithAdditionalAttributes, syncResEventNotificationWithUnknownEnum } from "../__mocks__/terminalApi/sync"; import Client from "../client"; import TerminalCloudAPI from "../services/terminalCloudAPI"; -import { terminal} from "../typings"; +import { terminal } from "../typings"; let client: Client; let terminalCloudAPI: TerminalCloudAPI; From 2fbf040b73f6af7cda0631353bee91a22a63ef96 Mon Sep 17 00:00:00 2001 From: Beppe Catanese Date: Fri, 11 Jul 2025 10:03:57 +0200 Subject: [PATCH 3/3] Refactor getEvent to use DisplayNotificationEvent values --- .../terminal/predefinedContentHelper.ts | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/src/typings/terminal/predefinedContentHelper.ts b/src/typings/terminal/predefinedContentHelper.ts index 38cefa3f0..4f78405a3 100644 --- a/src/typings/terminal/predefinedContentHelper.ts +++ b/src/typings/terminal/predefinedContentHelper.ts @@ -20,38 +20,12 @@ export class PredefinedContentHelper { getEvent(): DisplayNotificationEvent | null { const event = this.params.get("event"); - switch (event) { - case "TENDER_CREATED": - case "CARD_INSERTED": - case "CARD_PRESENTED": - case "CARD_SWIPED": - case "WAIT_FOR_APP_SELECTION": - case "APPLICATION_SELECTED": - case "ASK_SIGNATURE": - case "CHECK_SIGNATURE": - case "SIGNATURE_CHECKED": - case "WAIT_FOR_PIN": - case "PIN_ENTERED": - case "PRINT_RECEIPT": - case "RECEIPT_PRINTED": - case "CARD_REMOVED": - case "TENDER_FINAL": - case "ASK_DCC": - case "DCC_ACCEPTED": - case "DCC_REJECTED": - case "ASK_GRATUITY": - case "GRATUITY_ENTERED": - case "BALANCE_QUERY_STARTED": - case "BALANCE_QUERY_COMPLETED": - case "LOAD_STARTED": - case "LOAD_COMPLETED": - case "PROVIDE_CARD_DETAILS": - case "CARD_DETAILS_PROVIDED": - return event as DisplayNotificationEvent; - default: - return null; + if (event && Object.values(DisplayNotificationEvent).includes(event as DisplayNotificationEvent)) { + return event as DisplayNotificationEvent; } + return null; } + getTransactionId(): string | null { return this.params.get("TransactionID"); } @@ -65,14 +39,11 @@ export class PredefinedContentHelper { } toObject(): Record { - const result: Record = {}; - for (const [key, value] of this.params.entries()) { - result[key] = value; - } - return result; + return Object.fromEntries(this.params); } } +// Supported events for display notifications export enum DisplayNotificationEvent { TENDER_CREATED = "TENDER_CREATED", CARD_INSERTED = "CARD_INSERTED",