diff --git a/.env.template b/.env.template index 034abb4..8e4053a 100755 --- a/.env.template +++ b/.env.template @@ -2,13 +2,19 @@ FUNCTION_NAME=payment_platfrom_adapter PORT=3000 TMS_ENDPOINT='http://openfaas:8080/function/off-frm-tms-service.frm/monitor/transaction' -# ELASTIC APM -APM_LOGGING=true -APM_SERVICE_NAME=payment-platfrom-adapter -APM_URL=127.0.0.1:4857 -APM_SECRET_TOKEN=secrettokenhere +TMS_PAIN001_ENDPOINT=https://openfaas:8080/function/off-transaction-monitoring-service-rel-1-0-0/execute +TMS_PAIN013_ENDPOINT=https://openfaas:8080/function/off-transaction-monitoring-service-rel-1-0-0/quoteReply +TMS_PACS002_ENDPOINT=https://openfaas:8080/function/off-transaction-monitoring-service-rel-1-0-0/transfer-response +TMS_PACS008_ENDPOINT=https://openfaas:8080/function/off-transaction-monitoring-service-rel-1-0-0/transfer -NODE_ENV=dev +# kafka +KAFKA_URI=127.0.0.1:9092 +KAFKA_CLIENT_ID=example-producer +KAFKA_CONSUMER_GROUP=test-group +KAFKA_TOPIC_TO_CONSUME=test-topic -LOGSTASH_HOST=127.0.0.1 -LOGSTASH_PORT=8080 \ No newline at end of file +# redis +REDIS_URL=127.0.0.1 +REDIS_PORT=6379 + +NODE_ENV=dev \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c58d8f6..fd05ccf 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,17 @@ -version: "2" +version: "3" services: - redis: + payment-platform-adapter: + build: + context: . + dockerfile: Dockerfile + image: ppa + container_name: ppa + restart: unless-stopped + env_file: .env + depends_on: + - ppa-redis + ppa-redis: image: redis + container_name: ppa-redis ports: - - "6379:6379" \ No newline at end of file + - "6379:6379" diff --git a/dockerfile b/dockerfile index 2ff92ea..26d6882 100755 --- a/dockerfile +++ b/dockerfile @@ -11,7 +11,7 @@ RUN addgroup -S app && adduser -S -g app app RUN apk --no-cache add curl ca-certificates -RUN apk add --no-cache -t build-dependencies git make gcc g++ python libtool autoconf automake yarn +RUN apk add --no-cache -t build-dependencies git make gcc g++ python3 libtool autoconf automake yarn # Turn down the verbosity to default level. ENV NPM_CONFIG_LOGLEVEL warn @@ -55,14 +55,20 @@ ENV prefix_logs="false" ENV FUNCTION_NAME=payment-platfrom-adapter ENV PORT=3000 -ENV TMS_ENDPOINT=http://gateway.frm:8080/function/off-transaction-monitoring-service.frm-meshed/execute -ENV APM_LOGGING=true -ENV APM_SERVICE_NAME=payment-platfrom-adapter -ENV APM_URL=http://apm-server-apm-server.frm:8200 -ENV APM_SECRET_TOKEN= + +ENV TMS_ENDPOINT=TMS_ENDPOINT +ENV TMS_PAIN001_ENDPOINT=TMS_PAIN001_ENDPOINT +ENV TMS_PAIN013_ENDPOINT=TMS_PAIN013_ENDPOINT +ENV TMS_PACS002_ENDPOINT=TMS_PACS002_ENDPOINT +ENV TMS_PACS008_ENDPOINT=TMS_PACS008_ENDPOINT + +ENV KAFKA_URI=KAFKA_URI +ENV KAFKA_CLIENT_ID=KAFKA_CLIENT_ID +ENV KAFKA_CONSUMER_GROUP=KAFKA_CONSUMER_GROUP +ENV KAFKA_TOPIC_TO_CONSUME=KAFKA_TOPIC_TO_CONSUME +ENV REDIS_URL=REDIS_URL +ENV REDIS_PORT=REDIS_PORT ENV NODE_ENV=dev -ENV LOGSTASH_HOST=my-release-logstash.frm-meshed -ENV LOGSTASH_PORT=8080 HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1 diff --git a/jest.config.ts b/jest.config.ts index ab31835..a44b62d 100755 --- a/jest.config.ts +++ b/jest.config.ts @@ -171,6 +171,8 @@ export default { // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" // timers: "real", + testTimeout: 30000, + // A map from regular expressions to paths to transformers // transform: undefined, diff --git a/package.json b/package.json index dc710cf..900a5ec 100755 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "payment-platform-adapter", - "version": "0.1.0", + "version": "0.2.0", "description": "payment-platform-adapter", "main": "build/index.js", "scripts": { "dev": "nodemon", "build": "tsc", "start": "node build/index.js", - "cleanup": "rm -rf build template jest.config.js jest.config.js.map node_modules", + "cleanup": "rm -rf build template jest.config.js jest.config.js.map", + "cleanupAll": "rm -rf build template jest.config.js jest.config.js.map node_modules", "fix": "yarn fix:prettier && yarn fix:eslint", "fix:eslint": "eslint --fix \"**/*.ts\"", "fix:prettier": "prettier --write \"**/*.ts\"", @@ -31,17 +32,19 @@ { "name": "Joey Goksu", "email": "me@joeygoksu.com" + }, + { + "name": "Len Bekker", + "email": "Len.Bekker@sybrin.com" } ], "license": "ISC", "dependencies": { - "@log4js-node/logstash-http": "^1.1.0", - "@log4js-node/logstashudp": "^1.2.1", "@types/uuid": "^8.3.1", "arangojs": "^7.5.0", "axios": "^0.21.1", "dotenv": "8.2.0", - "elastic-apm-node": "^3.14.0", + "kafkajs": "^1.15.0", "koa": "^2.13.1", "koa-bodyparser": "^4.3.0", "koa-router": "^10.0.0", diff --git a/src/config.ts b/src/config.ts index 02fe9af..ad740a0 100755 --- a/src/config.ts +++ b/src/config.ts @@ -13,11 +13,15 @@ export const configuration: IConfig = { functionName: process.env.FUNCTION_NAME, port: parseInt(process.env.PORT!, 10), tmsEndpoint: process.env.TMS_ENDPOINT, - apmLogging: (process.env.APM_LOGGING === 'true'), - apmServiceName: process.env.APM_SERVICE_NAME, - apmSecretToken: process.env.APM_SECRET_TOKEN, - apmURL: process.env.APM_URL, + tmsPain001Endpoint: process.env.TMS_PAIN001_ENDPOINT, + tmsPain013Endpoint: process.env.TMS_PAIN013_ENDPOINT, + tmsPacs002Endpoint: process.env.TMS_PACS002_ENDPOINT, + tmsPacs008Endpoint: process.env.TMS_PACS008_ENDPOINT, + kafkaURI: process.env.KAFKA_URI, + kafkaClientId: process.env.KAFKA_CLIENT_ID, + kafkaConsumerGroup: process.env.KAFKA_CONSUMER_GROUP, + kafkaTopic: process.env.KAFKA_TOPIC_TO_CONSUME, + redisURL: process.env.REDIS_URL, + redisPort: parseInt(process.env.REDIS_PORT!, 10), dev: process.env.NODE_ENV, - logstashHost: process.env.LOGSTASH_HOST, - logstashPort: parseInt(process.env.LOGSTASH_PORT!, 10), }; diff --git a/src/constants/event-types.ts b/src/constants/event-types.ts new file mode 100644 index 0000000..7715833 --- /dev/null +++ b/src/constants/event-types.ts @@ -0,0 +1,7 @@ +export const eventType = { + AUDIT: 'audit', + UNSUPPORTED: 'Unsupported', + QUOTE: 'Quote', + TRANSFER: 'Transfer', + SETTLEMENT: 'Settlement', +}; diff --git a/src/constants/quote-constants.ts b/src/constants/quote-constants.ts new file mode 100644 index 0000000..6f8538d --- /dev/null +++ b/src/constants/quote-constants.ts @@ -0,0 +1,26 @@ +export const quotesConstants = [ + 'qs_quote_handleQuoteRequest', + 'qs_quote_forwardQuoteRequest', + 'qs_quote_forwardQuoteRequestResend', + 'qs_quote_handleQuoteUpdate', + 'qs_quote_forwardQuoteUpdate', + 'qs_quote_forwardQuoteUpdateResend', + 'qs_quote_handleQuoteError', + 'qs_quote_forwardQuoteGet', + 'qs_quote_sendErrorCallback', + + 'qs_bulkquote_forwardBulkQuoteRequest', + 'qs_quote_forwardBulkQuoteUpdate', + 'qs_quote_forwardBulkQuoteGet', + 'qs_quote_forwardBulkQuoteError', + 'qs_bulkQuote_sendErrorCallback', + + 'QuotesErrorByIDPut', + 'QuotesByIdGet', + 'QuotesByIdPut', + 'QuotesPost', + 'BulkQuotesErrorByIdPut', + 'BulkQuotesByIdGet', + 'BulkQuotesByIdPut', + 'BulkQuotesPost', +]; diff --git a/src/constants/transfer-constants.ts b/src/constants/transfer-constants.ts new file mode 100644 index 0000000..98f22ea --- /dev/null +++ b/src/constants/transfer-constants.ts @@ -0,0 +1,7 @@ +export const transferConstants = [ + 'ml_transfer_prepare', + 'ml_transfer_fulfil', + 'ml_transfer_abort', + 'ml_transfer_getById', + 'ml_notification_event', +]; diff --git a/src/controllers/misc.test.ts b/src/controllers/misc.test.ts index 580a63a..ce62646 100644 --- a/src/controllers/misc.test.ts +++ b/src/controllers/misc.test.ts @@ -1,77 +1,373 @@ -import axios from 'axios'; import { Context } from 'koa'; -import { configuration } from '../config'; -import { CustomerCreditTransferInitiation } from '../interfaces/iPain001Transaction'; -import { healthcheck, monitorTransaction } from './misc'; +import { Pacs002 } from '../interfaces/kafka/iPacs002Transfer'; +import { Pacs008 } from '../interfaces/kafka/iPacs008Transfer'; +import { Pain001 } from '../interfaces/kafka/iPain001Quote'; +import { Pain013 } from '../interfaces/kafka/iPain013Quote'; +import { + healthcheck, + sendPacs002, + sendPacs008, + sendPain001, + sendPain013, +} from './misc'; -describe('test misc functions', () => { - let axioPostSpy: jest.SpyInstance; +describe('test misc pain and pacs', () => { beforeEach(() => { - axioPostSpy = jest.spyOn(axios, 'post').mockImplementation(); + jest.resetAllMocks(); }); - test('should healtcheck return UP', () => { - const ctx = { - body: { - status: 'DOWN', + test('should sendPain001 for PAYER result is VALID', async () => { + const payload: Pain001 = { + TxTp: 'pain.001.001.11', + CstmrCdtTrfInitn: { + GrpHdr: { + MsgId: '6a349c386d54-4bdd-b9c2-4fc04ed62b1a', + CreDtTm: '2022-06-05T18:54:49.620Z', + NbOfTxs: 1, + InitgPty: { + Nm: 'Firstname-Test Lastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + }, + PmtInf: { + PmtInfId: '0a3e0da076c9-4e27-8f22-0c6c5382b6f9', + PmtMtd: 'TRA', + ReqdAdvcTp: { + DbtAdvc: { Cd: 'ADWD', Prtry: 'Advice with transaction details' }, + }, + ReqdExctnDt: { Dt: '2022-06-05', DtTm: '2022-06-05T18:54:49.620Z' }, + Dbtr: { + Nm: 'Firstname-TestLastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + DbtrAcct: { + Id: { Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'Firstname-Test Lastname-Test', + }, + DbtrAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'testfsp1' } } }, + CdtTrfTxInf: { + PmtId: { EndToEndId: '2f16297c-6eee-41ac-92c7-645e591637c7' }, + PmtTpInf: { CtgyPurp: { Prtry: 'TRANSFER' } }, + Amt: { + InstdAmt: { Amt: { Amt: 60, Ccy: 'USD' } }, + EqvtAmt: { Amt: { Amt: 60, Ccy: 'USD' }, CcyOfTrf: 'USD' }, + }, + ChrgBr: 'DEBT', + CdtrAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'testfsp2' } } }, + Cdtr: { + Nm: 'undefined undefined undefined', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '2022-06-05', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '17039811904' }, + }, + CdtrAcct: { + Id: { Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'undefined undefined', + }, + Purp: { Cd: 'MP2P' }, + RgltryRptg: { Dtls: { Cd: '100', Tp: 'BALANCE OF PAYMENTS' } }, + RmtInf: { Ustrd: 'test' }, + SplmtryData: { + Envlp: { + Doc: { + Dbtr: { + FrstNm: 'Firstname-Test', + MddlNm: '', + LastNm: 'Lastname-Test', + MrchntClssfctnCd: '', + }, + Cdtr: { + FrstNm: 'Firstname-Test', + MddlNm: '', + LastNm: 'Lastname-Test', + MrchntClssfctnCd: '', + }, + DbtrFinSvcsPrvdrFees: { Amt: 0, Ccy: 'USD' }, + Xprtn: '', + }, + }, + }, + }, + }, + SplmtryData: { + Envlp: { + Doc: { + InitgPty: { InitrTp: 'CONSUMER', Glctn: { Lat: '', Long: '' } }, + }, + }, + }, }, }; - - const ctxTest = healthcheck(ctx as Context); - - expect(ctxTest.body).toMatchObject({ - status: 'UP', - }); + const result = await sendPain001(payload); + expect(result.status.toLocaleString()).toMatch('200'); + expect(result.data).toBeDefined(); + expect(result.data.message).toBe('Transaction is valid'); }); - test('should monitorTransaction for PAYEE result is VALID', async () => { - const mlTran = JSON.parse( - '{ "quoteId": "ABC123", "transactionId": "asdf1234", "transactionRequestId": "string", "payee": { "partyIdInfo": { "partyIdType": "MSISDN", "partyIdentifier": "+27723748019", "partySubIdOrType": "string", "fspId": "bank1", "extensionList": { "extension": [ { "key": "somekey", "value": "somevalue" } ] } }, "merchantClassificationCode": "merchCode", "name": "string", "personalInfo": { "complexName": { "firstName": "payeefirstName", "middleName": "payeemiddleName", "lastName": "payeelastname" }, "dateOfBirth": "2021-05-28" } }, "payer": { "partyIdInfo": { "partyIdType": "MSISDN", "partyIdentifier": "+27723748020", "partySubIdOrType": "string", "fspId": "string", "extensionList": { "extension": [ { "key": "string", "value": "string" } ] } }, "merchantClassificationCode": "merchCode", "name": "string", "personalInfo": { "complexName": { "firstName": "payerfirstName", "middleName": "payermiddleName", "lastName": "payerlastname" }, "dateOfBirth": "2021-05-28" } }, "amountType": "SEND", "amount": { "currency": "USD", "amount": 123.45 }, "fees": { "currency": "USD", "amount": 12.34 }, "transactionType": { "scenario": "DEPOSIT", "subScenario": "string", "initiator": "PAYEE", "initiatorType": "CONSUMER", "refundInfo": { "originalTransactionId": "string", "refundReason": "string" }, "balanceOfPayments": "string" }, "geoCode": { "latitude": "string", "longitude": "string" }, "note": "string", "expiration": "string", "extensionList": { "extension": [ { "key": "string", "value": "string" } ] }}', - ); - const ctx = { - request: { - body: mlTran, + test('should sendPain013 for PAYER result is VALID', async () => { + const payload: Pain013 = { + TxTp: 'pain.013.001.09', + CdtrPmtActvtnReq: { + GrpHdr: { + MsgId: 'be61c34890ab-4038-81d7-a4ea30e2b498', + CreDtTm: '2022-06-05T18:57:23.219Z', + NbOfTxs: 1, + InitgPty: { + Nm: 'Firstname-Test Lastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + }, + PmtInf: { + PmtInfId: '0a3e0da076c9-4e27-8f22-0c6c5382b6f9', + PmtMtd: 'TRA', + ReqdAdvcTp: { + DbtAdvc: { Cd: 'ADWD', Prtry: 'Advice with transaction details' }, + }, + ReqdExctnDt: { DtTm: '2022-06-05T18:54:49.620Z' }, + XpryDt: { DtTm: '2021-09-03T08:06:02.012Z' }, + Dbtr: { + Nm: 'Firstname-TestLastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + DbtrAcct: { + Id: { Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'Firstname-Test Lastname-Test', + }, + DbtrAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'testfsp1' } } }, + CdtTrfTxInf: { + PmtId: { EndToEndId: '2f16297c-6eee-41ac-92c7-645e591637c7' }, + PmtTpInf: { CtgyPurp: { Prtry: 'TRANSFER' } }, + Amt: { + InstdAmt: { Amt: { Amt: 60.1234, Ccy: 'USD' } }, + EqvtAmt: { Amt: { Amt: 60.1234, Ccy: 'USD' }, CcyOfTrf: 'USD' }, + }, + ChrgBr: 'DEBT', + CdtrAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'testfsp2' } } }, + Cdtr: { + Nm: 'undefined undefined undefined', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '17039811904' }, + }, + CdtrAcct: { + Id: { Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'undefined undefined', + }, + Purp: { Cd: 'MP2P' }, + RgltryRptg: { Dtls: { Cd: '100', Tp: 'BALANCE OF PAYMENTS' } }, + SplmtryData: { + Envlp: { + Doc: { + PyeeRcvAmt: { Amt: { Amt: 60.1234, Ccy: 'USD' } }, + PyeeFinSvcsPrvdrFee: { Amt: { Amt: 3, Ccy: 'USD' } }, + PyeeFinSvcsPrvdrComssn: { Amt: { Amt: 3, Ccy: 'USD' } }, + }, + }, + }, + }, + }, + SplmtryData: { + Envlp: { Doc: { InitgPty: { Glctn: { Lat: '', Long: '' } } } }, + }, }, }; - - configuration.apmLogging = true; - const ctxTest = await monitorTransaction(ctx as Context); - const expected = JSON.stringify( - new CustomerCreditTransferInitiation(mlTran), - ); - - expect(JSON.stringify(ctxTest.body)).toMatch(expected); - axioPostSpy.mockRestore(); + const result: Context = await sendPain013(payload); + expect(result.data).toBeDefined(); + expect(result.status.toLocaleString()).toMatch('200'); + expect(result.data.message).toBe('Transaction is valid'); }); - test('should monitorTransaction for PAYER result is VALID', async () => { - const mlTran = JSON.parse( - '{ "quoteId": "ABC123", "transactionId": "asdf1234", "transactionRequestId": "string", "payee": { "partyIdInfo": { "partyIdType": "MSISDN", "partyIdentifier": "+27723748019", "partySubIdOrType": "string", "fspId": "bank1", "extensionList": { "extension": [ { "key": "somekey", "value": "somevalue" } ] } }, "merchantClassificationCode": "merchCode", "name": "string", "personalInfo": { "complexName": { "firstName": "payeefirstName", "middleName": "payeemiddleName", "lastName": "payeelastname" }, "dateOfBirth": "2021-05-28" } }, "payer": { "partyIdInfo": { "partyIdType": "MSISDN", "partyIdentifier": "+27", "partySubIdOrType": "string", "fspId": "string", "extensionList": { "extension": [ { "key": "string", "value": "string" } ] } }, "merchantClassificationCode": "merchCode", "name": "string", "personalInfo": { "complexName": { "firstName": "payerfirstName", "middleName": "payermiddleName", "lastName": "payerlastname" }, "dateOfBirth": "2021-05-28" } }, "amountType": "SEND", "amount": { "currency": "USD", "amount": 123.45 }, "fees": { "currency": "USD", "amount": 12.34 }, "transactionType": { "scenario": "DEPOSIT", "subScenario": "string", "initiator": "PAYER", "initiatorType": "CONSUMER", "refundInfo": { "originalTransactionId": "string", "refundReason": "string" }, "balanceOfPayments": "string" }, "geoCode": { "latitude": "string", "longitude": "string" }, "note": "string", "expiration": "string", "extensionList": { "extension": [ { "key": "string", "value": "string" } ] }}', - ); - const ctx = { - request: { - body: mlTran, + test('should sendPacs002 for PAYER result is VALID', async () => { + const payload: Pacs002 = { + TxTp: 'pacs.002.001.12', + FIToFIPmtSts: { + GrpHdr: { + MsgId: '0706324256bc43119e292b9a4cbb2d1b', + CreDtTm: '2022-03-01T12:26:35.000Z', + }, + TxInfAndSts: { + OrgnlInstrId: '5ab4fc7355de4ef8a75b78b00a681ed2', + OrgnlEndToEndId: '2c516801007642dfb892944dde1cf845', + TxSts: 'ACCC', + ChrgsInf: [ + { + Amt: { Amt: 307.14, Ccy: 'USD' }, + Agt: { FinInstnId: { ClrSysMmbId: { MmbId: 'dfsp001' } } }, + }, + { + Amt: { Amt: 153.57, Ccy: 'USD' }, + Agt: { FinInstnId: { ClrSysMmbId: { MmbId: 'dfsp001' } } }, + }, + { + Amt: { Amt: 30.71, Ccy: 'USD' }, + Agt: { FinInstnId: { ClrSysMmbId: { MmbId: 'dfsp002' } } }, + }, + ], + AccptncDtTm: '2022-03-01T12:26:34.000Z', + InstgAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'dfsp001' } } }, + InstdAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'dfsp002' } } }, + }, }, }; - configuration.apmLogging = false; - const ctxTest = await monitorTransaction(ctx as Context); - const expected = JSON.stringify( - new CustomerCreditTransferInitiation(mlTran), - ); + const result: Context = await sendPacs002(payload); + expect(result.status.toLocaleString()).toMatch('200'); + expect(result.data).toBeDefined(); + expect(result.data.message).toBe('Transaction is valid'); + }); - expect(JSON.stringify(ctxTest.body)).toMatch(expected); - axioPostSpy.mockRestore(); + test('should sendPacs008 for PAYER result is VALID', async () => { + const payload: Pacs008 = { + TxTp: 'pacs.008.001.10', + FIToFICstmrCdt: { + GrpHdr: { + MsgId: '92661a5729a7-4b22-a3f9-bef6c3210aa3', + CreDtTm: '2022-06-05T18:57:32.901Z', + NbOfTxs: 1, + SttlmInf: { SttlmMtd: 'CLRG' }, + }, + CdtTrfTxInf: { + PmtId: { + InstrId: '0a3e0da076c9-4e27-8f22-0c6c5382b6f9', + EndToEndId: '87d2437a8caa42ba9f7f5d12b809dbb1', + }, + IntrBkSttlmAmt: { Amt: { Amt: 60.1234, Ccy: 'USD' } }, + InstdAmt: { Amt: { Amt: 60.1234, Ccy: 'USD' } }, + ChrgBr: 'DEBT', + ChrgsInf: { + Amt: { Amt: 60.1234, Ccy: 'USD' }, + Agt: { + FinInstnId: { ClrSysMmbId: { MmbId: 'testingtoolkitdfsp' } }, + }, + }, + InitgPty: { + Nm: 'Firstname-Test Lastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CtryOfBirth: 'ZZ', + CityOfBirth: 'Unknown', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + Dbtr: { + Nm: 'Firstname-TestLastname-Test', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + CtryOfBirth: 'ZZ', + }, + Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811903' }, + }, + DbtrAcct: { + Id: { Othr: { Id: '17039811903', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'Firstname-Test Lastname-Test', + }, + DbtrAgt: { + FinInstnId: { ClrSysMmbId: { MmbId: 'testingtoolkitdfsp' } }, + }, + CdtrAgt: { FinInstnId: { ClrSysMmbId: { MmbId: 'payeefsp' } } }, + Cdtr: { + Nm: 'undefined undefined undefined', + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: '1984-01-01', + CityOfBirth: 'Unknown', + CtryOfBirth: 'ZZ', + }, + Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } }, + }, + }, + CtctDtls: { MobNb: '+1-7039811904' }, + }, + CdtrAcct: { + Id: { Othr: { Id: '17039811904', SchmeNm: { Prtry: 'MSISDN' } } }, + Nm: 'undefined undefined', + }, + Purp: { Cd: 'MP2P' }, + }, + RgltryRptg: { Dtls: { Cd: '100', Tp: 'BALANCE OF PAYMENTS' } }, + RmtInf: { Ustrd: '' }, + SplmtryData: { Envlp: { Doc: { Xprtn: '2021-09-02T08:02:12.839Z' } } }, + }, + }; + const result: Context = await sendPacs008(payload); + expect(result.data).toBeDefined(); + expect(result.status.toLocaleString()).toMatch('200'); }); +}); - test('should monitorTransaction result is INVALID', async () => { - const mlTran = JSON.parse('{"Nothing":"Something"}'); +describe('test health functions', () => { + test('should healtcheck return UP', () => { const ctx = { - request: { - body: mlTran, + body: { + status: 'DOWN', }, }; - const ctxTest = await monitorTransaction(ctx as Context); - expect(ctxTest.status.toString()).toMatch('500'); + const ctxTest = healthcheck(ctx as Context); + + expect(ctxTest.body).toMatchObject({ + status: 'UP', + }); }); }); diff --git a/src/controllers/misc.ts b/src/controllers/misc.ts index c23fe93..dc48f74 100755 --- a/src/controllers/misc.ts +++ b/src/controllers/misc.ts @@ -1,70 +1,117 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import axios from 'axios'; import { Context } from 'koa'; import { configuration } from '../config'; import { LoggerService } from '../helpers'; -import { CustomerCreditTransferInitiation } from '../interfaces/iPain001Transaction'; -import { iMLQuote } from '../interfaces/iMLQuote'; -import apm from 'elastic-apm-node'; -import { iMLTransfer } from '../interfaces/iMLTransfer'; -import { FIToFICustomerCreditTransferV10 } from '../interfaces/iPacs008'; +import { Pain001 } from '../interfaces/kafka/iPain001Quote'; +import { Pain013 } from '../interfaces/kafka/iPain013Quote'; +import { Pacs002 } from '../interfaces/kafka/iPacs002Transfer'; +import { Pacs008 } from '../interfaces/kafka/iPacs008Transfer'; -const monitorTransaction = async (ctx: Context): Promise => { - let transaction = {} as iMLQuote; - transaction = Object.assign(transaction, ctx.request.body); - let frmTransaction: CustomerCreditTransferInitiation; +const sendPacs002 = async (payload: Pacs002): Promise => { try { - if (configuration.apmLogging) { - const span = apm.startSpan('Convert ML quote to FRM message'); - frmTransaction = new CustomerCreditTransferInitiation(transaction); - if (span) span.end(); - } else frmTransaction = new CustomerCreditTransferInitiation(transaction); LoggerService.log( - `Converted:\r\n${JSON.stringify( - transaction, - )}\r\ninto:\r\n${JSON.stringify(frmTransaction)}`, + `Sending body\n${JSON.stringify(payload, null, 2)}\n to endpoint\n${ + configuration.tmsPacs002Endpoint + }`, ); const tmsReply = await axios.post( - configuration.tmsEndpoint, - frmTransaction, - ); - LoggerService.log(`Response from TMS Api: ${tmsReply}`); - ctx.body = frmTransaction; - ctx.status = 200; - return ctx; - } catch (err) { - LoggerService.error(err); - ctx.body = { err }; - ctx.status = 500; - return ctx; + configuration.tmsPacs002Endpoint, + payload, + ); + LoggerService.log( + `pacs002 response from TMS Api: ${JSON.stringify( + tmsReply.data?.message ? tmsReply.data.message : tmsReply.data, + null, + 2, + )}`, + ); + return tmsReply; + } catch (err: any) { + LoggerService.error( + `sending pacs002 error occured; event: 'error'; Error: ${err}`, + ); + return err; + } +}; +const sendPacs008 = async (payload: Pacs008): Promise => { + try { + LoggerService.log( + `Sending body\n${JSON.stringify(payload, null, 2)}\n to endpoint\n${ + configuration.tmsPacs008Endpoint + }`, + ); + const tmsReply = await axios.post( + configuration.tmsPacs008Endpoint, + payload, + ); + LoggerService.log( + `pacs008 response from TMS Api: ${ + tmsReply.data?.message + ? tmsReply.data.message + : JSON.stringify(tmsReply.data, null, 2) + }`, + ); + return tmsReply; + } catch (err: any) { + LoggerService.error( + `sending pacs008 error occured; event: 'error'; Error: ${err}`, + ); + return err; } }; -const transfer = async (ctx: Context): Promise => { - let mlTransfer = {} as iMLTransfer; - mlTransfer = Object.assign(mlTransfer, ctx.request.body); - let frmTransfer: FIToFICustomerCreditTransferV10; +const sendPain001 = async (payload: Pain001): Promise => { try { - if (configuration.apmLogging) { - const span = apm.startSpan('Convert ML transfer to FRM message'); - frmTransfer = new FIToFICustomerCreditTransferV10(mlTransfer); - if (span) span.end(); - } else frmTransfer = new FIToFICustomerCreditTransferV10(mlTransfer); + LoggerService.log( + `Sending body\n${JSON.stringify(payload, null, 2)}\n to endpoint\n${ + configuration.tmsPain001Endpoint + }`, + ); + const tmsReply = await axios.post( + configuration.tmsPain001Endpoint, + payload, + ); + LoggerService.log( + `pain001 response from TMS Api: ${ + tmsReply.data?.message + ? tmsReply.data.message + : JSON.stringify(tmsReply.data, null, 2) + }`, + ); + return tmsReply; + } catch (err: any) { + LoggerService.error( + `sending pain001 error occured; event: 'error'; Error: ${err}`, + ); + return err; + } +}; +const sendPain013 = async (payload: Pain013): Promise => { + try { LoggerService.log( - `Converted:\r\n${JSON.stringify(mlTransfer)}\r\ninto:\r\n${JSON.stringify( - frmTransfer, - )}`, + `Sending body\n${JSON.stringify(payload, null, 2)}\n to endpoint\n${ + configuration.tmsPain013Endpoint + }`, + ); + const tmsReply = await axios.post( + configuration.tmsPain013Endpoint, + payload, + ); + LoggerService.log( + `pain013 response from TMS Api: ${ + tmsReply.data?.message + ? tmsReply.data.message + : JSON.stringify(tmsReply.data, null, 2) + }`, + ); + return tmsReply; + } catch (err: any) { + LoggerService.error( + `sending pain013 error occured; event: 'error'; Error: ${err}`, ); - const tmsReply = await axios.post(configuration.tmsEndpoint, frmTransfer); - LoggerService.log(`Response from TMS Api: ${tmsReply}`); - ctx.body = frmTransfer; - ctx.status = 200; - return ctx; - } catch (err) { - LoggerService.error(err); - ctx.body = { err }; - ctx.status = 500; - return ctx; + return err; } }; @@ -77,4 +124,4 @@ const healthcheck = (ctx: Context): Context => { return ctx; }; -export { monitorTransaction, healthcheck, transfer }; +export { sendPain001, sendPain013, sendPacs002, sendPacs008, healthcheck }; diff --git a/src/helpers/events.ts b/src/helpers/events.ts new file mode 100644 index 0000000..1d2cdc7 --- /dev/null +++ b/src/helpers/events.ts @@ -0,0 +1,320 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Kafka } from 'kafkajs'; +import { Logger } from 'log4js'; +import { RedisClient } from 'redis'; +import { configuration } from '../config'; +import { eventType } from '../constants/event-types'; +import { quotesConstants } from '../constants/quote-constants'; +import { transferConstants } from '../constants/transfer-constants'; +import { + sendPacs002, + sendPacs008, + sendPain001, + sendPain013, +} from '../controllers/misc'; +import { Pacs002 } from '../interfaces/kafka/iPacs002Transfer'; +import { Pacs008 } from '../interfaces/kafka/iPacs008Transfer'; +import { Pain001 } from '../interfaces/kafka/iPain001Quote'; +import { Pain013 } from '../interfaces/kafka/iPain013Quote'; +import { LoggerService } from './logger'; +import { + eventToPacs002, + eventToPacs008, + eventToPain001, + eventToPain013, +} from './mapper'; + +export class EventsService { + initialized: boolean; + kafkaClient: any; + redis: any | RedisClient; + + constructor() { + this.initialized = false; + this.kafkaClient = undefined; + this.redis = undefined; + } + + initialize(redis: RedisClient) { + let result = false; + let error; + + this.redis = redis; + const brokerURI = configuration.kafkaURI; + const clientID = configuration.kafkaClientId; + try { + this.kafkaClient = new Kafka({ + brokers: [`${brokerURI}`], + clientId: clientID, + }); + result = true; + } catch (err) { + error = err; + } + + if (!result) { + const message = 'Failed to connect to Kafka Client'; + LoggerService.error(`${message} - ${error}`); + } + + this.initialized = result; + + return result; + } + + async startConsumer(messageHandleFunction: any) { + const consumerGroup = configuration.kafkaConsumerGroup; + const listeningTopic = configuration.kafkaTopic; + + const consumer = this.kafkaClient.consumer({ groupId: consumerGroup }); + + await consumer.connect(); + + await consumer.subscribe({ topic: listeningTopic, fromBeginning: false }); + + await consumer.run({ + eachMessage: async ({ topic, partition, message }: any) => + messageHandleFunction({ topic, partition, message }), + }); + } + + // handler + async messageHandler(args: any) { + const listeningTopic = configuration.kafkaTopic; + + if (args.topic === listeningTopic) { + let msg; + + try { + msg = JSON.parse(args.message.value.toString()); + } catch (error) { + LoggerService.error(`Failed to parse event message\n${error} - ${msg}`); + return; + } + + if (!this.isAudit(msg)) { + return; + } + + const typeResult = this.determineEventType(msg); + + if (typeResult === eventType.UNSUPPORTED) { + return; + } + + try { + const record = await this.processEvent(msg); + + if (record == null) return; + + const TxTp = record.TxTp; + if (TxTp === 'pain.001.001.11') { + (this.redis as RedisClient).set( + `[pain001]${(record as Pain001).CstmrCdtTrfInitn.PmtInf.PmtInfId}`, + JSON.stringify(record).replace('undefined', ''), + 'EX', + 600, + ); + (this.redis as RedisClient).set( + `[pain001]${ + (record as Pain001).CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.PmtId + .EndToEndId + }`, + JSON.stringify(record).replace('undefined', ''), + 'EX', + 600, + ); + } else if (TxTp === 'pain.013.001.09') { + (this.redis as RedisClient).set( + `[pain013]${(record as Pain013).CdtrPmtActvtnReq.PmtInf.PmtInfId}`, + JSON.stringify(record), + 'EX', + 600, + ); + } else if (TxTp === 'pacs.008.001.10') { + (this.redis as RedisClient).set( + `[pacs008]${ + (record as Pacs008).FIToFICstmrCdt.CdtTrfTxInf.PmtId.InstrId + }`, + JSON.stringify(record), + 'EX', + 600, + ); + } else { + (this.redis as RedisClient).set( + `[pacs002]${ + (record as Pacs002).FIToFIPmtSts.TxInfAndSts.OrgnlInstrId + }`, + JSON.stringify(record), + 'EX', + 600, + ); + } + + await this.executeTMS(record); + } catch (error) { + LoggerService.error(`${error}`); + } + } + } + + async executeTMS(record: any) { + const TxTp = record.TxTp; + + const options: any = { + 'pain.001.001.11': "event: 'execute'; sendPain001", + 'pain.013.001.09': "event: 'execute'; sendPain013", + 'pacs.008.001.10': "event: 'execute'; sendPacs008", + 'pacs.002.001.12': "event: 'execute'; sendPacs002", + }; + + LoggerService.log(`${options[TxTp]}`); + + const options2: any = { + 'pain.001.001.11': sendPain001, + 'pain.013.001.09': sendPain013, + 'pacs.008.001.10': sendPacs008, + 'pacs.002.001.12': sendPacs002, + }; + + await options2[TxTp](record); + } + + isAudit(msg: any): boolean { + return msg?.metadata?.event?.type === eventType.AUDIT || false; + } + + determineEventType(msg: any) { + if (msg.metadata?.trace?.service) { + for (const service of quotesConstants) { + if (msg.metadata.trace.service === service) { + return eventType.QUOTE; + } + } + for (const service of transferConstants) { + if (msg.metadata.trace.service === service) { + return eventType.TRANSFER; + } + } + } + return eventType.UNSUPPORTED; + } + + async processEvent( + msg: any, + ): Promise { + // pain001 - ML Quote + if ( + msg.metadata.trace.service === 'qs_quote_forwardQuoteRequest' && + msg.metadata.event.action === 'egress' && // start + msg.metadata.event.type !== 'trace' && + msg.metadata.trace.tags.transactionType === 'quote' + ) { + LoggerService.log("event: 'transformation'; Event to pain001 - ML Quote"); + const data = JSON.parse(msg.content.data); + + return eventToPain001(data); + } + + // pain013 - ML Quote Reply + if ( + msg.metadata.trace.tags.transactionType === 'quote' && + msg.metadata.trace.tags.transactionAction !== 'prepare' && + msg.metadata.trace.service === 'qs_quote_forwardQuoteUpdate' && + msg.metadata.event.action === 'egress' + ) { + LoggerService.log( + "event: 'transformation'; Event to pain013 - ML Quote Reply", + ); + const data = JSON.parse(msg.content.data); + const quoteId = msg.content.url + .toLowerCase() + .slice(msg.content.url.indexOf('quotes/') + 7) + .replace('/', ''); + + const parentQuote = await this.getKey( + `[pain001]${quoteId.replace('-', '')}`, + ); + if (!parentQuote) { + LoggerService.log( + `error: 'processEvent'; [pain001]${quoteId} not found`, + ); + return null; + } + + const pain001: Pain001 = JSON.parse(parentQuote as string); + + return eventToPain013(data, pain001, quoteId); + } + + // pacs008 - ML Transfer + if ( + msg.content.method === 'POST' && + msg.metadata.event.type === 'audit' && + msg.metadata.event.action === 'egress' && + msg.metadata.trace.service === 'ml_notification_event' + ) { + LoggerService.log( + "event: 'transformation'; Event to pacs008 - ML Transfer", + ); + const data = JSON.parse(msg.content.data); + const transactionId = data.transferId; + + const parentTransaction = await this.getKey(`[pain001]${transactionId}`); + if (!parentTransaction) { + LoggerService.log( + `error: 'processEvent'; [pain001]${transactionId} not found`, + ); + return null; + } + + const pain001: Pain001 = JSON.parse(parentTransaction as string); + + return eventToPacs008(data, pain001); + } + + // pacs002 - ML Transfer Reply + if ( + msg.content.method === 'PUT' && + msg.metadata.event.type === 'audit' && + msg.metadata.event.action === 'egress' && + msg.metadata.trace.service === 'ml_notification_event' && + msg.content.data.includes('COMMITTED') + ) { + LoggerService.log( + "event: 'transformation'; Event to pacs002 - ML Transfer Reply", + ); + const data = JSON.parse(msg.content.data); + const transactionId = msg.content.url + .toLowerCase() + .slice(msg.content.url.indexOf('transfers/') + 10) + .replace('/', ''); + + const parentTransaction = await this.getKey(`[pain001]${transactionId}`); + if (!parentTransaction) { + LoggerService.log( + `error: 'processEvent'; [pain001]${transactionId} not found`, + ); + return null; + } + + const pain001: Pain001 = JSON.parse(parentTransaction as string); + + return eventToPacs002(data, pain001, transactionId); + } + + return null; // none matched + } + + private async getKey(key: string) { + return new Promise((resolve) => { + this.redis.get(key, function (err: any, resp: string) { + if (err) { + resolve(null); + } else { + resolve(resp); + } + }); + }); + } +} diff --git a/src/helpers/logger.test.ts b/src/helpers/logger.test.ts index 6a39985..79a0aa5 100644 --- a/src/helpers/logger.test.ts +++ b/src/helpers/logger.test.ts @@ -2,6 +2,7 @@ import { configuration } from '../config'; import { LoggerService } from './logger'; +const logger = LoggerService.getLogger(); const loggerSource = configuration.functionName; const getTestRegex = ( msg: string, @@ -27,18 +28,20 @@ describe('Logger Service', () => { let consoleErrorSpy: jest.SpyInstance; beforeEach(() => { - consoleLoggerSpy = jest.spyOn(console, 'log').mockImplementation(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + consoleLoggerSpy = jest.spyOn(logger, 'info').mockImplementation(); + consoleWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); + consoleErrorSpy = jest.spyOn(logger, 'error').mockImplementation(); }); describe('Log', () => { it('should log a message with defined operation', async () => { LoggerService.isDebugging = true; + const expectedMessage = 'ExpectedLogMessage'; const operation = 'TestService'; await LoggerService.log(expectedMessage, operation); await LoggerService.log(expectedMessage); + expect(consoleLoggerSpy).toHaveBeenCalledTimes(2); consoleLoggerSpy.mockRestore(); @@ -46,7 +49,7 @@ describe('Logger Service', () => { it('should not log a message with defined operation', async () => { LoggerService.isDebugging = false; - const expectedMessage = 'ExpectedLogMessage'; + const expectedMessage = 'UnexpectedLogMessage'; const operation = 'TestService'; await LoggerService.log(expectedMessage, operation); await LoggerService.log(expectedMessage); @@ -63,7 +66,7 @@ describe('Logger Service', () => { const operation = 'TestService'; await LoggerService.warn(expectedMessage, operation); await LoggerService.warn(expectedMessage); - expect(console.warn).toHaveBeenCalledTimes(2); + expect(consoleWarnSpy).toHaveBeenCalledTimes(2); consoleWarnSpy.mockRestore(); }); @@ -74,7 +77,7 @@ describe('Logger Service', () => { const operation = 'TestService'; await LoggerService.warn(expectedMessage, operation); await LoggerService.warn(expectedMessage); - expect(console.warn).toHaveBeenCalledTimes(0); + expect(consoleWarnSpy).toHaveBeenCalledTimes(0); consoleWarnSpy.mockRestore(); }); @@ -84,6 +87,7 @@ describe('Logger Service', () => { it('should log a Error message with defined operation', async () => { const expectedMessage = 'ExpectedWarnMessage'; const operation = 'TestService'; + LoggerService.internalTimestamps = true; await LoggerService.error( expectedMessage, new Error('some error'), @@ -101,7 +105,7 @@ describe('Logger Service', () => { const error = new Error('some error'); error.stack = 'Some stack'; await LoggerService.error(error, new Error('some internal error')); - expect(console.error).toHaveBeenCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/helpers/logger.ts b/src/helpers/logger.ts index f1745a3..1bc9808 100644 --- a/src/helpers/logger.ts +++ b/src/helpers/logger.ts @@ -1,54 +1,36 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, no-logger */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { configuration } from '../config'; import log4js from 'log4js'; -/* - Sample logstash config: - udp { - codec => json - port => 10001 - queue_size => 2 - workers => 2 - type => myAppType - } -*/ - +const layoutType = { type: 'pattern', pattern: '%[[%d]%]%m' }; // { type: 'colored' } log4js.configure({ - appenders: { - logstash: { - type: '@log4js-node/logstash-http', - url: `http://${configuration.logstashHost}:${configuration.logstashPort}/_bulk`, - application: 'logstash-log4js', - logType: 'application', - logChannel: configuration.functionName, - }, - }, - categories: { - default: { appenders: ['logstash'], level: 'info' }, - }, + appenders: { console: { type: 'console', layout: layoutType } }, + categories: { default: { appenders: ['console'], level: 'info' } }, }); -const logger = log4js.getLogger(); - export abstract class LoggerService { private static source = configuration.functionName; + private static logger = log4js.getLogger(); public static isDebugging = configuration.dev === 'dev'; + public static internalTimestamps = layoutType.type !== 'pattern'; private static timeStamp() { + if (!this.internalTimestamps) return ''; const dateObj = new Date(); - let date = dateObj.toISOString(); date = date.substring(0, date.indexOf('T')); - const time = dateObj.toLocaleTimeString([], { hour12: false }); + return `[${date} ${time}]`; + } - return `${date} ${time}`; + static getLogger() { + return this.logger; } static log(message: string, serviceOperation?: string): Promise | any { this.isDebugging && - logger.info( - `[${LoggerService.timeStamp()}][${LoggerService.source}${ + this.logger.info( + `${LoggerService.timeStamp()}[${LoggerService.source}${ serviceOperation ? ' - ' + serviceOperation : '' }][INFO] - ${message}`, ); @@ -56,8 +38,8 @@ export abstract class LoggerService { static warn(message: string, serviceOperation?: string): Promise | any { this.isDebugging && - logger.warn( - `[${LoggerService.timeStamp()}][${LoggerService.source}${ + this.logger.warn( + `${LoggerService.timeStamp()}[${LoggerService.source}${ serviceOperation ? ' - ' + serviceOperation : '' }][WARN] - ${message}`, ); @@ -74,9 +56,8 @@ export abstract class LoggerService { errMessage += `\r\n${innerError.message}`; } - // this.isDebugging && - logger.error( - `[${LoggerService.timeStamp()}][${LoggerService.source}${ + this.logger.error( + `${LoggerService.timeStamp()}[${LoggerService.source}${ serviceOperation ? ' - ' + serviceOperation : '' }][ERROR] - ${errMessage}`, ); diff --git a/src/helpers/mapper.ts b/src/helpers/mapper.ts new file mode 100644 index 0000000..efef0d0 --- /dev/null +++ b/src/helpers/mapper.ts @@ -0,0 +1,736 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types */ +import { v4 } from 'uuid'; +import { Pacs002 } from '../interfaces/kafka/iPacs002Transfer'; +import { Pacs008 } from '../interfaces/kafka/iPacs008Transfer'; +import { Pain001 } from '../interfaces/kafka/iPain001Quote'; +import { Pain013 } from '../interfaces/kafka/iPain013Quote'; +import { toMobileNumber } from './numberConverter'; + +const eventToPain001 = (data: any): Pain001 => { + const dateNow = new Date().toISOString(); + const payerInitgPty = { + Nm: `${data.payer?.personalInfo?.complexName?.firstName ?? ''} ${ + data.payer?.personalInfo?.complexName?.lastName ?? '' + }`, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: data.payer?.personalInfo?.dateOfBirth ?? '0000-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { + Id: data.payer?.partyIdInfo?.partyIdentifier ?? '', + SchmeNm: { + Prtry: data.payer?.partyIdInfo?.partyIdType ?? '', + }, + }, + }, + }, + CtctDtls: { + MobNb: toMobileNumber(data.payer?.partyIdInfo?.partyIdentifier), + }, + }; + + const payeeInitgPty = { + Nm: `${data.payee?.personalInfo?.complexName?.firstName ?? ''} ${ + data.payee?.personalInfo?.complexName?.lastName ?? '' + }`, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: data.payee?.personalInfo?.dateOfBirth ?? '1970-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { + Id: data.payee?.partyIdInfo?.partyIdentifier ?? '', + SchmeNm: { + Prtry: data.payee?.partyIdInfo?.partyIdType ?? '', + }, + }, + }, + }, + CtctDtls: { + MobNb: toMobileNumber(data.payee?.partyIdInfo?.partyIdentifier), + }, + }; + return { + TxTp: 'pain.001.001.11', + CstmrCdtTrfInitn: { + GrpHdr: { + MsgId: v4().replace('-', ''), + CreDtTm: dateNow, + NbOfTxs: 1, + InitgPty: + data.transactionType.initiator === 'PAYER' + ? payerInitgPty + : payeeInitgPty, + }, + PmtInf: { + PmtInfId: data.quoteId.replace('-', ''), + PmtMtd: 'TRA', + ReqdAdvcTp: { + DbtAdvc: { + Cd: 'ADWD', + Prtry: 'Advice with transaction details', + }, + }, + ReqdExctnDt: { + Dt: dateNow.slice(0, dateNow.indexOf('T')), + DtTm: dateNow, + }, + Dbtr: { + Nm: `${data.payer?.personalInfo?.complexName?.firstName ?? ''} ${ + data.payer?.personalInfo?.complexName?.middleName ?? '' + } ${data.payer?.personalInfo?.complexName?.lastName ?? ''}`, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: data.payer.personalInfo.dateOfBirth, + CityOfBirth: 'Unknown', + }, + Othr: { + Id: data.payer.partyIdInfo.partyIdentifier, + SchmeNm: { + Prtry: data.payer.partyIdInfo.partyIdType, + }, + }, + }, + }, + CtctDtls: { + MobNb: toMobileNumber(data.payer.partyIdInfo.partyIdentifier), + }, + }, + DbtrAcct: { + Id: { + Othr: { + Id: data.payer.partyIdInfo.partyIdentifier, + SchmeNm: { + Prtry: data.payer.partyIdInfo.partyIdType, + }, + }, + }, + Nm: `${data.payer.personalInfo.complexName.firstName} ${data.payer.personalInfo.complexName.lastName}`, + }, + DbtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: data.payer.partyIdInfo.fspId, + }, + }, + }, + CdtTrfTxInf: { + PmtId: { + EndToEndId: data.transactionId, + }, + PmtTpInf: { + CtgyPurp: { + Prtry: data.transactionType.scenario, + }, + }, + Amt: { + InstdAmt: { + Amt: { + Amt: Number(data.amount.amount), + Ccy: data.amount.currency, + }, + }, + EqvtAmt: { + Amt: { + Amt: Number(data.amount.amount), + Ccy: data.amount.currency, + }, + CcyOfTrf: data.amount.currency, + }, + }, + ChrgBr: 'DEBT', + CdtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: data.payee.partyIdInfo.fspId, + }, + }, + }, + Cdtr: { + Nm: `${data.payee?.personalInfo?.complexName?.firstName} ${ + data.payee?.personalInfo?.complexName?.middleName ?? '' + } ${data.payee?.personalInfo?.complexName?.lastName}`, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + data.payee?.personalInfo?.dateOfBirth ?? '1970-01-01', + CityOfBirth: 'Unknown', + }, + Othr: { + Id: data.payee?.partyIdInfo?.partyIdentifier, + SchmeNm: { + Prtry: data.payee?.partyIdInfo?.partyIdType, + }, + }, + }, + }, + CtctDtls: { + MobNb: data.payee?.partyIdInfo?.partyIdentifier, + }, + }, + CdtrAcct: { + Id: { + Othr: { + Id: data.payee?.partyIdInfo?.partyIdentifier, + SchmeNm: { + Prtry: data.payee?.partyIdInfo?.partyIdType, + }, + }, + }, + Nm: `${data.payee?.personalInfo?.complexName?.firstName} ${data.payee?.personalInfo?.complexName?.lastName}`, + }, + Purp: { + Cd: 'MP2P', + }, + RgltryRptg: { + Dtls: { + Cd: data.transactionType?.balanceOfPayments || '100', + Tp: 'BALANCE OF PAYMENTS', + }, + }, + RmtInf: { + Ustrd: data.note, + }, + SplmtryData: { + Envlp: { + Doc: { + Dbtr: { + FrstNm: + data.payer?.personalInfo?.complexName?.firstName ?? '', + MddlNm: + data.payer?.personalInfo?.complexName?.middleName ?? '', + LastNm: data.payer?.personalInfo?.complexName?.lastName ?? '', + MrchntClssfctnCd: + data.payer?.merchantClassificationCode || '', + }, + Cdtr: { + FrstNm: + data.payee?.personalInfo?.complexName?.firstName ?? + 'undefined', + MddlNm: + data.payee?.personalInfo?.complexName?.middleName ?? '', + LastNm: + data.payee?.personalInfo?.complexName?.lastName ?? + 'undefined', + MrchntClssfctnCd: + data.payee?.merchantClassificationCode || '', + }, + DbtrFinSvcsPrvdrFees: { + Amt: 0, + Ccy: data.amount?.amount?.currency ?? data.amount.currency, + }, + Xprtn: data.expiration ?? '', + }, + }, + }, + }, + }, + SplmtryData: { + Envlp: { + Doc: { + InitgPty: { + InitrTp: data.transactionType.initiatorType, + Glctn: { + Lat: '', + Long: '', + }, + }, + }, + }, + }, + }, + }; +}; + +const eventToPain013 = ( + data: any, + pain001: Pain001, + quoteId: string, +): Pain013 => { + const dateNow = new Date().toISOString(); + return { + TxTp: 'pain.013.001.09', + CdtrPmtActvtnReq: { + GrpHdr: { + MsgId: v4().replace('-', ''), + CreDtTm: dateNow, + NbOfTxs: 1, + InitgPty: { + Nm: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId + .DtAndPlcOfBirth.BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId + .DtAndPlcOfBirth.CityOfBirth, + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId.Othr + .SchmeNm.Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.CtctDtls.MobNb, + }, + }, + }, + PmtInf: { + PmtInfId: quoteId, + PmtMtd: pain001.CstmrCdtTrfInitn.PmtInf.PmtMtd, + ReqdAdvcTp: { + DbtAdvc: { + Cd: pain001.CstmrCdtTrfInitn.PmtInf.ReqdAdvcTp.DbtAdvc.Cd, + Prtry: pain001.CstmrCdtTrfInitn.PmtInf.ReqdAdvcTp.DbtAdvc.Prtry, + }, + }, + ReqdExctnDt: { + DtTm: pain001.CstmrCdtTrfInitn.PmtInf.ReqdExctnDt.DtTm, + }, + XpryDt: { + DtTm: data.expiration, + }, + Dbtr: { + Nm: pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.DtAndPlcOfBirth + .BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.DtAndPlcOfBirth + .CityOfBirth, + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.Othr.SchmeNm + .Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.CtctDtls.MobNb, + }, + }, + DbtrAcct: { + Id: { + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Id.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Id.Othr.SchmeNm + .Prtry, + }, + }, + }, + Nm: pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Nm, + }, + DbtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: + pain001.CstmrCdtTrfInitn.PmtInf.DbtrAgt.FinInstnId.ClrSysMmbId + .MmbId, + }, + }, + }, + CdtTrfTxInf: { + PmtId: { + EndToEndId: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.PmtId.EndToEndId, + }, + PmtTpInf: { + CtgyPurp: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.PmtTpInf.CtgyPurp + .Prtry, + }, + }, + Amt: { + InstdAmt: { + Amt: { + Amt: data.transferAmount.amount, + Ccy: data.transferAmount.currency, + }, + }, + EqvtAmt: { + Amt: { + Amt: data.transferAmount.amount, + Ccy: data.transferAmount.currency, + }, + CcyOfTrf: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Amt.EqvtAmt + .CcyOfTrf, + }, + }, + ChrgBr: 'DEBT', + CdtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAgt.FinInstnId + .ClrSysMmbId.MmbId, + }, + }, + }, + Cdtr: { + Nm: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .DtAndPlcOfBirth.BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .DtAndPlcOfBirth.CityOfBirth, + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .Othr.SchmeNm.Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.CtctDtls.MobNb, + }, + }, + CdtrAcct: { + Id: { + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Id.Othr + .Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Id.Othr + .SchmeNm.Prtry, + }, + }, + }, + Nm: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Nm, + }, + Purp: { + Cd: 'MP2P', + }, + RgltryRptg: { + Dtls: { + Tp: 'BALANCE OF PAYMENTS', + Cd: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.RgltryRptg.Dtls + .Cd, + }, + }, + SplmtryData: { + Envlp: { + Doc: { + PyeeRcvAmt: { + Amt: { + Amt: data.transferAmount.amount, + Ccy: data.transferAmount.currency, + }, + }, + PyeeFinSvcsPrvdrFee: { + Amt: { + Amt: data.payeeFspCommission?.amount, + Ccy: data.payeeFspCommission?.currency, + }, + }, + PyeeFinSvcsPrvdrComssn: { + Amt: { + Amt: data.payeeFspFee?.amount, + Ccy: data.payeeFspFee?.currency, + }, + }, + }, + }, + }, + }, + }, + SplmtryData: { + Envlp: { + Doc: { + InitgPty: { + Glctn: { + Lat: pain001.CstmrCdtTrfInitn.SplmtryData.Envlp.Doc.InitgPty + .Glctn.Lat, + Long: pain001.CstmrCdtTrfInitn.SplmtryData.Envlp.Doc.InitgPty + .Glctn.Long, + }, + }, + }, + }, + }, + }, + }; +}; + +const eventToPacs008 = (data: any, pain001: Pain001): Pacs008 => { + const dateNow = new Date().toISOString(); + + return { + TxTp: 'pacs.008.001.10', + FIToFICstmrCdt: { + GrpHdr: { + MsgId: v4().replace('-', ''), + CreDtTm: dateNow, + NbOfTxs: 1, + SttlmInf: { + SttlmMtd: 'CLRG', + }, + }, + CdtTrfTxInf: { + PmtId: { + InstrId: pain001.CstmrCdtTrfInitn.PmtInf.PmtInfId, + EndToEndId: data.transferId.replace('-', ''), + }, + IntrBkSttlmAmt: { + Amt: { + Amt: Number(data.amount.amount), + Ccy: data.amount.currency, + }, + }, + InstdAmt: { + Amt: { + Amt: Number(data.amount.amount), + Ccy: data.amount.currency, + }, + }, + ChrgBr: 'DEBT', + ChrgsInf: { + Amt: { + Amt: Number(data.amount.amount), + Ccy: data.amount.currency, + }, + Agt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: data.payerFsp, + }, + }, + }, + }, + InitgPty: { + Nm: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId + .DtAndPlcOfBirth.BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId + .DtAndPlcOfBirth.CityOfBirth, + CtryOfBirth: 'ZZ', + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.Id.PrvtId.Othr + .SchmeNm.Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: pain001.CstmrCdtTrfInitn.GrpHdr.InitgPty.CtctDtls.MobNb, + }, + }, + Dbtr: { + Nm: pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.DtAndPlcOfBirth + .BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.DtAndPlcOfBirth + .CityOfBirth, + CtryOfBirth: 'ZZ', + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.Id.PrvtId.Othr.SchmeNm + .Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: toMobileNumber( + pain001.CstmrCdtTrfInitn.PmtInf.Dbtr.CtctDtls.MobNb, + ), + }, + }, + DbtrAcct: { + Id: { + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Id.Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Id.Othr.SchmeNm + .Prtry, + }, + }, + }, + Nm: pain001.CstmrCdtTrfInitn.PmtInf.DbtrAcct.Nm, + }, + DbtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: data.payerFsp, + }, + }, + }, + CdtrAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: data.payeeFsp, + }, + }, + }, + Cdtr: { + Nm: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Nm, + Id: { + PrvtId: { + DtAndPlcOfBirth: { + BirthDt: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .DtAndPlcOfBirth.BirthDt, + CityOfBirth: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .DtAndPlcOfBirth.CityOfBirth, + CtryOfBirth: 'ZZ', + }, + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .Othr.Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.Id.PrvtId + .Othr.SchmeNm.Prtry, + }, + }, + }, + }, + CtctDtls: { + MobNb: toMobileNumber( + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.Cdtr.CtctDtls.MobNb, + ), + }, + }, + CdtrAcct: { + Id: { + Othr: { + Id: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Id.Othr + .Id, + SchmeNm: { + Prtry: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Id.Othr + .SchmeNm.Prtry, + }, + }, + }, + Nm: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAcct.Nm, + }, + Purp: { + Cd: 'MP2P', + }, + }, + RgltryRptg: { + Dtls: { + Tp: 'BALANCE OF PAYMENTS', + Cd: pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.RgltryRptg.Dtls.Cd, + }, + }, + RmtInf: { + Ustrd: '', + }, + SplmtryData: { + Envlp: { + Doc: { + Xprtn: data.expiration, + }, + }, + }, + }, + }; +}; + +const eventToPacs002 = ( + data: any, + pain001: Pain001, + transactionId: string, +): Pacs002 => { + const dateNow = new Date().toISOString(); + + let TxSts = ''; + if (data.transferState === 'RECEIVED') TxSts = 'RCVD'; + + if (data.transferState === 'RESERVED') TxSts = 'ACSC'; + + if (data.transferState === 'COMMITTED') TxSts = 'ACCC'; + + if (data.transferState === 'ABORTED') TxSts = 'RJCT'; + + return { + TxTp: 'pacs.002.001.12', + FIToFIPmtSts: { + GrpHdr: { + MsgId: v4().replace('-', ''), + CreDtTm: dateNow, + }, + TxInfAndSts: { + OrgnlInstrId: pain001.CstmrCdtTrfInitn.PmtInf.PmtInfId, + OrgnlEndToEndId: transactionId.replace('-', ''), + TxSts: TxSts, + ChrgsInf: [], + AccptncDtTm: data.completedTimestamp, + InstgAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: + pain001.CstmrCdtTrfInitn.PmtInf.DbtrAgt.FinInstnId.ClrSysMmbId + .MmbId, + }, + }, + }, + InstdAgt: { + FinInstnId: { + ClrSysMmbId: { + MmbId: + pain001.CstmrCdtTrfInitn.PmtInf.CdtTrfTxInf.CdtrAgt.FinInstnId + .ClrSysMmbId.MmbId, + }, + }, + }, + }, + }, + }; +}; + +export { eventToPain001, eventToPain013, eventToPacs008, eventToPacs002 }; diff --git a/src/helpers/numberConverter.ts b/src/helpers/numberConverter.ts new file mode 100644 index 0000000..788227d --- /dev/null +++ b/src/helpers/numberConverter.ts @@ -0,0 +1,236 @@ +export function toMobileNumber(num: string): string { + if (!num || num.length < 4) return num; + let toReturn = num + .replace('+', '') + .replace(' ', '') + .replace('(', '') + .replace(')', ''); + for (let index = 0; index < dialingCodes.length; index++) { + const element = dialingCodes[index]; + if (toReturn.startsWith(element)) { + toReturn = `+${element}-${toReturn.substr(element.length)}`; + return toReturn; + } + } + return ''; +} + +const dialingCodes = [ + '1', + '20', + '27', + '211', + '212', + '213', + '216', + '218', + '220', + '221', + '222', + '223', + '224', + '225', + '226', + '227', + '228', + '229', + '230', + '231', + '232', + '233', + '234', + '235', + '236', + '237', + '238', + '239', + '240', + '241', + '242', + '243', + '244', + '245', + '246', + '247', + '248', + '249', + '250', + '251', + '252', + '253', + '254', + '255', + '256', + '257', + '258', + '260', + '261', + '262', + '263', + '264', + '265', + '266', + '267', + '268', + '269', + '290', + '291', + '297', + '298', + '299', + '30', + '31', + '32', + '33', + '34', + '36', + '39', + '350', + '351', + '352', + '353', + '354', + '355', + '356', + '357', + '358', + '359', + '370', + '371', + '372', + '373', + '374', + '375', + '376', + '377', + '378', + '379', + '3801', + '381', + '382', + '383', + '385', + '386', + '387', + '389', + '40', + '41', + '43', + '44', + '45', + '46', + '47', + '48', + '49', + '420', + '421', + '423', + '51', + '52', + '53', + '54', + '55', + '56', + '57', + '58', + '500', + '501', + '502', + '503', + '504', + '505', + '506', + '507', + '508', + '509', + '590', + '591', + '592', + '593', + '594', + '595', + '596', + '597', + '598', + '599', + '60', + '61', + '62', + '63', + '64', + '65', + '66', + '670', + '672', + '673', + '674', + '675', + '676', + '677', + '678', + '679', + '680', + '681', + '682', + '683', + '685', + '686', + '687', + '688', + '689', + '690', + '691', + '692', + '7', + '81', + '82', + '84', + '86', + '800', + '808', + '850', + '852', + '853', + '855', + '856', + '870', + '878', + '880', + '881', + '882', + '883', + '886', + '888', + '91', + '92', + '93', + '94', + '95', + '98', + '960', + '961', + '962', + '963', + '964', + '965', + '966', + '967', + '968', + '970', + '971', + '972', + '973', + '974', + '975', + '976', + '977', + '979', + '991', + '992', + '993', + '994', + '995', + '996', + '997', + '998', +]; diff --git a/src/index.ts b/src/index.ts index 99c1393..7429aa2 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,11 @@ -/* eslint-disable no-console */ -import { configuration } from './config'; +/* eslint-disable no-console, @typescript-eslint/no-explicit-any */ import { Context } from 'koa'; +import log4js from 'log4js'; import App from './app'; -import apm from 'elastic-apm-node'; +import { configuration } from './config'; import { LoggerService } from './helpers'; -import log4js from 'log4js'; - -if (configuration.apmLogging) { - apm.start({ - serviceName: configuration.apmServiceName, - secretToken: configuration.apmSecretToken, - serverUrl: configuration.apmURL, - usePathAsTransactionName: true, - }); -} +import { EventsService } from './helpers/events'; +import { redisClient } from './redis'; const app = new App(); @@ -61,6 +53,18 @@ if ( signals.forEach((signal) => { process.once(signal, () => terminate(signal)); }); + + // Start events service + const eventService = new EventsService(); + const initialized = eventService.initialize(redisClient); + + if (initialized) { + eventService.startConsumer((args: any) => { + eventService.messageHandler(args); + }); + } else { + LoggerService.log("event: 'error'; Could not start up Events Service"); + } } export default app; diff --git a/src/interfaces/iConfig.ts b/src/interfaces/iConfig.ts index f4e7b37..ce7337f 100644 --- a/src/interfaces/iConfig.ts +++ b/src/interfaces/iConfig.ts @@ -2,11 +2,15 @@ export interface IConfig { functionName: string; port: number; tmsEndpoint: string; - apmLogging: boolean; - apmServiceName: string; - apmSecretToken: string; - apmURL: string; + tmsPain001Endpoint: string; + tmsPain013Endpoint: string; + tmsPacs002Endpoint: string; + tmsPacs008Endpoint: string; + kafkaURI: string; + kafkaClientId: string; + kafkaConsumerGroup: string; + kafkaTopic: string; + redisURL: string; + redisPort: number; dev: string; - logstashHost: string; - logstashPort: number; } diff --git a/src/interfaces/iPacs008.ts b/src/interfaces/iPacs008.ts index 96ce847..4dd251e 100644 --- a/src/interfaces/iPacs008.ts +++ b/src/interfaces/iPacs008.ts @@ -124,7 +124,8 @@ export class GroupHeader { export class FIToFICustomerCreditTransferV10 { GroupHeader = new GroupHeader(); - CreditTransferTransactionInformation = new CreditTransferTransactionInformation(); + CreditTransferTransactionInformation = + new CreditTransferTransactionInformation(); constructor(transfer: iMLTransfer) { this.CreditTransferTransactionInformation.PaymentIdentification.EndToEndIdentification = @@ -135,9 +136,8 @@ export class FIToFICustomerCreditTransferV10 { transfer.payeeFsp; this.CreditTransferTransactionInformation.DebtorAgent.FinancialInstitutionIdentification.ClearingSystemMemberIdentification.MemberIdentification = transfer.payerFsp; - this.CreditTransferTransactionInformation.InterbankSettlementAmount.Amount = Number.parseFloat( - transfer.amount.amount, - ); + this.CreditTransferTransactionInformation.InterbankSettlementAmount.Amount = + Number.parseFloat(transfer.amount.amount); this.CreditTransferTransactionInformation.InterbankSettlementAmount.Currency = transfer.amount.currency; // info.ilpPacket missing diff --git a/src/interfaces/iPain001Transaction.ts b/src/interfaces/iPain001Transaction.ts index a88a18c..af0a89f 100644 --- a/src/interfaces/iPain001Transaction.ts +++ b/src/interfaces/iPain001Transaction.ts @@ -268,6 +268,13 @@ class CategoryPurpose { Proprietary = ''; // scenario } +class OtherIdentification { + Identification = ''; // partyIdentifier + SchemeName: SchemeName = new SchemeName(); + // PrivateIdentification: PrivateIdentification = new PrivateIdentification(); + ContactDetails: ContactDetails = new ContactDetails(); +} + class PrivateIdentification { DateAndPlaceOfBirth: DateAndPlaceOfBirth = new DateAndPlaceOfBirth(); Other: OtherIdentification = new OtherIdentification(); @@ -282,13 +289,6 @@ class PaymentTypeInformation { CategoryPurpose: CategoryPurpose = new CategoryPurpose(); } -class OtherIdentification { - Identification = ''; // partyIdentifier - SchemeName: SchemeName = new SchemeName(); - // PrivateIdentification: PrivateIdentification = new PrivateIdentification(); - ContactDetails: ContactDetails = new ContactDetails(); -} - class Identification { Identification = ''; // partyIdefntifier Other: OtherIdentification = new OtherIdentification(); @@ -319,7 +319,8 @@ class ActiveOrHistoricCurrencyAndAmount { } class Amount { - InstructedAmount: ActiveOrHistoricCurrencyAndAmount = new ActiveOrHistoricCurrencyAndAmount(); + InstructedAmount: ActiveOrHistoricCurrencyAndAmount = + new ActiveOrHistoricCurrencyAndAmount(); EquivalentAmount: EquivalentAmount = new EquivalentAmount(); } @@ -335,11 +336,13 @@ class ClearingSystemMemberIdentification { } class FinancialInstitutionIdentification { - ClearingSystemMemberIdentification: ClearingSystemMemberIdentification = new ClearingSystemMemberIdentification(); + ClearingSystemMemberIdentification: ClearingSystemMemberIdentification = + new ClearingSystemMemberIdentification(); } class Agent { - FinancialInstitutionIdentification: FinancialInstitutionIdentification = new FinancialInstitutionIdentification(); + FinancialInstitutionIdentification: FinancialInstitutionIdentification = + new FinancialInstitutionIdentification(); } class SupplementaryData { @@ -351,7 +354,9 @@ class StructuredRemittanceInformation { } class RemittanceInformation { - Structured: StructuredRemittanceInformation = new StructuredRemittanceInformation(); + Structured: StructuredRemittanceInformation = + new StructuredRemittanceInformation(); + Unstructured = ''; } @@ -390,7 +395,9 @@ class DateAndDateTime2Choice { class PaymentInformation { PaymentInformationIdentification = ''; // quoteId - CreditTransferTransactionInformation: CreditTransferTransactionInformation = new CreditTransferTransactionInformation(); + CreditTransferTransactionInformation: CreditTransferTransactionInformation = + new CreditTransferTransactionInformation(); + PaymentMethod = ''; DebtorAccount: Account = new Account(); DebtorAgent: Agent = new Agent(); @@ -431,8 +438,10 @@ export class CustomerCreditTransferInitiation { // PAYEE if (transaction.payee) { if (transaction.payee.partyIdInfo.partyIdType === 'MSISDN') - this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.ContactDetails.MobileNumber = transaction.payee.partyIdInfo.partyIdentifier.toMobileNumber(); - this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.ContactDetails.MobileNumber = transaction.payee.partyIdInfo.partyIdentifier.toMobileNumber(); + this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.ContactDetails.MobileNumber = + transaction.payee.partyIdInfo.partyIdentifier.toMobileNumber(); + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.ContactDetails.MobileNumber = + transaction.payee.partyIdInfo.partyIdentifier.toMobileNumber(); this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.Other.Identification = transaction.payee.partyIdInfo.partyIdentifier; // transaction.payee.partyIdInfo.partySubIdOrType @@ -452,20 +461,21 @@ export class CustomerCreditTransferInitiation { ? ' ' + transaction.payee.personalInfo.complexName.lastName : '' }`; - this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = new Date( - transaction.payee.personalInfo.dateOfBirth, - ) - .toISOString() - .split('T')[0]; + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = + new Date(transaction.payee.personalInfo.dateOfBirth) + .toISOString() + .split('T')[0]; if (transaction.payee.partyIdInfo.partyIdType === 'PERSONAL_ID') { this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary = transaction.payee.partyIdInfo.partySubIdOrType; - this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.Other.SchemeName.Proprietary = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary; + this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.Other.SchemeName.Proprietary = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary; } else { this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary = transaction.payee.partyIdInfo.partyIdType; - this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.Other.SchemeName.Proprietary = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary; + this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.Other.SchemeName.Proprietary = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.Other.SchemeName.Proprietary; } this.PaymentInformation.CreditTransferTransactionInformation.SupplementaryData[ @@ -487,7 +497,8 @@ export class CustomerCreditTransferInitiation { this.PaymentInformation.DebtorAccount.Identification.Other.SchemeName.Proprietary = transaction.payer.partyIdInfo.partyIdType; if (transaction.payer.partyIdInfo.partyIdType === 'MSISDN') - this.PaymentInformation.Debtor.Identification.ContactDetails.MobileNumber = transaction.payer.partyIdInfo.partyIdentifier.toMobileNumber(); + this.PaymentInformation.Debtor.Identification.ContactDetails.MobileNumber = + transaction.payer.partyIdInfo.partyIdentifier.toMobileNumber(); this.PaymentInformation.DebtorAccount.Identification.Other.Identification = transaction.payer.partyIdInfo.partyIdentifier; // transaction.payee.partyIdInfo.partySubIdOrType @@ -508,11 +519,10 @@ export class CustomerCreditTransferInitiation { ? ' ' + transaction.payer.personalInfo.complexName.lastName : '' }`; - this.PaymentInformation.Debtor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = new Date( - transaction.payer.personalInfo.dateOfBirth, - ) - .toISOString() - .split('T')[0]; + this.PaymentInformation.Debtor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = + new Date(transaction.payer.personalInfo.dateOfBirth) + .toISOString() + .split('T')[0]; if (transaction.payer.partyIdInfo.partyIdType === 'PERSONAL_ID') this.PaymentInformation.Debtor.Identification.PrivateIdentification.Other.SchemeName.Proprietary = transaction.payer.partyIdInfo.partySubIdOrType; @@ -538,15 +548,16 @@ export class CustomerCreditTransferInitiation { this.PaymentInformation.CreditTransferTransactionInformation.Amount.EquivalentAmount.CurrencyOfTransfer = transaction.amount.currency; if (transaction.amountType === 'SEND' || !transaction.fees) { - this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount = Number.parseFloat( - transaction.amount.amount, - ); - this.PaymentInformation.CreditTransferTransactionInformation.Amount.EquivalentAmount.Amount = this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount; + this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount = + Number.parseFloat(transaction.amount.amount); + this.PaymentInformation.CreditTransferTransactionInformation.Amount.EquivalentAmount.Amount = + this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount; } else { this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount = Number.parseFloat(transaction.amount.amount) + Number.parseFloat(transaction.fees.amount); - this.PaymentInformation.CreditTransferTransactionInformation.Amount.EquivalentAmount.Amount = this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount; + this.PaymentInformation.CreditTransferTransactionInformation.Amount.EquivalentAmount.Amount = + this.PaymentInformation.CreditTransferTransactionInformation.Amount.InstructedAmount.Amount; } } @@ -568,17 +579,27 @@ export class CustomerCreditTransferInitiation { transaction.transactionType.scenario; // transaction.transactionType.scenario.subScenario if (transaction.transactionType.initiator === 'PAYER') { - this.GroupHeader.InitiatingParty.Identification.ContactDetails.MobileNumber = this.PaymentInformation.Debtor.Identification.ContactDetails.MobileNumber; - this.GroupHeader.InitiatingParty.Name = this.PaymentInformation.Debtor.Name; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = this.PaymentInformation.Debtor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.Identification = this.PaymentInformation.Debtor.Identification.PrivateIdentification.Other.Identification; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.SchemeName.Proprietary = this.PaymentInformation.Debtor.Identification.PrivateIdentification.Other.SchemeName.Proprietary; + this.GroupHeader.InitiatingParty.Identification.ContactDetails.MobileNumber = + this.PaymentInformation.Debtor.Identification.ContactDetails.MobileNumber; + this.GroupHeader.InitiatingParty.Name = + this.PaymentInformation.Debtor.Name; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = + this.PaymentInformation.Debtor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.Identification = + this.PaymentInformation.Debtor.Identification.PrivateIdentification.Other.Identification; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.SchemeName.Proprietary = + this.PaymentInformation.Debtor.Identification.PrivateIdentification.Other.SchemeName.Proprietary; } else { - this.GroupHeader.InitiatingParty.Identification.ContactDetails.MobileNumber = this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.ContactDetails.MobileNumber; - this.GroupHeader.InitiatingParty.Name = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Name; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.Identification = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.Other.Identification; - this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.SchemeName.Proprietary = this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.Other.SchemeName.Proprietary; + this.GroupHeader.InitiatingParty.Identification.ContactDetails.MobileNumber = + this.PaymentInformation.CreditTransferTransactionInformation.CreditorAccount.Identification.ContactDetails.MobileNumber; + this.GroupHeader.InitiatingParty.Name = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Name; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.DateAndPlaceOfBirth.Birthdate; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.Identification = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.Other.Identification; + this.GroupHeader.InitiatingParty.Identification.PrivateIdentification.Other.SchemeName.Proprietary = + this.PaymentInformation.CreditTransferTransactionInformation.Creditor.Identification.PrivateIdentification.Other.SchemeName.Proprietary; } this.SupplementaryData['transactionType.initiatorType'] = transaction.transactionType.initiatorType; diff --git a/src/interfaces/kafka/iPacs002Transfer.ts b/src/interfaces/kafka/iPacs002Transfer.ts new file mode 100644 index 0000000..d7c5b48 --- /dev/null +++ b/src/interfaces/kafka/iPacs002Transfer.ts @@ -0,0 +1,54 @@ +interface GrpHdr { + MsgId: string; + CreDtTm: string; +} + +interface Amt { + Amt: number; + Ccy: string; +} + +interface ClrSysMmbId { + MmbId: string; +} + +interface FinInstnId { + ClrSysMmbId: ClrSysMmbId; +} + +interface Agt { + FinInstnId: FinInstnId; +} + +interface ChrgsInf { + Amt: Amt; + Agt: Agt; +} + +interface InstgAgt { + FinInstnId: FinInstnId; +} + +interface InstdAgt { + FinInstnId: FinInstnId; +} + +interface TxInfAndSts { + OrgnlInstrId: string; + OrgnlEndToEndId: string; + TxSts: string; + ChrgsInf: ChrgsInf[]; + AccptncDtTm: string; + InstgAgt: InstgAgt; + InstdAgt: InstdAgt; +} + +interface FIToFIPmtSts { + GrpHdr: GrpHdr; + TxInfAndSts: TxInfAndSts; +} + +export interface Pacs002 { + TxTp: string; + FIToFIPmtSts: FIToFIPmtSts; +} diff --git a/src/interfaces/kafka/iPacs008Transfer.ts b/src/interfaces/kafka/iPacs008Transfer.ts new file mode 100644 index 0000000..cfbd54e --- /dev/null +++ b/src/interfaces/kafka/iPacs008Transfer.ts @@ -0,0 +1,275 @@ +interface SttlmInf { + SttlmMtd: string; +} + +interface GrpHdr { + MsgId: string; + CreDtTm: string; + NbOfTxs: number; + SttlmInf: SttlmInf; +} + +interface PmtId { + InstrId: string; + EndToEndId: string; +} + +interface Amt { + Amt: number; + Ccy: string; +} + +interface IntrBkSttlmAmt { + Amt: Amt; +} + +interface Amt2 { + Amt: number; + Ccy: string; +} + +interface InstdAmt { + Amt: Amt2; +} + +interface Amt3 { + Amt: number; + Ccy: string; +} + +interface ClrSysMmbId { + MmbId: string; +} + +interface FinInstnId { + ClrSysMmbId: ClrSysMmbId; +} + +interface Agt { + FinInstnId: FinInstnId; +} + +interface ChrgsInf { + Amt: Amt3; + Agt: Agt; +} + +interface DtAndPlcOfBirth { + BirthDt: string; + CityOfBirth: string; + CtryOfBirth: string; +} + +interface SchmeNm { + Prtry: string; +} + +interface Othr { + Id: string; + SchmeNm: SchmeNm; +} + +interface PrvtId { + DtAndPlcOfBirth: DtAndPlcOfBirth; + Othr: Othr; +} + +interface Id { + PrvtId: PrvtId; +} + +interface CtctDtls { + MobNb: string; +} + +interface InitgPty { + Nm: string; + Id: Id; + CtctDtls: CtctDtls; +} + +interface DtAndPlcOfBirth2 { + BirthDt: string; + CityOfBirth: string; + CtryOfBirth: string; +} + +interface SchmeNm2 { + Prtry: string; +} + +interface Othr2 { + Id: string; + SchmeNm: SchmeNm2; +} + +interface PrvtId2 { + DtAndPlcOfBirth: DtAndPlcOfBirth2; + Othr: Othr2; +} + +interface Id2 { + PrvtId: PrvtId2; +} + +interface CtctDtls2 { + MobNb: string; +} + +interface Dbtr { + Nm: string; + Id: Id2; + CtctDtls: CtctDtls2; +} + +interface SchmeNm3 { + Prtry: string; +} + +interface Othr3 { + Id: string; + SchmeNm: SchmeNm3; +} + +interface Id3 { + Othr: Othr3; +} + +interface DbtrAcct { + Id: Id3; + Nm: string; +} + +interface ClrSysMmbId2 { + MmbId: string; +} + +interface FinInstnId2 { + ClrSysMmbId: ClrSysMmbId2; +} + +interface DbtrAgt { + FinInstnId: FinInstnId2; +} + +interface ClrSysMmbId3 { + MmbId: string; +} + +interface FinInstnId3 { + ClrSysMmbId: ClrSysMmbId3; +} + +interface CdtrAgt { + FinInstnId: FinInstnId3; +} + +interface DtAndPlcOfBirth3 { + BirthDt: string; + CityOfBirth: string; + CtryOfBirth: string; +} + +interface SchmeNm4 { + Prtry: string; +} + +interface Othr4 { + Id: string; + SchmeNm: SchmeNm4; +} + +interface PrvtId3 { + DtAndPlcOfBirth: DtAndPlcOfBirth3; + Othr: Othr4; +} + +interface Id4 { + PrvtId: PrvtId3; +} + +interface CtctDtls3 { + MobNb: string; +} + +interface Cdtr { + Nm: string; + Id: Id4; + CtctDtls: CtctDtls3; +} + +interface SchmeNm5 { + Prtry: string; +} + +interface Othr5 { + Id: string; + SchmeNm: SchmeNm5; +} + +interface Id5 { + Othr: Othr5; +} + +interface CdtrAcct { + Id: Id5; + Nm: string; +} + +interface Purp { + Cd: string; +} + +interface CdtTrfTxInf { + PmtId: PmtId; + IntrBkSttlmAmt: IntrBkSttlmAmt; + InstdAmt: InstdAmt; + ChrgBr: string; + ChrgsInf: ChrgsInf; + InitgPty: InitgPty; + Dbtr: Dbtr; + DbtrAcct: DbtrAcct; + DbtrAgt: DbtrAgt; + CdtrAgt: CdtrAgt; + Cdtr: Cdtr; + CdtrAcct: CdtrAcct; + Purp: Purp; +} + +interface Dtls { + Tp: string; + Cd: string; +} + +interface RgltryRptg { + Dtls: Dtls; +} + +interface RmtInf { + Ustrd: string; +} + +interface Doc { + Xprtn: string; +} + +interface Envlp { + Doc: Doc; +} + +interface SplmtryData { + Envlp: Envlp; +} + +interface FIToFICstmrCdt { + GrpHdr: GrpHdr; + CdtTrfTxInf: CdtTrfTxInf; + RgltryRptg: RgltryRptg; + RmtInf: RmtInf; + SplmtryData: SplmtryData; +} + +export interface Pacs008 { + TxTp: string; + FIToFICstmrCdt: FIToFICstmrCdt; +} diff --git a/src/interfaces/kafka/iPain001Quote.ts b/src/interfaces/kafka/iPain001Quote.ts new file mode 100644 index 0000000..0604c45 --- /dev/null +++ b/src/interfaces/kafka/iPain001Quote.ts @@ -0,0 +1,324 @@ +interface DtAndPlcOfBirth { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm { + Prtry: string; +} + +interface Othr { + Id: string; + SchmeNm: SchmeNm; +} + +interface PrvtId { + DtAndPlcOfBirth: DtAndPlcOfBirth; + Othr: Othr; +} + +interface Id { + PrvtId: PrvtId; +} + +interface CtctDtls { + MobNb: string; +} + +interface InitgPty { + Nm: string; + Id: Id; + CtctDtls: CtctDtls; +} + +interface GrpHdr { + MsgId: string; + CreDtTm: string; + NbOfTxs: number; + InitgPty: InitgPty; +} + +interface DbtAdvc { + Cd: string; + Prtry: string; +} + +interface ReqdAdvcTp { + DbtAdvc: DbtAdvc; +} + +interface ReqdExctnDt { + Dt: string; + DtTm: string; +} + +interface DtAndPlcOfBirth2 { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm2 { + Prtry: string; +} + +interface Othr2 { + Id: string; + SchmeNm: SchmeNm2; +} + +interface PrvtId2 { + DtAndPlcOfBirth: DtAndPlcOfBirth2; + Othr: Othr2; +} + +interface Id2 { + PrvtId: PrvtId2; +} + +interface CtctDtls2 { + MobNb: string; +} + +interface Dbtr { + Nm: string; + Id: Id2; + CtctDtls: CtctDtls2; +} + +interface SchmeNm3 { + Prtry: string; +} + +interface Othr3 { + Id: string; + SchmeNm: SchmeNm3; +} + +interface Id3 { + Othr: Othr3; +} + +interface DbtrAcct { + Id: Id3; + Nm: string; +} + +interface ClrSysMmbId { + MmbId: string; +} + +interface FinInstnId { + ClrSysMmbId: ClrSysMmbId; +} + +interface DbtrAgt { + FinInstnId: FinInstnId; +} + +interface PmtId { + EndToEndId: string; +} + +interface CtgyPurp { + Prtry: string; +} + +interface PmtTpInf { + CtgyPurp: CtgyPurp; +} + +interface Amt2 { + Amt: number; + Ccy: string; +} + +interface InstdAmt { + Amt: Amt2; +} + +interface Amt3 { + Amt: number; + Ccy: string; +} + +interface EqvtAmt { + Amt: Amt3; + CcyOfTrf: string; +} + +interface Amt { + InstdAmt: InstdAmt; + EqvtAmt: EqvtAmt; +} + +interface ClrSysMmbId2 { + MmbId: string; +} + +interface FinInstnId2 { + ClrSysMmbId: ClrSysMmbId2; +} + +interface CdtrAgt { + FinInstnId: FinInstnId2; +} + +interface DtAndPlcOfBirth3 { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm4 { + Prtry: string; +} + +interface Othr4 { + Id: string; + SchmeNm: SchmeNm4; +} + +interface PrvtId3 { + DtAndPlcOfBirth: DtAndPlcOfBirth3; + Othr: Othr4; +} + +interface Id4 { + PrvtId: PrvtId3; +} + +interface CtctDtls3 { + MobNb: string; +} + +interface Cdtr { + Nm: string; + Id: Id4; + CtctDtls: CtctDtls3; +} + +interface SchmeNm5 { + Prtry: string; +} + +interface Othr5 { + Id: string; + SchmeNm: SchmeNm5; +} + +interface Id5 { + Othr: Othr5; +} + +interface CdtrAcct { + Id: Id5; + Nm: string; +} + +interface Purp { + Cd: string; +} + +interface Dtls { + Tp: string; + Cd: string; +} + +interface RgltryRptg { + Dtls: Dtls; +} + +interface RmtInf { + Ustrd: string; +} + +interface Dbtr2 { + FrstNm: string; + MddlNm: string; + LastNm: string; + MrchntClssfctnCd: string; +} + +interface Cdtr2 { + FrstNm: string; + MddlNm: string; + LastNm: string; + MrchntClssfctnCd: string; +} + +interface DbtrFinSvcsPrvdrFees { + Ccy: string; + Amt: number; +} + +interface Doc { + Dbtr: Dbtr2; + Cdtr: Cdtr2; + DbtrFinSvcsPrvdrFees: DbtrFinSvcsPrvdrFees; + Xprtn: string; +} + +interface Envlp { + Doc: Doc; +} + +interface SplmtryData { + Envlp: Envlp; +} + +interface CdtTrfTxInf { + PmtId: PmtId; + PmtTpInf: PmtTpInf; + Amt: Amt; + ChrgBr: string; + CdtrAgt: CdtrAgt; + Cdtr: Cdtr; + CdtrAcct: CdtrAcct; + Purp: Purp; + RgltryRptg: RgltryRptg; + RmtInf: RmtInf; + SplmtryData: SplmtryData; +} + +interface PmtInf { + PmtInfId: string; + PmtMtd: string; + ReqdAdvcTp: ReqdAdvcTp; + ReqdExctnDt: ReqdExctnDt; + Dbtr: Dbtr; + DbtrAcct: DbtrAcct; + DbtrAgt: DbtrAgt; + CdtTrfTxInf: CdtTrfTxInf; +} + +interface Glctn { + Lat: string; + Long: string; +} + +interface InitgPty2 { + InitrTp: string; + Glctn: Glctn; +} + +interface Doc2 { + InitgPty: InitgPty2; +} + +interface Envlp2 { + Doc: Doc2; +} + +interface SplmtryData2 { + Envlp: Envlp2; +} + +interface CstmrCdtTrfInitn { + GrpHdr: GrpHdr; + PmtInf: PmtInf; + SplmtryData: SplmtryData2; +} + +export interface Pain001 { + TxTp: string; + CstmrCdtTrfInitn: CstmrCdtTrfInitn; +} diff --git a/src/interfaces/kafka/iPain013Quote.ts b/src/interfaces/kafka/iPain013Quote.ts new file mode 100644 index 0000000..b1c949b --- /dev/null +++ b/src/interfaces/kafka/iPain013Quote.ts @@ -0,0 +1,329 @@ +interface DtAndPlcOfBirth { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm { + Prtry: string; +} + +interface Othr { + Id: string; + SchmeNm: SchmeNm; +} + +interface PrvtId { + DtAndPlcOfBirth: DtAndPlcOfBirth; + Othr: Othr; +} + +interface Id { + PrvtId: PrvtId; +} + +interface CtctDtls { + MobNb: string; +} + +interface InitgPty { + Nm: string; + Id: Id; + CtctDtls: CtctDtls; +} + +interface GrpHdr { + MsgId: string; + CreDtTm: string; + NbOfTxs: number; + InitgPty: InitgPty; +} + +interface DbtAdvc { + Cd: string; + Prtry: string; +} + +interface ReqdAdvcTp { + DbtAdvc: DbtAdvc; +} + +interface ReqdExctnDt { + DtTm: string; +} + +interface XpryDt { + DtTm: string; +} + +interface DtAndPlcOfBirth2 { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm2 { + Prtry: string; +} + +interface Othr2 { + Id: string; + SchmeNm: SchmeNm2; +} + +interface PrvtId2 { + DtAndPlcOfBirth: DtAndPlcOfBirth2; + Othr: Othr2; +} + +interface Id2 { + PrvtId: PrvtId2; +} + +interface CtctDtls2 { + MobNb: string; +} + +interface Dbtr { + Nm: string; + Id: Id2; + CtctDtls: CtctDtls2; +} + +interface SchmeNm3 { + Prtry: string; +} + +interface Othr3 { + Id: string; + SchmeNm: SchmeNm3; +} + +interface Id3 { + Othr: Othr3; +} + +interface DbtrAcct { + Id: Id3; + Nm: string; +} + +interface ClrSysMmbId { + MmbId: string; +} + +interface FinInstnId { + ClrSysMmbId: ClrSysMmbId; +} + +interface DbtrAgt { + FinInstnId: FinInstnId; +} + +interface PmtId { + EndToEndId: string; +} + +interface CtgyPurp { + Prtry: string; +} + +interface PmtTpInf { + CtgyPurp: CtgyPurp; +} + +interface Amt2 { + Amt: number; + Ccy: string; +} + +interface InstdAmt { + Amt: Amt2; +} + +interface Amt3 { + Amt: number; + Ccy: string; +} + +interface EqvtAmt { + Amt: Amt3; + CcyOfTrf: string; +} + +interface Amt { + InstdAmt: InstdAmt; + EqvtAmt: EqvtAmt; +} + +interface ClrSysMmbId2 { + MmbId: string; +} + +interface FinInstnId2 { + ClrSysMmbId: ClrSysMmbId2; +} + +interface CdtrAgt { + FinInstnId: FinInstnId2; +} + +interface DtAndPlcOfBirth3 { + BirthDt: string; + CityOfBirth: string; +} + +interface SchmeNm4 { + Prtry: string; +} + +interface Othr4 { + Id: string; + SchmeNm: SchmeNm4; +} + +interface PrvtId3 { + DtAndPlcOfBirth: DtAndPlcOfBirth3; + Othr: Othr4; +} + +interface Id4 { + PrvtId: PrvtId3; +} + +interface CtctDtls3 { + MobNb: string; +} + +interface Cdtr { + Nm: string; + Id: Id4; + CtctDtls: CtctDtls3; +} + +interface SchmeNm5 { + Prtry: string; +} + +interface Othr5 { + Id: string; + SchmeNm: SchmeNm5; +} + +interface Id5 { + Othr: Othr5; +} + +interface CdtrAcct { + Id: Id5; + Nm: string; +} + +interface Purp { + Cd: string; +} + +interface Dtls { + Tp: string; + Cd: string; +} + +interface RgltryRptg { + Dtls: Dtls; +} + +interface Amt4 { + Amt: number; + Ccy: string; +} + +interface PyeeRcvAmt { + Amt: Amt4; +} + +interface Amt5 { + Amt: number; + Ccy: string; +} + +interface PyeeFinSvcsPrvdrFee { + Amt: Amt5; +} + +interface Amt6 { + Amt: number; + Ccy: string; +} + +interface PyeeFinSvcsPrvdrComssn { + Amt: Amt6; +} + +interface Doc { + PyeeRcvAmt: PyeeRcvAmt; + PyeeFinSvcsPrvdrFee: PyeeFinSvcsPrvdrFee; + PyeeFinSvcsPrvdrComssn: PyeeFinSvcsPrvdrComssn; +} + +interface Envlp { + Doc: Doc; +} + +interface SplmtryData { + Envlp: Envlp; +} + +interface CdtTrfTxInf { + PmtId: PmtId; + PmtTpInf: PmtTpInf; + Amt: Amt; + ChrgBr: string; + CdtrAgt: CdtrAgt; + Cdtr: Cdtr; + CdtrAcct: CdtrAcct; + Purp: Purp; + RgltryRptg: RgltryRptg; + SplmtryData: SplmtryData; +} + +interface PmtInf { + PmtInfId: string; + PmtMtd: string; + ReqdAdvcTp: ReqdAdvcTp; + ReqdExctnDt: ReqdExctnDt; + XpryDt: XpryDt; + Dbtr: Dbtr; + DbtrAcct: DbtrAcct; + DbtrAgt: DbtrAgt; + CdtTrfTxInf: CdtTrfTxInf; +} + +interface Glctn { + Lat: string; + Long: string; +} + +interface InitgPty2 { + Glctn: Glctn; +} + +interface Doc2 { + InitgPty: InitgPty2; +} + +interface Envlp2 { + Doc: Doc2; +} + +interface SplmtryData2 { + Envlp: Envlp2; +} + +interface CdtrPmtActvtnReq { + GrpHdr: GrpHdr; + PmtInf: PmtInf; + SplmtryData: SplmtryData2; +} + +export interface Pain013 { + TxTp: string; + CdtrPmtActvtnReq: CdtrPmtActvtnReq; +} diff --git a/src/interfaces/toMobileNumber.d.ts b/src/interfaces/toMobileNumber.d.ts index 5e61d5a..668a2e1 100644 --- a/src/interfaces/toMobileNumber.d.ts +++ b/src/interfaces/toMobileNumber.d.ts @@ -1,4 +1,3 @@ declare interface String { - toMobileNumber(): string; + toMobileNumber(): string; } - diff --git a/src/mojaloop-api.yaml b/src/mojaloop-api.yaml index d8fac51..7fc5d3d 100644 --- a/src/mojaloop-api.yaml +++ b/src/mojaloop-api.yaml @@ -37,56 +37,6 @@ paths: $ref: '#/definitions/Health' 500: $ref: '#/responses/500' - /execute: - post: - summary: 'Monitor transaction' - description: 'Mojaloop Transation' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Validates request params' - required: true - schema: - $ref: '#/definitions/mojaloopQuotePostRequest' - responses: - 200: - description: 'successful operation' - schema: - $ref: '#/definitions/CustomerCreditTransferInitiationV11' - 400: - $ref: '#/responses/400' - 401: - $ref: '#/responses/401' - 500: - $ref: '#/responses/500' - /transfer: - post: - summary: 'Transfer' - description: 'Mojaloop transfer message interpreter' - consumes: - - 'application/json' - produces: - - 'application/json' - parameters: - - in: 'body' - name: 'body' - description: 'Validates request params' - required: true - responses: - 200: - description: 'successful operation' - schema: - $ref: '#/definitions/Transfer' - 400: - $ref: '#/responses/400' - 401: - $ref: '#/responses/401' - 500: - $ref: '#/responses/500' responses: 400: @@ -111,561 +61,6 @@ definitions: enum: - 'UP' - 'DOWN' - Transfer: - type: 'object' - Transaction: - type: 'object' - required: - - AuthenticationType - properties: - AuthenticationType: - type: string - TransactionType: - type: 'object' - required: - - TransactionScenario - properties: - TransactionScenario: - type: string - TransactionInitiator: - type: string - TransactionInitiatorType: - type: string - partyIdInfo: - type: 'object' - required: - - partyIdType - - partyIdentifier - - fspId - complexName: - required: - - firstName - - lastName - personalInfo: - type: 'object' - required: - - complexName - - dateOfBirth - properties: - complexName: - $ref: '#/definitions/complexName' - dateOfBirth: - type: 'string' - Payer: - type: 'object' - required: - - partyIdInfo - - personalInfo - properties: - partyIdInfo: - $ref: '#/definitions/partyIdInfo' - personalInfo: - $ref: '#/definitions/personalInfo' - Payee: - type: 'object' - required: - - partyIdInfo - properties: - partyIdInfo: - $ref: '#/definitions/partyIdInfo' - amount: - type: 'object' - required: - - amount - - currency - transactionType: - type: 'object' - required: - - scenario - - initiator - - initiatorType - mojaloopQuotePostRequest: - type: 'object' - required: - - quoteId - - transactionId - - payer - - payee - - amountType - - amount - - transactionType - properties: - quoteId: - type: string - transactionId: - type: string - payer: - $ref: '#/definitions/Payer' - payee: - $ref: '#/definitions/Payee' - amountType: - type: string - amount: - $ref: '#/definitions/amount' - transactionType: - $ref: '#/definitions/transactionType' - CustomerCreditTransferInitiationV11: - type: object - properties: - GroupHeader: - $ref: '#/definitions/GroupHeader' - PaymentInformation: - $ref: '#/definitions/PaymentInformation' - SupplementaryData: - $ref: '#/definitions/SupplementaryData' - required: - - GroupHeader - - PaymentInformation - PaymentInformation: - type: object - properties: - PaymentInformationIdentification: - type: string - maxLength: 35 - CreditTransferTransactionInformation: - type: object - properties: - PaymentIdentification: - type: object - properties: - EndToEndIdentification: - type: string - maxLength: 35 - required: - - EndToEndIdentification - CreditorAccount: - type: object - properties: - Identification: - type: object - properties: - Identification: - type: string - Other: - type: object - properties: - Identification: - type: string - maxLength: 34 - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - required: - - Identification - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - Proxy: - type: string - Name: - type: string - CreditorAgent: - type: object - properties: - FinancialInstitutionIdentification: - type: object - properties: - ClearingSystemMemberIdentification: - type: object - properties: - MemberIdentification: - type: string - maxLength: 35 - required: - - MemberIdentification - Creditor: - type: object - properties: - Name: - type: string - maxLength: 140 - Identification: - type: object - properties: - Identification: - type: string - Other: - type: object - properties: - Identification: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - format: date - ProvinceOfBirth: - type: string - CityOfBirth: - type: string - maxLength: 35 - CountryOfBirth: - type: string - pattern: '[A-Z]{2,2}' - required: - - Birthdate - - CityOfBirth - - CountryOfBirth - ContactDetails: - type: object - properties: - MobileNumber: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - Amount: - type: object - properties: - InstructedAmount: - type: object - EquivalentAmount: - type: object - properties: - CurrencyOfTransfer: - type: string - pattern: '[A-Z]{3,3}' - Amount: - type: number - format: float - minimum: 0 - exclusiveMinimum: true - required: - - CurrencyOfTransfer - - Amount - SupplementaryData: - type: object - properties: - fees.currency: - type: string - fees.amount: - type: number - format: float - minimum: 0 - PaymentTypeInformation: - type: object - properties: - CategoryPurpose: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - RegulatoryReporting: - type: object - properties: - Details: - type: object - properties: - Code: - type: string - maxLength: 10 - RemittanceInformation: - type: object - properties: - Structured: - type: object - properties: - AdditionalRemittanceInformation: - type: string - maxLength: 140 - DebtorAccount: - type: object - properties: - Identification: - type: object - properties: - Identification: - type: string - Other: - type: object - properties: - Identification: - type: string - maxLength: 34 - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - required: - - Identification - Proxy: - type: string - Name: - type: string - maxLength: 140 - DebtorAgent: - type: object - properties: - FinancialInstitutionIdentification: - type: object - properties: - ClearingSystemMemberIdentification: - type: object - properties: - MemberIdentification: - type: string - maxLength: 35 - required: - - MemberIdentification - Debtor: - type: object - properties: - Name: - type: string - maxLength: 140 - Identification: - type: object - properties: - Identification: - type: string - Other: - type: object - properties: - Identification: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - format: date - ProvinceOfBirth: - type: string - CityOfBirth: - type: string - maxLength: 35 - CountryOfBirth: - type: string - pattern: '[A-Z]{2,2}' - required: - - Birthdate - - CityOfBirth - - CountryOfBirth - ContactDetails: - type: object - properties: - MobileNumber: - type: string - required: - - PaymentInformationIdentification - - CreditTransferTransactionInformation - - DebtorAccount - - DebtorAgent - - Debtor - SupplementaryData: - type: object - properties: - payee.merchantClassificationCode: - type: string - payer.merchantClassificationCode: - type: string - transactionType.initiatorType: - type: string - geoCode.latitude: - type: string - geoCode.longitude: - type: string - GroupHeader: - type: object - properties: - InitiatingParty: - type: object - properties: - Name: - type: string - maxLength: 140 - Identification: - type: object - properties: - Identification: - type: string - Other: - type: object - properties: - Identification: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - SchemeName: - type: object - properties: - Proprietary: - type: string - maxLength: 35 - required: - - Proprietary - PrivateIdentification: - type: object - properties: - DateAndPlaceOfBirth: - type: object - properties: - Birthdate: - type: string - ContactDetails: - type: object - properties: - MobileNumber: - type: string - pattern: '\+[0-9]{1,3}-[0-9()+\-]{1,30}' Error: type: 'object' @@ -675,6 +70,7 @@ definitions: properties: error: type: 'string' + parameters: accept: in: header diff --git a/src/redis.ts b/src/redis.ts new file mode 100644 index 0000000..d7ca30b --- /dev/null +++ b/src/redis.ts @@ -0,0 +1,19 @@ +import { configuration } from './config'; +import { LoggerService } from './helpers'; +import { createClient } from 'redis'; + +// Connect to redis +export const redisClient = createClient( + configuration.redisPort, + configuration.redisURL, +); + +redisClient.on('ready', () => { + LoggerService.log( + `event: 'execute'; Redis client connected on PORT ${configuration.redisPort}`, + ); +}); + +redisClient.on('error', () => { + LoggerService.log("event: 'error'; Could not connect to Redis"); +}); diff --git a/src/routes/index.ts b/src/routes/index.ts index ceee9f4..8092681 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -4,8 +4,5 @@ const router = new Router(); router.get('/', miscController.healthcheck); router.get('/health', miscController.healthcheck); -// Change this to your liking -router.post('/execute', miscController.monitorTransaction); -router.post('/transfer', miscController.transfer); export default router;