From 364da4ffde4199b9bb3969ce418f0378b3751393 Mon Sep 17 00:00:00 2001 From: Ricardo Ambrogi Date: Fri, 22 May 2020 08:58:56 +0200 Subject: [PATCH] Refactoring of Notification type definitions and Object.serializer --- src/__mocks__/notification/captureFalse.json | 2 +- src/__tests__/hmacValidator.spec.ts | 16 +- src/__tests__/notification.spec.ts | 25 +- src/__tests__/platforms.spec.ts | 2 +- src/notification/notificationRequest.ts | 15 +- src/typings/index.ts | 2 +- src/typings/notification.ts | 250 ------------------ src/typings/notification/additionalData.ts | 89 +++++++ src/typings/notification/amount.ts | 45 ++++ src/typings/notification/models.ts | 148 +++++++++++ src/typings/notification/notification.ts | 43 +++ src/typings/notification/notificationItem.ts | 31 +++ .../notification/notificationRequestItem.ts | 177 +++++++++++++ src/utils/hmacValidator.ts | 4 +- 14 files changed, 570 insertions(+), 279 deletions(-) delete mode 100644 src/typings/notification.ts create mode 100644 src/typings/notification/additionalData.ts create mode 100644 src/typings/notification/amount.ts create mode 100644 src/typings/notification/models.ts create mode 100644 src/typings/notification/notification.ts create mode 100644 src/typings/notification/notificationItem.ts create mode 100644 src/typings/notification/notificationRequestItem.ts diff --git a/src/__mocks__/notification/captureFalse.json b/src/__mocks__/notification/captureFalse.json index fb2a6fd..665d2ba 100644 --- a/src/__mocks__/notification/captureFalse.json +++ b/src/__mocks__/notification/captureFalse.json @@ -22,4 +22,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/__tests__/hmacValidator.spec.ts b/src/__tests__/hmacValidator.spec.ts index 6cc0177..31c375e 100644 --- a/src/__tests__/hmacValidator.spec.ts +++ b/src/__tests__/hmacValidator.spec.ts @@ -1,21 +1,21 @@ import HmacValidator from "../utils/hmacValidator"; -import {NotificationRequestItem} from "../typings/notification"; +import {AdditionalData, NotificationRequestItem} from "../typings/notification/models"; import {ApiConstants} from "../constants/apiConstants"; const key = "DFB1EB5485895CFA84146406857104ABB4CBCABDC8AAF103A624C8F6A3EAAB00"; -const expectedSign = "ipnxGCaUZ4l8TUW75a71/ghd2Fe5ffvX0pV4TLTntIc="; +const expectedSign = "xhpHvYq2u2Np0rstbZ6QjLGu7JWhvf7Iwaa6EviJSX0="; const notificationRequestItem: NotificationRequestItem = { pspReference: "pspReference", originalReference: "originalReference", merchantAccountCode: "merchantAccount", merchantReference: "reference", amount: {currency: "EUR", value: 1000}, - eventCode: "EVENT", - eventDate: new Date("01-01-1970"), + eventCode: NotificationRequestItem.EventCodeEnum.CAPTURE, + eventDate: new Date("01-01-1970").toISOString(), paymentMethod: "VISA", reason: "reason", - success: "true", - additionalData: { [ApiConstants.HMAC_SIGNATURE]: expectedSign }, + success: NotificationRequestItem.SuccessEnum.True, + additionalData: { [ApiConstants.HMAC_SIGNATURE as keyof AdditionalData]: expectedSign }, }; describe("HMAC Validator", function (): void { @@ -39,7 +39,7 @@ describe("HMAC Validator", function (): void { }); it("should get correct data to sign", function (): void { const data = hmacValidator.getDataToSign(notificationRequestItem); - expect(data).toEqual("pspReference:originalReference:merchantAccount:reference:1000:EUR:EVENT:true"); + expect(data).toEqual("pspReference:originalReference:merchantAccount:reference:1000:EUR:CAPTURE:true"); }); it("should have valid hmac", function (): void { @@ -51,7 +51,7 @@ describe("HMAC Validator", function (): void { it("should have invalid hmac", function (): void { const invalidNotification = { ...notificationRequestItem, - additionalData: { [ApiConstants.HMAC_SIGNATURE]: "notValidSign" } + additionalData: { [ApiConstants.HMAC_SIGNATURE as keyof NotificationRequestItem]: "notValidSign" } }; const result = hmacValidator.validateHMAC(invalidNotification, key); expect(result).toBeFalsy(); diff --git a/src/__tests__/notification.spec.ts b/src/__tests__/notification.spec.ts index cb840d7..cb7d6b6 100644 --- a/src/__tests__/notification.spec.ts +++ b/src/__tests__/notification.spec.ts @@ -4,7 +4,10 @@ import captureFalse from "../__mocks__/notification/captureFalse.json"; import refundTrue from "../__mocks__/notification/refundTrue.json"; import refundFalse from "../__mocks__/notification/refundFalse.json"; import NotificationRequest from "../notification/notificationRequest"; -import {Notification, NotificationEnum, NotificationRequestItem} from "../typings/notification"; +import {Notification, NotificationRequestItem} from "../typings/notification/models"; + +import NotificationEnum = NotificationRequestItem.EventCodeEnum; +import SuccessEnum = NotificationRequestItem.SuccessEnum; describe("Notification Test", function (): void { it("should return authorisation success", function (): void { @@ -13,8 +16,8 @@ describe("Notification Test", function (): void { if (notificationRequest.notificationItems) { const notificationRequestItem: NotificationRequestItem = notificationRequest.notificationItems[0]; - expect(NotificationEnum.EVENT_CODE_AUTHORISATION).toEqual(notificationRequestItem.eventCode); - expect(notificationRequestItem.success === "true").toBeTruthy(); + expect(NotificationEnum.AUTHORISATION).toEqual(notificationRequestItem.eventCode); + expect(notificationRequestItem.success === SuccessEnum.True).toBeTruthy(); expect(notificationRequestItem.pspReference).toEqual("123456789"); } else { fail(); @@ -27,8 +30,8 @@ describe("Notification Test", function (): void { if (notificationRequest.notificationItems) { const notificationRequestItem = notificationRequest.notificationItems[0]; - expect(NotificationEnum.EVENT_CODE_CAPTURE).toEqual(notificationRequestItem.eventCode); - expect(notificationRequestItem.success === "true").toBeTruthy(); + expect(NotificationEnum.CAPTURE).toEqual(notificationRequestItem.eventCode); + expect(notificationRequestItem.success === SuccessEnum.True).toBeTruthy(); expect(notificationRequestItem.pspReference).toEqual("PSP_REFERENCE"); expect(notificationRequestItem.originalReference).toEqual("ORIGINAL_PSP"); } else { @@ -42,8 +45,8 @@ describe("Notification Test", function (): void { if (notificationRequest.notificationItems) { const notificationRequestItem = notificationRequest.notificationItems[0]; - expect(NotificationEnum.EVENT_CODE_CAPTURE).toEqual(notificationRequestItem.eventCode); - expect(notificationRequestItem.success === "true").toBeFalsy(); + expect(NotificationEnum.CAPTURE).toEqual(notificationRequestItem.eventCode); + expect(notificationRequestItem.success === SuccessEnum.True).toBeFalsy(); expect(notificationRequestItem.pspReference).toEqual("PSP_REFERENCE"); expect(notificationRequestItem.originalReference).toEqual("ORIGINAL_PSP"); } else { @@ -57,8 +60,8 @@ describe("Notification Test", function (): void { if (notificationRequest.notificationItems) { const notificationRequestItem = notificationRequest.notificationItems[0]; - expect(NotificationEnum.EVENT_CODE_REFUND).toEqual(notificationRequestItem.eventCode); - expect(notificationRequestItem.success === "true").toBeTruthy(); + expect(NotificationEnum.REFUND).toEqual(notificationRequestItem.eventCode); + expect(notificationRequestItem.success === SuccessEnum.True).toBeTruthy(); expect(notificationRequestItem.pspReference).toEqual("PSP_REFERENCE"); expect(notificationRequestItem.originalReference).toEqual("ORIGINAL_PSP"); expect(notificationRequestItem.eventDate).toBeDefined(); @@ -73,8 +76,8 @@ describe("Notification Test", function (): void { if (notificationRequest.notificationItems) { const notificationRequestItem = notificationRequest.notificationItems[0]; - expect(NotificationEnum.EVENT_CODE_REFUND).toEqual(notificationRequestItem.eventCode); - expect(notificationRequestItem.success === "true").toBeFalsy(); + expect(NotificationEnum.REFUND).toEqual(notificationRequestItem.eventCode); + expect(notificationRequestItem.success === SuccessEnum.True).toBeFalsy(); expect(notificationRequestItem.pspReference).toEqual("PSP_REFERENCE"); expect(notificationRequestItem.originalReference).toEqual("ORIGINAL_PSP"); expect(notificationRequestItem.eventDate).toBeDefined(); diff --git a/src/__tests__/platforms.spec.ts b/src/__tests__/platforms.spec.ts index 8ee0afe..44eee5d 100644 --- a/src/__tests__/platforms.spec.ts +++ b/src/__tests__/platforms.spec.ts @@ -23,7 +23,7 @@ let accountHolderToUnSuspend: A.CreateAccountHolderResponse; let accountHolderToClose: A.CreateAccountHolderResponse; let notificationConfigurationToRetrieve: N.GetNotificationConfigurationResponse; -const generateRandomCode = () => Math.floor(Math.random() * Date.now()).toString(); +const generateRandomCode = (): string => Math.floor(Math.random() * Date.now()).toString(); const accountHolderDetails: AccountHolderDetails = { email: "random_email@example.com", fullPhoneNumber: "312030291928", diff --git a/src/notification/notificationRequest.ts b/src/notification/notificationRequest.ts index 6539b22..9a4839c 100644 --- a/src/notification/notificationRequest.ts +++ b/src/notification/notificationRequest.ts @@ -18,25 +18,30 @@ * This file is open source and available under the MIT license. * See the LICENSE file for more info. */ -import {Convert, Notification, NotificationItem, NotificationRequestItem} from "../typings/notification"; +import { + Notification, + NotificationItem, + NotificationRequestItem, + ObjectSerializer +} from "../typings/notification/models"; class NotificationRequest { public constructor(json: Notification) { - const notification = Convert.toNotification(JSON.stringify(json)); + const notification: Notification = ObjectSerializer.deserialize(json, "Notification"); this.notificationItemContainers = notification.notificationItems; this.live = notification.live; } - public get notificationItems(): NotificationRequestItem[] | undefined { + public get notificationItems(): Array | undefined { if (!this.notificationItemContainers) { return undefined; } - return this.notificationItemContainers.map((container): NotificationRequestItem => container.NotificationRequestItem); + return this.notificationItemContainers.map((container): NotificationRequestItem => container.notificationRequestItem); } public live: string; - public notificationItemContainers: NotificationItem[]; + public notificationItemContainers: Array; } export default NotificationRequest; \ No newline at end of file diff --git a/src/typings/index.ts b/src/typings/index.ts index 043d1c1..f14a08b 100644 --- a/src/typings/index.ts +++ b/src/typings/index.ts @@ -7,7 +7,7 @@ /// /// /// -/// +/// /// /// /// diff --git a/src/typings/notification.ts b/src/typings/notification.ts deleted file mode 100644 index 05fba00..0000000 --- a/src/typings/notification.ts +++ /dev/null @@ -1,250 +0,0 @@ -/* - * ###### - * ###### - * ############ ####( ###### #####. ###### ############ ############ - * ############# #####( ###### #####. ###### ############# ############# - * ###### #####( ###### #####. ###### ##### ###### ##### ###### - * ###### ###### #####( ###### #####. ###### ##### ##### ##### ###### - * ###### ###### #####( ###### #####. ###### ##### ##### ###### - * ############# ############# ############# ############# ##### ###### - * ############ ############ ############# ############ ##### ###### - * ###### - * ############# - * ############ - * - * Adyen NodeJS API Library - * - * Copyright (c) 2019 Adyen B.V. - * This file is open source and available under the MIT license. - * See the LICENSE file for more info. - */ - -// To parse this data: -// -// import { Convert, Notification } from "./file"; -// -// const notification = Convert.toNotification(json); -// -// These functions will throw an error if the JSON doesn't -// match the expected interface, even if the JSON is valid. - -export interface Notification { - live: string; - notificationItems: NotificationItem[]; -} - -export interface NotificationItem { - NotificationRequestItem: NotificationRequestItem; -} - -export interface NotificationRequestItem { - additionalData: AdditionalData; - amount: Amount; - eventCode: string; - eventDate: Date; - merchantAccountCode: string; - merchantReference: string; - operations?: string[]; - originalReference?: string; - paymentMethod: string; - pspReference: string; - reason: string; - success: string; -} - -export interface AdditionalData { - [key: string]: string; -} - -export interface Amount { - currency: string; - value: number; -} - -export enum NotificationEnum { - EVENT_CODE_AUTHORISATION = "AUTHORISATION", - - //Event codes - EVENT_CODE_CANCELLATION = "CANCELLATION", - EVENT_CODE_REFUND = "REFUND", - EVENT_CODE_CANCEL_OR_REFUND = "CANCEL_OR_REFUND", - EVENT_CODE_CAPTURE = "CAPTURE", - EVENT_CODE_CAPTURE_FAILED = "CAPTURE_FAILED", - EVENT_CODE_REFUND_FAILED = "REFUND_FAILED", - EVENT_CODE_REFUNDED_REVERSED = "REFUNDED_REVERSED", - EVENT_CODE_PAIDOUT_REVERSED = "PAIDOUT_REVERSED", - - //Additional Data - ADDITIONAL_DATA_TOTAL_FRAUD_SCORE = "totalFraudScore", - ADDITIONAL_DATA_FRAUD_CHECK_PATTERN = "fraudCheck-(\\d+)-([A-Za-z0-9]+)", -} - -// Converts JSON strings to/from your types -// and asserts the results of JSON.parse at runtime -export class Convert { - public static toNotification(json: string): Notification { - return cast(JSON.parse(json), r("Notification")); - } - - public static notificationToJson(value: Notification): string { - return JSON.stringify(uncast(value, r("Notification")), null, 2); - } -} - -function invalidValue(typ: any, val: any): never { - throw Error(`Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`); -} - -function jsonToJSProps(typ: any): any { - if (typ.jsonToJS === undefined) { - var map: any = {}; - typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ }); - typ.jsonToJS = map; - } - return typ.jsonToJS; -} - -function jsToJSONProps(typ: any): any { - if (typ.jsToJSON === undefined) { - var map: any = {}; - typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ }); - typ.jsToJSON = map; - } - return typ.jsToJSON; -} - -function transform(val: any, typ: any, getProps: any): any { - function transformPrimitive(typ: string, val: any): any { - if (typeof typ === typeof val) return val; - return invalidValue(typ, val); - } - - function transformUnion(typs: any[], val: any): any { - // val must validate against one typ in typs - var l = typs.length; - for (var i = 0; i < l; i++) { - var typ = typs[i]; - try { - return transform(val, typ, getProps); - } catch (_) {} - } - return invalidValue(typs, val); - } - - function transformEnum(cases: string[], val: any): any { - if (cases.indexOf(val) !== -1) return val; - return invalidValue(cases, val); - } - - function transformArray(typ: any, val: any): any { - // val must be an array with no invalid elements - if (!Array.isArray(val)) return invalidValue("array", val); - return val.map(el => transform(el, typ, getProps)); - } - - function transformDate(typ: any, val: any): any { - if (val === null) { - return null; - } - const d = new Date(val); - if (isNaN(d.valueOf())) { - return invalidValue("Date", val); - } - return d; - } - - function transformObject(props: { [k: string]: any }, additional: any, val: any): any { - if (val === null || typeof val !== "object" || Array.isArray(val)) { - return invalidValue("object", val); - } - var result: any = {}; - Object.getOwnPropertyNames(props).forEach(key => { - const prop = props[key]; - const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined; - result[prop.key] = transform(v, prop.typ, getProps); - }); - Object.getOwnPropertyNames(val).forEach(key => { - if (!Object.prototype.hasOwnProperty.call(props, key)) { - result[key] = transform(val[key], additional, getProps); - } - }); - return result; - } - - if (typ === "any") return val; - if (typ === null) { - if (val === null) return val; - return invalidValue(typ, val); - } - if (typ === false) return invalidValue(typ, val); - while (typeof typ === "object" && typ.ref !== undefined) { - typ = typeMap[typ.ref]; - } - if (Array.isArray(typ)) return transformEnum(typ, val); - if (typeof typ === "object") { - return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val) - : typ.hasOwnProperty("arrayItems") ? transformArray(typ.arrayItems, val) - : typ.hasOwnProperty("props") ? transformObject(getProps(typ), typ.additional, val) - : invalidValue(typ, val); - } - // Numbers can be parsed by Date but shouldn't be. - if (typ === Date && typeof val !== "number") return transformDate(typ, val); - return transformPrimitive(typ, val); -} - -function cast(val: any, typ: any): T { - return transform(val, typ, jsonToJSProps); -} - -function uncast(val: T, typ: any): any { - return transform(val, typ, jsToJSONProps); -} - -function a(typ: any) { - return { arrayItems: typ }; -} - -function u(...typs: any[]) { - return { unionMembers: typs }; -} - -function o(props: any[], additional: any) { - return { props, additional }; -} - -function m(additional: any) { - // @ts-ignore - return { props: [], additional }; -} - -function r(name: string) { - return { ref: name }; -} - -const typeMap: any = { - "Notification": o([ - { json: "live", js: "live", typ: "" }, - { json: "notificationItems", js: "notificationItems", typ: a(r("NotificationItem")) }, - ], false), - "NotificationItem": o([ - { json: "NotificationRequestItem", js: "NotificationRequestItem", typ: r("NotificationRequestItem") }, - ], false), - "NotificationRequestItem": o([ - { json: "additionalData", js: "additionalData", typ: u(undefined, m("any")) }, - { json: "amount", js: "amount", typ: r("Amount") }, - { json: "eventCode", js: "eventCode", typ: "" }, - { json: "eventDate", js: "eventDate", typ: Date }, - { json: "merchantAccountCode", js: "merchantAccountCode", typ: "" }, - { json: "merchantReference", js: "merchantReference", typ: "" }, - { json: "operations", js: "operations", typ: u(undefined, a("")) }, - { json: "originalReference", js: "originalReference", typ: u(undefined, "") }, - { json: "paymentMethod", js: "paymentMethod", typ: "" }, - { json: "pspReference", js: "pspReference", typ: "" }, - { json: "reason", js: "reason", typ: "" }, - { json: "success", js: "success", typ: "" }, - ], false), - "Amount": o([ - { json: "currency", js: "currency", typ: "" }, - { json: "value", js: "value", typ: 0 }, - ], false), -}; \ No newline at end of file diff --git a/src/typings/notification/additionalData.ts b/src/typings/notification/additionalData.ts new file mode 100644 index 0000000..3e24d1d --- /dev/null +++ b/src/typings/notification/additionalData.ts @@ -0,0 +1,89 @@ +/** + * Notification API + * Definition of Notification API Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** +* The additionalData object is a generic container that can hold extra fields. For more information, refer to NotificationRequestItem.additionalData . +*/ +export class AdditionalData { + /** + * The ID that uniquely identifies the shopper. This shopperReference is the same as the shopperReference used in the initial payment. + */ + 'shopperReference'?: string; + /** + * The shopper\'s email address. + */ + 'shopperEmail'?: string; + /** + * Authorisation code: When the payment is authorised successfully, this field holds the authorisation code for the payment. When the payment is not authorised, this field is empty. + */ + 'authCode'?: string; + /** + * Returns the last 4 digits of the credit card. + */ + 'cardSummary'?: string; + /** + * Returns the card expiry date. + */ + 'expiryDate'?: string; + /** + * Value of the amount authorised. + */ + 'authorisedAmountValue'?: string; + /** + * Currency of the authorised amount. + */ + 'authorisedAmountCurrency'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "shopperReference", + "baseName": "shopperReference", + "type": "string" + }, + { + "name": "shopperEmail", + "baseName": "shopperEmail", + "type": "string" + }, + { + "name": "authCode", + "baseName": "authCode", + "type": "string" + }, + { + "name": "cardSummary", + "baseName": "cardSummary", + "type": "string" + }, + { + "name": "expiryDate", + "baseName": "expiryDate", + "type": "string" + }, + { + "name": "authorisedAmountValue", + "baseName": "authorisedAmountValue", + "type": "string" + }, + { + "name": "authorisedAmountCurrency", + "baseName": "authorisedAmountCurrency", + "type": "string" + } ]; + + static getAttributeTypeMap() { + return AdditionalData.attributeTypeMap; + } +} + diff --git a/src/typings/notification/amount.ts b/src/typings/notification/amount.ts new file mode 100644 index 0000000..eeae27e --- /dev/null +++ b/src/typings/notification/amount.ts @@ -0,0 +1,45 @@ +/** + * Notification API + * Definition of Notification API Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** +* A container object for the payable amount information for the transaction.For HTTP POST notifications, currency and value are returned as URL parameters. +*/ +export class Amount { + /** + * The three-character ISO currency code. https://docs.adyen.com/development-resources/currency-codes + */ + 'currency'?: string; + /** + * The payable amount that can be charged for the transaction. The transaction amount needs to be represented in minor units according to the following table: https://docs.adyen.com/development-resources/currency-codes + */ + 'value'?: number; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "currency", + "baseName": "currency", + "type": "string" + }, + { + "name": "value", + "baseName": "value", + "type": "number" + } ]; + + static getAttributeTypeMap() { + return Amount.attributeTypeMap; + } +} + diff --git a/src/typings/notification/models.ts b/src/typings/notification/models.ts new file mode 100644 index 0000000..e14fbf2 --- /dev/null +++ b/src/typings/notification/models.ts @@ -0,0 +1,148 @@ +export * from './additionalData'; +export * from './amount'; +export * from './notification'; +export * from './notificationItem'; +export * from './notificationRequestItem'; + +import { AdditionalData } from './additionalData'; +import { Amount } from './amount'; +import { Notification } from './notification'; +import { NotificationItem } from './notificationItem'; +import { NotificationRequestItem } from './notificationRequestItem'; + +/* tslint:disable:no-unused-variable */ +let primitives = [ + "string", + "boolean", + "double", + "integer", + "long", + "float", + "number", + "any" + ]; + +let enumsMap: {[index: string]: any} = { + "NotificationRequestItem.EventCodeEnum": NotificationRequestItem.EventCodeEnum, + "NotificationRequestItem.OperationsEnum": NotificationRequestItem.OperationsEnum, + "NotificationRequestItem.SuccessEnum": NotificationRequestItem.SuccessEnum, +} + +let typeMap: {[index: string]: any} = { + "AdditionalData": AdditionalData, + "Amount": Amount, + "Notification": Notification, + "NotificationItem": NotificationItem, + "NotificationRequestItem": NotificationRequestItem, +} + +export class ObjectSerializer { + public static findCorrectType(data: any, expectedType: string) { + if (data == undefined) { + return expectedType; + } else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) { + return expectedType; + } else if (expectedType === "Date") { + return expectedType; + } else { + if (enumsMap[expectedType]) { + return expectedType; + } + + if (!typeMap[expectedType]) { + return expectedType; // w/e we don't know the type + } + + // Check the discriminator + let discriminatorProperty = typeMap[expectedType].discriminator; + if (discriminatorProperty == null) { + return expectedType; // the type does not have a discriminator. use it. + } else { + if (data[discriminatorProperty]) { + var discriminatorType = data[discriminatorProperty]; + if(typeMap[discriminatorType]){ + return discriminatorType; // use the type given in the discriminator + } else { + return expectedType; // discriminator did not map to a type + } + } else { + return expectedType; // discriminator was not present (or an empty string) + } + } + } + } + + public static serialize(data: any, type: string) { + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index in data) { + let date = data[index]; + transformedData.push(ObjectSerializer.serialize(date, subType)); + } + return transformedData; + } else if (type === "Date") { + return data.toISOString(); + } else { + if (enumsMap[type]) { + return data; + } + if (!typeMap[type]) { // in case we dont know the type + return data; + } + + // Get the actual type of this object + type = this.findCorrectType(data, type); + + // get the map for the correct type. + let attributeTypes = typeMap[type].getAttributeTypeMap(); + let instance: {[index: string]: any} = {}; + for (let index in attributeTypes) { + let attributeType = attributeTypes[index]; + instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.name], attributeType.type); + } + return instance; + } + } + + public static deserialize(data: any, type: string) { + // polymorphism may change the actual type. + type = ObjectSerializer.findCorrectType(data, type); + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index in data) { + let date = data[index]; + transformedData.push(ObjectSerializer.deserialize(date, subType)); + } + return transformedData; + } else if (type === "Date") { + return new Date(data); + } else { + if (enumsMap[type]) {// is Enum + return data; + } + + if (!typeMap[type]) { // dont know the type + return data; + } + let instance = new typeMap[type](); + let attributeTypes = typeMap[type].getAttributeTypeMap(); + for (let index in attributeTypes) { + let attributeType = attributeTypes[index]; + instance[attributeType.name] = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type); + } + return instance; + } + } +} diff --git a/src/typings/notification/notification.ts b/src/typings/notification/notification.ts new file mode 100644 index 0000000..d6f27af --- /dev/null +++ b/src/typings/notification/notification.ts @@ -0,0 +1,43 @@ +/** + * Notification API + * Definition of Notification API Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { NotificationItem } from './notificationItem'; + +export class Notification { + /** + * Informs about the origin of the notification: - true: the notification originated from the live environment. - false: the notification originated from the test environment. + */ + 'live': string; + /** + * A container object for the details included in the notification. + */ + 'notificationItems': Array; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "live", + "baseName": "live", + "type": "string" + }, + { + "name": "notificationItems", + "baseName": "notificationItems", + "type": "Array" + } ]; + + static getAttributeTypeMap() { + return Notification.attributeTypeMap; + } +} + diff --git a/src/typings/notification/notificationItem.ts b/src/typings/notification/notificationItem.ts new file mode 100644 index 0000000..2f7262a --- /dev/null +++ b/src/typings/notification/notificationItem.ts @@ -0,0 +1,31 @@ +/** + * Notification API + * Definition of Notification API Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { NotificationRequestItem } from './notificationRequestItem'; + +export class NotificationItem { + 'notificationRequestItem': NotificationRequestItem; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "notificationRequestItem", + "baseName": "NotificationRequestItem", + "type": "NotificationRequestItem" + } ]; + + static getAttributeTypeMap() { + return NotificationItem.attributeTypeMap; + } +} + diff --git a/src/typings/notification/notificationRequestItem.ts b/src/typings/notification/notificationRequestItem.ts new file mode 100644 index 0000000..5eac79a --- /dev/null +++ b/src/typings/notification/notificationRequestItem.ts @@ -0,0 +1,177 @@ +/** + * Notification API + * Definition of Notification API Schema + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { AdditionalData } from './additionalData'; +import { Amount } from './amount'; + +export class NotificationRequestItem { + 'additionalData'?: AdditionalData; + 'amount': Amount; + /** + * Adyen\'s 16-character unique reference associated with the transaction/the request. This value is globally unique; quote it when communicating with us about this request. + */ + 'pspReference': string; + /** + * The type of event the notification item refers to. When eventCode returns AUTHORISATION, it does not necessarily mean that the payment authorisation request has been successfully processed. The authorisation is successful if success = true. If success = false, check the the reason value for further information on the authorisation failure cause. This can occur, for example, if an error occurs or if the transaction is refused. + */ + 'eventCode': NotificationRequestItem.EventCodeEnum; + /** + * The time when the event was generated. + */ + 'eventDate': string; + /** + * The merchant account identifier used in the transaction the notification item refers to. + */ + 'merchantAccountCode': string; + /** + * This field holds a list of the modification operations supported by the transaction the notification item refers to. The available operations in the list give information on the follow-up actions concerning the payment. You may be requested to: Capture the payment (for example, if auto-capture is not set up), Cancel the payment (if the payment has not been captured yet), or Refund the payment (if the payment has already been captured). + */ + 'operations'?: Array; + /** + * A reference to uniquely identify the payment.This reference is used in all communication with you about the payment status.We recommend using a unique value per payment; however, it is not a requirement. If you need to provide multiple references for a transaction, you can enter them in this field. Separate each reference value with a hyphen character (\'-\'). This field has a length restriction: you can enter max. 80 characters. + */ + 'merchantReference': string; + /** + * If the notification item is about a payment authorisation, this field is not populated and is blank. If the notification item is about a modification, the originalReference value corresponds to the payment request assigned to the original payment. + */ + 'originalReference'?: string; + /** + * The payment method used in the transaction the notification item refers to. + */ + 'paymentMethod'?: string; + /** + * This field holds plain text. For more information, refer to the reason field values below. + */ + 'reason'?: string; + /** + * Informs about the outcome of the event ( eventCode ) the notification refers to. true: the event ( eventCode ) the notification refers to was executed successfully. false: the event was not executed successfully. + */ + 'success': NotificationRequestItem.SuccessEnum; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "additionalData", + "baseName": "additionalData", + "type": "AdditionalData" + }, + { + "name": "amount", + "baseName": "amount", + "type": "Amount" + }, + { + "name": "pspReference", + "baseName": "pspReference", + "type": "string" + }, + { + "name": "eventCode", + "baseName": "eventCode", + "type": "NotificationRequestItem.EventCodeEnum" + }, + { + "name": "eventDate", + "baseName": "eventDate", + "type": "string" + }, + { + "name": "merchantAccountCode", + "baseName": "merchantAccountCode", + "type": "string" + }, + { + "name": "operations", + "baseName": "operations", + "type": "Array" + }, + { + "name": "merchantReference", + "baseName": "merchantReference", + "type": "string" + }, + { + "name": "originalReference", + "baseName": "originalReference", + "type": "string" + }, + { + "name": "paymentMethod", + "baseName": "paymentMethod", + "type": "string" + }, + { + "name": "reason", + "baseName": "reason", + "type": "string" + }, + { + "name": "success", + "baseName": "success", + "type": "NotificationRequestItem.SuccessEnum" + } ]; + + static getAttributeTypeMap() { + return NotificationRequestItem.attributeTypeMap; + } +} + +export namespace NotificationRequestItem { + export enum EventCodeEnum { + AUTHORISATION = 'AUTHORISATION', + AUTHORISATIONADJUSTMENT = 'AUTHORISATION_ADJUSTMENT', + CANCELLATION = 'CANCELLATION', + CANCELORREFUND = 'CANCEL_OR_REFUND', + CAPTURE = 'CAPTURE', + CAPTUREFAILED = 'CAPTURE_FAILED', + HANDLEDEXTERNALLY = 'HANDLED_EXTERNALLY', + ORDEROPENED = 'ORDER_OPENED', + ORDERCLOSED = 'ORDER_CLOSED', + PENDING = 'PENDING', + PROCESSRETRY = 'PROCESS_RETRY', + REFUND = 'REFUND', + REFUNDFAILED = 'REFUND_FAILED', + REFUNDEDREVERSED = 'REFUNDED_REVERSED', + REFUNDWITHDATA = 'REFUND_WITH_DATA', + REPORTAVAILABLE = 'REPORT_AVAILABLE', + VOIDPENDINGREFUND = 'VOID_PENDING_REFUND', + CHARGEBACK = 'CHARGEBACK', + CHARGEBACKREVERSED = 'CHARGEBACK_REVERSED', + NOTIFICATIONOFCHARGEBACK = 'NOTIFICATION_OF_CHARGEBACK', + NOTIFICATIONOFFRAUD = 'NOTIFICATION_OF_FRAUD', + PREARBITRATIONLOST = 'PREARBITRATION_LOST', + PREARBITRATIONWON = 'PREARBITRATION_WON', + REQUESTFORINFORMATION = 'REQUEST_FOR_INFORMATION', + SECONDCHARGEBACK = 'SECOND_CHARGEBACK', + PAYOUTEXPIRE = 'PAYOUT_EXPIRE', + PAYOUTDECLINE = 'PAYOUT_DECLINE', + PAYOUTTHIRDPARTY = 'PAYOUT_THIRDPARTY', + PAIDOUTREVERSED = 'PAIDOUT_REVERSED', + AUTORESCUE = 'AUTORESCUE', + CANCELAUTORESCUE = 'CANCEL_AUTORESCUE', + RECURRINGCONTRACT = 'RECURRING_CONTRACT', + POSTPONEDREFUND = 'POSTPONED_REFUND', + OFFERCLOSED = 'OFFER_CLOSED', + MANUALREVIEWACCEPT = 'MANUAL_REVIEW_ACCEPT', + MANUALREVIEWREJECT = 'MANUAL_REVIEW_REJECT' + } + export enum OperationsEnum { + CANCEL = 'CANCEL', + CAPTURE = 'CAPTURE', + REFUND = 'REFUND' + } + export enum SuccessEnum { + True = 'true', + False = 'false' + } +} diff --git a/src/utils/hmacValidator.ts b/src/utils/hmacValidator.ts index 547e9b6..2399ceb 100644 --- a/src/utils/hmacValidator.ts +++ b/src/utils/hmacValidator.ts @@ -1,5 +1,5 @@ import {createHmac} from "crypto"; -import {NotificationRequestItem} from "../typings/notification"; +import {NotificationRequestItem} from "../typings/notification/models"; import {ApiConstants} from "../constants/apiConstants"; type DataToSign = NotificationRequestItem | { [key: string]: string } @@ -16,7 +16,7 @@ class HmacValidator { public validateHMAC(notificationRequestItem: NotificationRequestItem, key: string): boolean { const expectedSign = this.calculateHmac(notificationRequestItem, key); - const merchantSign = notificationRequestItem.additionalData[ApiConstants.HMAC_SIGNATURE]; + const merchantSign = notificationRequestItem.additionalData?.[ApiConstants.HMAC_SIGNATURE]; return expectedSign === merchantSign; }