diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 03cf163e2..dced5944e 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -59,8 +59,6 @@ jobs: ${{ runner.os }}-node- - name: Install dependencies run: npm install - - name: Run tests - run: npm test - name: Run tests with coverage run: npm run test:coverage diff --git a/src/__mocks__/legalEntityManagement/responses.ts b/src/__mocks__/legalEntityManagement/responses.ts index 027e5c27c..4e0c20ff8 100644 --- a/src/__mocks__/legalEntityManagement/responses.ts +++ b/src/__mocks__/legalEntityManagement/responses.ts @@ -193,6 +193,47 @@ export const legalEntity = { "type": "individual" }; +export const legalEntityAdditionalAttributes = { + "id": "123456789", + "additionalAttribute": "something", + "individual": { + "email": "string", + "name": { + "firstName": "string", + "infix": "string", + "lastName": "string" + }, + "nationality": "string", + "phone": { + "number": "string", + "type": "string" + }, + }, + "reference": "string", + "transferInstruments": [{ "id": "string" }], + "type": "individual" +}; + +export const legalEntityUnknownEnum = { + "id": "123456789", + "individual": { + "email": "string", + "name": { + "firstName": "string", + "infix": "string", + "lastName": "string" + }, + "nationality": "string", + "phone": { + "number": "string", + "type": "string" + }, + }, + "reference": "string", + "transferInstruments": [{ "id": "string" }], + "type": "this is unknown" +}; + export const businessLines = { "businessLines": [{ "capability": "receivePayments", diff --git a/src/__mocks__/terminalApi/sync.ts b/src/__mocks__/terminalApi/sync.ts index d1999dde2..2994b1260 100644 --- a/src/__mocks__/terminalApi/sync.ts +++ b/src/__mocks__/terminalApi/sync.ts @@ -386,3 +386,43 @@ export const syncResEventNotification = { } } }; + +export const syncResEventNotificationWithAdditionalAttributes = { + "SaleToPOIRequest":{ + "EventNotification":{ + "EventDetails":"newstate=IDLE&oldstate=START", + "EventToNotify":"Shutdown", + "TimeStamp":"2019-08-07T10:16:10.000Z", + "AdditionalAttribute": "Something" + }, + "MessageHeader":{ + "SaleID":"POSSystemID12345", + "ProtocolVersion":"3.0", + "MessageType":"Notification", + "POIID":"V400m-324688179", + "MessageClass":"Event", + "MessageCategory":"Event", + "DeviceID":"1517998561", + "AdditionalAttribute": "SomethingElse" + } + } +}; + +export const syncResEventNotificationWithUnknownEnum = { + "SaleToPOIRequest":{ + "EventNotification":{ + "EventDetails":"newstate=IDLE&oldstate=START", + "EventToNotify":"this is unknown", + "TimeStamp":"2019-08-07T10:16:10.000Z" + }, + "MessageHeader":{ + "SaleID":"POSSystemID12345", + "ProtocolVersion":"3.0", + "MessageType":"Notification", + "POIID":"V400m-324688179", + "MessageClass":"Event", + "MessageCategory":"Event", + "DeviceID":"1517998561" + } + } +}; diff --git a/src/__tests__/checkout.spec.ts b/src/__tests__/checkout.spec.ts index 33dbf51fa..66d624f52 100644 --- a/src/__tests__/checkout.spec.ts +++ b/src/__tests__/checkout.spec.ts @@ -56,18 +56,6 @@ export function createPaymentsCheckoutRequest(): checkout.PaymentRequest { }; } -// function createPaymentSessionRequest(): checkout.PaymentSetupRequest { -// return { -// amount: createAmountObject("USD", 1000), -// countryCode: "NL", -// merchantAccount, -// reference, -// returnUrl: "https://your-company.com/...", -// channel: checkout.PaymentSetupRequest.ChannelEnum.Web, -// sdkVersion: "3.7.0" -// }; -// } - function createUpdatePaymentLinkRequest(): checkout.UpdatePaymentLinkRequest { return { "status": checkout.UpdatePaymentLinkRequest.StatusEnum.Expired @@ -203,20 +191,6 @@ describe("Checkout", (): void => { .matchHeader("Idempotency-Key", "testKey"); await checkoutService.PaymentsApi.paymentsDetails(createPaymentsDetailsRequest(), {idempotencyKey: "testKey"}); - // scope.post("/paymentSession") - // .reply(200, paymentSessionSuccess) - // .matchHeader("Idempotency-Key", "testKey"); - // const paymentSessionRequest: checkout.PaymentSetupRequest = createPaymentSessionRequest(); - // await checkoutService.ClassicCheckoutSDKApi.paymentSession(paymentSessionRequest, {idempotencyKey: "testKey"}); - - // scope.post("/payments/result") - // .reply(200, paymentsResultSuccess) - // .matchHeader("Idempotency-Key", "testKey"); - // const paymentResultRequest: checkout.PaymentVerificationRequest = { - // payload: "This is a test payload", - // }; - // await checkoutService.ClassicCheckoutSDKApi.verifyPaymentResult(paymentResultRequest, {idempotencyKey: "testKey"}); - const orderRequest: checkout.CreateOrderRequest = { amount: createAmountObject("USD", 1000), merchantAccount, diff --git a/src/__tests__/legalEntityManagement.spec.ts b/src/__tests__/legalEntityManagement.spec.ts index 36b880db0..edbd341ea 100644 --- a/src/__tests__/legalEntityManagement.spec.ts +++ b/src/__tests__/legalEntityManagement.spec.ts @@ -13,6 +13,8 @@ import { businessLines, document, legalEntity, + legalEntityAdditionalAttributes, + legalEntityUnknownEnum, onboardingLink, onboardingTheme, onboardingThemes, @@ -69,6 +71,29 @@ describe("Legal Entity Management", (): void => { expect(response.type).toBe("individual"); }); + it("should support GET /legalEntities/{id} with additional attributes", async (): Promise => { + scope.get(`/legalEntities/${id}`) + .reply(200, legalEntityAdditionalAttributes); + + await expect(async () => { + const response = await legalEntityManagement.LegalEntitiesApi.getLegalEntity("123456789"); + expect(response.id).toBe(id); + expect(response.type).toBe("individual"); + }).not.toThrow(); + }); + + it("should support GET /legalEntities/{id} with unknown enum", async (): Promise => { + scope.get(`/legalEntities/${id}`) + .reply(200, legalEntityUnknownEnum); + + await expect(async () => { + const response = await legalEntityManagement.LegalEntitiesApi.getLegalEntity("123456789"); + expect(response.id).toBe(id); + // type is unknown, so it holds whatever value is found in the payload + expect(response.type).toBe("this is unknown"); + }).not.toThrow(); + }); + it("should support PATCH /legalEntities/{id}", async (): Promise => { scope.patch(`/legalEntities/${id}`) .reply(200, legalEntity); diff --git a/src/__tests__/terminalCloudAPI.spec.ts b/src/__tests__/terminalCloudAPI.spec.ts index c4ea15302..ce3851a6a 100644 --- a/src/__tests__/terminalCloudAPI.spec.ts +++ b/src/__tests__/terminalCloudAPI.spec.ts @@ -1,7 +1,7 @@ import nock from "nock"; import { createClient, createTerminalAPIPaymentRequest, createTerminalAPIRefundRequest } from "../__mocks__/base"; import { asyncRes } from "../__mocks__/terminalApi/async"; -import { syncRefund, syncRes, syncResEventNotification } from "../__mocks__/terminalApi/sync"; +import { syncRefund, syncRes, syncResEventNotification, syncResEventNotificationWithAdditionalAttributes, syncResEventNotificationWithUnknownEnum } from "../__mocks__/terminalApi/sync"; import Client from "../client"; import TerminalCloudAPI from "../services/terminalCloudAPI"; import { terminal} from "../typings"; @@ -45,7 +45,20 @@ describe("Terminal Cloud API", (): void => { expect(terminalAPIResponse.SaleToPOIResponse?.MessageHeader).toBeDefined(); }); - test("should return event notification if response contains it", async (): Promise => { + test("should make a sync payment request with additional attributes", async (): Promise => { + scope.post("/sync").reply(200, syncTerminalPaymentResponse); + + const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); + + await expect(async () => { + const terminalAPIResponse = await terminalCloudAPI.sync(terminalAPIPaymentRequest); + expect(terminalAPIResponse.SaleToPOIResponse?.PaymentResponse).toBeDefined(); + expect(terminalAPIResponse.SaleToPOIResponse?.MessageHeader).toBeDefined(); + }).not.toThrow(); + + }); + + test("should return event notification Reject", async (): Promise => { const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); scope.post("/sync").reply(200, syncResEventNotification); @@ -53,6 +66,35 @@ describe("Terminal Cloud API", (): void => { const terminalAPIResponse = await terminalCloudAPI.sync(terminalAPIPaymentRequest); expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification).toBeDefined(); + expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("Reject"); + + }); + + test("should return event notification Shutdown with additional attributes", async (): Promise => { + + const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); + scope.post("/sync").reply(200, syncResEventNotificationWithAdditionalAttributes); + + await expect(async () => { + const terminalAPIResponse = await terminalCloudAPI.sync(terminalAPIPaymentRequest); + expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification).toBeDefined(); + expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("Shutdown"); + expect(terminalAPIResponse.SaleToPOIRequest?.MessageHeader).toBeDefined(); + }).not.toThrow(); + }); + + test("should return event notification with unknown enum", async (): Promise => { + + const terminalAPIPaymentRequest = createTerminalAPIPaymentRequest(); + scope.post("/sync").reply(200, syncResEventNotificationWithUnknownEnum); + + await expect(async () => { + const terminalAPIResponse = await terminalCloudAPI.sync(terminalAPIPaymentRequest); + expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification).toBeDefined(); + // EventToNotify is unknown, so it holds whatever value is found in the payload + expect(terminalAPIResponse.SaleToPOIRequest?.EventNotification?.EventToNotify).toBe("this is unknown"); + + }).not.toThrow(); }); test("should make an async refund request", async (): Promise => { @@ -77,3 +119,338 @@ describe("Terminal Cloud API", (): void => { expect(terminalAPIRefundResponse.SaleToPOIResponse?.ReversalResponse?.Response.Result).toBe("Success"); }, 20000); }); + + +export const syncTerminalPaymentResponse = { + "SaleToPOIResponse": { + "PaymentResponse": { + "POIData": { + "POITransactionID": { + "TimeStamp": "2019-04-29T00:00:00.000Z", + "TransactionID": "4r7i001556529591000.8515565295894301" + }, + "POIReconciliationID": "1000" + }, + "SaleData": { + "SaleTransactionID": { + "TimeStamp": "2019-04-29T00:00:00.000Z", + "TransactionID": "001" + } + }, + "additionalAttribute": "something", + "PaymentReceipt": [ + { + "RequiredSignatureFlag": false, + "DocumentQualifier": "CashierReceipt", + "OutputContent": { + "OutputFormat": "Text", + "OutputText": [ + { + "CharacterStyle": "Bold", + "Text": "key=header1", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "key=header2", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=MERCHANT%20COPY&key=merchantTitle", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Date&value=29%2f04%2f19&key=txdate", + "EndOfLineFlag": true + }, + { + "Text": "name=Time&value=10%3a19%3a51&key=txtime", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Card&value=%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a3511&key=pan", + "EndOfLineFlag": true + }, + { + "Text": "name=Pref.%20name&value=MCC%20351%20v1%202&key=preferredName", + "EndOfLineFlag": true + }, + { + "Text": "name=Card%20type&value=mc&key=cardType", + "EndOfLineFlag": true + }, + { + "Text": "name=Payment%20method&value=mc&key=paymentMethod", + "EndOfLineFlag": true + }, + { + "Text": "name=Payment%20variant&value=mc&key=paymentMethodVariant", + "EndOfLineFlag": true + }, + { + "Text": "name=Entry%20mode&value=Contactless%20swipe&key=posEntryMode", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=AID&value=A0000000041010&key=aid", + "EndOfLineFlag": true + }, + { + "Text": "name=MID&value=1000&key=mid", + "EndOfLineFlag": true + }, + { + "Text": "name=TID&value=P400Plus-275039202&key=tid", + "EndOfLineFlag": true + }, + { + "Text": "name=PTID&value=75039202&key=ptid", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Auth.%20code&value=123456&key=authCode", + "EndOfLineFlag": true + }, + { + "Text": "name=Tender&value=4r7i001556529591000&key=txRef", + "EndOfLineFlag": true + }, + { + "Text": "name=Reference&value=003&key=mref", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Type&value=GOODS_SERVICES&key=txtype", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=TOTAL&value=%e2%82%ac%c2%a01.00&key=totalAmount", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=APPROVED&key=approved", + "EndOfLineFlag": true + } + ] + } + }, + { + "RequiredSignatureFlag": false, + "DocumentQualifier": "CustomerReceipt", + "OutputContent": { + "OutputFormat": "Text", + "OutputText": [ + { + "CharacterStyle": "Bold", + "Text": "key=header1", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "key=header2", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=CARDHOLDER%20COPY&key=cardholderHeader", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Date&value=29%2f04%2f19&key=txdate", + "EndOfLineFlag": true + }, + { + "Text": "name=Time&value=10%3a19%3a51&key=txtime", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Card&value=%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a%2a3511&key=pan", + "EndOfLineFlag": true + }, + { + "Text": "name=Pref.%20name&value=MCC%20351%20v1%202&key=preferredName", + "EndOfLineFlag": true + }, + { + "Text": "name=Card%20type&value=mc&key=cardType", + "EndOfLineFlag": true + }, + { + "Text": "name=Payment%20method&value=mc&key=paymentMethod", + "EndOfLineFlag": true + }, + { + "Text": "name=Payment%20variant&value=mc&key=paymentMethodVariant", + "EndOfLineFlag": true + }, + { + "Text": "name=Entry%20mode&value=Contactless%20swipe&key=posEntryMode", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=AID&value=A0000000041010&key=aid", + "EndOfLineFlag": true + }, + { + "Text": "name=MID&value=1000&key=mid", + "EndOfLineFlag": true + }, + { + "Text": "name=TID&value=P400Plus-275039202&key=tid", + "EndOfLineFlag": true + }, + { + "Text": "name=PTID&value=75039202&key=ptid", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Auth.%20code&value=123456&key=authCode", + "EndOfLineFlag": true + }, + { + "Text": "name=Tender&value=4r7i001556529591000&key=txRef", + "EndOfLineFlag": true + }, + { + "Text": "name=Reference&value=003&key=mref", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Type&value=GOODS_SERVICES&key=txtype", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=TOTAL&value=%e2%82%ac%c2%a01.00&key=totalAmount", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "CharacterStyle": "Bold", + "Text": "name=APPROVED&key=approved", + "EndOfLineFlag": true + }, + { + "Text": "key=filler", + "EndOfLineFlag": true + }, + { + "Text": "name=Please%20retain%20for%20your%20records&key=retain", + "EndOfLineFlag": true + }, + { + "Text": "name=Thank%20you&key=thanks", + "EndOfLineFlag": true + } + ] + } + } + ], + "PaymentResult": { + "OnlineFlag": true, + "PaymentAcquirerData": { + "AcquirerPOIID": "P400Plus-123456789", + "ApprovalCode": "123456", + "AcquirerTransactionID": { + "TimeStamp": "2019-04-29T09:19:51.000Z", + "TransactionID": "8515565295894301" + }, + "MerchantID": "TestMerchant" + }, + "CurrencyConversion": [ + { + "ConvertedAmount": { + "AmountValue": 48.32, + "Currency": "EUR" + }, + "CustomerApprovedFlag": true, + "Markup": 3, + "Rate": 0.035 + } + ], + "PaymentInstrumentData": { + "CardData": { + "EntryMode": [ + "Tapped" + ], + "PaymentBrand": "mc", + "MaskedPan": "411111 **** 1111", + "SensitiveCardData": { + "ExpiryDate": "1225" + } + }, + "PaymentInstrumentType": "Card" + }, + "AmountsResp": { + "AuthorizedAmount": 1, + "Currency": "EUR" + } + }, + "Response": { + "Result": "Success", + "AdditionalResponse": "tid=75039202&AID=A0000000041010&transactionType=GOODS_SERVICES&backendGiftcardIndicator=false&expiryYear=2025&acquirerAccountCode=TestPmmAcquirerAccount&alias=M900978995070104&posOriginalAmountCurrency=EUR&giftcardIndicator=false&authorisedAmountValue=100&pspReference=8515565295894301&paymentMethodVariant=mc&cardHolderName=N%2fA&refusalReasonRaw=APPROVED&authorisationMid=1000&expiryDate=12%2f2025&applicationPreferredName=MCC%20351%20v1%202&acquirerCode=TestPmmAcquirer&txtime=10%3a19%3a51&iso8601TxDate=2019-04-29T09%3a19%3a51.0000000%2b0000&cardType=mc&posOriginalAmountValue=100&offline=false&aliasType=Default&txdate=29-04-2019&paymentMethod=mc&cvcResult=0%20Unknown&avsResult=0%20Unknown&mid=1000&merchantReference=003&transactionReferenceNumber=8515565295894301&expiryMonth=12&cardSummary=3511&posTotalAmountValue=100&posAuthAmountCurrency=EUR&cardHolderVerificationMethodResults=3F0300&authCode=123456&shopperCountry=NL&posEntryMode=CLESS_SWIPE&cardScheme=mc&cardBin=541333&posAuthAmountValue=100" + } + }, + "MessageHeader": { + "ProtocolVersion": "3.0", + "SaleID": "001", + "MessageClass": "Service", + "MessageCategory": "Payment", + "ServiceID": "1234567890", + "POIID": "P400Plus-123456789", + "MessageType": "Response", + "additionalAttribute": "somethingElse", + } + } +} + diff --git a/src/__tests__/webhooks/bankingWebhooks.spec.ts b/src/__tests__/webhooks/bankingWebhooks.spec.ts index 08615d6d5..d48fa605d 100644 --- a/src/__tests__/webhooks/bankingWebhooks.spec.ts +++ b/src/__tests__/webhooks/bankingWebhooks.spec.ts @@ -53,6 +53,56 @@ describe("BankingWebhooks Tests", function (): void { expect(genericWebhook instanceof AccountHolderNotificationRequest).toBe(true); }); + it("should not throw when deserializing AccountHolderNotification webhook with additional attributes", function (): void { + const json = { + "data": { + "balancePlatform": "YOUR_BALANCE_PLATFORM", + "additionalAttribute": "something", + "accountHolder": { + "contactDetails": { + "address": { + "country": "NL", + "houseNumberOrName": "274", + "postalCode": "1020CD", + "street": "Brannan Street" + }, "email": "s.hopper@example.com", "phone": {"number": "+315551231234", "type": "Mobile"} + }, "description": "S.Hopper - Staff 123", "id": "AH00000000000000000000001", "status": "Active" + } + }, "environment": "test", "type": "balancePlatform.accountHolder.created", + "additionalAttribute": "something else" + }; + const jsonString = JSON.stringify(json); + expect(() => { + const configurationWebhooksHandler = new ConfigurationWebhooksHandler(jsonString); + const accountHolderNotificationRequest = configurationWebhooksHandler.getAccountHolderNotificationRequest(); + expect(accountHolderNotificationRequest.environment).toEqual("test"); + }).not.toThrow(); + }); + + it("should not throw when deserializing AccountHolderNotification webhook with unknown enum", function (): void { + const json = { + "data": { + "balancePlatform": "YOUR_BALANCE_PLATFORM", + "accountHolder": { + "contactDetails": { + "address": { + "country": "NL", + "houseNumberOrName": "274", + "postalCode": "1020CD", + "street": "Brannan Street" + }, "email": "s.hopper@example.com", "phone": {"number": "+315551231234", "type": "Mobile"} + }, "description": "S.Hopper - Staff 123", "id": "AH00000000000000000000001", "status": "This is unknown" + } + }, "environment": "test", "type": "balancePlatform.accountHolder.created" + }; + const jsonString = JSON.stringify(json); + expect(() => { + const configurationWebhooksHandler = new ConfigurationWebhooksHandler(jsonString); + const accountHolderNotificationRequest = configurationWebhooksHandler.getAccountHolderNotificationRequest(); + expect(accountHolderNotificationRequest.environment).toEqual("test"); + }).not.toThrow(); + }); + it("should deserialize BalanceAccountNotification Webhook", function (): void { const json = { "data": {