@mojaloop/sdk-scheme-adapter 24.10.7 → 24.10.9-snapshot.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/cache/{@babel-core-npm-7.28.0-2c03249042-1c86eec8d7.zip → @babel-core-npm-7.28.3-fb967e901c-0faded84ed.zip} +0 -0
- package/.yarn/cache/@babel-generator-npm-7.28.3-1529434ded-d00d1e6b51.zip +0 -0
- package/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.28.3-de056c24da-32d01bdd60.zip +0 -0
- package/.yarn/cache/@babel-helper-module-transforms-npm-7.28.3-7b69ec189a-598fdd8aa5.zip +0 -0
- package/.yarn/cache/{@babel-helpers-npm-7.28.2-20c7a44ade-09fd7965e8.zip → @babel-helpers-npm-7.28.3-8e4849da45-6d39031bf0.zip} +0 -0
- package/.yarn/cache/@babel-parser-npm-7.28.3-8acaa30019-9fa08282e3.zip +0 -0
- package/.yarn/cache/@babel-plugin-bugfix-v8-static-class-fields-redefine-readonly-npm-7.28.3-19e1b3699f-eeacdb7fa5.zip +0 -0
- package/.yarn/cache/@babel-plugin-transform-class-static-block-npm-7.28.3-13af84b676-c0ba8f0cbf.zip +0 -0
- package/.yarn/cache/{@babel-plugin-transform-classes-npm-7.28.0-3815bda6ff-1a812a02f6.zip → @babel-plugin-transform-classes-npm-7.28.3-22fe11bcef-0aefcabe68.zip} +0 -0
- package/.yarn/cache/{@babel-plugin-transform-regenerator-npm-7.28.1-2cc4798981-45e3a63bf2.zip → @babel-plugin-transform-regenerator-npm-7.28.3-36bb1b5a59-f95e929d41.zip} +0 -0
- package/.yarn/cache/{@babel-preset-env-npm-7.28.0-964e29aeee-8814453ffe.zip → @babel-preset-env-npm-7.28.3-ec87d1a73a-b09991276a.zip} +0 -0
- package/.yarn/cache/@babel-traverse-npm-7.28.3-7786c501c7-fe521591b7.zip +0 -0
- package/.yarn/cache/{@redis-bloom-npm-5.8.0-436cff1687-a4ab9add75.zip → @redis-bloom-npm-5.8.1-139b45b8e4-f691b1dce2.zip} +0 -0
- package/.yarn/cache/{@redis-client-npm-5.8.0-3bdc0e2d9d-7db93f3e6f.zip → @redis-client-npm-5.8.1-66d46a9ca1-329d76de06.zip} +0 -0
- package/.yarn/cache/{@redis-json-npm-5.8.0-c6aea5063f-3347bfd1ec.zip → @redis-json-npm-5.8.1-1374d9e2de-9eabbf9a2c.zip} +0 -0
- package/.yarn/cache/{@redis-search-npm-5.8.0-cd8c59f8e9-055c9804f9.zip → @redis-search-npm-5.8.1-ebc7760a31-a5e12dd2c7.zip} +0 -0
- package/.yarn/cache/{@redis-time-series-npm-5.8.0-a8eada922a-98a8123a5b.zip → @redis-time-series-npm-5.8.1-1f5e30ede4-c9440ce935.zip} +0 -0
- package/.yarn/cache/@redocly-openapi-core-npm-1.34.5-6e131049a1-9ecce1975b.zip +0 -0
- package/.yarn/cache/{@typescript-eslint-eslint-plugin-npm-8.39.0-7cc58b0ab6-31f879990a.zip → @typescript-eslint-eslint-plugin-npm-8.39.1-8ad46b0385-446050aa43.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-parser-npm-8.39.0-c138f72ca9-9785994ff0.zip → @typescript-eslint-parser-npm-8.39.1-e931b25728-ff45ce7635.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-project-service-npm-8.39.0-4cecf00a1b-990ae23308.zip → @typescript-eslint-project-service-npm-8.39.1-f6db73ca22-1970633d1a.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-scope-manager-npm-8.39.0-45f3f86773-c2b232a172.zip → @typescript-eslint-scope-manager-npm-8.39.1-bf78e0253c-8874f74790.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-tsconfig-utils-npm-8.39.0-444fac8997-3457da49e7.zip → @typescript-eslint-tsconfig-utils-npm-8.39.1-e46dac00aa-38c1e19825.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-type-utils-npm-8.39.0-02f1fd51a1-3efe4001b6.zip → @typescript-eslint-type-utils-npm-8.39.1-41cbec8085-1195d65970.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-types-npm-8.39.0-c051b2516d-b08a42e8b5.zip → @typescript-eslint-types-npm-8.39.1-8cea531133-8013f4f48a.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-typescript-estree-npm-8.39.0-73bf7427a0-7e9dc461fe.zip → @typescript-eslint-typescript-estree-npm-8.39.1-eb0cf5436f-07ed9d7ab4.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-utils-npm-8.39.0-26129b3d3c-ed340f36fa.zip → @typescript-eslint-utils-npm-8.39.1-a6c63e4cf7-39bb105f26.zip} +0 -0
- package/.yarn/cache/{@typescript-eslint-visitor-keys-npm-8.39.0-76eaf78702-2eb89b9e4d.zip → @typescript-eslint-visitor-keys-npm-8.39.1-d0b0654c5b-6d4e4d0b19.zip} +0 -0
- package/.yarn/cache/openapi-backend-npm-5.14.0-231377503b-1bd3e6cb71.zip +0 -0
- package/.yarn/cache/openapi-typescript-npm-7.9.1-753f55d26c-1d2d967e40.zip +0 -0
- package/.yarn/cache/{redis-npm-5.8.0-e751103f9d-8a645185df.zip → redis-npm-5.8.1-201a0a72a3-26d97c6ddf.zip} +0 -0
- package/.yarn/cache/supports-color-npm-10.1.0-48517f80a7-28d191c4ad.zip +0 -0
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +12 -0
- package/docker/k6/package.json +17 -0
- package/docker/k6/src/test.js +43 -0
- package/docker-compose.yml +1 -0
- package/modules/api-svc/package.json +5 -5
- package/modules/api-svc/src/InboundServer/handlers.js +1 -1
- package/modules/api-svc/src/lib/cache.js +106 -0
- package/modules/api-svc/src/lib/model/InboundTransfersModel.js +5 -3
- package/modules/api-svc/src/lib/model/OutboundTransfersModel.js +93 -118
- package/modules/api-svc/src/lib/model/common/TimeoutError.js +41 -0
- package/modules/api-svc/src/lib/model/common/index.js +2 -0
- package/modules/api-svc/src/lib/model/lib/requests/backendRequests.js +2 -2
- package/modules/api-svc/test/unit/api/transfers/transfers.test.js +1 -1
- package/modules/api-svc/test/unit/inboundApi/handlers.test.js +2 -2
- package/modules/api-svc/test/unit/lib/cache.test.js +150 -0
- package/modules/api-svc/test/unit/lib/model/InboundTransfersModel.test.js +16 -32
- package/modules/api-svc/test/unit/lib/model/mockedLibRequests.js +2 -2
- package/modules/outbound-command-event-handler/package.json +5 -5
- package/modules/outbound-domain-event-handler/package.json +5 -5
- package/modules/private-shared-lib/package.json +4 -4
- package/package.json +3 -3
- package/{sbom-v24.10.6.csv → sbom-v24.10.8.csv} +36 -36
- package/.yarn/cache/@babel-plugin-bugfix-v8-static-class-fields-redefine-readonly-npm-7.27.1-424bedd466-dfa68da5f6.zip +0 -0
- package/.yarn/cache/@babel-plugin-transform-class-static-block-npm-7.27.1-a1a8a0d79f-2d49de0f5f.zip +0 -0
|
Binary file
|
package/.yarn/cache/@babel-helper-create-class-features-plugin-npm-7.28.3-de056c24da-32d01bdd60.zip
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/.yarn/cache/@babel-plugin-transform-class-static-block-npm-7.28.3-13af84b676-c0ba8f0cbf.zip
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
# Changelog: [mojaloop/sdk-scheme-adapter](https://github.com/mojaloop/sdk-scheme-adapter)
|
|
2
|
+
### [24.10.8](https://github.com/mojaloop/sdk-scheme-adapter/compare/v24.10.7...v24.10.8) (2025-08-11)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* concurrency issues ([#601](https://github.com/mojaloop/sdk-scheme-adapter/issues/601)) ([b693e2f](https://github.com/mojaloop/sdk-scheme-adapter/commit/b693e2fe618f256e4511fe9fad256f48d814ca01))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Chore
|
|
11
|
+
|
|
12
|
+
* **sbom:** update sbom [skip ci] ([63b0609](https://github.com/mojaloop/sdk-scheme-adapter/commit/63b06090017264a5d375d48bb9800ca9ae183ddd))
|
|
13
|
+
|
|
2
14
|
### [24.10.7](https://github.com/mojaloop/sdk-scheme-adapter/compare/v24.10.6...v24.10.7) (2025-08-08)
|
|
3
15
|
|
|
4
16
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "k6-tests",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "```bash docker run --rm -i grafana/k6 run --vus 1 --duration 10s - <./src/test.js ```",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "Eugen Klymniuk",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"k6": "0.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/k6": "1.1.1"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { sleep } from 'k6';
|
|
3
|
+
// import { Options } from 'k6/options';
|
|
4
|
+
|
|
5
|
+
export const options = {
|
|
6
|
+
vus: 3,
|
|
7
|
+
iterations: 10,
|
|
8
|
+
duration: '3s'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// // The default exported function is gonna be picked up by k6 as the entry point for the test script. It will be executed repeatedly in "iterations" for the whole duration of the test.
|
|
12
|
+
// export default function () {
|
|
13
|
+
// // Make a GET request to the target URL
|
|
14
|
+
// http.get('https://quickpizza.grafana.com');
|
|
15
|
+
//
|
|
16
|
+
// // Sleep for 1 second to simulate real-world usage
|
|
17
|
+
// sleep(1);
|
|
18
|
+
// }
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// const url = 'http://localhost:4000/parties/MSISDN/1233641534556178';
|
|
22
|
+
const url = 'https://quickpizza.grafana.com/api/put';
|
|
23
|
+
|
|
24
|
+
export default function () {
|
|
25
|
+
// const headers = {
|
|
26
|
+
// 'Content-Type': 'application/json',
|
|
27
|
+
// Accept: 'application/vnd.interoperability.iso20022.parties+json;version=2.0'
|
|
28
|
+
// };
|
|
29
|
+
// const data = {};
|
|
30
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
31
|
+
const data = { name: 'Bert' };
|
|
32
|
+
|
|
33
|
+
const res = http.put(url, JSON.stringify(data), { headers });
|
|
34
|
+
|
|
35
|
+
let body = {};
|
|
36
|
+
try {
|
|
37
|
+
body = JSON.parse(res.body)
|
|
38
|
+
} catch {}
|
|
39
|
+
|
|
40
|
+
console.log(body);
|
|
41
|
+
|
|
42
|
+
sleep(0.5)
|
|
43
|
+
}
|
package/docker-compose.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojaloop/sdk-scheme-adapter-api-svc",
|
|
3
|
-
"version": "21.0.0-snapshot.
|
|
3
|
+
"version": "21.0.0-snapshot.52",
|
|
4
4
|
"description": "An adapter for connecting to Mojaloop API enabled switches.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -96,13 +96,13 @@
|
|
|
96
96
|
"prom-client": "15.1.3",
|
|
97
97
|
"promise-timeout": "1.3.0",
|
|
98
98
|
"random-word-slugs": "0.1.7",
|
|
99
|
-
"redis": "5.8.
|
|
99
|
+
"redis": "5.8.1",
|
|
100
100
|
"uuidv4": "6.2.13",
|
|
101
101
|
"ws": "8.18.3"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@babel/core": "7.28.
|
|
105
|
-
"@babel/preset-env": "7.28.
|
|
104
|
+
"@babel/core": "7.28.3",
|
|
105
|
+
"@babel/preset-env": "7.28.3",
|
|
106
106
|
"@redocly/openapi-cli": "1.0.0-beta.95",
|
|
107
107
|
"@types/jest": "30.0.0",
|
|
108
108
|
"axios-mock-adapter": "2.1.0",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"jest-junit": "16.0.0",
|
|
116
116
|
"npm-check-updates": "16.7.10",
|
|
117
117
|
"openapi-response-validator": "12.1.3",
|
|
118
|
-
"openapi-typescript": "7.
|
|
118
|
+
"openapi-typescript": "7.9.1",
|
|
119
119
|
"redis-mock": "0.56.3",
|
|
120
120
|
"replace": "1.2.2",
|
|
121
121
|
"standard-version": "9.5.0",
|
|
@@ -1079,7 +1079,7 @@ const patchFxTransfersById = async (ctx) => {
|
|
|
1079
1079
|
|
|
1080
1080
|
const model = createInboundTransfersModel(ctx);
|
|
1081
1081
|
|
|
1082
|
-
const response = await model.
|
|
1082
|
+
const response = await model.sendFxPutNotificationToBackend(req.data, idValue);
|
|
1083
1083
|
|
|
1084
1084
|
// log the result
|
|
1085
1085
|
ctx.state.logger.isDebugEnabled && ctx.state.logger.push({response}).
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
'use strict';
|
|
28
28
|
|
|
29
29
|
const redis = require('redis');
|
|
30
|
+
const EventEmitter = require('events');
|
|
31
|
+
const { TimeoutError } = require('./model/common/TimeoutError');
|
|
30
32
|
|
|
31
33
|
const CONN_ST = {
|
|
32
34
|
CONNECTED: 'CONNECTED',
|
|
@@ -41,6 +43,7 @@ const CONN_ST = {
|
|
|
41
43
|
class Cache {
|
|
42
44
|
constructor(config) {
|
|
43
45
|
this._config = config;
|
|
46
|
+
this._channelEmitter = new EventEmitter();
|
|
44
47
|
|
|
45
48
|
if(!config.cacheUrl || !config.logger) {
|
|
46
49
|
throw new Error('Cache config requires cacheUrl and logger properties');
|
|
@@ -196,6 +199,109 @@ class Cache {
|
|
|
196
199
|
return id;
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Subscribes to a channel and waits for a single message with timeout support.
|
|
204
|
+
*
|
|
205
|
+
* NOTE:
|
|
206
|
+
* This implementation uses EventEmitter to handle Redis pub/sub concurrency issues
|
|
207
|
+
* that occur when multiple subscribers listen to the same channel simultaneously.
|
|
208
|
+
* It's designed to prevent race conditions in party lookups where concurrent requests
|
|
209
|
+
* for the same party ID could interfere with each other.
|
|
210
|
+
* Currently used for: Party lookup operations
|
|
211
|
+
* Future potential: This function can be extended to other scenarios and potentially
|
|
212
|
+
* replace the existing subscribe, unsubscribe, and subscribeToOneMessageWithTimer
|
|
213
|
+
* functions for a more robust and concurrency-safe pub/sub implementation.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} channel - The channel name to subscribe to
|
|
216
|
+
* @param {number} requestProcessingTimeoutSeconds - Timeout in seconds before rejecting with TimeoutError
|
|
217
|
+
* @param {boolean} [needParse=true] - Whether to JSON.parse the received message
|
|
218
|
+
*
|
|
219
|
+
* @returns {Promise<any>} Promise that resolves with the message or rejects with TimeoutError/Error
|
|
220
|
+
*/
|
|
221
|
+
async subscribeToOneMessageWithTimerNew(channel, requestProcessingTimeoutSeconds, needParse = true) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
let timeoutHandle = null;
|
|
224
|
+
let subscription = null;
|
|
225
|
+
let isResolved = false;
|
|
226
|
+
|
|
227
|
+
// Helper to safely unsubscribe from Redis channel
|
|
228
|
+
const unsubscribeFromRedis = async (reason = 'cleanup') => {
|
|
229
|
+
if (this._channelEmitter.listenerCount(channel) === 0 && this._subscriptionClient) {
|
|
230
|
+
try {
|
|
231
|
+
await this._subscriptionClient.unsubscribe(channel);
|
|
232
|
+
this._logger.push({ channel, reason }).debug('Unsubscribed from Redis channel');
|
|
233
|
+
} catch (unsubscribeErr) {
|
|
234
|
+
this._logger.push({ channel, reason }).warn('Failed to unsubscribe from Redis channel', unsubscribeErr);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Helper to clean up resources and prevent multiple resolutions
|
|
240
|
+
const cleanup = () => {
|
|
241
|
+
if (timeoutHandle) {
|
|
242
|
+
clearTimeout(timeoutHandle);
|
|
243
|
+
timeoutHandle = null;
|
|
244
|
+
}
|
|
245
|
+
if (subscription) {
|
|
246
|
+
this._channelEmitter.removeListener(channel, subscription);
|
|
247
|
+
subscription = null;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Set up timeout handler
|
|
252
|
+
timeoutHandle = setTimeout(async () => {
|
|
253
|
+
if (isResolved) return;
|
|
254
|
+
isResolved = true;
|
|
255
|
+
|
|
256
|
+
cleanup();
|
|
257
|
+
await unsubscribeFromRedis('timeout');
|
|
258
|
+
|
|
259
|
+
const errMessage = `Subscription timeout after ${requestProcessingTimeoutSeconds}s`;
|
|
260
|
+
this._logger.push({ channel, timeout: requestProcessingTimeoutSeconds }).warn(errMessage);
|
|
261
|
+
reject(new TimeoutError(errMessage));
|
|
262
|
+
}, requestProcessingTimeoutSeconds * 1000);
|
|
263
|
+
|
|
264
|
+
// Set up message handler
|
|
265
|
+
subscription = (message) => {
|
|
266
|
+
if (isResolved) return;
|
|
267
|
+
isResolved = true;
|
|
268
|
+
|
|
269
|
+
this._logger.push({ channel, needParse }).debug('Received message on subscribed channel');
|
|
270
|
+
|
|
271
|
+
cleanup();
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const result = needParse ? JSON.parse(message) : message;
|
|
275
|
+
resolve(result);
|
|
276
|
+
} catch (parseErr) {
|
|
277
|
+
this._logger.push({ channel, message }).error('Failed to parse received message', parseErr);
|
|
278
|
+
reject(parseErr);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Register the one-time listener
|
|
283
|
+
this._channelEmitter.once(channel, subscription);
|
|
284
|
+
|
|
285
|
+
// Subscribe to Redis channel
|
|
286
|
+
this._subscriptionClient.subscribe(channel, (msg) => {
|
|
287
|
+
this._channelEmitter.emit(channel, msg);
|
|
288
|
+
|
|
289
|
+
// Auto-unsubscribe if no more listeners
|
|
290
|
+
unsubscribeFromRedis('auto-cleanup').catch(err => {
|
|
291
|
+
this._logger.push({ channel }).warn('Auto-unsubscribe failed', err);
|
|
292
|
+
});
|
|
293
|
+
})
|
|
294
|
+
.catch(subscribeErr => {
|
|
295
|
+
if (isResolved) return;
|
|
296
|
+
isResolved = true;
|
|
297
|
+
|
|
298
|
+
cleanup();
|
|
299
|
+
this._logger.push({ channel }).error('Failed to subscribe to Redis channel', subscribeErr);
|
|
300
|
+
reject(subscribeErr);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
199
305
|
/**
|
|
200
306
|
* Subscribes to a channel for some period and always returns resolved promise
|
|
201
307
|
*
|
|
@@ -953,8 +953,10 @@ class InboundTransfersModel {
|
|
|
953
953
|
}
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
-
async
|
|
956
|
+
async sendFxPutNotificationToBackend(body, conversionId) {
|
|
957
|
+
const log = this._logger.child({ conversionId });
|
|
957
958
|
try {
|
|
959
|
+
log.verbose('sendFxPutNotificationToBackend payload: ', { body });
|
|
958
960
|
this.data = await this.loadFxState(conversionId);
|
|
959
961
|
|
|
960
962
|
if(!this.data) {
|
|
@@ -974,10 +976,10 @@ class InboundTransfersModel {
|
|
|
974
976
|
|
|
975
977
|
await this.saveFxState();
|
|
976
978
|
|
|
977
|
-
const res = await this._backendRequests.
|
|
979
|
+
const res = await this._backendRequests.putFxTransfersNotification(body, conversionId);
|
|
978
980
|
return res;
|
|
979
981
|
} catch (err) {
|
|
980
|
-
|
|
982
|
+
log.error('error in sendFxPutNotificationToBackend: ', err);
|
|
981
983
|
}
|
|
982
984
|
}
|
|
983
985
|
/**
|
|
@@ -40,6 +40,7 @@ const PartiesModel = require('./PartiesModel');
|
|
|
40
40
|
const {
|
|
41
41
|
AmountTypes,
|
|
42
42
|
BackendError,
|
|
43
|
+
TimeoutError,
|
|
43
44
|
CacheKeyPrefixes,
|
|
44
45
|
Directions,
|
|
45
46
|
ErrorMessages,
|
|
@@ -321,147 +322,121 @@ class OutboundTransfersModel {
|
|
|
321
322
|
|
|
322
323
|
let latencyTimerDone;
|
|
323
324
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
this.metrics.partyLookupResponses.inc();
|
|
331
|
-
|
|
332
|
-
this.data.getPartiesResponse = JSON.parse(msg);
|
|
333
|
-
if (this.data.getPartiesResponse.body?.errorInformation) {
|
|
334
|
-
// this is an error response to our GET /parties request
|
|
335
|
-
const err = new BackendError(`Got an error response resolving party: ${safeStringify(this.data.getPartiesResponse.body, { depth: Infinity })}`, 500);
|
|
336
|
-
err.mojaloopError = this.data.getPartiesResponse.body;
|
|
337
|
-
// cancel the timeout handler
|
|
338
|
-
clearTimeout(timeout);
|
|
339
|
-
return reject(err);
|
|
340
|
-
}
|
|
341
|
-
let payee = this.data.getPartiesResponse.body;
|
|
342
|
-
|
|
343
|
-
if(!payee.party) {
|
|
344
|
-
// we should never get a non-error response without a party, but just in case...
|
|
345
|
-
// cancel the timeout handler
|
|
346
|
-
clearTimeout(timeout);
|
|
347
|
-
return reject(new Error(`Resolved payee has no party object: ${safeStringify(payee)}`));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
payee = payee.party;
|
|
351
|
-
|
|
352
|
-
// cancel the timeout handler
|
|
353
|
-
clearTimeout(timeout);
|
|
354
|
-
|
|
355
|
-
this._logger.isVerboseEnabled && this._logger.push({ payee }).verbose('Payee resolved');
|
|
325
|
+
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
326
|
+
// a GET /parties request to the switch
|
|
327
|
+
try {
|
|
328
|
+
const channel = payeeKey;
|
|
329
|
+
const subscribing = this._cache.subscribeToOneMessageWithTimerNew(channel, this._requestProcessingTimeoutSeconds);
|
|
356
330
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this.
|
|
361
|
-
|
|
362
|
-
|
|
331
|
+
latencyTimerDone = this.metrics.partyLookupLatency.startTimer();
|
|
332
|
+
const res = await this._requests.getParties(
|
|
333
|
+
this.data.to.idType,
|
|
334
|
+
this.data.to.idValue,
|
|
335
|
+
this.data.to.idSubValue,
|
|
336
|
+
this.data.to.fspId,
|
|
337
|
+
this.#createOtelHeaders()
|
|
338
|
+
);
|
|
363
339
|
|
|
364
|
-
|
|
365
|
-
if(payee.partyIdInfo.partyIdType !== this.data.to.idType) {
|
|
366
|
-
const err = new Error(`Expecting resolved payee party IdType to be ${this.data.to.idType} but got ${payee.partyIdInfo.partyIdType}`);
|
|
367
|
-
return reject(err);
|
|
368
|
-
}
|
|
340
|
+
this.data.getPartiesRequest = res.originalRequest;
|
|
369
341
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return reject(err);
|
|
373
|
-
}
|
|
342
|
+
this.metrics.partyLookupRequests.inc();
|
|
343
|
+
this._logger.push({ peer: res }).debug('Party lookup sent to peer');
|
|
374
344
|
|
|
375
|
-
|
|
376
|
-
const err = new Error(`Expecting resolved payee party subTypeId to be ${this.data.to.idSubValue} but got ${payee.partyIdInfo.partySubIdOrType}`);
|
|
377
|
-
return reject(err);
|
|
378
|
-
}
|
|
345
|
+
const message = await subscribing;
|
|
379
346
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
347
|
+
if(latencyTimerDone) {
|
|
348
|
+
latencyTimerDone();
|
|
349
|
+
}
|
|
350
|
+
this.metrics.partyLookupResponses.inc();
|
|
384
351
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
this.data.to.firstName = payee.personalInfo.complexName.firstName || this.data.to.firstName;
|
|
394
|
-
this.data.to.middleName = payee.personalInfo.complexName.middleName || this.data.to.middleName;
|
|
395
|
-
this.data.to.lastName = payee.personalInfo.complexName.lastName || this.data.to.lastName;
|
|
396
|
-
}
|
|
397
|
-
this.data.to.dateOfBirth = payee.personalInfo.dateOfBirth;
|
|
398
|
-
}
|
|
352
|
+
this.data.getPartiesResponse = message;
|
|
353
|
+
if (this.data.getPartiesResponse.body?.errorInformation) {
|
|
354
|
+
// this is an error response to our GET /parties request
|
|
355
|
+
const err = new BackendError(`Got an error response resolving party: ${safeStringify(this.data.getPartiesResponse.body, { depth: Infinity })}`, 500);
|
|
356
|
+
err.mojaloopError = this.data.getPartiesResponse.body;
|
|
357
|
+
return reject(err);
|
|
358
|
+
}
|
|
359
|
+
let payee = this.data.getPartiesResponse.body;
|
|
399
360
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
361
|
+
if(!payee.party) {
|
|
362
|
+
// we should never get a non-error response without a party, but just in case...
|
|
363
|
+
return reject(new Error(`Resolved payee has no party object: ${safeStringify(payee)}`));
|
|
364
|
+
}
|
|
404
365
|
|
|
405
|
-
|
|
406
|
-
this.data.supportedCurrencies = payee.supportedCurrencies;
|
|
407
|
-
}
|
|
366
|
+
payee = payee.party;
|
|
408
367
|
|
|
409
|
-
|
|
410
|
-
transferId: this.data.transferId,
|
|
411
|
-
homeTransactionId: this.data.homeTransactionId,
|
|
412
|
-
needFx: this.data.needFx,
|
|
413
|
-
}).verbose('Payee validation passed');
|
|
368
|
+
this._logger.push({ payee }).verbose('Payee resolved');
|
|
414
369
|
|
|
415
|
-
|
|
370
|
+
// check we got the right payee and info we need
|
|
371
|
+
if(payee.partyIdInfo.partyIdType !== this.data.to.idType) {
|
|
372
|
+
const err = new Error(`Expecting resolved payee party IdType to be ${this.data.to.idType} but got ${payee.partyIdInfo.partyIdType}`);
|
|
373
|
+
return reject(err);
|
|
416
374
|
}
|
|
417
|
-
|
|
375
|
+
|
|
376
|
+
if(payee.partyIdInfo.partyIdentifier !== this.data.to.idValue) {
|
|
377
|
+
const err = new Error(`Expecting resolved payee party identifier to be ${this.data.to.idValue} but got ${payee.partyIdInfo.partyIdentifier}`);
|
|
418
378
|
return reject(err);
|
|
419
379
|
}
|
|
420
|
-
});
|
|
421
380
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
381
|
+
if(payee.partyIdInfo.partySubIdOrType !== this.data.to.idSubValue) {
|
|
382
|
+
const err = new Error(`Expecting resolved payee party subTypeId to be ${this.data.to.idSubValue} but got ${payee.partyIdInfo.partySubIdOrType}`);
|
|
383
|
+
return reject(err);
|
|
384
|
+
}
|
|
425
385
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
386
|
+
if(!payee.partyIdInfo.fspId) {
|
|
387
|
+
const err = new Error(`Expecting resolved payee party to have an FSPID: ${safeStringify(payee.partyIdInfo)}`);
|
|
388
|
+
return reject(err);
|
|
389
|
+
}
|
|
430
390
|
|
|
431
|
-
|
|
432
|
-
|
|
391
|
+
// now we got the payee, add the details to our data so we can use it
|
|
392
|
+
// in the quote request
|
|
393
|
+
this.data.to.fspId = payee.partyIdInfo.fspId;
|
|
394
|
+
if(payee.partyIdInfo.extensionList) {
|
|
395
|
+
this.data.to.extensionList = payee.partyIdInfo.extensionList.extension;
|
|
396
|
+
}
|
|
397
|
+
if(payee.personalInfo) {
|
|
398
|
+
if(payee.personalInfo.complexName) {
|
|
399
|
+
this.data.to.firstName = payee.personalInfo.complexName.firstName || this.data.to.firstName;
|
|
400
|
+
this.data.to.middleName = payee.personalInfo.complexName.middleName || this.data.to.middleName;
|
|
401
|
+
this.data.to.lastName = payee.personalInfo.complexName.lastName || this.data.to.lastName;
|
|
402
|
+
}
|
|
403
|
+
this.data.to.dateOfBirth = payee.personalInfo.dateOfBirth;
|
|
433
404
|
}
|
|
434
405
|
|
|
435
|
-
|
|
436
|
-
|
|
406
|
+
if (Array.isArray(payee.supportedCurrencies)) {
|
|
407
|
+
if (!payee.supportedCurrencies.length) {
|
|
408
|
+
throw new Error(ErrorMessages.noSupportedCurrencies);
|
|
409
|
+
}
|
|
437
410
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
latencyTimerDone = this.metrics.partyLookupLatency.startTimer();
|
|
442
|
-
const res = await this._requests.getParties(
|
|
443
|
-
this.data.to.idType,
|
|
444
|
-
this.data.to.idValue,
|
|
445
|
-
this.data.to.idSubValue,
|
|
446
|
-
this.data.to.fspId,
|
|
447
|
-
this.#createOtelHeaders()
|
|
448
|
-
);
|
|
411
|
+
this.data.needFx = this._isFxNeeded(this._supportedCurrencies, payee.supportedCurrencies, this.data.currency, this.data.amountType);
|
|
412
|
+
this.data.supportedCurrencies = payee.supportedCurrencies;
|
|
413
|
+
}
|
|
449
414
|
|
|
450
|
-
this.
|
|
415
|
+
this._logger.push({
|
|
416
|
+
transferId: this.data.transferId,
|
|
417
|
+
homeTransactionId: this.data.homeTransactionId,
|
|
418
|
+
needFx: this.data.needFx,
|
|
419
|
+
}).verbose('Payee validation passed');
|
|
451
420
|
|
|
452
|
-
|
|
453
|
-
this._logger.isDebugEnabled && this._logger.push({ peer: res }).debug('Party lookup sent to peer');
|
|
421
|
+
return resolve(payee);
|
|
454
422
|
}
|
|
455
423
|
catch(err) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
424
|
+
this._logger.error(`Error in resolvePayee ${payeeKey}:`, err);
|
|
425
|
+
// If type of error is BackendError, it will be handled by the state machine
|
|
426
|
+
if (err instanceof BackendError) {
|
|
427
|
+
this.data.lastError = err;
|
|
428
|
+
return reject(err);
|
|
429
|
+
}
|
|
430
|
+
// Check if the error is a TimeoutError, and if so, reject with a BackendError
|
|
431
|
+
if (err instanceof TimeoutError) {
|
|
432
|
+
const error = new BackendError(`Timeout resolving payee for transfer ${this.data.transferId}`, 504);
|
|
433
|
+
this.data.lastError = error;
|
|
434
|
+
return reject(error);
|
|
435
|
+
}
|
|
436
|
+
// otherwise, just throw a generic error
|
|
437
|
+
const error = new BackendError(`Error resolving payee for transfer ${this.data.transferId}: ${err.message}`, 500);
|
|
438
|
+
this.data.lastError = error;
|
|
439
|
+
return reject(error);
|
|
465
440
|
}
|
|
466
441
|
});
|
|
467
442
|
}
|