From b47f4ba92212bf34881ddf5a4bc11749cf606c5b Mon Sep 17 00:00:00 2001 From: Lewis Daly Date: Thu, 13 Aug 2020 15:14:27 +0800 Subject: [PATCH] feat(endpoints): Inbound authorizations handler (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(swagger): Add new `POST .../authorizations` to swagger spec * feat(authorizations): Implementing POST authorizations domain methods feat(authorizations): Basic handler tests feat(authorizations): add outbound call tests * fix(authorizations): Parsing of request id * fix(modules): Disable the fancy modules for now * fix(paths): rename thirdpartyRequests dir to thirdpartyrequest * fix(paths): revert to tpr path until we have sorted out swagger issues * chore(linting): Fix linting errors * feat(swagger): Add `POST /thirdpartyRequests/transactions/{ID}/authorizations` to api-template * chore(repo): Refactor repo structure to line up with Sridhar's changes * chore(test): Fixing unit tests * chore(auth): refactor our setimmediate * feat(histogram): Extracted histogram into generic wrapper for handlers * feat(handlers): Update authorizations handler to add span tags * chore(ambient.d.ts): Updated central-services-shared along with definitions * fix(config): Rename `SWITCH_ENDPOINT` to more specific `ENDPOINT_SERVICE_URL` * feat(transactions): Minor logging improvements * clean up template and tests * feature(docs): Update docs about testing * fixing tests and removing logs * feature(tests): Fixing broken unit tests after switch from `setImmediate` * feat(authorization.test.ts): Expanding test coverage * feat(coverage): Improving test coverage for span * test(start.ts): Add tests to start.ts * feat(coverage): Improving test coverage domain * chore(lint): fix linting issues * fix(transactions.test.ts): Fixed invalid test mock value * test(bdd): Add bdd tests - rename `template` bdd test to `health` * chore(docs): Add Runtime configuration section to the Readme * fix(tests): Fix tests and incorporate PR feedback fix(errors): Improve error logging for unhandled promise rejections feat(test): Expanding histogram error handling tests feat(docs): Improve jsdoc for util.ts Clean up test timers Clean up test timers fix(mocks): Improve response toolkit mocking fix(mocks): Improve response toolkit mocking * fix(coverage): Fix missing test coverage * fix(vulns): update audit commands to scan production modules only * fix(ci): Fix ci/cd build-local step * fix(ci): remove docker specific build step * fix(ci): fix sed escape character hell * fix(ambient.d.ts): fix jsdoc optional param references * chore: rename Bill & Melinda Gates Foundation > Copyright © 2020 Mojaloop Foundation * fix(license): header references to old foundation --- LICENSE.md | 4 +- README.md | 21 +- ambient.d.ts | 93 +- config/default.json | 4 +- docs/README.md | 44 +- package-lock.json | 434 ++- package.json | 11 +- src/__mocks__/server.ts | 4 +- src/cli.ts | 4 +- .../thirdpartyRequests/authorizations.ts | 176 ++ src/domain/thirdpartyRequests/index.ts | 8 +- src/domain/thirdpartyRequests/transactions.ts | 53 +- src/index.ts | 4 +- src/interface/api-template.yaml | 105 +- src/interface/api.yaml | 104 +- src/interface/thirdparty-api.yaml | 2394 ----------------- src/interface/types.ts | 4 +- src/server/create.ts | 4 +- src/server/handlers/index.ts | 19 +- src/server/handlers/onPreHandler.ts | 4 +- src/server/handlers/onValidateFail.ts | 4 +- .../thirdpartyRequests/transactions.ts | 34 +- .../transactions/{ID}/authorizations.ts | 89 + src/server/index.ts | 4 +- src/server/plugins/good.ts | 4 +- src/server/run.ts | 4 +- src/server/start.ts | 4 +- src/shared/config.ts | 6 +- src/shared/histogram.ts | 34 + src/shared/inspect.ts | 4 +- src/shared/logger.ts | 4 +- src/shared/util.ts | 23 +- ...enario.feature => health.scenario.feature} | 0 test/features/transactionRequests.feature | 4 + .../{template.step.ts => health.step.ts} | 2 +- .../transactionRequests.step.ts | 63 +- test/unit/__mocks__/responseToolkit.ts | 25 + test/unit/__mocks__/span.ts | 21 + test/unit/__mocks__/util.ts | 4 +- test/unit/cli.test.ts | 6 +- .../thirdpartyRequests/authorizations.test.ts | 218 ++ .../thirdpartyRequests/transactions.test.ts | 42 +- test/unit/index.test.ts | 131 +- .../server/handlers/onValidateFail.test.ts | 4 +- .../thirdpartyRequests/transactions.test.ts | 72 +- .../transactions/{ID}/authorizations.test.ts | 148 + test/unit/server/start.test.ts | 134 + test/unit/shared/histogram.test.ts | 134 + test/unit/shared/inspect.test.ts | 4 +- test/unit/shared/logger.test.ts | 4 +- test/unit/shared/util.test.ts | 36 +- tsconfig.json | 2 +- 52 files changed, 2130 insertions(+), 2632 deletions(-) create mode 100644 src/domain/thirdpartyRequests/authorizations.ts delete mode 100644 src/interface/thirdparty-api.yaml create mode 100644 src/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.ts create mode 100644 src/shared/histogram.ts rename test/features/{template.scenario.feature => health.scenario.feature} (100%) rename test/step-definitions/{template.step.ts => health.step.ts} (94%) create mode 100644 test/unit/__mocks__/responseToolkit.ts create mode 100644 test/unit/__mocks__/span.ts create mode 100644 test/unit/domain/thirdpartyRequests/authorizations.test.ts create mode 100644 test/unit/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.test.ts create mode 100644 test/unit/server/start.test.ts create mode 100644 test/unit/shared/histogram.test.ts diff --git a/LICENSE.md b/LICENSE.md index 26fb7bd..630b7a2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +1,8 @@ # LICENSE -Copyright © 2017 Bill & Melinda Gates Foundation +Copyright © 2020 Mojaloop Foundation -The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 +The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/README.md b/README.md index 8bbf1f6..e565736 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # thirdparty-api-adapter (Work in Progress) [![Git Commit](https://img.shields.io/github/last-commit/mojaloop/thirdparty-api-adapter.svg?style=flat)](https://github.com/mojaloop/thirdparty-api-adapter/commits/master) [![Git Releases](https://img.shields.io/github/release/mojaloop/thirdparty-api-adapter.svg?style=flat)](https://github.com/mojaloop/thirdparty-api-adapter/releases) -[![Npm Version](https://img.shields.io/npm/v/@mojaloop/thirdparty-api-adapter.svg?style=flat)](https://www.npmjs.com/package/@mojaloop/thirdparty-api-adapter) -[![NPM Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/@mojaloop/thirdparty-api-adapter.svg?style=flat)](https://www.npmjs.com/package/@mojaloop/thirdparty-api-adapter) [![CircleCI](https://circleci.com/gh/mojaloop/thirdparty-api-adapter.svg?style=svg)](https://circleci.com/gh/mojaloop/thirdparty-api-adapter) The thirdparty-api-adapter service is used to handle HTTP requests from third parties. @@ -11,6 +9,25 @@ The thirdparty-api-adapter service is used to handle HTTP requests from third pa - [Documentation](./docs/README.md) +## Runtime Configuration + +Runtime configuration is handled by `rc`, and can be specified using either Environment Variables, or a `.json` file. + +See [`./config/default.json`](./config/default.json) for an example config file. + +When setting configuration using environment variables, the `THIRD_PARTY` environment variable prefix is required. See [`src/shared/config.ts`](src/shared/config.ts) to understand how these variables are configured. + +### Key Config Options + +> ***Note:** See [`./config/default.json`](./config/default.json) for all available config options, and their default values.* + +| Name | Env Var | jsonPath | Description | +| ---- | ------- | -------- | ----------- | +| `PORT` | `THIRD_PARTY_PORT` | `.PORT` | The TCP port the Hapi server should start on | +| `HOST` | `THIRD_PARTY_HOST` | `.HOST` | The hostname the Hapi server should bind to | +| `ENDPOINT_SERVICE_URL` | `ENDPOINT_SERVICE_URL` | `.ENDPOINT_SERVICE_URL` | The internal service used to retrieve endpoints for Mojaloop Participants. Currently this is the `central-ledger`. | + + ## Setup ### Clone repo diff --git a/ambient.d.ts b/ambient.d.ts index 7872291..fb0a5bc 100644 --- a/ambient.d.ts +++ b/ambient.d.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -22,9 +22,51 @@ -------------- ******/ + // for mojaloop there is lack for @types files // to stop typescript complains, we have to declare some modules here declare module '@mojaloop/central-services-logger' +declare module '@mojaloop/central-services-metrics' { + import { Histogram } from 'prom-client' + + interface metricOptionsType { + prefix: string + timeout: number + } + interface Metrics { + + /** + * @function getHistogram + * @description Get the histogram values for given name + * @param {string} name - The name of the histogram to get. If the name doesn't exist, it creates a new histogram + * @param {string} [help] - (Optional) Help description of the histogram (only used with creating a new histogram) + * @param {Array} [labelNames] - (Optional) Keys of the label to attach to the histogram + * @param {Array} [buckets] - (Optional) Buckets used in the histogram + * @returns {Histogram} - The Prometheus Histogram object + * @throws {Error} - + */ + getHistogram: (name: string, help?: string, labelNames?: string[], buckets?: number[]) => Histogram + + /** + * @function getMetricsForPrometheus + * @description Gets the metrics + */ + getMetricsForPrometheus: () => string + + /** + * @function setup + * @description Setup the prom client for collecting metrics using the options passed + * @param {metricOptionsType} - Config option for Metrics setup + * @returns boolean + */ + setup: (options: metricOptionsType) => boolean + } + + // `@mojaloop/central-services/metrics` exports a new class + // i.e. `new metrics.Metrics()` + const defaultMetrics: Metrics; + export default defaultMetrics +} declare module '@mojaloop/central-services-shared' { interface ReturnCode { CODE: number; @@ -93,7 +135,20 @@ declare module '@mojaloop/central-services-shared' { THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST = 'FSPIOP_THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST', THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_PUT = 'FSPIOP_THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_PUT', THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_ERROR = 'FSPIOP_THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_PUT_ERROR', - THIRDPARTY_CALLBACK_URL_TRX_REQ_POST = 'THIRDPARTY_CALLBACK_URL_TRX_REQ_POST' + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST', + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT', + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT_ERROR = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT_ERROR', + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_POST = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_POST', + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT', + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR = 'THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR', + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_POST = 'THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_POST', + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT = 'THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT', + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT_ERROR = 'THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT_ERROR', + THIRDPARTY_CALLBACK_URL_CREATE_CREDENTIAL_POST = 'THIRDPARTY_CALLBACK_URL_CREATE_CREDENTIAL_POST', + THIRDPARTY_CALLBACK_URL_CONSENT_POST = 'THIRDPARTY_CALLBACK_URL_CONSENT_POST', + THIRDPARTY_CALLBACK_URL_CONSENT_GET = 'THIRDPARTY_CALLBACK_URL_CONSENT_GET', + THIRDPARTY_CALLBACK_URL_CONSENT_PUT = 'THIRDPARTY_CALLBACK_URL_CONSENT_PUT', + THIRDPARTY_CALLBACK_URL_CONSENT_PUT_ERROR = 'THIRDPARTY_CALLBACK_URL_CONSENT_PUT_ERROR', } interface EndPointsEnum { EndpointType: { @@ -136,7 +191,20 @@ declare module '@mojaloop/central-services-shared' { THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST: FspEndpointTypesEnum.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST; THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_PUT: FspEndpointTypesEnum.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_PUT; THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_ERROR: FspEndpointTypesEnum.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_ERROR; - THIRDPARTY_CALLBACK_URL_TRX_REQ_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRX_REQ_POST; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT_ERROR: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_PUT_ERROR; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_POST; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT; + THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR; + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_POST; + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT; + THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT_ERROR: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_REQUEST_PUT_ERROR; + THIRDPARTY_CALLBACK_URL_CREATE_CREDENTIAL_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CREATE_CREDENTIAL_POST; + THIRDPARTY_CALLBACK_URL_CONSENT_POST: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_POST; + THIRDPARTY_CALLBACK_URL_CONSENT_GET: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_GET; + THIRDPARTY_CALLBACK_URL_CONSENT_PUT: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_PUT; + THIRDPARTY_CALLBACK_URL_CONSENT_PUT_ERROR: FspEndpointTypesEnum.THIRDPARTY_CALLBACK_URL_CONSENT_PUT_ERROR; }; FspEndpointTemplates: { TRANSACTION_REQUEST_POST: string; @@ -159,8 +227,22 @@ declare module '@mojaloop/central-services-shared' { BULK_TRANSFERS_POST: string; BULK_TRANSFERS_PUT: string; BULK_TRANSFERS_PUT_ERROR: string; - THIRDPARTY_TRANSACTION_REQUEST_PUT_ERROR: string; THIRDPARTY_TRANSACTION_REQUEST_POST: string; + THIRDPARTY_TRANSACTION_REQUEST_PUT: string; + THIRDPARTY_TRANSACTION_REQUEST_PUT_ERROR: string; + THIRDPARTY_AUTHORIZATIONS_POST: string; + THIRDPARTY_AUTHORIZATIONS_PUT: string; + THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_POST: string; + THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT: string; + THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR: string; + THIRDPARTY_CONSENT_REQUEST_POST: string; + THIRDPARTY_CONSENT_REQUEST_PUT: string; + THIRDPARTY_CONSENT_REQUEST_PUT_ERROR: string; + THIRDPARTY_CONSENT_CREATE_CREDENTIAL_POST: string; + THIRDPARTY_CONSENT_POST: string; + THIRDPARTY_CONSENT_GET: string; + THIRDPARTY_CONSENT_PUT: string; + THIRDPARTY_CONSENT_PUT_ERROR: string; }; } interface Enum { @@ -172,6 +254,7 @@ declare module '@mojaloop/central-services-shared' { POST: string; }; Type: { + AUTHORIZATION: string; TRANSACTION_REQUEST: string; }; }; diff --git a/config/default.json b/config/default.json index 4fc9b14..99c6a82 100644 --- a/config/default.json +++ b/config/default.json @@ -10,7 +10,7 @@ "expiresIn": 180000, "generateTimeout": 30000 }, - "SWITCH_ENDPOINT": "http://central-ledger.local:3001", + "ENDPOINT_SERVICE_URL": "http://central-ledger.local:3001", "ERROR_HANDLING": { "includeCauseExtension": true, "truncateExtensions": true @@ -30,4 +30,4 @@ } } } -} \ No newline at end of file +} diff --git a/docs/README.md b/docs/README.md index 5a58ef9..b37b2f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,9 +29,32 @@ npm run test:junit There is `mojaloop` convention to use `test/unit` path to keep tests. The placement of test folder should be similar to placement of tested code in `src` folder +### Jest Tips + +The [Jest CLI](https://jestjs.io/docs/en/cli) has some powerful commands for filtering, watching and running tests + +```bash +# run tests on just 1 file: +npm run test:unit -- -- test/unit/index.test.ts + +# run tests on file change: +npm run test:unit -- --watch + +# After the tests are run, you can configure the watch settings like so: +Ran all test suites related to changed files. + +Watch Usage + › Press a to run all tests. + › Press f to run only failed tests. + › Press p to filter by a filename regex pattern. + › Press t to filter by a test name regex pattern. + › Press q to quit watch mode. + › Press Enter to trigger a test run. +``` + ## linting -[eslint]() setup compatible with javascript [standard](https://standardjs.com/) and dedicated for TypeScript [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint). +[eslint](https://eslint.org/) setup compatible with javascript [standard](https://standardjs.com/) and dedicated for TypeScript [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint). - it is much more flexible - has good support for editors to visualize linting problem during typing. @@ -95,7 +118,7 @@ Generate the named "alpha" pre-release npm run release -- --prerelase alpha ``` -### Docker setup +## Docker setup Minimal working Docker image you can find in [Dockerfile](../Dockerfile). To build the image @@ -113,9 +136,24 @@ When the image is run you should be able to reach the dockerized _thirdparty-api If you already added the `127.0.0.1 thirdparty-api-adapter.local` entry in your `/etc/hosts` then the _thirdparty-api-adapter_ is reachable on `http://thirdparty-api-adapter.local:3008`. -### external links +## External Links - [about conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) - [standard-version](https://github.com/conventional-changelog/standard-version) - [conventional-changelog-config-spec](https://github.com/conventional-changelog/conventional-changelog-config-spec/tree/master/versions/2.1.0) + + +## Tips + Tricks: + +### 1. How do I disable the event-sdk logging in my unit tests? + +You can use the `EVENT_SDK_LOG_FILTER` environment variable. This setting is for configuring the internal config of the event-sdk, which isn't currently exposed to us in the `@mojaloop/central-services-shared` library. + +e.g. +```bash +export EVENT_SDK_LOG_FILTER="" +npm run test:unit -- --watch + +``` + diff --git a/package-lock.json b/package-lock.json index 1735224..a49249b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -639,6 +639,19 @@ "dev": true, "requires": { "conventional-changelog-conventionalcommits": "4.3.0" + }, + "dependencies": { + "conventional-changelog-conventionalcommits": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz", + "integrity": "sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + } } }, "@commitlint/ensure": { @@ -2158,9 +2171,9 @@ } }, "@mojaloop/central-services-shared": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@mojaloop/central-services-shared/-/central-services-shared-11.0.0.tgz", - "integrity": "sha512-YfixmZ4DZc/Q+9+kdgRqE8QUrS5Njtfj4tE1JM3SlW8vQrGcOjkO/tc1mNKRoy4UCAXuLi4hQtSs1xxlXKiwfg==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@mojaloop/central-services-shared/-/central-services-shared-11.1.2.tgz", + "integrity": "sha512-MGqtFlYijTdDrxboZhLg/IZgH5+8tF2X2FlHMbENrl5lSxNjD1vNklKj6fIV9335HlImzd4QPnUO4vrjkcKaIw==", "requires": { "@hapi/catbox": "11.1.0", "@hapi/catbox-memory": "5.0.0", @@ -2761,6 +2774,18 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "@types/yargs": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", @@ -3456,6 +3481,12 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -3959,6 +3990,22 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chokidar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", + "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -4343,13 +4390,25 @@ } }, "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } }, "conventional-changelog-atom": { @@ -4377,24 +4436,36 @@ "dev": true }, "conventional-changelog-conventionalcommits": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz", - "integrity": "sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.1.tgz", + "integrity": "sha512-EQa7TJzF7H4EMkfjjJV7d+gragejDqa8NirZnCfRpruCMZqRbAJ8DqmYbkHrYtBYicXqgfM0zkk6HlvLPcyOdQ==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "lodash": "^4.17.15", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } }, "conventional-changelog-core": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.7.tgz", - "integrity": "sha512-UBvSrQR2RdKbSQKh7RhueiiY4ZAIOW3+CSWdtKOwRv+KxIMNFKm1rOcGBFx0eA8AKhGkkmmacoTWJTqyz7Q0VA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.1.8.tgz", + "integrity": "sha512-M7VWA/RiVyjXIVt3SdfbgZFh0se67WBl78EzIYlBlFmDszzb00BwHlaNpgR1XrN0v56vtfBVq1sKEwQo2HbmkA==", "dev": true, "requires": { "add-stream": "^1.0.0", - "conventional-changelog-writer": "^4.0.16", + "conventional-changelog-writer": "^4.0.17", "conventional-commits-parser": "^3.1.0", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", @@ -4410,6 +4481,34 @@ "through2": "^3.0.0" }, "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "conventional-changelog-writer": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", + "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.6", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -4467,6 +4566,12 @@ "find-up": "^2.0.0", "read-pkg": "^3.0.0" } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -4507,13 +4612,25 @@ } }, "conventional-changelog-jshint": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.7.tgz", - "integrity": "sha512-qHA8rmwUnLiIxANJbz650+NVzqDIwNtc0TcpIa0+uekbmKHttidvQ1dGximU3vEDdoJVKFgR3TXFqYuZmYy9ZQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.8.tgz", + "integrity": "sha512-hB/iI0IiZwnZ+seYI+qEQ4b+EMQSEC8jGIvhO2Vpz1E5p8FgLz75OX8oB1xJWl+s4xBMB6f8zJr0tC/BL7YOjw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + } } }, "conventional-changelog-preset-loader": { @@ -4522,32 +4639,6 @@ "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", "dev": true }, - "conventional-changelog-writer": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", - "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", - "dev": true, - "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.6", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^7.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "conventional-commits-filter": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz", @@ -5087,6 +5178,15 @@ "stream-shift": "^1.0.0" } }, + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, "easy-table": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", @@ -7545,6 +7645,15 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -14568,6 +14677,15 @@ "once": "^1.3.0" } }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "realpath-native": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", @@ -15689,6 +15807,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "conventional-changelog-conventionalcommits": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.3.0.tgz", + "integrity": "sha512-oYHydvZKU+bS8LnGqTMlNrrd7769EsuEHKy4fh1oMdvvDi7fem8U+nvfresJ1IDB8K00Mn4LpiA/lR+7Gs6rgg==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -16497,6 +16626,12 @@ "random-poly-fill": "^1.0.1" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, "trim-newlines": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", @@ -16567,6 +16702,215 @@ "yn": "^3.0.0" } }, + "ts-node-dev": { + "version": "1.0.0-pre.56", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.56.tgz", + "integrity": "sha512-+2a3FAShOja+W5X6ZxKgf1PG3kOOkHCiYzSu6s3lwhLVxeMBusJudcv7W6cZKOTp7+L7hPkKW97t1CGw7bCDaA==", + "dev": true, + "requires": { + "chokidar": "^3.4.0", + "dateformat": "~1.0.4-1.2.3", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^8.10.2", + "tsconfig": "^7.0.0" + }, + "dependencies": { + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + } + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", diff --git a/package.json b/package.json index 0d8b145..1281a8a 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,11 @@ "dist" ], "scripts": { - "audit:resolve": "SHELL=sh resolve-audit", - "audit:check": "SHELL=sh check-audit", + "audit:resolve": "SHELL=sh resolve-audit --production", + "audit:check": "SHELL=sh check-audit --production", "build": "tsc -p ./tsconfig.build.json", "build:openapi": "swagger-cli bundle -o ./src/interface/api.yaml -t yaml ./src/interface/api-template.yaml && swagger-cli validate ./src/interface/api.yaml", + "dev": "ts-node-dev --no-notify -r tsconfig-paths/register -P ./tsconfig.json ./src/cli.ts", "docker:build": "docker build -t thirdparty-api-adapter:local -f ./Dockerfile ./", "docker:run": "docker run -p 3008:3008 thirdparty-api-adapter:local", "lint": "eslint ./src/**/*.ts *.js", @@ -84,9 +85,11 @@ "npm-audit-resolver": "2.2.0", "npm-check-updates": "6.0.0", "prettier": "^2.0.5", + "prom-client": "11.5.3", "source-map-support": "^0.5.19", "standard-version": "^8.0.2", "swagger-cli": "^4.0.3", + "ts-node-dev": "^1.0.0-pre.56", "ts-jest": "^26.0.0", "tsconfig-paths": "^3.9.0" }, @@ -101,8 +104,8 @@ "@mojaloop/central-services-health": "10.6.0", "@mojaloop/central-services-logger": "10.6.0", "@mojaloop/central-services-metrics": "9.5.0", - "@mojaloop/central-services-shared": "11.0.0", - "@mojaloop/event-sdk": "10.6.0", + "@mojaloop/central-services-shared": "11.1.2", + "@mojaloop/event-sdk": "^10.6.0", "@types/hapi": "^18.0.3", "@types/hapi__hapi": "^19.0.3", "@types/hapi__inert": "^5.2.0", diff --git a/src/__mocks__/server.ts b/src/__mocks__/server.ts index 9912048..b56b621 100644 --- a/src/__mocks__/server.ts +++ b/src/__mocks__/server.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/cli.ts b/src/cli.ts index 8f3f065..cb6b8c0 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,8 +3,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/domain/thirdpartyRequests/authorizations.ts b/src/domain/thirdpartyRequests/authorizations.ts new file mode 100644 index 0000000..71ce04a --- /dev/null +++ b/src/domain/thirdpartyRequests/authorizations.ts @@ -0,0 +1,176 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Lewis Daly + + -------------- + ******/ + +import { Util as HapiUtil } from '@hapi/hapi' +import Mustache from 'mustache' +import Logger from '@mojaloop/central-services-logger' + +import { + Enum, + Util +} from '@mojaloop/central-services-shared' +import Config from '~/shared/config' +import { inspect } from 'util' +import { FSPIOPError, ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' +import { finishChildSpan } from '~/shared/util' + +export interface PostAuthorizationPayload { + challenge: string; + value: string; + consentId: string; + sourceAccountId: string; + status: 'PENDING'; +} + +/** + * @function forwardPostAuthorization + * @description Forwards a POST /thirdpartyRequests/transactions/{ID}/authorizations request + * @param {string} path Callback endpoint path + * @param {HapiUtil.Dictionary} headers Headers object of the request + * @param {string} transactionRequestId the ID of the thirdpartyRequests/transactions resource + * @param {object} payload Body of the POST request + * @param {object} span optional request span + * @throws {FSPIOPError} Will throw an error if no endpoint to forward the transactions requests is + * found, if there are network errors or if there is a bad response + * @returns {Promise} + */ +export async function forwardPostAuthorization ( + path: string, + headers: HapiUtil.Dictionary, + transactionRequestId: string, + payload: PostAuthorizationPayload, + span?: any): Promise { + const childSpan = span?.getChild('forwardPostAuthorization') + const sourceDfspId = headers[Enum.Http.Headers.FSPIOP.SOURCE] + const destinationDfspId = headers[Enum.Http.Headers.FSPIOP.DESTINATION] + const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST + + try { + const endpoint = await Util.Endpoints.getEndpoint( + Config.ENDPOINT_SERVICE_URL, + destinationDfspId, + endpointType + ) + Logger.info(`authorizations::forwardPostAuthorization - Resolved destination party ${endpointType} endpoint for thirdpartyTransaction: ${transactionRequestId} to: ${inspect(endpoint)}`) + const url: string = Mustache.render(endpoint + path, { ID: transactionRequestId }) + Logger.info(`authorizations::forwardPostAuthorization - Forwarding authorization to endpoint: ${url}`) + + await Util.Request.sendRequest( + url, + headers, + sourceDfspId, + destinationDfspId, + Enum.Http.RestMethods.POST, + payload, + Enum.Http.ResponseTypes.JSON, + childSpan + ) + + Logger.info(`authorizations::forwardPostAuthorization - Forwarded thirdpartyTransaction authorization: ${transactionRequestId} from ${sourceDfspId} to ${destinationDfspId}`) + if (childSpan && !childSpan.isFinished) { + childSpan.finish() + } + } catch (err) { + Logger.error(`authorizations::forwardPostAuthorization - Error forwarding thirdpartyTransaction authorization to endpoint: ${inspect(err)}`) + const errorHeaders = { + ...headers, + 'fspiop-source': Enum.Http.Headers.FSPIOP.SWITCH.value, + 'fspiop-destination': sourceDfspId + } + const fspiopError: FSPIOPError = ReformatFSPIOPError(err) + await forwardPostAuthorizationError( + Enum.EndPoints.FspEndpointTemplates.THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR, + errorHeaders, + transactionRequestId, + fspiopError.toApiErrorObject(Config.ERROR_HANDLING.includeCauseExtension, Config.ERROR_HANDLING.truncateExtensions), + childSpan + ) + + if (childSpan && !childSpan.isFinished) { + await finishChildSpan(fspiopError, childSpan) + } + throw fspiopError + } + +} + +/** + * @function forwardPostAuthorizationError + * @description Generic function to handle sending `PUT .../authorizations/error` back to + * the FSPIOP-Source + * @param {string} path Callback endpoint path + * @param {HapiUtil.Dictionary} headers Headers object of the request + * @param {string} transactionRequestId the ID of the thirdpartyRequests/transactions resource + * @param {object} payload Body of the POST request + * @param {object} span optional request span + * @throws {FSPIOPError} Will throw an error if no endpoint to forward the transactions requests is + * found, if there are network errors or if there is a bad response + * @returns {Promise} + */ +export async function forwardPostAuthorizationError(path: string, + headers: HapiUtil.Dictionary, + transactionRequestId: string, + payload: PostAuthorizationPayload, + span?: any): Promise { + const childSpan = span?.getChild('forwardPostAuthorizationError') + const sourceDfspId = headers[Enum.Http.Headers.FSPIOP.SOURCE] + const destinationDfspId = headers[Enum.Http.Headers.FSPIOP.DESTINATION] + const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR + + try { + const endpoint = await Util.Endpoints.getEndpoint( + Config.ENDPOINT_SERVICE_URL, + destinationDfspId, + endpointType) + Logger.info(`authorizations::forwardPostAuthorizationError - Resolved destinationDfsp endpoint: ${endpointType} for transactionRequest${transactionRequestId} to: ${inspect(endpoint)}`) + const url: string = Mustache.render(endpoint + path, { ID: transactionRequestId }) + Logger.info(`authorizations::forwardPostAuthorizationError - Forwarding thirdpartyTransaction authorization error callback to endpoint: ${url}`) + + await Util.Request.sendRequest( + url, + headers, + sourceDfspId, + destinationDfspId, + Enum.Http.RestMethods.PUT, + payload, + Enum.Http.ResponseTypes.JSON, + childSpan + ) + + Logger.info(`authorizations::forwardPostAuthorization - Forwarded thirdpartyTransaction authorization error callback: ${transactionRequestId} from ${sourceDfspId} to ${destinationDfspId}`) + if (childSpan && !childSpan.isFinished) { + childSpan.finish() + } + + } catch (err) { + Logger.error(`authorizations::forwardPostAuthorizationError - Error forwarding thirdpartyTransaction authorization error to endpoint: ${inspect(err)}`) + const fspiopError: FSPIOPError = ReformatFSPIOPError(err) + if (childSpan && !childSpan.isFinished) { + await finishChildSpan(fspiopError, childSpan) + } + throw fspiopError + } + +} diff --git a/src/domain/thirdpartyRequests/index.ts b/src/domain/thirdpartyRequests/index.ts index fddba07..a73d400 100644 --- a/src/domain/thirdpartyRequests/index.ts +++ b/src/domain/thirdpartyRequests/index.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation The Mojaloop files are made available by the Bill - & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not + Copyright © 2020 Mojaloop Foundation The Mojaloop files are made available by the Mojaloop Foundation + under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS @@ -19,7 +19,9 @@ - Name Surname - Sridhar Voruganti + - Lewis Daly -------------- ******/ -export * as Transactions from './transactions' \ No newline at end of file +export * as Transactions from './transactions' +export * as Authorizations from './authorizations' diff --git a/src/domain/thirdpartyRequests/transactions.ts b/src/domain/thirdpartyRequests/transactions.ts index dec4d83..d63e444 100644 --- a/src/domain/thirdpartyRequests/transactions.ts +++ b/src/domain/thirdpartyRequests/transactions.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation The Mojaloop files are made available by the Bill - & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not + Copyright © 2020 Mojaloop Foundation The Mojaloop files are made available by the Mojaloop Foundation + under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS @@ -28,19 +28,18 @@ import Hapi from '@hapi/hapi' import Logger from '@mojaloop/central-services-logger' import { FSPIOPError, ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' import { Enum, Util } from '@mojaloop/central-services-shared' -import { EventStateMetadata, EventStatusType } from '@mojaloop/event-sdk' import Mustache from 'mustache' import Config from '~/shared/config' import inspect from '~/shared/inspect' -import { getStackOrInspect } from '~/shared/util' +import { getStackOrInspect, finishChildSpan } from '~/shared/util' import * as types from '~/interface/types' /** * Forwards POST transactions requests to destination FSP for processing * @param {string} path Callback endpoint path - * @param {object} headers Headers object of the request + * @param {object} headers Headers object of the request * @param {string} method The http method POST - * @param {object} params Params object of the request + * @param {object} params Params object of the request * @param {object} payload Body of the POST request * @param {object} span request span * @throws {FSPIOPError} Will throw an error if no endpoint to forward the transactions requests is @@ -54,16 +53,16 @@ async function forwardTransactionRequest(path: string, headers: Hapi.Util.Dictio const fspiopDest: string = headers[Enum.Http.Headers.FSPIOP.DESTINATION] const payloadLocal = payload || { transactionRequestId: params.ID } const transactionRequestId: string = (payload && payload.transactionRequestId) || params.ID - const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRX_REQ_POST + const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST try { const endpoint = await Util.Endpoints.getEndpoint( - Config.SWITCH_ENDPOINT, + Config.ENDPOINT_SERVICE_URL, fspiopDest, endpointType) - Logger.info(`Resolved PAYER party ${endpointType} endpoint for transactionRequest + Logger.info(`transactions::forwardTransactionRequest - Resolved PAYER party ${endpointType} endpoint for transactionRequest ${transactionRequestId} to: ${inspect(endpoint)}`) const fullUrl: string = Mustache.render(endpoint + path, { ID: transactionRequestId }) - Logger.info(`Forwarding transaction request to endpoint: ${fullUrl}`) + Logger.info(`transactions::forwardTransactionRequest - Forwarding transaction request to endpoint: ${fullUrl}`) await Util.Request.sendRequest( fullUrl, headers, @@ -74,13 +73,13 @@ async function forwardTransactionRequest(path: string, headers: Hapi.Util.Dictio Enum.Http.ResponseTypes.JSON, childSpan) - Logger.info(`Forwarded transaction request ${transactionRequestId} from ${fspiopSource} to ${fspiopDest}`) + Logger.info(`transactions::forwardTransactionRequest - Forwarded transaction request ${transactionRequestId} from ${fspiopSource} to ${fspiopDest}`) if (childSpan && !childSpan.isFinished) { childSpan.finish() } } catch (err) { - Logger.info(`Error forwarding transaction request to endpoint : ${inspect(err)}`) + Logger.error(`transactions::forwardTransactionRequest - Error forwarding transaction request to endpoint : ${inspect(err)}`) const errorHeaders = { ...headers, 'fspiop-source': Enum.Http.Headers.FSPIOP.SWITCH.value, @@ -102,20 +101,6 @@ async function forwardTransactionRequest(path: string, headers: Hapi.Util.Dictio } } -/** - * Finish childSpan - * @param {object} fspiopError error object - * @param {object} span request span - * @returns {Promise} - */ -async function finishChildSpan(fspiopError: FSPIOPError, childSpan: any): Promise { - const state = new EventStateMetadata( - EventStatusType.failed, - fspiopError.apiErrorCode.code, - fspiopError.apiErrorCode.message) - await childSpan.error(fspiopError, state) - await childSpan.finish(fspiopError.message, state) -} /** * Forwards transactions requests errors to destination FSP @@ -125,26 +110,26 @@ async function finishChildSpan(fspiopError: FSPIOPError, childSpan: any): Promis * @param {string} transactionRequestId Transaction request id that the transaction is for * @param {object} payload Body of the request * @param {object} span request span - * @throws {FSPIOPError} Will throw an error, if no endpoint is found to forward the transactions + * @throws {FSPIOPError} Will throw an error, if no endpoint is found to forward the transactions * error or if there are network errors or if there is a bad response. * @returns {Promise} */ async function forwardTransactionRequestError(errorHeaders: Hapi.Util.Dictionary, path: string, method: string, transactionRequestId: string, payload: types.ErrorInformation, span?: any): Promise { - const childSpan = span?.getChild('forwardTransactionRequestError') const fspiopSource: string = errorHeaders[Enum.Http.Headers.FSPIOP.SOURCE] const fspiopDestination: string = errorHeaders[Enum.Http.Headers.FSPIOP.DESTINATION] - const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRX_REQ_POST + const endpointType = Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST + try { const endpoint = await Util.Endpoints.getEndpoint( - Config.SWITCH_ENDPOINT, + Config.ENDPOINT_SERVICE_URL, fspiopDestination, endpointType) - Logger.info(`Resolved PAYER party ${endpointType} endpoint for transactionRequest + Logger.info(`transactions::forwardTransactionRequestError - Resolved PAYER party ${endpointType} endpoint for transactionRequest ${transactionRequestId} to: ${inspect(endpoint)}`) const fullUrl: string = Mustache.render(endpoint + path, { ID: transactionRequestId }) - Logger.info(`Forwarding transaction request error to endpoint: ${fullUrl}`) + Logger.info(`transactions::forwardTransactionRequestError - Forwarding transaction request error to endpoint: ${fullUrl}`) await Util.Request.sendRequest( fullUrl, @@ -156,13 +141,13 @@ async function forwardTransactionRequestError(errorHeaders: Hapi.Util.Dictionary Enum.Http.ResponseTypes.JSON, childSpan) - Logger.info(`Forwarding transaction request error for ${transactionRequestId} from ${fspiopSource} to ${fspiopDestination}`) + Logger.info(`transactions::forwardTransactionRequestError - Forwarding transaction request error for ${transactionRequestId} from ${fspiopSource} to ${fspiopDestination}`) if (childSpan && !childSpan.isFinished) { childSpan.finish() } } catch (err) { - Logger.info(`Error forwarding transaction request error to endpoint : ${getStackOrInspect(err)}`) + Logger.error(`transactions::forwardTransactionRequestError - Error forwarding transaction request error to endpoint : ${getStackOrInspect(err)}`) const fspiopError: FSPIOPError = ReformatFSPIOPError(err) if (childSpan && !childSpan.isFinished) { await finishChildSpan(fspiopError, childSpan) diff --git a/src/index.ts b/src/index.ts index 329fe49..5d5e11e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/interface/api-template.yaml b/src/interface/api-template.yaml index a140cba..d62ec1e 100644 --- a/src/interface/api-template.yaml +++ b/src/interface/api-template.yaml @@ -2,13 +2,13 @@ openapi: 3.0.2 info: title: Mojaloop Thirdparty API Adapter version: '1.0' - description: + description: A Mojaloop API for thirdparty interactions between `PISPs` (Payment Initiation Service Providers) and `DFSPs` (Digital Financial Service Providers) license: name: TBD url: TBD -servers: +servers: - url: / paths: /health: @@ -16,7 +16,7 @@ paths: tags: - health responses: - '200': + '200': $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/200' '400': $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/400' @@ -74,7 +74,7 @@ paths: description: | The HTTP request `POST /thirdpartyRequests/transactions` is used to request the creation of a third party transaction. - - Called by a `PISP` to initiate a third party transaction flow + - Called by a `PISP` to initiate a third party transaction flow parameters: #Headers - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/Accept.yaml' @@ -141,6 +141,63 @@ paths: $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/501' 503: $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/503' + /thirdpartyRequests/transactions/{ID}/authorizations: + post: + tags: + - thirdpartyRequests + - sampled + operationId: VerifyThirdPartyAuthorization + summary: VerifyThirdPartyAuthorization + description: | + The HTTP request `POST /thirdpartyRequests/transactions/{id}/authorizations` is used by the DFSP to verify a third party authorization. + parameters: + #Path + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/ID.yaml' + #Headers + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/Accept.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/Content-Length.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/Content-Type.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/Date.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/X-Forwarded-For.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-Source.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-Destination.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-Encryption.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-Signature.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-URI.yaml' + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/parameters/FSPIOP-HTTP-Method.yaml' + requestBody: + description: The thirdparty authorization details to verify + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerifyThirdPartyAuthorizationRequest' + example: + challenge: "" + value: "" + consentId: "8d34f91d-d078-4077-8263-2c0498dhbjr" + sourceAccountId: "dfspa.alice.1234" + status: "PENDING" + responses: + 202: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/202' + 400: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/400' + 401: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/401' + 403: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/403' + 404: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/404' + 405: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/405' + 406: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/406' + 501: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/501' + 503: + $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/responses/index.yaml#/503' + components: schemas: AccountId: @@ -158,7 +215,7 @@ components: allOf: - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/schemas/CorrelationId.yaml' description: > - Common ID between the FSPs for the transaction request object. + Common ID between the FSPs for the transaction request object. The ID should be reused for resends of the same transaction request. A new ID should be generated for each new transaction request. sourceAccountId: @@ -194,7 +251,7 @@ components: expiration: type: string description: > - Date and time until when the transaction request is valid. + Date and time until when the transaction request is valid. It can be set to get a quick failure in case the peer FSP takes too long to respond. example: '2016-05-24T08:38:08.699-04:00' required: @@ -206,4 +263,38 @@ components: - amountType - amount - transactionType - - expiration \ No newline at end of file + - expiration + + VerifyThirdPartyAuthorizationRequest: + title: VerifyThirdPartyAuthorizationRequest + type: object + description: The object sent in the POST /thirdpartyRequests/transactions/{id}/authorizations request. + properties: + challenge: + type: string + description: The original Challenge Object as a JSON string + value: + allOf: + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/schemas/BinaryString.yaml' + description: "Base64 encoded binary string - the signed challenge" + consentId: + allOf: + - $ref: '../../node_modules/@mojaloop/api-snippets/v1.0/openapi3/schemas/CorrelationId.yaml' + description: > + Common ID between the PISP and FSP for the Consent object + This tells DFSP and auth-service which constent allows the PISP to initiate transaction. + sourceAccountId: + allOf: + - $ref: '#/components/schemas/AccountId' + description: DFSP specific account identifiers, e.g. `dfspa.alice.1234` + status: + type: string + enum: + - PENDING + description: The status of the authorization. This MUST be PENDING for a POST request + required: + - challenge + - value + - consentId + - sourceAccountId + - status diff --git a/src/interface/api.yaml b/src/interface/api.yaml index fc7dd62..908c22e 100644 --- a/src/interface/api.yaml +++ b/src/interface/api.yaml @@ -218,7 +218,7 @@ paths: description: | The HTTP request `POST /thirdpartyRequests/transactions` is used to request the creation of a third party transaction. - - Called by a `PISP` to initiate a third party transaction flow + - Called by a `PISP` to initiate a third party transaction flow parameters: - name: Accept in: header @@ -376,6 +376,65 @@ paths: $ref: '#/paths/~1health/get/responses/501' '503': $ref: '#/paths/~1health/get/responses/503' + '/thirdpartyRequests/transactions/{ID}/authorizations': + post: + tags: + - thirdpartyRequests + - sampled + operationId: VerifyThirdPartyAuthorization + summary: VerifyThirdPartyAuthorization + description: | + The HTTP request `POST /thirdpartyRequests/transactions/{id}/authorizations` is used by the DFSP to verify a third party authorization. + parameters: + - name: ID + in: path + required: true + schema: + type: string + description: The identifier value. + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/0' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/1' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/2' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/3' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/4' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/5' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/6' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/7' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/8' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/9' + - $ref: '#/paths/~1thirdpartyRequests~1transactions/post/parameters/10' + requestBody: + description: The thirdparty authorization details to verify + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/VerifyThirdPartyAuthorizationRequest' + example: + challenge: + value: + consentId: 8d34f91d-d078-4077-8263-2c0498dhbjr + sourceAccountId: dfspa.alice.1234 + status: PENDING + responses: + '202': + $ref: '#/paths/~1thirdpartyRequests~1transactions/post/responses/202' + '400': + $ref: '#/paths/~1health/get/responses/400' + '401': + $ref: '#/paths/~1health/get/responses/401' + '403': + $ref: '#/paths/~1health/get/responses/403' + '404': + $ref: '#/paths/~1health/get/responses/404' + '405': + $ref: '#/paths/~1health/get/responses/405' + '406': + $ref: '#/paths/~1health/get/responses/406' + '501': + $ref: '#/paths/~1health/get/responses/501' + '503': + $ref: '#/paths/~1health/get/responses/503' components: schemas: AccountId: @@ -391,7 +450,7 @@ components: allOf: - $ref: '#/components/schemas/ThirdpartyTransactionRequest/properties/consentId/allOf/0' description: | - Common ID between the FSPs for the transaction request object. The ID should be reused for resends of the same transaction request. A new ID should be generated for each new transaction request. + Common ID between the FSPs for the transaction request object. The ID should be reused for resends of the same transaction request. A new ID should be generated for each new transaction request. sourceAccountId: allOf: - $ref: '#/components/schemas/AccountId' @@ -873,7 +932,7 @@ components: expiration: type: string description: | - Date and time until when the transaction request is valid. It can be set to get a quick failure in case the peer FSP takes too long to respond. + Date and time until when the transaction request is valid. It can be set to get a quick failure in case the peer FSP takes too long to respond. example: '2016-05-24T08:38:08.699-04:00' required: - transactionRequestId @@ -885,3 +944,42 @@ components: - amount - transactionType - expiration + VerifyThirdPartyAuthorizationRequest: + title: VerifyThirdPartyAuthorizationRequest + type: object + description: 'The object sent in the POST /thirdpartyRequests/transactions/{id}/authorizations request.' + properties: + challenge: + type: string + description: The original Challenge Object as a JSON string + value: + allOf: + - type: string + pattern: '^[A-Za-z0-9-_]+[=]{0,2}$' + description: | + The API data type BinaryString is a JSON String. + The string is a base64url encoding of a string of raw bytes, + where padding (character ‘=’) is added at the end of the data if + needed to ensure that the string is a multiple of 4 characters. + The length restriction indicates the allowed number of characters. + description: Base64 encoded binary string - the signed challenge + consentId: + allOf: + - $ref: '#/components/schemas/ThirdpartyTransactionRequest/properties/consentId/allOf/0' + description: | + Common ID between the PISP and FSP for the Consent object This tells DFSP and auth-service which constent allows the PISP to initiate transaction. + sourceAccountId: + allOf: + - $ref: '#/components/schemas/AccountId' + description: 'DFSP specific account identifiers, e.g. `dfspa.alice.1234`' + status: + type: string + enum: + - PENDING + description: The status of the authorization. This MUST be PENDING for a POST request + required: + - challenge + - value + - consentId + - sourceAccountId + - status diff --git a/src/interface/thirdparty-api.yaml b/src/interface/thirdparty-api.yaml deleted file mode 100644 index bce1515..0000000 --- a/src/interface/thirdparty-api.yaml +++ /dev/null @@ -1,2394 +0,0 @@ -openapi: 3.0.2 -info: - version: "1.0" - title: Mojaloop Thirdparty API Adapter - description: - A Mojaloop API for thirdparty interactions between `PISPs` (Payment Initiation - Service Providers) and `DFSPs` (Digital Financial Service Providers) - license: - name: TBD - url: TBD -servers: - - url: '{protocol}://hostname:/' - variables: - protocol: - enum: - - http - - https - default: https -paths: - /health: - get: - operationId: HealthGet - summary: Get current status of the API - description: The HTTP request `GET /health` is used to return the current status of the API. - tags: - - health - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /metrics: - get: - operationId: MetricsGet - summary: Prometheus metrics endpoint - description: The HTTP request `GET /metrics` is used to return metrics for the API. - tags: - - metrics - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - /parties/{Type}/{ID}: - parameters: - #Path - - $ref: '#/components/parameters/Type' - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - get: - description: > - The HTTP request `GET /parties/{Type}/{ID}` (or `GET /parties/{Type}/{ID}/{SubId}`) is used to look up information - regarding the requested Party, defined by `{Type}`, `{ID}` and optionally `{SubId}` - (for example, GET /parties/MSISDN/123456789, or GET /parties/BUSINESS/shoecompany/employee1). - summary: Look up party information - tags: - - parties - operationId: PartiesByTypeAndIDGet - parameters: - #Headers - - $ref: '#/components/parameters/Accept' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - put: - description: > - The callback `PUT /parties/{Type}/{ID}` (or `PUT /parties/{Type}/{ID}/{SubId}`) is used to inform the client of a successful result - of the Party information lookup. - summary: Return party information - tags: - - parties - operationId: PartiesByTypeAndIDPut - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - requestBody: - description: Party information returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PartiesTypeIDPutResponse' - example: - party: - accounts: - account: - - currency: 'USD' - description: 'savings' - address: 'moja.blue.8f027046-b82a5456-4fa9-838b-70210fcf8136' - - currency: 'USD' - description: 'checkings' - address: 'moja.blue.8f027046-b8236345a-4fa9-838b-70210fcf8137' - partyIdInfo: - partyIdType: 'PERSONAL_ID' - partyIdentifier: '16135551212' - partySubIdOrType: 'DRIVING_LICENSE' - fspId: 'dfspa' - extensionList: - extension: - - key: 'Account Type' - value: 'Wallet' - merchantClassificationCode: '4321' - name: 'Alice Alpaca' - personalInfo: - complexName: - firstName: 'Alice' - middleName: 'K' - lastName: 'Alpaca' - dateOfBirth: '1971-12-25' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /parties/{Type}/{ID}/error: - put: - description: > - If the server is unable to find Party information of the provided identity, or another processing error occurred, - the error callback `PUT /parties/{Type}/{ID}/error` (or `PUT /parties/{Type}/{ID}/{SubI}/error`) is used. - summary: Return party information error - tags: - - parties - operationId: PartiesErrorByTypeAndID - parameters: - #Path - - $ref: '#/components/parameters/Type' - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Details of the error returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationObject' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /parties/{Type}/{ID}/{SubId}: - parameters: - #Path - - $ref: '#/components/parameters/Type' - - $ref: '#/components/parameters/ID' - - $ref: '#/components/parameters/SubId' - #Headers - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - get: - description: > - The HTTP request `GET /parties/{Type}/{ID}` (or `GET /parties/{Type}/{ID}/{SubId}`) is used to look up information - regarding the requested Party, defined by `{Type}`, `{ID}` and optionally `{SubId}` (for example, GET /parties/MSISDN/123456789, - or GET /parties/BUSINESS/shoecompany/employee1). - summary: Look up party information - tags: - - parties - operationId: PartiesSubIdByTypeAndID - parameters: - #Headers - - $ref: '#/components/parameters/Accept' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - put: - description: > - The callback `PUT /parties/{Type}/{ID}` (or `PUT /parties/{Type}/{ID}/{SubId}`) is used to inform the client of a successful - result of the Party information lookup. - summary: Return party information - tags: - - parties - operationId: PartiesSubIdByTypeAndIDPut - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - requestBody: - description: Party information returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PartiesTypeIDPutResponse' - example: - party: - accounts: - account: - - currency: 'USD' - description: 'savings' - address: 'moja.blue.8f027046-b82a5456-4fa9-838b-70210fcf8136' - - currency: 'USD' - description: 'checkings' - address: 'moja.blue.8f027046-b8236345a-4fa9-838b-70210fcf8137' - partyIdInfo: - partyIdType: 'PERSONAL_ID' - partyIdentifier: '16135551212' - partySubIdOrType: 'DRIVING_LICENSE' - fspId: 'dfspa' - extensionList: - extension: - - key: 'Account Type' - value: 'Wallet' - merchantClassificationCode: '4321' - name: 'Justin Trudeau' - personalInfo: - complexName: - firstName: 'Justin' - middleName: 'Pierre' - lastName: 'Trudeau' - dateOfBirth: '1971-12-25' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /parties/{Type}/{ID}/{SubId}/error: - put: - description: > - If the server is unable to find Party information of the provided identity, or another processing error occurred, - the error callback `PUT /parties/{Type}/{ID}/error` (or `PUT /parties/{Type}/{ID}/{SubId}/error`) is used. - summary: Return party information error - tags: - - parties - operationId: PartiesSubIdErrorByTypeAndID - parameters: - #Path - - $ref: '#/components/parameters/Type' - - $ref: '#/components/parameters/ID' - - $ref: '#/components/parameters/SubId' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Details of the error returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationObject' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - /consentRequests: - post: - description: | - The HTTP request `POST /consentRequests` is used to create a consent request. - - - Called by a PISP to start the process of establishing consent between three parties(`PISP, DFSP and User(DFSP customer)`). - summary: CreateConsentRequest - tags: - - consentRequest - operationId: CreateConsentRequest - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: CreateConsentRequest - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ConsentRequest' - example: - id: '456' - initiatorId: 'pispa' - accountIds: - - 'dfspa.alice.1234' - - 'dfspa.alice.5678' - authChannels: - - 'Web' - - 'OTP' - scopes: - - 'accounts.getBalance' - - 'accounts.transfer' - callbackUri: 'pisp-app://callback...' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /consentRequests/{ID}: - put: - description: | - The HTTP request `PUT /consentRequests/{ID}` is used to update a specified consent request. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /consentRequests`. - - - Called by a PISP to add the authToken parameter - - - Called by a DFSP to include the authorizationUri - summary: UpdateConsentRequest - tags: - - consentRequest - operationId: UpdateConsentRequest - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: UpdateConsentRequest. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ConsentRequest' - example: - initiatorId: 'pispa' - accountIds: - - 'dfspa.alice.1234' - - 'dfspa.alice.5678' - authChannels: - - 'WEB' - scopes: - - 'accounts.getBalance' - - 'accounts.transfer' - callbackUri: 'pisp-app://callback...' - authorizationUri: 'dfspa.com/authorize?consentRequestId=456' - authToken: '000111' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /consentRequests/{ID}/error: - put: - description: > - If the server is unable to find the consent request, or another processing error occurs, - the error callback `PUT /consentRequests/{ID}/error` is used. The `{ID}` in the URI should contain the `{ID}` - that was used in the `POST /consentRequests`. - summary: UpdateConsentRequestError - tags: - - consentRequest - operationId: UpdateConsentRequestError - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Details of the error returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationObject' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - /consents: - post: - description: | - The HTTP request `POST /consents` is used to create a consent object. - - - Called by `DFSP` after the succesful creation and validation of a consentRequest. - summary: CreateConsent - tags: - - consent - operationId: CreateConsent - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: CreateConsent - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Consent' - example: - id: '123' - requestId: '456' - initiatorId: 'pispa' - participantId: 'dfspa' - scopes: - - scope: 'account.balanceInquiry' - accountId: 'dfspa.alice.1234' - - scope: 'account.sendTransfer' - accountId: 'dfspa.alice.1234' - - scope: 'account.sendTransfer' - accountId: 'dfspa.alice.5678' - credential: null - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /consents/{ID}: - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - get: - description: > - The HTTP request `GET /consents/{ID}` is used to get information regarding a consent object created or requested earlier. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /consents`. - summary: GetConsent - tags: - - consent - operationId: GetConsent - parameters: - #Headers - - $ref: '#/components/parameters/Accept' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - put: - description: | - The HTTP request `PUT /consents/{ID}` is used to update a specified consent object. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /consents`. - - - Called by a `auth-service` to add the credential details - - - Called by a `PISP` to add a signature of the challenge - summary: UpdateConsent - tags: - - consent - operationId: UpdateConsent - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - requestBody: - description: UpdateConsent. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Consent' - example: - requestId: '456' - initiatorId: 'dfspa' - participantId: 'pispa' - scopes: - - scope: 'account.balanceInquiry' - accountId: 'dfspa.alice.1234' - - scope: 'account.sendTransfer' - accountId: 'dfspa.alice.1234' - - scope: 'account.sendTransfer' - accountId: 'dfspa.alice.5678' - credential: - id: '5678' - credentialType: 'FIDO' - credentialStatus: 'ACTIVE' - challenge: - payload: 'base64(...)' - signature: 'base64(...)' - payload: 'base64(...)' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /consents/{ID}/createCredential: - post: - description: | - The HTTP request `POST /consents/{ID}/createCredential` is used to create a credential for the given Consent object. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /consents`. - - - Called by a `PISP` to request a challenge from the `auth-service`, which will be returned to the PISP via `PUT /consents/{ID}` - summary: CreateCredential - tags: - - consent - operationId: CreateCredential - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: CreateCredential - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateCredential' - example: - type: 'FIDO' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - #thirdpartyRequests - /thirdpartyRequests/transactions: - post: - description: | - The HTTP request `POST /thirdpartyRequests/transactions` is used to request the creation of a third party transaction. - - - Called by a `PISP` to initiate a third party transaction flow - summary: CreateThirdpartyTransactionRequests - tags: - - thirdpartyRequests - - sampled - operationId: CreateThirdpartyTransactionRequests - parameters: - #Headers - - $ref: '#/components/parameters/Accept' - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Transaction request to be created. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ThirdpartyTransactionRequest' - example: - transactionRequestId: '8d34f91d-d078-4077-8263-2c047876fcf6' - sourceAccountId: 'dfspa.alice.1234' - consentId: '111' - payee: - partyIdInfo: - partyIdType: 'MSISDN' - partyIdentifier: '+44 1234 5678' - fspId: 'dfspb' - payer: - personalInfo: - complexName: - firstName: 'Alice' - lastName: 'K' - partyIdInfo: - partyIdType: 'MSISDN' - partyIdentifier: '+44 8765 4321' - fspId: 'dfspa' - amountType: 'SEND' - amount: - amount: '100' - currency: 'USD' - transactionType: - scenario: 'TRANSFER' - initiator: 'PAYER' - initiatorType: 'CONSUMER' - expiration: '2020-07-15T22:17:28.985-01:00' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /thirdpartyRequests/transactions/{ID}: - parameters: - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - get: - description: > - The HTTP request `GET /thirdpartyRequests/transactions/{ID}` is used to get information regarding a transaction request - created or requested earlier. The `{ID}` in the URI should contain the `transactionRequestId` that was used for the creation - of the transaction request. - summary: GetThirdpartyTransactionRequests - tags: - - thirdpartyRequests - operationId: GetThirdpartyTransactionRequests - parameters: - #Headers - - $ref: '#/components/parameters/Accept' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - put: - description: > - The callback `PUT /thirdpartyRequests/transactions/{ID}` is used to inform the client of the result of a - previously-requested transaction. The `{ID}` in the URI should contain the `transactionRequestId` that was used for - the creation of the transaction request. - summary: Update third party transaction requests - tags: - - thirdpartyRequests - operationId: UpdateThirdpartyTransactionRequests - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - requestBody: - description: Transaction request result returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ThirdpartyTransactionRequest' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /thirdpartyRequests/transactions/{ID}/error: - put: - description: > - If the server is unable to find the transaction request, or another processing error occurs, - the error callback `PUT /thirdpartyRequests/transactions/{ID}/error` is used. - The `{ID}` in the URI should contain the `transactionRequestId` that was used for the creation of the transaction request. - summary: Return transaction error - tags: - - thirdpartyRequests - operationId: UpdateThirdpartyTransactionRequestsError - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Details of the error returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationObject' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - #Authorizations - /authorizations: - post: - description: > - The HTTP request `POST /authorizations` is used to request the Payer to enter the applicable credentials in the PISP system. - summary: Perform PISP authorization - tags: - - authorizations - operationId: AuthorizationsByIDPost - parameters: - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Perform authorization - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizationsPostRequest' - responses: - 202: - $ref: '#/components/responses/202' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - - /authorizations/{ID}: - put: - description: > - The callback `PUT /authorizations/{ID}` is used to inform the client of the result of a previously-requested authorization. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /authorizations` request. - summary: Return authorization result - tags: - - authorizations - operationId: AuthorizationsByIDPut - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Authorization result returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizationsIDPutResponse' - example: - authenticationInfo: - authentication: 'U2F' - authenticationValue: - pinValue: 'base64(xxx)' - counter: '1' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - /authorizations/{ID}/error: - put: - description: > - If the server is unable to find the authorizations request, or another processing error occurs, - the error callback `PUT /authorizations/{ID}/error` is used. - The `{ID}` in the URI should contain the `{ID}` that was used in the `POST /authorizations/{ID}`. - summary: Return authorization error - tags: - - authorizations - operationId: AuthorizationsByIDAndError - parameters: - #Path - - $ref: '#/components/parameters/ID' - #Headers - - $ref: '#/components/parameters/Content-Length' - - $ref: '#/components/parameters/Content-Type' - - $ref: '#/components/parameters/Date' - - $ref: '#/components/parameters/X-Forwarded-For' - - $ref: '#/components/parameters/FSPIOP-Source' - - $ref: '#/components/parameters/FSPIOP-Destination' - - $ref: '#/components/parameters/FSPIOP-Encryption' - - $ref: '#/components/parameters/FSPIOP-Signature' - - $ref: '#/components/parameters/FSPIOP-URI' - - $ref: '#/components/parameters/FSPIOP-HTTP-Method' - requestBody: - description: Details of the error returned. - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationObject' - responses: - 200: - $ref: '#/components/responses/200' - 400: - $ref: '#/components/responses/400' - 401: - $ref: '#/components/responses/401' - 403: - $ref: '#/components/responses/403' - 404: - $ref: '#/components/responses/404' - 405: - $ref: '#/components/responses/405' - 406: - $ref: '#/components/responses/406' - 501: - $ref: '#/components/responses/501' - 503: - $ref: '#/components/responses/503' - -components: - schemas: - AccountAddress: - title: AccountAddress - type: string - description: Unique routable address which is DFSP specific. - pattern: ^([0-9A-Za-z_~\-\.]+[0-9A-Za-z_~\-])$ - minLength: 1 - maxLength: 1023 - AccountId: - type: string - description: > - A long-lived account identifier provided by the DFSP - this MUST NOT be Bank Account Number or anything that - may expose a User's private bank account information - AuthChannelsEnum: - title: AuthChannelsEnum - type: string - enum: - - WEB - - OTP - description: | - The channels requested for a ConsentRequest - - "WEB" The PISP is requesting the WEB Consent flow - - "OTP" The PISP is requesting the OTP Consent flow - AuthenticationType: - title: AuthenticationType - type: string - enum: - - OTP - - QRCODE - - U2F - description: | - Below are the allowed values for the enumeration AuthenticationType. - - OTP - One-time password generated by the Payer FSP. - - QRCODE - QR code used as One Time Password. - - U2F challenge-response, where payer FSP verifies if the response provided by end-user device matches the previously registered key. - AuthScopesEnum: - title: AuthScopesEnum - type: string - enum: - - accounts.getBalance - - accounts.transfer - description: | - The scopes requested for a ConsentRequest - - "accounts.getBalance" - Get the balance of a given account - - "accounts.transfer" - initiate a transfer from an account - Counter: - allOf: - - $ref: '#/components/schemas/Integer' - title: Counter - description: Sequential counter used for cloning detection. Present only for U2F authentication. - CorrelationId: - title: CorrelationId - type: string - pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ - description: > - Identifier that correlates all messages of the same sequence. - The API data type UUID (Universally Unique Identifier) is a JSON String in canonical format, conforming to RFC 4122, - that is restricted by a regular expression for interoperability reasons. - An UUID is always 36 characters long, 32 hexadecimal symbols and 4 dashes (‘-‘). - CredentialTypeEnum: - title: CredentialTypeEnum - type: string - enum: - - FIDO - description: | - The type of the Credential - - "FIDO" - A FIDO public/private keypair - CredentialStatusEnum: - title: CredentialStatus - type: string - enum: - - PENDING - - ACTIVE - description: | - The status of the Credential's creation - - "PENDING" - The PISP has requested a challenge, or the challenge has initialized but not yet answered by the PISP - - "ACTIVE" - The Credential is valid, and ready to be used by the PISP - Currency: - title: Currency - description: The currency codes is a three-letter alphabetic codes are used as the standard naming representation for currencies. - type: string - minLength: 3 - maxLength: 3 - enum: - - AED - - AFN - - ALL - - AMD - - ANG - - AOA - - ARS - - AUD - - AWG - - AZN - - BAM - - BBD - - BDT - - BGN - - BHD - - BIF - - BMD - - BND - - BOB - - BRL - - BSD - - BTN - - BWP - - BYN - - BZD - - CAD - - CDF - - CHF - - CLP - - CNY - - COP - - CRC - - CUC - - CUP - - CVE - - CZK - - DJF - - DKK - - DOP - - DZD - - EGP - - ERN - - ETB - - EUR - - FJD - - FKP - - GBP - - GEL - - GGP - - GHS - - GIP - - GMD - - GNF - - GTQ - - GYD - - HKD - - HNL - - HRK - - HTG - - HUF - - IDR - - ILS - - IMP - - INR - - IQD - - IRR - - ISK - - JEP - - JMD - - JOD - - JPY - - KES - - KGS - - KHR - - KMF - - KPW - - KRW - - KWD - - KYD - - KZT - - LAK - - LBP - - LKR - - LRD - - LSL - - LYD - - MAD - - MDL - - MGA - - MKD - - MMK - - MNT - - MOP - - MRO - - MUR - - MVR - - MWK - - MXN - - MYR - - MZN - - NAD - - NGN - - NIO - - NOK - - NPR - - NZD - - OMR - - PAB - - PEN - - PGK - - PHP - - PKR - - PLN - - PYG - - QAR - - RON - - RSD - - RUB - - RWF - - SAR - - SBD - - SCR - - SDG - - SEK - - SGD - - SHP - - SLL - - SOS - - SPL - - SRD - - STD - - SVC - - SYP - - SZL - - THB - - TJS - - TMT - - TND - - TOP - - TRY - - TTD - - TVD - - TWD - - TZS - - UAH - - UGX - - USD - - UYU - - UZS - - VEF - - VND - - VUV - - WST - - XAF - - XCD - - XDR - - XOF - - XPF - - YER - - ZAR - - ZMW - - ZWD - Date: - title: Date - type: string - pattern: ^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)$ - description: > - The API data type Date is a JSON String in a lexical format that is restricted by a regular expression for interoperability reasons. - This format, as specified in ISO 8601, contains a date only. A more readable version of the format is yyyy-MM-dd. - Examples - "1982-05-23", "1987-08-05" - DateTime: - title: DateTime - type: string - pattern: ^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:(\.\d{3}))(?:Z|[+-][01]\d:[0-5]\d)$ - description: > - The API data type DateTime is a JSON String in a lexical format that is restricted by a regular expression for interoperability reasons. - The format is according to ISO 8601, expressed in a combined date, time and time zone format. - A more readable version of the format is yyyy-MM-ddTHH:mm:ss.SSS[-HH:MM]. - Examples - "2016-05-24T08:38:08.699-04:00", "2016-05-24T08:38:08.699Z" (where Z indicates Zulu time zone, same as UTC). - ErrorCode: - title: ErrorCode - type: string - pattern: ^[1-9]\d{3}$ - description: > - The API data type ErrorCode is a JSON String of four characters, consisting of digits only. - Negative numbers are not allowed. A leading zero is not allowed. Each error code in the API is a four-digit number, - for example, 1234, where the first number (1 in the example) represents the high-level error category, - the second number (2 in the example) represents the low-level error category, and the last two numbers (34 in the example) - represents the specific error. - ErrorDescription: - title: ErrorDescription - type: string - minLength: 1 - maxLength: 128 - description: Error description string. - ExtensionKey: - title: ExtensionKey - type: string - minLength: 1 - maxLength: 32 - description: Extension key. - ExtensionValue: - title: ExtensionValue - type: string - minLength: 1 - maxLength: 128 - description: Extension value. - FspId: - title: FspId - type: string - minLength: 1 - maxLength: 32 - description: FSP identifier. - Integer: - title: Integer - type: string - pattern: ^[1-9]\d*$ - description: > - The API data type Integer is a JSON String consisting of digits only. - Negative numbers and leading zeroes are not allowed. The data type is always limited to a specific number of digits. - Name: - title: Name - type: string - pattern: ^(?!\s*$)[\w .,'-]{1,128}$ - description: > - The API data type Name is a JSON String, restricted by a regular expression to avoid characters which are generally - not used in a name. Regular Expression - The regular expression for restricting the Name type is "^(?!\s*$)[\w .,''-]{1,128}$". - The restriction does not allow a string consisting of whitespace only, all Unicode characters are allowed, as well as the period (.) - (apostrophe (‘), dash (-), comma (,) and space characters ( ). Note - In some programming languages, - Unicode support must be specifically enabled. For example, if Java is used the flag UNICODE_CHARACTER_CLASS must be enabled - to allow Unicode characters. - Note: - title: Note - type: string - minLength: 1 - maxLength: 128 - description: Memo assigned to transaction - OtpValue: - title: OtpValue - type: string - pattern: ^\d{3,10}$ - description: > - The API data type OtpValue is a JSON String of 3 to 10 characters, consisting of digits only. - Negative numbers are not allowed. One or more leading zeros are allowed. - QRCODE: - title: QRCODE - type: string - pattern: ^\S{1,64}$ - minLength: 1 - maxLength: 64 - description: QR code used as a One Time Password. - U2FPIN: - title: U2FPIN - type: string - pattern: ^\S{1,64}$ - minLength: 1 - maxLength: 64 - description: > - U2F challenge-response, where payer FSP verifies if the response provided by end-user device matches the previously registered key. - U2FPinValue: - title: U2FPinValue - type: object - description: > - U2F challenge-response, where payer FSP verifies if the response provided by end-user device matches the previously registered key. - properties: - pinValue: - allOf: - - $ref: '#/components/schemas/U2FPIN' - description: U2F challenge-response. - counter: - allOf: - - $ref: '#/components/schemas/Counter' - description: Sequential counter used for cloning detection. Present only for U2F authentication. - required: - - pinValue - - counter - RetriesLeft: - allOf: - - $ref: '#/components/schemas/Integer' - title: RetriesLeft - description: > - RetriesLeft is the number of retries left before the financial transaction is rejected. - It must be expressed in the form of the data type Integer. retriesLeft=1 means that this is the last retry before - the financial transaction is rejected. - - #Complex Types - Account: - title: Account - type: object - description: Data model for the complex type Account - properties: - address: - allOf: - - $ref: '#/components/schemas/AccountAddress' - type: string - description: Unique routable address which is DFSP specific. - currency: - allOf: - - $ref: '#/components/schemas/Currency' - type: string - description: Currency of the amount. - description: - allOf: - - $ref: '#/components/schemas/Name' - type: string - description: The name of the account. - required: - - address - - currency - - description - AccountList: - title: AccountList - type: object - description: Data model for the complex type AccountList - properties: - account: - type: array - items: - $ref: '#/components/schemas/Account' - minItems: 1 - maxItems: 32 - description: Accounts associated with the Party - required: - - account - Amount: - title: Amount - type: string - pattern: ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ - description: > - The API data type Amount is a JSON String in a canonical format that is restricted by a regular expression for interoperability reasons. - This pattern does not allow any trailing zeroes at all, but allows an amount without a minor currency unit. - It also only allows four digits in the minor currency unit; a negative value is not allowed. - Using more than 18 digits in the major currency unit is not allowed. - AmountType: - title: AmountType - type: string - enum: - - SEND - - RECEIVE - description: | - Below are the allowed values for the enumeration AmountType. - - - SEND - Amount the Payer would like to send, that is, the amount that should be withdrawn from the Payer account including any fees. - - - RECEIVE - Amount the Payer would like the Payee to receive, that is, the amount that should be sent to the receiver - exclusive of any fees. - AuthenticationInfo: - title: AuthenticationInfo - type: object - description: Data model for the complex type AuthenticationInfo. - properties: - authentication: - allOf: - - $ref: '#/components/schemas/AuthenticationType' - description: Type of authentication. - authenticationValue: - allOf: - - $ref: '#/components/schemas/AuthenticationValue' - description: Authentication value. - required: - - authentication - - authenticationValue - AuthenticationValue: - title: AuthenticationValue - oneOf: - - $ref: '#/components/schemas/OtpValue' - - $ref: '#/components/schemas/QRCODE' - - $ref: '#/components/schemas/U2FPinValue' - description: Contains the authentication value. The format depends on the authentication type used in the AuthenticationInfo complex type. - AuthorizationsIDPutResponse: - title: AuthorizationsIDPutResponse - type: object - description: The object sent in the PUT /authorizations/{ID} callback. - properties: - authenticationInfo: - allOf: - - $ref: '#/components/schemas/AuthenticationInfo' - description: OTP or QR Code or U2F if entered, otherwise empty. - responseType: - type: string - description: > - Enum containing response information; if the customer entered the authentication value, rejected the transaction, or - requested a resend of the authentication value. - example: ENTERED - required: - - responseType - AuthorizationsPostRequest: - title: AuthorizationsPostRequest - type: object - description: POST /authorizations Request object - properties: - authenticationType: - allOf: - - $ref: '#/components/schemas/AuthenticationType' - description: This value is a valid authentication type from the enumeration AuthenticationType(OTP or QR Code or U2F). - retriesLeft: - allOf: - - $ref: '#/components/schemas/RetriesLeft' - description: > - RetriesLeft is the number of retries left before the financial transaction is rejected. - It must be expressed in the form of the data type Integer. retriesLeft=1 means that this is the last retry before - the financial transaction is rejected. - amount: - allOf: - - $ref: '#/components/schemas/Money' - description: This is the transaction amount that will be withdrawn from the Payer’s account. - transactionId: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - Common ID (decided by the Payer FSP) between the FSPs for the future transaction object. - The actual transaction will be created as part of a successful transfer process. - transactionRequestId: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: The transactionRequestID, received from the POST /transactionRequests service earlier in the process. - quote: - allOf: - - $ref: '#/components/schemas/QuotesIDPutResponse' - description: Quotes object - required: - - authenticationType - - retriesLeft - - amount - - transactionId - - transactionRequestId - - quote - Consent: - title: Consent - type: object - description: Data model for the complex type Consent - properties: - id: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - Common ID between the PISP and FSP for the Consent object - decided by the DFSP who creates the Consent - - This field is REQUIRED for POST /consent - requestId: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - The id of the ConsentRequest that was used to initiate the - creation of this Consent - participantId: - allOf: - - $ref: '#/components/schemas/FspId' - description: FSP identifier who issued this Consent - initiatorId: - allOf: - - $ref: '#/components/schemas/FspId' - description: PISP identifier who uses this Consent - scopes: - type: array - items: - $ref: '#/components/schemas/Scope' - credential: - $ref: '#/components/schemas/Credential' - ConsentRequest: - title: ConsentRequest - type: object - description: Data model for the complex type ConsentRequest - properties: - id: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - Common ID between the PISP and FSP for the ConsentRequest object decided by the PISP. - The ID should be reused for resends of the same ConsentRequest. - A new ID should be generated for each new ConsentRequest. - This field is REQUIRED for POST /consentRequest - initiatorId: - allOf: - - $ref: '#/components/schemas/FspId' - description: PISP identifier who initiated this ConsentRequest - accountIds: - type: array - minItems: 1 - description: Array of DFSP specific account identifiers, e.g. `dfspa.alice.1234` - items: - $ref: '#/components/schemas/AccountId' - authChannels: - type: array - minItems: 1 - items: - $ref: '#/components/schemas/AuthChannelsEnum' - description: A list of requested authorization channels. - scopes: - type: array - minItems: 1 - items: - $ref: '#/components/schemas/AuthScopesEnum' - description: A list of the requested scopes a PISP would like for the given Consent - callbackUri: - type: string - # TODO: this should be a uri type with a regex to enforce - description: > - When using the WEB auth channel, `callbackUri` determines where the webpage should - redirect at the end of the WEB login flow. This field is REQUIRED when authChannels - contains at least 'WEB' - authorizationUri: - type: string - # TODO: this should be a uri type with a regex to enforce - description: > - `authorizationUri` tells the PISP where to direct their user to perform the WEB login flow. - For OTP based authorization, this will be set to the same value as the `callbackUri`, to inform - the PISP to prompt their user for an OTP. - authToken: - type: string - description: | - The token issued to the PISP at the end of the auth flow. The PISP must fill out this value. - - - For the WEB auth flow this token will be passed in along as a URL parameter in the `callbackUri`. - - - For the OTP auth flow, this will be the otp value delivered to the user directly from the DFSP - required: - - id - - initiatorId - - authChannels - - scopes - Credential: - title: Credential - type: object - description: > - A credential used to allow a user to prove their identity and access to an account with a DFSP - properties: - id: - type: string - description: The id of a Credential - type: - $ref: '#/components/schemas/CredentialTypeEnum' - status: - $ref: '#/components/schemas/CredentialStatusEnum' - challenge: - $ref: '#/components/schemas/CredentialChallenge' - payload: - type: string - description: Base64 encoded bytes - The public key of the Public/Private keypair - required: - - id - - credentialType - - credentialStatus - CreateCredential: - title: CreateCredential - type: object - description: > - A credential used to allow a user to prove their identity and access to an account with a DFSP - properties: - type: - $ref: '#/components/schemas/CredentialTypeEnum' - required: - - credentialType - CredentialChallenge: - title: CredentialChallenge - type: object - description: > - The challenge issued by a DFSP that must be answered by the PISP - properties: - payload: - type: string - description: Base64 encoded binary of the challenge that must be answered by the PISP - signature: - type: string - description: Base64 enoded binary string or result of the payload signed by the PISP using the private key - required: - - payload - ErrorInformation: - title: ErrorInformation - type: object - description: Data model for the complex type ErrorInformation. - properties: - errorCode: - allOf: - - $ref: '#/components/schemas/ErrorCode' - description: Specific error number. - errorDescription: - allOf: - - $ref: '#/components/schemas/ErrorDescription' - description: Error description string. - extensionList: - allOf: - - $ref: '#/components/schemas/ExtensionList' - description: Optional list of extensions, specific to deployment. - required: - - errorCode - - errorDescription - ErrorInformationObject: - title: ErrorInformationObject - type: object - description: Data model for the complex type object that contains ErrorInformation. - properties: - errorInformation: - $ref: '#/components/schemas/ErrorInformation' - required: - - errorInformation - ErrorInformationResponse: - title: ErrorInformationResponse - type: object - description: > - Data model for the complex type object that contains an optional element - ErrorInformation used along with 4xx and 5xx responses. - properties: - errorInformation: - $ref: '#/components/schemas/ErrorInformation' - Extension: - title: Extension - type: object - description: Data model for the complex type Extension - properties: - key: - allOf: - - $ref: '#/components/schemas/ExtensionKey' - description: Extension key. - value: - allOf: - - $ref: '#/components/schemas/ExtensionValue' - description: Extension value. - required: - - key - - value - ExtensionList: - title: ExtensionList - type: object - description: Data model for the complex type ExtensionList - properties: - extension: - type: array - items: - $ref: '#/components/schemas/Extension' - minItems: 1 - maxItems: 16 - description: Number of Extension elements - required: - - extension - GeoCode: - title: GeoCode - type: object - description: > - Data model for the complex type GeoCode. Indicates the geographic location from where the transaction was initiated. - properties: - latitude: - type: string - description: Latitude of the Party. - example: '+45.4215' - longitude: - type: string - description: Longitude of the Party. - example: '+75.6972' - required: - - latitude - - longitude - Money: - title: Money - type: object - description: Data model for the complex type Money. - properties: - currency: - $ref: '#/components/schemas/Currency' - amount: - $ref: '#/components/schemas/Amount' - required: - - currency - - amount - Party: - title: Party - type: object - description: Data model for the complex type Party. - properties: - accounts: - allOf: - - $ref: '#/components/schemas/AccountList' - description: List of accounts associated with the party containing and DFSP routable address, currency identifier and description. - partyIdInfo: - allOf: - - $ref: '#/components/schemas/PartyIdInfo' - description: Party Id type, id, sub ID or type, and FSP Id. - merchantClassificationCode: - type: string - description: Used in the context of Payee Information, where the Payee happens to be a merchant accepting merchant payments. - example: 4321 - name: - type: string - description: Display name of the Party, could be a real name or a nick name. - example: Henrik Karlsson - personalInfo: - allOf: - - $ref: '#/components/schemas/PartyPersonalInfo' - description: Personal information used to verify identity of Party such as first, middle, last name and date of birth. - required: - - partyIdInfo - PartyComplexName: - title: PartyComplexName - type: object - description: Data model for the complex type PartyComplexName. - properties: - firstName: - type: string - description: Party’s first name. - example: Henrik - middleName: - type: string - description: Party’s middle name. - example: Johannes - lastName: - type: string - description: Party’s last name. - example: Karlsson - PartyIdInfo: - title: PartyIdInfo - type: object - description: Data model for the complex type PartyIdInfo. - properties: - partyIdType: - type: string - description: Type of the identifier. - example: PERSONAL_ID - partyIdentifier: - type: string - description: An identifier for the Party. - example: 16135551212 - partySubIdOrType: - type: string - description: A sub-identifier or sub-type for the Party. - example: DRIVING_LICENSE - fspId: - type: string - description: FSP ID (if known). - example: 1234 - required: - - partyIdType - - partyIdentifier - PartyPersonalInfo: - title: PartyPersonalInfo - type: object - description: Data model for the complex type PartyPersonalInfo. - properties: - complexName: - allOf: - - $ref: '#/components/schemas/PartyComplexName' - description: First, middle and last name for the Party. - dateOfBirth: - type: string - description: Date of birth for the Party. - example: '1966-06-16' - PartiesTypeIDPutResponse: - title: PartiesTypeIDPutResponse - type: object - description: The object sent in the PUT /parties/{Type}/{ID} callback. - properties: - party: - allOf: - - $ref: '#/components/schemas/Party' - description: Information regarding the requested Party. - required: - - party - QuotesIDPutResponse: - title: QuotesIDPutResponse - type: object - description: The object sent in the PUT /quotes/{ID} callback. - properties: - transferAmount: - allOf: - - $ref: '#/components/schemas/Money' - description: The amount of money that the Payee FSP should receive. - type: object - payeeReceiveAmount: - allOf: - - $ref: '#/components/schemas/Money' - description: > - The amount of Money that the Payee should receive in the end-to-end transaction. - Optional as the Payee FSP might not want to disclose any optional Payee fees. - type: object - payeeFspFee: - allOf: - - $ref: '#/components/schemas/Money' - description: Payee FSP’s part of the transaction fee. - type: object - payeeFspCommission: - allOf: - - $ref: '#/components/schemas/Money' - description: Transaction commission from the Payee FSP. - type: object - expiration: - type: string - description: Date and time until when the quotation is valid and can be honored when used in the subsequent transaction. - example: '2016-05-24T08:38:08.699-04:00' - geoCode: - allOf: - - $ref: '#/components/schemas/GeoCode' - description: Longitude and Latitude of the Payee. Can be used to detect fraud. - ilpPacket: - type: string - description: The ILP Packet that must be attached to the transfer by the Payer. - example: “AYIBgQAAAAAAAASwNGxldmVsb25lLmRmc3AxLm1lci45T2RTOF81MDdqUUZERmZlakgyOVc4bXFmNEpLMHlGTFGCAUBQU0svMS4wCk5vbmNlOiB1SXlweUYzY3pYSXBFdzVVc05TYWh3CkVuY3J5cHRpb246IG5vbmUKUGF5bWVudC1JZDogMTMyMzZhM2ItOGZhOC00MTYzLTg0NDctNGMzZWQzZGE5OGE3CgpDb250ZW50LUxlbmd0aDogMTM1CkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbgpTZW5kZXItSWRlbnRpZmllcjogOTI4MDYzOTEKCiJ7XCJmZWVcIjowLFwidHJhbnNmZXJDb2RlXCI6XCJpbnZvaWNlXCIsXCJkZWJpdE5hbWVcIjpcImFsaWNlIGNvb3BlclwiLFwiY3JlZGl0TmFtZVwiOlwibWVyIGNoYW50XCIsXCJkZWJpdElkZW50aWZpZXJcIjpcIjkyODA2MzkxXCJ9IgA” - condition: - type: string - description: The condition that must be attached to the transfer by the Payer. - example: f5sqb7tBTWPd5Y8BDFdMm9BJR_MNI4isf8p8n4D5pHA - extensionList: - allOf: - - $ref: '#/components/schemas/ExtensionList' - description: Optional extension, specific to deployment. - required: - - transferAmount - - expiration - - ilpPacket - - condition - Refund: - title: Refund - type: object - description: Data model for the complex type Refund. - properties: - originalTransactionId: - type: string - description: Reference to the original transaction ID that is requested to be refunded. - example: b51ec534-ee48-4575-b6a9-ead2955b8069 - refundReason: - type: string - description: Free text indicating the reason for the refund. - example: Free text indicating reason for the refund. - required: - - originalTransactionId - Scope: - title: Scope - type: object - description: Scope + Account Identifier mapping for a Consent - properties: - scope: - $ref: '#/components/schemas/AuthScopesEnum' - accountId: - $ref: '#/components/schemas/AccountId' - required: - - scope - - accountId - ThirdpartyTransactionRequest: - title: ThirdpartyTransactionRequest - type: object - description: The object sent in the POST /thirdpartyRequests/transactions request. - properties: - transactionRequestId: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - Common ID between the FSPs for the transaction request object. - The ID should be reused for resends of the same transaction request. - A new ID should be generated for each new transaction request. - sourceAccountId: - allOf: - - $ref: '#/components/schemas/AccountId' - description: DFSP specific account identifiers, e.g. `dfspa.alice.1234` - consentId: - allOf: - - $ref: '#/components/schemas/CorrelationId' - description: > - Common ID between the PISP and FSP for the Consent object - This tells DFSP and auth-service which constent allows the PISP to initiate transaction. - payee: - allOf: - - $ref: '#/components/schemas/Party' - description: Information about the Payee in the proposed financial transaction. - payer: - allOf: - - $ref: '#/components/schemas/Party' - description: Information about the Payer in the proposed financial transaction. - amountType: - allOf: - - $ref: '#/components/schemas/AmountType' - description: SEND for sendAmount, RECEIVE for receiveAmount. - amount: - allOf: - - $ref: '#/components/schemas/Money' - description: Requested amount to be transferred from the Payer to Payee. - transactionType: - allOf: - - $ref: '#/components/schemas/TransactionType' - description: Type of transaction. - expiration: - type: string - description: > - Date and time until when the transaction request is valid. - It can be set to get a quick failure in case the peer FSP takes too long to respond. - example: '2016-05-24T08:38:08.699-04:00' - required: - - transactionRequestId - - sourceAccountId - - consentId - - payee - - payer - - amountType - - amount - - transactionType - - expiration - TransactionType: - title: TransactionType - type: object - description: Data model for the complex type TransactionType. - properties: - scenario: - type: string - description: Deposit, withdrawal, refund, … - example: DEPOSIT - subScenario: - type: string - description: Possible sub-scenario, defined locally within the scheme. - example: Locally defined sub-scenario. - initiator: - type: string - description: Who is initiating the transaction - Payer or Payee. - example: PAYEE - initiatorType: - type: string - description: Consumer, agent, business, … - example: CONSUMER - refundInfo: - allOf: - - $ref: '#/components/schemas/Refund' - description: Extra information specific to a refund scenario. Should only be populated if scenario is REFUND. - balanceOfPayments: - type: string - description: Balance of Payments code. - example: 123 - required: - - scenario - - initiator - - initiatorType - - responses: - 200: - description: OK - 202: - description: Accepted - 400: - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 401: - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 403: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 404: - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 405: - description: Method Not Allowed - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 406: - description: Not Acceptable - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 501: - description: Not Implemented - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - 503: - description: Service Unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorInformationResponse' - headers: - Content-Length: - $ref: '#/components/parameters/Content-Length' - Content-Type: - $ref: '#/components/parameters/Content-Type' - - parameters: - #Header parameters - Accept: - name: Accept - in: header - required: true - schema: - type: string - description: The `Accept` header field indicates the version of the API the client would like the server to use. - Content-Length: - name: Content-Length - in: header - required: false - schema: - type: integer - description: > - The `Content-Length` header field indicates the anticipated size of the payload body. - Only sent if there is a body.Note-The API supports a maximum size of 5242880 bytes (5 Megabytes). - Content-Type: - name: Content-Type - in: header - schema: - type: string - required: true - description: The `Content-Type` header indicates the specific version of the API used to send the payload body. - Date: - name: Date - in: header - schema: - type: string - required: true - description: The `Date` header field indicates the date when the request was sent. - X-Forwarded-For: - name: X-Forwarded-For - in: header - schema: - type: string - required: false - description: > - The `X-Forwarded-For` header field is an unofficially accepted standard used for informational purposes of the originating - client IP address, as a request might pass multiple proxies, firewalls, and so on. Multiple `X-Forwarded-For` values should - be expected and supported by implementers of the API. - Note-An alternative to `X-Forwarded-For` is defined in [RFC 7239](https://tools.ietf.org/html/rfc7239). - However, to this point RFC 7239 is less-used and supported than `X-Forwarded-For`. - FSPIOP-Source: - name: FSPIOP-Source - in: header - schema: - type: string - required: true - description: > - The `FSPIOP-Source` header field is a non-HTTP standard field used by the API for identifying the sender of the HTTP request. - The field should be set by the original sender of the request. - Required for routing and signature verification (see header field `FSPIOP-Signature`). - FSPIOP-Destination: - name: FSPIOP-Destination - in: header - schema: - type: string - required: false - description: > - The `FSPIOP-Destination` header field is a non-HTTP standard field used by the API - for HTTP header based routing of requests and responses to the destination. - The field should be set by the original sender of the request (if known), so that any entities between the client and - the server do not need to parse the payload for routing purposes. - FSPIOP-Encryption: - name: FSPIOP-Encryption - in: header - schema: - type: string - required: false - description: > - The `FSPIOP-Encryption` header field is a non-HTTP standard field used by the API - for applying end-to-end encryption of the request. - FSPIOP-Signature: - name: FSPIOP-Signature - in: header - schema: - type: string - required: false - description: > - The `FSPIOP-Signature` header field is a non-HTTP standard field used by the API - for applying an end-to-end request signature. - FSPIOP-URI: - name: FSPIOP-URI - in: header - schema: - type: string - required: false - description: > - The `FSPIOP-URI` header field is a non-HTTP standard field used by the API for signature verification, - should contain the service URI. Required if signature verification is used, for more information, - see [the API Signature document](https://github.com/mojaloop/docs/tree/master/Specification%20Document%20Set). - FSPIOP-HTTP-Method: - name: FSPIOP-HTTP-Method - in: header - schema: - type: string - required: false - description: > - The `FSPIOP-HTTP-Method` header field is a non-HTTP standard field used by the API for signature verification, - should contain the service HTTP method. Required if signature verification is used, for more information, - see [the API Signature document](https://github.com/mojaloop/docs/tree/master/Specification%20Document%20Set). - #Path parameters - ID: - name: ID - in: path - required: true - schema: - type: string - description: The identifier value. - Type: - name: Type - in: path - required: true - schema: - type: string - description: The type of the party identifier. For example, `MSISDN`, `PERSONAL_ID`. - SubId: - name: SubId - in: path - required: true - schema: - type: string - description: > - A sub-identifier of the party identifier, or a sub-type of the party identifier's type. - For example, `PASSPORT`, `DRIVING_LICENSE`. diff --git a/src/interface/types.ts b/src/interface/types.ts index 2dc2ec3..98a1da6 100644 --- a/src/interface/types.ts +++ b/src/interface/types.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation The Mojaloop files are made available by the Bill - & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not + Copyright © 2020 Mojaloop Foundation The Mojaloop files are made available by the Mojaloop Foundation + under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS diff --git a/src/server/create.ts b/src/server/create.ts index 4c8a08b..bfeeff2 100644 --- a/src/server/create.ts +++ b/src/server/create.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/handlers/index.ts b/src/server/handlers/index.ts index 2dc962e..66b36f5 100644 --- a/src/server/handlers/index.ts +++ b/src/server/handlers/index.ts @@ -26,12 +26,29 @@ import { Util } from '@mojaloop/central-services-shared' import Health from './health' import Metrics from './metrics' import ThirdpartyTransactions from './thirdpartyRequests/transactions' +import Authorizations from './thirdpartyRequests/transactions/{ID}/authorizations' +import { wrapWithHistogram } from '~/shared/histogram' const OpenapiBackend = Util.OpenapiBackend export default { HealthGet: Health.get, MetricsGet: Metrics.get, - CreateThirdpartyTransactionRequests: ThirdpartyTransactions.post, + CreateThirdpartyTransactionRequests: wrapWithHistogram( + ThirdpartyTransactions.post, + [ + 'thirdpartyRequests_transactions_post', + 'Post thirdpartyRequests transactions request', + ['success'] + ] + ), + VerifyThirdPartyAuthorization: wrapWithHistogram( + Authorizations.post, + [ + 'thirdpartyRequests_transactions_authorizations_post', + 'Post thirdpartyRequests transactions authorizations request', + ['success'] + ] + ), validationFail: OpenapiBackend.validationFail, notFound: OpenapiBackend.notFound, methodNotAllowed: OpenapiBackend.methodNotAllowed diff --git a/src/server/handlers/onPreHandler.ts b/src/server/handlers/onPreHandler.ts index ea9a509..f495f8f 100644 --- a/src/server/handlers/onPreHandler.ts +++ b/src/server/handlers/onPreHandler.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/handlers/onValidateFail.ts b/src/server/handlers/onValidateFail.ts index 25b02a9..44d9137 100644 --- a/src/server/handlers/onValidateFail.ts +++ b/src/server/handlers/onValidateFail.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/handlers/thirdpartyRequests/transactions.ts b/src/server/handlers/thirdpartyRequests/transactions.ts index a5a866d..89daa0f 100644 --- a/src/server/handlers/thirdpartyRequests/transactions.ts +++ b/src/server/handlers/thirdpartyRequests/transactions.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation The Mojaloop files are made available by the Bill - & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not + Copyright © 2020 Mojaloop Foundation The Mojaloop files are made available by the Mojaloop Foundation + under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS @@ -29,28 +29,21 @@ import Logger from '@mojaloop/central-services-logger' import { ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' import { Enum } from '@mojaloop/central-services-shared' import { AuditEventAction } from '@mojaloop/event-sdk' -import Metrics from '@mojaloop/central-services-metrics' import { Transactions } from '~/domain/thirdpartyRequests' import { getSpanTags } from '~/shared/util' import * as types from '~/interface/types' /** * summary: CreateThirdpartyTransactionRequests - * description: The HTTP request POST /thirdpartyRequests/transactions is used to creation of a transaction request + * description: The HTTP request POST /thirdpartyRequests/transactions is used to creation of a transaction request * for the provided financial transaction in the server. * parameters: body, accept, content-length, content-type, date, x-forwarded-for, fspiop-source, * fspiop-destination, fspiop-encryption,fspiop-signature, fspiop-urifspiop-http-method - * produces: application/json + * produces: application/json * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 */ // eslint-disable-next-line @typescript-eslint/no-explicit-any const post = async (_context: any, request: Request, h: ResponseToolkit): Promise => { - - const histTimerEnd = Metrics.getHistogram( - 'thirdpartyRequests_transactions_post', - 'Post thirdpartyRequests transactions request', - ['success'] - ).startTimer() const span = (request as any).span try { @@ -59,32 +52,37 @@ const post = async (_context: any, request: Request, h: ResponseToolkit): Promis request, Enum.Events.Event.Type.TRANSACTION_REQUEST, Enum.Events.Event.Action.POST, - { transactionId: payload.transactionRequestId }) - + { transactionRequestId: payload.transactionRequestId }) + span?.setTags(tags) await span?.audit({ headers: request.headers, payload: request.payload }, AuditEventAction.start) - // not waiting for a promise + // Note: calling async function without `await` Transactions.forwardTransactionRequest( Enum.EndPoints.FspEndpointTemplates.THIRDPARTY_TRANSACTION_REQUEST_POST, request.headers, Enum.Http.RestMethods.POST, request.params, - request.payload as types.ThirdPartyTransactionRequest) + payload, + span + ) + .catch(err => { + // Do nothing with the error - forwardTransactionRequest takes care of async errors + Logger.error('Transactions::post - forwardTransactionRequest async handler threw an unhandled error') + Logger.error(ReformatFSPIOPError(err)) + }) - histTimerEnd({ success: 'true' }) return h.response().code(Enum.Http.ReturnCodes.ACCEPTED.CODE) } catch (err) { const fspiopError = ReformatFSPIOPError(err) Logger.error(fspiopError) - histTimerEnd({ success: 'false' }) throw fspiopError } } export default { post -} \ No newline at end of file +} diff --git a/src/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.ts b/src/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.ts new file mode 100644 index 0000000..0b3e675 --- /dev/null +++ b/src/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.ts @@ -0,0 +1,89 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Lewis Daly + + -------------- + ******/ + +import { Request, ResponseToolkit, ResponseObject } from '@hapi/hapi' +import { Enum } from '@mojaloop/central-services-shared' +import { ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' +import Logger from '@mojaloop/central-services-logger' +import { AuditEventAction } from '@mojaloop/event-sdk' + +import { Authorizations } from '~/domain/thirdpartyRequests' +import { getSpanTags } from '~/shared/util' + + +/** + * summary: VerifyThirdPartyAuthorization + * description: The method POST /thirdpartyRequests/transactions/{ID}/authorizations is used + * by the DFSP to verify an authorization result with the Auth-Service. + * parameters: body, content-length + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function post(_context: any, request: Request, h: ResponseToolkit): Promise { + const span = (request as any).span + // Trust that hapi parsed the ID and Payload for us + const transactionRequestId: string = request.params.ID + const payload = request.payload as Authorizations.PostAuthorizationPayload + + try { + const tags: { [id: string]: string } = getSpanTags( + request, + Enum.Events.Event.Type.AUTHORIZATION, + Enum.Events.Event.Action.POST, + { transactionRequestId }) + + span?.setTags(tags) + await span?.audit({ + headers: request.headers, + payload: request.payload + }, AuditEventAction.start) + + // Note: calling async function without `await` + Authorizations.forwardPostAuthorization( + Enum.EndPoints.FspEndpointTemplates.THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_POST, + request.headers, + transactionRequestId, + payload, + span + ) + .catch(err => { + // Do nothing with the error - forwardPostAuthorization takes care of async errors + Logger.error('Authorizations::post - forwardPostAuthorization async handler threw an unhandled error') + Logger.error(ReformatFSPIOPError(err)) + }) + + return h.response().code(Enum.Http.ReturnCodes.ACCEPTED.CODE) + } catch (err) { + const fspiopError = ReformatFSPIOPError(err) + Logger.error(fspiopError) + throw fspiopError + } +} + +export default { + post +} + diff --git a/src/server/index.ts b/src/server/index.ts index e37e783..c818d22 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -2,8 +2,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/plugins/good.ts b/src/server/plugins/good.ts index a352823..b420504 100644 --- a/src/server/plugins/good.ts +++ b/src/server/plugins/good.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/run.ts b/src/server/run.ts index 42b2b80..9b81378 100644 --- a/src/server/run.ts +++ b/src/server/run.ts @@ -2,8 +2,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/server/start.ts b/src/server/start.ts index 07698a9..f41fd08 100644 --- a/src/server/start.ts +++ b/src/server/start.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/shared/config.ts b/src/shared/config.ts index 15eba94..b8abe62 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -46,7 +46,7 @@ export interface ServiceConfig { expiresIn: number; generateTimeout: number; }; - SWITCH_ENDPOINT: string; + ENDPOINT_SERVICE_URL: string; ERROR_HANDLING: { includeCauseExtension: boolean; truncateExtensions: boolean; diff --git a/src/shared/histogram.ts b/src/shared/histogram.ts new file mode 100644 index 0000000..cbf4ba5 --- /dev/null +++ b/src/shared/histogram.ts @@ -0,0 +1,34 @@ +import { Request, ResponseToolkit, ResponseObject } from '@hapi/hapi' +import Metrics from '@mojaloop/central-services-metrics' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type THandlerFunc = (_context: any, request: Request, h: ResponseToolkit) => Promise + +/** + * @function wrapWithHistogram + * @description Wraps a handler function with a histogram of the given name + * @param {THandlerFunc} handler The handler function to be wrapped + * @param {[string, string, Array]} histogramParams The params of the histogram + */ +function wrapWithHistogram (handler: THandlerFunc, histogramParams: [string, string, string[]]): THandlerFunc { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return async (_context: any, request: Request, h: ResponseToolkit) => { + let histTimerEnd + try { + histTimerEnd = Metrics.getHistogram(...histogramParams).startTimer() + const response = await handler(_context, request, h) + histTimerEnd({ success: 'true' }) + + return response + } catch (err) { + if (typeof histTimerEnd === 'function') { + histTimerEnd({ success: 'false' }) + } + throw err + } + } +} + +export { + wrapWithHistogram +} diff --git a/src/shared/inspect.ts b/src/shared/inspect.ts index 5989cac..d4ed656 100644 --- a/src/shared/inspect.ts +++ b/src/shared/inspect.ts @@ -2,8 +2,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/shared/logger.ts b/src/shared/logger.ts index a86312d..af60d06 100644 --- a/src/shared/logger.ts +++ b/src/shared/logger.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/src/shared/util.ts b/src/shared/util.ts index d3f0605..b505f15 100644 --- a/src/shared/util.ts +++ b/src/shared/util.ts @@ -2,8 +2,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -27,6 +27,24 @@ import Hapi from '@hapi/hapi' import util from 'util' import { Enum } from '@mojaloop/central-services-shared' +import { EventStateMetadata, EventStatusType } from '@mojaloop/event-sdk' +import { FSPIOPError } from '@mojaloop/central-services-error-handling' + +/** + * @function finishChildSpan + * @description Helper function for reporting errors to a childSpan, and then finishing it + * @param {FSPIOPError} fspiopError error object + * @param {any} span request span + * @returns {Promise} + */ +async function finishChildSpan (fspiopError: FSPIOPError, childSpan: any): Promise { + const state = new EventStateMetadata( + EventStatusType.failed, + fspiopError.apiErrorCode.code, + fspiopError.apiErrorCode.message) + await childSpan.error(fspiopError, state) + await childSpan.finish(fspiopError.message, state) +} /** * @function getStackOrInspect @@ -57,6 +75,7 @@ function getSpanTags (request: Hapi.Request, eventType: string, eventAction: str } export { + finishChildSpan, getStackOrInspect, getSpanTags } diff --git a/test/features/template.scenario.feature b/test/features/health.scenario.feature similarity index 100% rename from test/features/template.scenario.feature rename to test/features/health.scenario.feature diff --git a/test/features/transactionRequests.feature b/test/features/transactionRequests.feature index 8147359..ab9b541 100644 --- a/test/features/transactionRequests.feature +++ b/test/features/transactionRequests.feature @@ -5,3 +5,7 @@ Scenario: CreateThirdpartyTransactionRequests When I get 'CreateThirdpartyTransactionRequests' response Then The status code should be '202' +Scenario: CreateThirdpartyTransactionRequestAuthorization + Given thirdparty-api-adapter server + When I send a 'CreateThirdpartyTransactionRequestAuthorization' request + Then I get a response with a status code of '202' diff --git a/test/step-definitions/template.step.ts b/test/step-definitions/health.step.ts similarity index 94% rename from test/step-definitions/template.step.ts rename to test/step-definitions/health.step.ts index d4f1e62..e837c72 100644 --- a/test/step-definitions/template.step.ts +++ b/test/step-definitions/health.step.ts @@ -5,7 +5,7 @@ import Config from '~/shared/config' import ThirdPartyAPIAdapterService from '~/server' -const featurePath = path.join(__dirname, '../features/template.scenario.feature') +const featurePath = path.join(__dirname, '../features/health.scenario.feature') const feature = loadFeature(featurePath) defineFeature(feature, (test): void => { diff --git a/test/step-definitions/transactionRequests.step.ts b/test/step-definitions/transactionRequests.step.ts index 56fa857..154327a 100644 --- a/test/step-definitions/transactionRequests.step.ts +++ b/test/step-definitions/transactionRequests.step.ts @@ -4,13 +4,14 @@ import { Server, ServerInjectResponse } from '@hapi/hapi' import Config from '~/shared/config' import ThirdPartyAPIAdapterService from '~/server' -import { Transactions } from '~/domain/thirdpartyRequests' +import { Transactions, Authorizations } from '~/domain/thirdpartyRequests' import TestData from 'test/unit/data/mockData.json' const featurePath = path.join(__dirname, '../features/transactionRequests.feature') const feature = loadFeature(featurePath) const mockForwardTransactionRequest = jest.spyOn(Transactions, 'forwardTransactionRequest') +const mockForwardAuthorizationPost = jest.spyOn(Authorizations, 'forwardPostAuthorization') const mockData = JSON.parse(JSON.stringify(TestData)) defineFeature(feature, (test): void => { @@ -39,19 +40,73 @@ defineFeature(feature, (test): void => { method: 'POST', url: '/thirdpartyRequests/transactions', headers: reqHeaders, - payload: mockData.transactionRequest.payload + payload: mockData.transactionRequest.payload, + } response = await server.inject(request) return response }) then('The status code should be \'202\'', (): void => { - const expected = ['/thirdpartyRequests/transactions', expect.any(Object), 'POST', {}, - mockData.transactionRequest.payload] + const expected = [ + '/thirdpartyRequests/transactions', + expect.any(Object), + 'POST', + {}, + mockData.transactionRequest.payload, + expect.any(Object) + ] expect(response.statusCode).toBe(202) expect(response.result).toBeNull() expect(mockForwardTransactionRequest).toHaveBeenCalledWith(...expected) }) }) + + test('CreateThirdpartyTransactionRequestAuthorization', ({ given, when, then }): void => { + const reqHeaders = { + ...mockData.transactionRequest.headers, + date: (new Date()).toISOString(), + 'fspiop-source': 'dfspA', + 'fspiop-destination': 'dfspA', + accept: 'application/json' + } + const request = { + method: 'POST', + url: '/thirdpartyRequests/transactions/7d34f91d-d078-4077-8263-2c047876fcf6/authorizations', + headers: reqHeaders, + payload: { + challenge: '12345', + value: '12345', + consentId: '8e34f91d-d078-4077-8263-2c047876fcf6', + sourceAccountId: 'dfspa.alice.1234', + status: 'PENDING', + } + } + + given('thirdparty-api-adapter server', async (): Promise => { + server = await ThirdPartyAPIAdapterService.run(Config) + return server + }) + + when('I send a \'CreateThirdpartyTransactionRequestAuthorization\' request', async (): Promise => { + mockForwardAuthorizationPost.mockResolvedValueOnce() + response = await server.inject(request) + return response + }) + + then('I get a response with a status code of \'202\'', (): void => { + const expected = [ + '/thirdpartyRequests/transactions/{{ID}}/authorizations', + expect.objectContaining(request.headers), + '7d34f91d-d078-4077-8263-2c047876fcf6', + request.payload, + expect.any(Object) + ] + + expect(response.result).toBeNull() + expect(response.statusCode).toBe(202) + expect(mockForwardAuthorizationPost).toHaveBeenCalledWith(...expected) + }) + }) }) diff --git a/test/unit/__mocks__/responseToolkit.ts b/test/unit/__mocks__/responseToolkit.ts new file mode 100644 index 0000000..f65a8ee --- /dev/null +++ b/test/unit/__mocks__/responseToolkit.ts @@ -0,0 +1,25 @@ +import { ResponseToolkit, ResponseObject } from '@hapi/hapi' + + + +/** + * mockResponseToolkit + * @description A mock Response toolkit for testing handler functions + * Currently only mocks out the `statusCode` parameter + */ +const mockResponseToolkit = { + response: (): ResponseObject => { + return { + code: (statusCode: number): ResponseObject => { + const response: ResponseObject = { + statusCode, + } as unknown as ResponseObject + return response + } + } as unknown as ResponseObject + } +} as unknown as ResponseToolkit + +export { + mockResponseToolkit +} diff --git a/test/unit/__mocks__/span.ts b/test/unit/__mocks__/span.ts new file mode 100644 index 0000000..7157b56 --- /dev/null +++ b/test/unit/__mocks__/span.ts @@ -0,0 +1,21 @@ +/** + * Mock Span + */ +class Span { + public child: Span | undefined + public isFinished: boolean + public constructor() { + this.isFinished = false + } + + public getChild() { + this.child = new Span() + return this.child + } + + public audit = jest.fn() + public error = jest.fn() + public finish = jest.fn() +} + +export default Span diff --git a/test/unit/__mocks__/util.ts b/test/unit/__mocks__/util.ts index edaac6a..1e3fb43 100644 --- a/test/unit/__mocks__/util.ts +++ b/test/unit/__mocks__/util.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/test/unit/cli.test.ts b/test/unit/cli.test.ts index e3efc8d..eb967be 100644 --- a/test/unit/cli.test.ts +++ b/test/unit/cli.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -42,7 +42,7 @@ describe('cli', (): void => { expiresIn: 180000, generateTimeout: 30000 }, - SWITCH_ENDPOINT: 'http://central-ledger.local:3001', + ENDPOINT_SERVICE_URL: 'http://central-ledger.local:3001', ERROR_HANDLING: { includeCauseExtension: true, truncateExtensions: true diff --git a/test/unit/domain/thirdpartyRequests/authorizations.test.ts b/test/unit/domain/thirdpartyRequests/authorizations.test.ts new file mode 100644 index 0000000..47e41cc --- /dev/null +++ b/test/unit/domain/thirdpartyRequests/authorizations.test.ts @@ -0,0 +1,218 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Lewis Daly + + -------------- + ******/ + +import { Authorizations } from '~/domain/thirdpartyRequests' +import Logger from '@mojaloop/central-services-logger' +import { + Util, Enum, +} from '@mojaloop/central-services-shared' +import { ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' +import Span from 'test/unit/__mocks__/span' + +const mockGetEndpoint = jest.spyOn(Util.Endpoints, 'getEndpoint') +const mockSendRequest = jest.spyOn(Util.Request, 'sendRequest') +const mockLoggerPush = jest.spyOn(Logger, 'push') +const mockLoggerError = jest.spyOn(Logger, 'error') + +describe('domain/authorizations', () => { + describe('forwardPostAuthorization', () => { + const path = Enum.EndPoints.FspEndpointTemplates.THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_POST + + beforeEach((): void => { + jest.clearAllMocks() + mockLoggerPush.mockReturnValue(null) + mockLoggerError.mockReturnValue(null) + }) + + it('forwards the POST `thirdpartyRequests/transactions/{id}/authorizations request', async () => { + // Arrange + mockGetEndpoint.mockResolvedValue('http://auth-service.local') + mockSendRequest.mockResolvedValue({ status: 202, payload: null }) + const headers = { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + } + const id = "123456" + const payload: Authorizations.PostAuthorizationPayload = { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + } + + const getEndpointExpected: Array = [ + 'http://central-ledger.local:3001', + 'dfspA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST + ] + const sendRequestExpected: Array = [ + 'http://auth-service.local/thirdpartyRequests/transactions/123456/authorizations', + headers, + 'pispA', + 'dfspA', + Enum.Http.RestMethods.POST, + payload, + Enum.Http.ResponseTypes.JSON, + expect.objectContaining({ isFinished: false }) + ] + const mockSpan = new Span() + + // Act + await Authorizations.forwardPostAuthorization(path, headers, id, payload, mockSpan) + + // Assert + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpected) + expect(mockSendRequest).toHaveBeenCalledWith(...sendRequestExpected) + }) + + it('handles `getEndpoint` failure', async () => { + // Arrange + mockGetEndpoint + .mockRejectedValueOnce(new Error('Cannot find endpoint')) + .mockResolvedValueOnce('http://pispA.local') + const headers = { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + } + const id = "123456" + const payload: Authorizations.PostAuthorizationPayload = { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + } + const mockSpan = new Span() + + const getEndpointExpectedFirst: Array = [ + 'http://central-ledger.local:3001', + 'dfspA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST, + ] + const getEndpointExpectedSecond: Array = [ + 'http://central-ledger.local:3001', + 'pispA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR, + ] + + // Act + const action = async () => await Authorizations.forwardPostAuthorization(path, headers, id, payload, mockSpan) + + // Assert + await expect(action).rejects.toThrow('Cannot find endpoint') + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpectedFirst) + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpectedSecond) + // Children's children in `forwardPostAuthorizationError()` + expect(mockSpan.child?.child?.finish).toHaveBeenCalledTimes(1) + expect(mockSpan.child?.child?.error).toHaveBeenCalledTimes(0) + // Children in `forwardPostAuthorization()` + expect(mockSpan.child?.finish).toHaveBeenCalledTimes(1) + expect(mockSpan.child?.error).toHaveBeenCalledTimes(1) + }) + + it('handles `getEndpoint` failure twice', async () => { + // Arrange + mockGetEndpoint.mockRejectedValue(new Error('Cannot find endpoint')) + const headers = { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + } + const id = "123456" + const payload: Authorizations.PostAuthorizationPayload = { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + } + const mockSpan = new Span() + + const getEndpointExpectedFirst: Array = [ + 'http://central-ledger.local:3001', + 'dfspA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_TRANSACTIONS_AUTHORIZATIONS_POST, + ] + const getEndpointExpectedSecond: Array = [ + 'http://central-ledger.local:3001', + 'pispA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR, + ] + + // Act + const action = async () => await Authorizations.forwardPostAuthorization(path, headers, id, payload, mockSpan) + + // Assert + await expect(action).rejects.toThrow('Cannot find endpoint') + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpectedFirst) + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpectedSecond) + }) + }) + + describe('forwardPostAuthorizationError', () => { + const path = Enum.EndPoints.FspEndpointTemplates.THIRDPARTY_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR + + beforeEach((): void => { + jest.clearAllMocks() + mockLoggerPush.mockReturnValue(null) + mockLoggerError.mockReturnValue(null) + }) + + it('forwards the POST /../authorization error', async () => { + // Arrange + mockGetEndpoint.mockResolvedValue('http://pisp.local') + mockSendRequest.mockResolvedValue({ status: 202, payload: null }) + const headers = { + 'fspiop-source': 'switch', + 'fspiop-destination': 'pispA' + } + const id = "123456" + const fspiopError = ReformatFSPIOPError(new Error('Test Error')) + const payload = fspiopError.toApiErrorObject(true, true) + const getEndpointExpected: Array = [ + 'http://central-ledger.local:3001', + 'pispA', + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_AUTHORIZATIONS_PUT_ERROR + ] + const sendRequestExpected: Array = [ + 'http://pisp.local/thirdpartyRequests/transactions/123456/authorizations/error', + headers, + 'switch', + 'pispA', + Enum.Http.RestMethods.PUT, + payload, + Enum.Http.ResponseTypes.JSON, + undefined + ] + + // Act + await Authorizations.forwardPostAuthorizationError(path, headers, id, payload) + + // Assert + expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpected) + expect(mockSendRequest).toHaveBeenCalledWith(...sendRequestExpected) + }) + }) +}) diff --git a/test/unit/domain/thirdpartyRequests/transactions.test.ts b/test/unit/domain/thirdpartyRequests/transactions.test.ts index a080ea0..7ab2808 100644 --- a/test/unit/domain/thirdpartyRequests/transactions.test.ts +++ b/test/unit/domain/thirdpartyRequests/transactions.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -28,6 +28,7 @@ import Logger from '@mojaloop/central-services-logger' import { Util, Enum } from '@mojaloop/central-services-shared' import * as ErrorHandler from '@mojaloop/central-services-error-handling' import TestData from 'test/unit/data/mockData.json' +import Span from 'test/unit/__mocks__/span' const mockGetEndpoint = jest.spyOn(Util.Endpoints, 'getEndpoint') const mockSendRequest = jest.spyOn(Util.Request, 'sendRequest') @@ -37,37 +38,12 @@ const apiPath = '/thirdpartyRequests/transactions' const MockData = JSON.parse(JSON.stringify(TestData)) const request = MockData.transactionRequest -/** - * Mock Span - */ -class Span { - public isFinished: boolean - public constructor () { - this.isFinished = false - } - - public getChild () { - return new Span() - } - - public audit () { - return jest.fn() - } - - public error () { - return jest.fn() - } - - public finish () { - return jest.fn() - } -} let MockSpan = new Span() const getEndpointExpected = [ 'http://central-ledger.local:3001', request.headers['fspiop-destination'], - Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRX_REQ_POST + Enum.EndPoints.FspEndpointTypes.THIRDPARTY_CALLBACK_URL_TRANSACTION_REQUEST_POST ] const sendRequestExpected = [ 'http://dfspa-sdk/thirdpartyRequests/transactions', @@ -77,7 +53,7 @@ const sendRequestExpected = [ Enum.Http.RestMethods.POST, request.payload, Enum.Http.ResponseTypes.JSON, - { isFinished: false } + expect.objectContaining({ isFinished: false }) ] const expectedErrorHeaders = { 'fspiop-source': Enum.Http.Headers.FSPIOP.SWITCH.value, @@ -91,7 +67,7 @@ const sendRequestErrExpected = [ Enum.Http.RestMethods.PUT, expect.any(Object), Enum.Http.ResponseTypes.JSON, - { isFinished: false } + expect.objectContaining({ isFinished: false }) ] describe('domain /thirdpartyRequests/transactions', (): void => { @@ -150,11 +126,11 @@ describe('domain /thirdpartyRequests/transactions', (): void => { expect(mockSendRequest).toHaveBeenLastCalledWith(...sendRequestErrExpected) }) - it('if destination endpoint is not found for both source and destiantion', async (): Promise => { - mockGetEndpoint.mockRejectedValue(new Error('Endpoint not found for both source and destiantion')) + it('if destination endpoint is not found for both source and destination', async (): Promise => { + mockGetEndpoint.mockRejectedValue(new Error('Endpoint not found for both source and destination')) mockSendRequest.mockResolvedValue({ ok: true, status: 202, statusText: 'Accepted', payload: null }) - await expect(Transactions.forwardTransactionRequest(apiPath, request.headers, Enum.Http.RestMethods.POST, {}, request.payload, MockSpan)).rejects.toThrowError(new RegExp('Endpoint not found for both source and destiantion')) + await expect(Transactions.forwardTransactionRequest(apiPath, request.headers, Enum.Http.RestMethods.POST, {}, request.payload, MockSpan)).rejects.toThrowError(new RegExp('Endpoint not found for both source and destination')) expect(mockGetEndpoint).toHaveBeenCalledTimes(2) expect(mockGetEndpoint).toHaveBeenCalledWith(...getEndpointExpected) diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 2c7e903..a5c35a8 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -27,11 +27,12 @@ import index from '~/index' import Config from '~/shared/config' import { Server } from '@hapi/hapi' -import { Transactions } from '~/domain/thirdpartyRequests' +import { Authorizations, Transactions } from '~/domain/thirdpartyRequests' import Logger from '@mojaloop/central-services-logger' import TestData from 'test/unit/data/mockData.json' const mockForwardTransactionRequest = jest.spyOn(Transactions, 'forwardTransactionRequest') +const mockForwardAuthorizationPost = jest.spyOn(Authorizations, 'forwardPostAuthorization') const mockLoggerPush = jest.spyOn(Logger, 'push') const mockLoggerError = jest.spyOn(Logger, 'error') const mockData = JSON.parse(JSON.stringify(TestData)) @@ -79,7 +80,14 @@ describe('index', (): void => { payload: trxnRequest.payload } - const expected = ['/thirdpartyRequests/transactions', expect.any(Object), 'POST', {}, request.payload] + const expected = [ + '/thirdpartyRequests/transactions', + expect.any(Object), + 'POST', + {}, + request.payload, + expect.any(Object) + ] const response = await server.inject(request) expect(response.statusCode).toBe(202) @@ -109,6 +117,120 @@ describe('index', (): void => { }) }) + describe('/thirdpartyRequests/transactions/{ID}/authorizations', () => { + beforeAll((): void => { + mockLoggerPush.mockReturnValue(null) + mockLoggerError.mockReturnValue(null) + }) + + beforeEach((): void => { + jest.clearAllMocks() + }) + + it('POST', async (): Promise => { + mockForwardAuthorizationPost.mockResolvedValueOnce() + const request = { + method: 'POST', + url: '/thirdpartyRequests/transactions/7d34f91d-d078-4077-8263-2c047876fcf6/authorizations', + headers: { + accept: 'application/json', + date: (new Date()).toISOString(), + 'fspiop-source': 'dfspA', + 'fspiop-destination': 'dfspA', + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '8e34f91d-d078-4077-8263-2c047876fcf6', + sourceAccountId: 'dfspa.alice.1234', + status: 'PENDING', + } + } + const expected: Array = [ + '/thirdpartyRequests/transactions/{{ID}}/authorizations', + expect.objectContaining(request.headers), + '7d34f91d-d078-4077-8263-2c047876fcf6', + request.payload, + expect.any(Object) + ] + + // Act + const response = await server.inject(request) + + // Assert + expect(response.statusCode).toBe(202) + expect(response.result).toBeNull() + expect(mockForwardAuthorizationPost).toHaveBeenCalledWith(...expected) + }) + + it('responds with a 400 when status !== PENDING', async (): Promise => { + const request = { + method: 'POST', + url: '/thirdpartyRequests/transactions/7d34f91d-d078-4077-8263-2c047876fcf6/authorizations', + headers: { + accept: 'application/json', + date: (new Date()).toISOString(), + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA', + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '8e34f91d-d078-4077-8263-2c047876fcf6', + sourceAccountId: 'dfspa.alice.1234', + status: 'VERIFIED', + } + } + const expected = { + errorInformation: { + errorCode: '3100', + errorDescription: 'Generic validation error - .requestBody.status should be equal to one of the allowed values' + } + } + + // Act + const response = await server.inject(request) + + // Assert + expect(response.statusCode).toBe(400) + expect(response.result).toStrictEqual(expected) + expect(mockForwardAuthorizationPost).not.toHaveBeenCalled() + }) + + it('requires all fields to be set', async (): Promise => { + const request = { + method: 'POST', + url: '/thirdpartyRequests/transactions/7d34f91d-d078-4077-8263-2c047876fcf6/authorizations', + headers: { + accept: 'application/json', + date: (new Date()).toISOString(), + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA', + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '8e34f91d-d078-4077-8263-2c047876fcf6', + status: 'PENDING', + } + } + const expected = { + errorInformation: { + errorCode: '3102', + errorDescription: 'Missing mandatory element - .requestBody should have required property \'sourceAccountId\'' + } + } + + // Act + const response = await server.inject(request) + + // Assert + expect(response.statusCode).toBe(400) + expect(response.result).toStrictEqual(expected) + expect(mockForwardAuthorizationPost).not.toHaveBeenCalled() + }) + }) + describe('/health', (): void => { it('GET', async (): Promise => { interface HealthResponse { @@ -132,6 +254,7 @@ describe('index', (): void => { expect(result.uptime).toBeGreaterThan(1.0) }) }) + describe('/metrics', (): void => { it('GET', async (): Promise => { const request = { diff --git a/test/unit/server/handlers/onValidateFail.test.ts b/test/unit/server/handlers/onValidateFail.test.ts index d723042..4438136 100644 --- a/test/unit/server/handlers/onValidateFail.test.ts +++ b/test/unit/server/handlers/onValidateFail.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/test/unit/server/handlers/thirdpartyRequests/transactions.test.ts b/test/unit/server/handlers/thirdpartyRequests/transactions.test.ts index 8180507..2045a8f 100644 --- a/test/unit/server/handlers/thirdpartyRequests/transactions.test.ts +++ b/test/unit/server/handlers/thirdpartyRequests/transactions.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -23,28 +23,19 @@ -------------- ******/ 'use strict' -import { ResponseObject, ResponseToolkit, Request } from '@hapi/hapi' +import { Request } from '@hapi/hapi' import Logger from '@mojaloop/central-services-logger' import Handler from '~/server/handlers/thirdpartyRequests/transactions' import { Transactions } from '~/domain/thirdpartyRequests' import TestData from 'test/unit/data/mockData.json' +import { mockResponseToolkit } from 'test/unit/__mocks__/responseToolkit' const mockForwardTransactionRequest = jest.spyOn(Transactions, 'forwardTransactionRequest') const mockLoggerPush = jest.spyOn(Logger, 'push') const mockLoggerError = jest.spyOn(Logger, 'error') const MockData = JSON.parse(JSON.stringify(TestData)) -const request: Request = MockData.transactionRequest -// @ts-ignore -const h: ResponseToolkit = { - response: (): ResponseObject => { - return { - code: (num: number): ResponseObject => { - return num as unknown as ResponseObject - } - } as unknown as ResponseObject - } -} +const request: Request = MockData.transactionRequest describe('transactions handler', (): void => { describe('POST /thirdpartyRequests/transactions', (): void => { @@ -60,17 +51,56 @@ describe('transactions handler', (): void => { it('handles a successful request', async (): Promise => { mockForwardTransactionRequest.mockResolvedValueOnce() - const expected = ['/thirdpartyRequests/transactions', request.headers, 'POST', {}, request.payload] - const response = await Handler.post(null, request, h as ResponseToolkit) - expect(response).toBe(202) + const expected = [ + '/thirdpartyRequests/transactions', + request.headers, + 'POST', + {}, + request.payload, + undefined + ] + + // Act + const response = await Handler.post(null, request, mockResponseToolkit) + + // Assert + expect(response.statusCode).toBe(202) + expect(mockForwardTransactionRequest).toHaveBeenCalledTimes(1) + expect(mockForwardTransactionRequest).toHaveBeenCalledWith(...expected) + }) + + it('handles errors in async manner', async (): Promise => { + // Arrange + const MockData = JSON.parse(JSON.stringify(TestData)) + const request: Request = MockData.transactionRequest + mockForwardTransactionRequest.mockResolvedValueOnce() + mockForwardTransactionRequest.mockRejectedValueOnce(new Error('Transactions forward Error')) + const expected = ['/thirdpartyRequests/transactions', request.headers, 'POST', {}, request.payload, undefined] + + // Act + const response = await Handler.post(null, request, mockResponseToolkit) + + // Assert + expect(response.statusCode).toBe(202) expect(mockForwardTransactionRequest).toHaveBeenCalledTimes(1) expect(mockForwardTransactionRequest).toHaveBeenCalledWith(...expected) + // Note: no promise rejection here! }) - it('handles errors', async (): Promise => { - const err = new Error('Transactions forward Error') - mockForwardTransactionRequest.mockImplementation(() => { throw err }) - await expect(Handler.post(null, request, h as ResponseToolkit)).rejects.toThrowError(new RegExp('Transactions forward Error')) + it('handles validation errors synchonously', async (): Promise => { + // Arrange + const badSpanRequest = { + ...request, + // Setting to empty span dict will cause a validation error + span: { } + } + + // Act + const action = async () => await Handler.post(null, badSpanRequest as unknown as Request, mockResponseToolkit) + + // Assert + await expect(action).rejects.toThrowError('span.setTags is not a function') + }) }) }) diff --git a/test/unit/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.test.ts b/test/unit/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.test.ts new file mode 100644 index 0000000..5c30dcc --- /dev/null +++ b/test/unit/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations.test.ts @@ -0,0 +1,148 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Lewis Daly + + -------------- + ******/ + +import {Request } from "@hapi/hapi" +import Logger from '@mojaloop/central-services-logger' + +import AuthorizationsHandler from '~/server/handlers/thirdpartyRequests/transactions/{ID}/authorizations' +import { Authorizations } from '~/domain/thirdpartyRequests' +import { mockResponseToolkit } from 'test/unit/__mocks__/responseToolkit' + +const mockForwardPostAuthorization = jest.spyOn(Authorizations, 'forwardPostAuthorization') +const mockLoggerPush = jest.spyOn(Logger, 'push') +const mockLoggerError = jest.spyOn(Logger, 'error') + +describe('authorizations handler', () => { + describe('POST /thirdpartyRequests/transactions/{ID}/authorizations', () => { + beforeEach((): void => { + jest.clearAllMocks() + mockLoggerPush.mockReturnValue(null) + mockLoggerError.mockReturnValue(null) + }) + + it('handles a successful request', async () => { + // Arrange + mockForwardPostAuthorization.mockResolvedValueOnce() + const request = { + headers: { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + }, + params: { + ID: '1234' + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + } + } + const expected: Array = [ + '/thirdpartyRequests/transactions/{{ID}}/authorizations', + request.headers, + request.params.ID, + request.payload, + undefined + ] + + // Act + const response = await AuthorizationsHandler.post(null, request as unknown as Request, mockResponseToolkit) + + // Assert + expect(response.statusCode).toBe(202) + expect(mockForwardPostAuthorization).toHaveBeenCalledWith(...expected) + }) + + it('handles errors asynchronously', async () => { + // Arrange + mockForwardPostAuthorization.mockRejectedValueOnce(new Error('Test Error')) + const request = { + headers: { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + }, + params: { + ID: '1234' + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + } + } + const expected: Array = [ + '/thirdpartyRequests/transactions/{{ID}}/authorizations', + request.headers, + request.params.ID, + request.payload, + undefined + ] + + // Act + const response = await AuthorizationsHandler.post(null, request as unknown as Request, mockResponseToolkit) + + // Assert + expect(response.statusCode).toBe(202) + // wait once more for the event loop - since we can't await `runAllImmediates` + // this helps make sure the tests don't become flaky + await new Promise(resolve => setImmediate(resolve)) + // The main test here is that there is no unhandledPromiseRejection! + expect(mockForwardPostAuthorization).toHaveBeenCalledWith(...expected) + }) + + it('handles validation errors synchnously', async () => { + // Arrange + const request = { + headers: { + 'fspiop-source': 'pispA', + 'fspiop-destination': 'dfspA' + }, + params: { + ID: '1234' + }, + payload: { + challenge: '12345', + value: '12345', + consentId: '12345', + sourceAccountId: 'dfspa.12345.67890', + status: 'PENDING', + }, + // Will setting the span to null do stuff? + span: { + } + } + + // Act + const action = async () => await AuthorizationsHandler.post(null, request as unknown as Request, mockResponseToolkit) + + // Assert + await expect(action).rejects.toThrowError('span.setTags is not a function') + }) + }) +}) diff --git a/test/unit/server/start.test.ts b/test/unit/server/start.test.ts new file mode 100644 index 0000000..2b2ae30 --- /dev/null +++ b/test/unit/server/start.test.ts @@ -0,0 +1,134 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Lewis Daly + -------------- + ******/ +import { Server } from '@hapi/hapi' +import { ServiceConfig } from '~/shared/config' + +const defaultMockConfig: ServiceConfig = { + PACKAGE: { + version: '11.0.0' + }, + PORT: 1234, + HOST: 'auth-service.local', + ENDPOINT_CACHE_CONFIG: { + expiresIn: 5000, + generateTimeout: 5000 + }, + ENDPOINT_SERVICE_URL: 'central-ledger.local', + ERROR_HANDLING: { + includeCauseExtension: true, + truncateExtensions: true, + }, + INSTRUMENTATION: { + METRICS: { + DISABLED: false, + labels: { + eventId: "*" + }, + config: { + timeout: 5000, + prefix: "moja_3p_api" + } + } + } +} + +let mockInitializeCache: any; +let mockSetupMetrics: any; + +describe('start', () => { + beforeEach(async () => { + jest.resetAllMocks() + //Make sure the Config gets re-imported + jest.resetModules() + + // Since we need to reset modules between every test + // we must also import inline the modules we need to mock + const Util = (await import('@mojaloop/central-services-shared')).Util + const Metrics = (await import('@mojaloop/central-services-metrics')).default + mockInitializeCache = jest.spyOn(Util.Endpoints, 'initializeCache') + mockSetupMetrics = jest.spyOn(Metrics, 'setup') + }) + + it('starts with Metrics Disabled', async () => { + // Arrange + jest.mock('../../../src/shared/config', () => ({ + __esModule: true, + default: { + ...defaultMockConfig, + INSTRUMENTATION: { + METRICS: { + DISABLED: true + } + } + } + })) + const { default: start } = await import('~/server/start') + + const mockServer = { + info: { + uri: 'test.com' + }, + start: jest.fn().mockResolvedValueOnce(undefined) + } as unknown as Server + + mockInitializeCache.mockResolvedValueOnce(true) + + // Act + const result = await start(mockServer) + + // Assert + expect(mockInitializeCache).toHaveBeenCalledTimes(1) + expect(mockSetupMetrics).toHaveBeenCalledTimes(0) + expect(mockServer.start).toHaveBeenCalledTimes(1) + expect(result).toStrictEqual(mockServer) + }) + + it('starts with Metrics Enabled', async () => { + // Arrange + jest.mock('../../../src/shared/config', () => ({ + __esModule: true, + default: defaultMockConfig + })) + const { default: start } = await import('~/server/start') + + const mockServer = { + info: { + uri: 'test.com' + }, + start: jest.fn().mockResolvedValueOnce(undefined) + } as unknown as Server + mockInitializeCache.mockResolvedValueOnce(true) + + // Act + const result = await start(mockServer) + + // Assert + expect(mockInitializeCache).toHaveBeenCalledTimes(1) + expect(mockSetupMetrics).toHaveBeenCalledTimes(1) + expect(mockSetupMetrics).toHaveBeenCalledWith(defaultMockConfig.INSTRUMENTATION.METRICS.config) + expect(mockServer.start).toHaveBeenCalledTimes(1) + expect(result).toStrictEqual(mockServer) + }) + +}) diff --git a/test/unit/shared/histogram.test.ts b/test/unit/shared/histogram.test.ts new file mode 100644 index 0000000..909c17a --- /dev/null +++ b/test/unit/shared/histogram.test.ts @@ -0,0 +1,134 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + - Lewis Daly + + -------------- + ******/ +'use strict' +import { Request } from '@hapi/hapi' +import Logger from '@mojaloop/central-services-logger' +import Metrics from '@mojaloop/central-services-metrics' + +import Handler from '~/server/handlers/thirdpartyRequests/transactions' +import { Transactions } from '~/domain/thirdpartyRequests' +import TestData from 'test/unit/data/mockData.json' +import { wrapWithHistogram } from '~/shared/histogram' +import { mockResponseToolkit } from '../__mocks__/responseToolkit' + +const mockForwardTransactionRequest = jest.spyOn(Transactions, 'forwardTransactionRequest') +const mockLoggerPush = jest.spyOn(Logger, 'push') +const mockLoggerError = jest.spyOn(Logger, 'error') +const mockMetrics = jest.spyOn(Metrics, 'getHistogram') +const MockData = JSON.parse(JSON.stringify(TestData)) + +const request: Request = MockData.transactionRequest + + +describe('histogram', (): void => { + describe('wrapWithHistogram', () => { + beforeAll((): void => { + mockLoggerPush.mockReturnValue(null) + mockLoggerError.mockReturnValue(null) + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('does not call the histogram twice if an error occours', async (): Promise => { + // Arrange + const mockHistTimerEnd = jest.fn() + //@ts-ignore + mockMetrics.mockReturnValue({ + startTimer: jest.fn().mockReturnValue(mockHistTimerEnd) + }) + mockForwardTransactionRequest.mockRejectedValueOnce(() => { throw new Error('Test Error') }) + const wrappedHandler = wrapWithHistogram( + Handler.post, + [ + 'thirdpartyRequests_transactions_authorizations_post', + 'Post thirdpartyRequests transactions authorizations request', + ['success'] + ] + ) + + // Act + const response = await wrappedHandler(null, request, mockResponseToolkit) + + // Assert + expect(response.statusCode).toBe(202) + expect(mockForwardTransactionRequest).toHaveBeenCalledTimes(1) + expect(mockHistTimerEnd).toHaveBeenCalledTimes(1) + expect(mockHistTimerEnd).toHaveBeenCalledWith({ success: 'true' }) + + // wait once more for the event loop - since we can't await `forwardTransactionRequest` + await new Promise(resolve => setImmediate(resolve)) + }) + + it('handles a handler error', async (): Promise => { + // Arrange + const mockHistTimerEnd = jest.fn() + //@ts-ignore + mockMetrics.mockReturnValue({ + startTimer: jest.fn().mockReturnValue(mockHistTimerEnd) + }) + const mockHandler = jest.fn().mockRejectedValueOnce(new Error('Test Error')) + + const wrappedHandler = wrapWithHistogram( + mockHandler, + [ + 'thirdpartyRequests_transactions_authorizations_post', + 'Post thirdpartyRequests transactions authorizations request', + ['success'] + ] + ) + + // Act + const action = async () => await wrappedHandler(null, request, mockResponseToolkit) + + // Assert + await expect(action).rejects.toThrow('Test Error') + expect(mockHandler).toHaveBeenCalledTimes(1) + expect(mockHistTimerEnd).toHaveBeenCalledTimes(1) + expect(mockHistTimerEnd).toHaveBeenCalledWith({ success: 'false' }) + }) + + it('throws original `Metrics.getHistogram()` error', async (): Promise => { + // Arrange + mockMetrics.mockImplementationOnce(() => { throw new Error('Test Error') }) + const mockHandler = jest.fn() + const wrappedHandler = wrapWithHistogram( + mockHandler, + [ + 'thirdpartyRequests_transactions_authorizations_post', + 'Post thirdpartyRequests transactions authorizations request', + ['success'] + ] + ) + + // Act + const action = async () => await wrappedHandler(null, request, mockResponseToolkit) + + // Assert + await expect(action).rejects.toThrow('Test Error') + }) + }) +}) diff --git a/test/unit/shared/inspect.test.ts b/test/unit/shared/inspect.test.ts index ecf132b..f1965df 100644 --- a/test/unit/shared/inspect.test.ts +++ b/test/unit/shared/inspect.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/test/unit/shared/logger.test.ts b/test/unit/shared/logger.test.ts index 9dc8a05..e810f4c 100644 --- a/test/unit/shared/logger.test.ts +++ b/test/unit/shared/logger.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors diff --git a/test/unit/shared/util.test.ts b/test/unit/shared/util.test.ts index abbf4ad..eb66f81 100644 --- a/test/unit/shared/util.test.ts +++ b/test/unit/shared/util.test.ts @@ -1,8 +1,8 @@ /***** License -------------- - Copyright © 2017 Bill & Melinda Gates Foundation - The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + Copyright © 2020 Mojaloop Foundation + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors @@ -25,8 +25,14 @@ 'use strict' import { Request } from '@hapi/hapi' -import { getStackOrInspect, getSpanTags } from '~/shared/util' +import { + finishChildSpan, + getStackOrInspect, + getSpanTags +} from '~/shared/util' import * as types from '~/interface/types' +import { FSPIOPError, ReformatFSPIOPError } from '@mojaloop/central-services-error-handling' +import { EventStateMetadata, EventStatusType } from '@mojaloop/event-sdk' const headers = { 'fspiop-source': 'pispA', @@ -34,6 +40,30 @@ const headers = { } describe('util', (): void => { + describe('finishChildSpan', (): void => { + it('calls error and finish', async (): Promise => { + // Arrange + const error: FSPIOPError = ReformatFSPIOPError(new Error('Test Error')) + const mockSpan = { + error: jest.fn(), + finish: jest.fn(), + } + const expectedState = new EventStateMetadata( + EventStatusType.failed, + error.apiErrorCode.code, + error.apiErrorCode.message + ) + + // Act + await finishChildSpan(error, mockSpan) + + // Assert + expect(mockSpan.error).toHaveBeenCalledTimes(1) + expect(mockSpan.error).toHaveBeenCalledWith(error, expectedState) + expect(mockSpan.finish).toHaveBeenCalledWith(error.message, expectedState) + }) + }) + describe('getStackOrInspect', (): void => { it('handles an error without a stack', (): void => { const input = new Error('This is a normal error') diff --git a/tsconfig.json b/tsconfig.json index 0119666..30e9d80 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -51,4 +51,4 @@ "esModuleInterop": true, "resolveJsonModule": true } -} \ No newline at end of file +}