account-lookup-service 17.4.0-csi-1233.5 → 17.4.1-csi-1300.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "account-lookup-service",
3
3
  "description": "Account Lookup Service is used to validate Party and Participant lookups.",
4
- "version": "17.4.0-csi-1233.5",
4
+ "version": "17.4.1-csi-1300.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ModusBox",
7
7
  "contributors": [
@@ -24,50 +24,37 @@
24
24
  **********/
25
25
 
26
26
  const { Enum, Util } = require('@mojaloop/central-services-shared')
27
- const ErrorHandler = require('@mojaloop/central-services-error-handling')
27
+ // const ErrorHandler = require('@mojaloop/central-services-error-handling')
28
28
  const Metrics = require('@mojaloop/central-services-metrics')
29
29
 
30
- const config = require('../../lib/config')
31
30
  const oracle = require('../../models/oracle/facade')
32
31
  const participant = require('../../models/participantEndpoint/facade')
33
- const { createCallbackHeaders } = require('../../lib/headers')
34
- const { ERROR_MESSAGES } = require('../../constants')
35
- const logger = require('../../lib').logger.child({ component: 'domain.getPartiesByTypeAndID' })
36
- const Config = require('../../lib/config')
37
- const utils = require('./utils')
38
- const util = require('../../lib/util')
39
-
40
- const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
41
- const { Headers, RestMethods } = Enum.Http
42
-
43
- const proxyCacheTtlSec = 40 // todo: make configurable
44
-
45
- const validateRequester = async ({ source, proxy, proxyCache }) => {
46
- const log = logger.child({ source, method: 'validateRequester' })
47
- const sourceParticipant = await participant.validateParticipant(source)
48
- if (sourceParticipant) {
49
- log.debug('source is in scheme')
50
- return source
51
- }
32
+ const config = require('../../lib/config')
33
+ const libUtil = require('../../lib/util')
34
+ const { logger } = require('../../lib')
35
+ // const { createCallbackHeaders } = require('../../lib/headers')
36
+ // const { ERROR_MESSAGES } = require('../../constants')
37
+ const { GetPartiesService } = require('./services')
38
+ const partiesUtils = require('./partiesUtils')
52
39
 
53
- if (!proxy) {
54
- const errMessage = ERROR_MESSAGES.sourceFspNotFound
55
- log.warn(errMessage)
56
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
57
- }
40
+ // const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
41
+ // const { Headers, RestMethods } = Enum.Http
42
+ const { Headers } = Enum.Http
58
43
 
59
- const proxyParticipant = await participant.validateParticipant(proxy)
60
- if (!proxyParticipant) {
61
- const errMessage = ERROR_MESSAGES.partyProxyNotFound
62
- log.warn(errMessage, { proxy })
63
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
64
- }
44
+ // const proxyCacheTtlSec = 40 // todo: make configurable
65
45
 
66
- const isCached = await proxyCache.addDfspIdToProxyMapping(source, proxy)
67
- // think, what if isCached !== true?
68
- log.info('source is added to proxyMapping cache:', { proxy, isCached })
69
- return proxy
70
- }
46
+ const createDeps = ({ cache, proxyCache, childSpan, log, stepState }) => Object.freeze({
47
+ cache,
48
+ proxyCache,
49
+ childSpan,
50
+ log,
51
+ stepState,
52
+ config,
53
+ oracle,
54
+ participant,
55
+ proxies: Util.proxies,
56
+ partiesUtils
57
+ })
71
58
 
72
59
  /**
73
60
  * @function getPartiesByTypeAndID
@@ -83,178 +70,267 @@ const validateRequester = async ({ source, proxy, proxyCache }) => {
83
70
  * @param {IProxyCache} [proxyCache] - IProxyCache instance
84
71
  */
85
72
  const getPartiesByTypeAndID = async (headers, params, method, query, span, cache, proxyCache = undefined) => {
73
+ const component = getPartiesByTypeAndID.name
86
74
  const histTimerEnd = Metrics.getHistogram(
87
- 'getPartiesByTypeAndID',
75
+ component,
88
76
  'Get party by Type and Id',
89
77
  ['success']
90
78
  ).startTimer()
91
- const log = logger.child({ params })
92
- const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
93
- const type = params.Type
94
- const partySubId = params.SubId
95
- const callbackEndpointType = utils.getPartyCbType(partySubId)
79
+ const log = logger.child({ component, params })
80
+ const stepState = libUtil.initStepState()
81
+ const childSpan = span ? span.getChild(component) : undefined
82
+
83
+ const service = new GetPartiesService(createDeps({
84
+ cache, proxyCache, childSpan, log, stepState
85
+ }))
86
+
87
+ const proxyEnabled = !!(config.PROXY_CACHE_CONFIG.enabled && proxyCache)
96
88
  const source = headers[Headers.FSPIOP.SOURCE]
97
89
  const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
98
- let destination = headers[Headers.FSPIOP.DESTINATION]
90
+ const destination = headers[Headers.FSPIOP.DESTINATION]
99
91
  // see https://github.com/mojaloop/design-authority/issues/79
100
92
  // the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
101
93
  // without consulting any oracles.
102
94
 
103
- const childSpan = span ? span.getChild('getPartiesByTypeAndID') : undefined
104
95
  log.info('parties::getPartiesByTypeAndID start', { source, destination, proxy })
105
96
 
106
97
  let requester
107
98
  let fspiopError
108
- let step
109
- try {
110
- requester = await validateRequester({ source, proxy, proxyCache })
111
99
 
112
- const options = {
113
- partyIdType: type,
114
- partyIdentifier: params.ID,
115
- ...(partySubId && { partySubIdOrType: partySubId })
116
- }
100
+ try {
101
+ requester = await service.validateRequester({ source, proxy })
117
102
 
118
103
  if (destination) {
119
- step = 'validateParticipant-1'
120
- const destParticipantModel = await participant.validateParticipant(destination)
121
- if (!destParticipantModel) {
122
- step = 'lookupProxyByDfspId-2'
123
- const proxyId = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
124
-
125
- if (!proxyId) {
126
- log.warn('no destination participant, and no dfsp-to-proxy mapping', { destination })
127
- const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
128
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
129
- }
130
- destination = proxyId
131
- }
132
- // all ok, go ahead and forward the request
133
- step = 'sendRequest-3'
134
- await participant.sendRequest(headers, destination, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
135
-
104
+ await service.forwardRequestToDestination({ destination, headers, params })
105
+ // await forwardRequestToDestination({
106
+ // destination, headers, options, callbackEndpointType, childSpan, proxyEnabled, proxyCache, log, stepState
107
+ // })
136
108
  histTimerEnd({ success: true })
137
- log.info('discovery getPartiesByTypeAndID request was sent to destination', { destination })
138
109
  return
139
110
  }
140
111
 
141
- step = 'oracleRequest-4'
142
- const response = await oracle.oracleRequest(headers, method, params, query, undefined, cache)
143
- if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
144
- // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records. We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
145
- let filteredResponsePartyList
146
- switch (callbackEndpointType) {
147
- case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
148
- filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
149
- break
150
- case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
151
- filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType === partySubId) // Filter records that match partySubIdOrType
152
- break
153
- default:
154
- filteredResponsePartyList = response // Fallback to providing the standard list
155
- }
156
-
157
- if (!Array.isArray(filteredResponsePartyList) || !filteredResponsePartyList.length) {
158
- const errMessage = 'Requested FSP/Party not found'
159
- log.warn(errMessage)
160
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
161
- }
112
+ const response = await service.sendOracleDiscoveryRequest({ headers, params, query })
162
113
 
163
- let sentCount = 0 // if sentCount === 0 after sending, should we restart the whole process?
164
- const sending = filteredResponsePartyList.map(async party => {
165
- const { fspId } = party
166
- const clonedHeaders = { ...headers }
167
- if (!destination) {
168
- clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
169
- }
170
- step = 'validateParticipant-5'
171
- const schemeParticipant = await participant.validateParticipant(fspId)
172
- if (schemeParticipant) {
173
- sentCount++
174
- log.verbose('participant is in scheme', { fspId })
175
- return participant.sendRequest(clonedHeaders, party.fspId, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
176
- }
177
-
178
- // If the participant is not in the scheme and proxy routing is enabled,
179
- // we should check if there is a proxy for it and send the request to the proxy
180
- if (proxyEnabled) {
181
- step = 'lookupProxyByDfspId-6'
182
- const proxyName = await proxyCache.lookupProxyByDfspId(fspId)
183
- if (!proxyName) {
184
- log.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
185
- // todo: delete reference in oracle
186
- } else {
187
- sentCount++
188
- log.verbose('participant is NOT in scheme, use proxy name', { fspId, proxyName })
189
- return participant.sendRequest(clonedHeaders, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
190
- }
191
- }
192
- })
193
- step = 'sendRequests-7'
194
- await Promise.all(sending)
195
- log.info('participant.sendRequests to filtered oracle partyList are done', { sentCount })
196
- // todo: think what if sentCount === 0 here
114
+ if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
115
+ const partyList = service.filterOraclePartyList({ response, params })
116
+ await service.processOraclePartyList({ partyList, headers, params, destination })
117
+ // const partyList = filterOraclePartyList({ response, params, log, stepState })
118
+ // await processOraclePartyList({
119
+ // partyList, headers, params, destination, childSpan, proxyEnabled, proxyCache, log, stepState
120
+ // })
197
121
  } else {
198
122
  log.info('empty partyList form oracle, getting proxies list...', { proxyEnabled, params })
199
- let filteredProxyNames = []
200
-
201
- if (proxyEnabled) {
202
- step = 'getAllProxiesNames-8'
203
- const proxyNames = await Util.proxies.getAllProxiesNames(Config.SWITCH_ENDPOINT)
204
- filteredProxyNames = proxyNames.filter(name => name !== proxy)
205
- }
123
+ const proxyNames = await service.getFilteredProxyList(proxy)
124
+ // getFilteredProxyList({ config, proxy, proxyEnabled, stepState })
206
125
 
207
- if (!filteredProxyNames.length) {
208
- const callbackHeaders = createCallbackHeaders({
209
- requestHeaders: headers,
210
- partyIdType: type,
211
- partyIdentifier: params.ID,
212
- endpointTemplate: partySubId
213
- ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
214
- : FspEndpointTemplates.PARTIES_PUT_ERROR
215
- })
216
- fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
217
- const errorCallbackEndpointType = utils.errorPartyCbType(partySubId)
218
- step = 'sendErrorToParticipant-9'
219
- await participant.sendErrorToParticipant(requester, errorCallbackEndpointType,
220
- fspiopError.toApiErrorObject(config.ERROR_HANDLING), callbackHeaders, params, childSpan)
126
+ if (!proxyNames.length) {
127
+ fspiopError = await service.sendErrorCallback({ requester, headers, params })
128
+ // sendErrorCallback({
129
+ // headers, params, requester, childSpan, stepState
130
+ // })
221
131
  } else {
222
- const alsReq = utils.alsRequestDto(source, params)
223
- log.info('starting setSendToProxiesList flow: ', { filteredProxyNames, alsReq, proxyCacheTtlSec })
224
- step = 'setSendToProxiesList-10'
225
- const isCached = await proxyCache.setSendToProxiesList(alsReq, filteredProxyNames, proxyCacheTtlSec)
226
- if (!isCached) {
227
- log.warn('failed to setSendToProxiesList')
228
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
229
- }
230
-
231
- step = 'sending-11'
232
- const sending = filteredProxyNames.map(
233
- proxyName => participant.sendRequest(headers, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
234
- .then(({ status, data } = {}) => ({ status, data }))
235
- )
236
- const results = await Promise.allSettled(sending)
237
- const isOk = results.some(result => result.status === 'fulfilled')
238
- // If, at least, one request is sent to proxy, we treat the whole flow as successful.
239
- // Failed requests should be handled by TTL expired/timeout handler
240
- // todo: - think, if we should handle failed requests here (e.g., by calling receivedErrorResponse)
241
- log.info('setSendToProxiesList flow is done:', { isOk, results, filteredProxyNames, alsReq })
242
- if (!isOk) {
243
- log.warn('no successful requests sent to proxies')
244
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.proxyConnectionError)
245
- }
132
+ await service.triggerSendToProxiesFlow({ proxyNames, headers, params, source })
133
+ // await triggerSendToProxiesFlow({
134
+ // proxyNames, headers, params, source, childSpan, proxyCache, log, stepState
135
+ // })
246
136
  }
247
137
  }
138
+ log.info('parties::getPartiesByTypeAndID is done')
248
139
  histTimerEnd({ success: true })
249
140
  } catch (err) {
250
- fspiopError = await utils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, requester)
141
+ fspiopError = await partiesUtils.createErrorHandlerOnSendingCallback(config, log)(err, headers, params, requester)
251
142
  histTimerEnd({ success: false })
252
143
  if (fspiopError) {
253
- util.countFspiopError(fspiopError, { operation: 'getPartiesByTypeAndID', step })
144
+ libUtil.countFspiopError(fspiopError, { operation: component, step: stepState.step })
254
145
  }
255
146
  } finally {
256
- await utils.finishSpanWithError(childSpan, fspiopError)
147
+ await libUtil.finishSpanWithError(childSpan, fspiopError)
257
148
  }
258
149
  }
259
150
 
151
+ // const validateRequester = async ({ source, proxy, proxyCache, stepState }) => {
152
+ // stepState.inProgress('validateRequester-0')
153
+ // const log = logger.child({ source, method: 'validateRequester' })
154
+ // const sourceParticipant = await participant.validateParticipant(source)
155
+ // if (sourceParticipant) {
156
+ // log.debug('source is in scheme')
157
+ // return source
158
+ // }
159
+ //
160
+ // if (!proxy) {
161
+ // const errMessage = ERROR_MESSAGES.sourceFspNotFound
162
+ // log.warn(errMessage)
163
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
164
+ // }
165
+ //
166
+ // const proxyParticipant = await participant.validateParticipant(proxy)
167
+ // if (!proxyParticipant) {
168
+ // const errMessage = ERROR_MESSAGES.partyProxyNotFound
169
+ // log.warn(errMessage, { proxy })
170
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
171
+ // }
172
+ //
173
+ // const isCached = await proxyCache.addDfspIdToProxyMapping(source, proxy)
174
+ // // think, what if isCached !== true?
175
+ // log.info('source is added to proxyMapping cache:', { proxy, isCached })
176
+ // return proxy
177
+ // }
178
+
179
+ // const forwardRequestToDestination = async ({
180
+ // destination, headers, options, callbackEndpointType, childSpan, proxyEnabled, proxyCache, log, stepState
181
+ // }) => {
182
+ // let sendTo = destination
183
+ // stepState.inProgress('validateDestination-1')
184
+ //
185
+ // const destParticipantModel = await participant.validateParticipant(destination)
186
+ // if (!destParticipantModel) {
187
+ // stepState.inProgress('lookupProxyDestination-2')
188
+ // const proxyId = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
189
+ //
190
+ // if (!proxyId) {
191
+ // log.warn('no destination participant, and no dfsp-to-proxy mapping', { destination })
192
+ // const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
193
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
194
+ // }
195
+ // sendTo = proxyId
196
+ // }
197
+ // // all ok, go ahead and forward the request
198
+ // stepState.inProgress('forwardRequest-3')
199
+ // await participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
200
+ // log.info('discovery getPartiesByTypeAndID request was sent', { sendTo })
201
+ // }
202
+
203
+ // const filterOraclePartyList = ({ response, params, log, stepState }) => {
204
+ // // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records. We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
205
+ // stepState.inProgress('filterOraclePartyList-5')
206
+ // const callbackEndpointType = partiesUtils.getPartyCbType(params.SubId)
207
+ // let filteredResponsePartyList
208
+ //
209
+ // switch (callbackEndpointType) {
210
+ // case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
211
+ // filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
212
+ // break
213
+ // case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
214
+ // filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
215
+ // break
216
+ // default:
217
+ // filteredResponsePartyList = response // Fallback to providing the standard list
218
+ // }
219
+ //
220
+ // if (!Array.isArray(filteredResponsePartyList) || !filteredResponsePartyList.length) {
221
+ // const errMessage = 'Requested FSP/Party not found'
222
+ // log.warn(errMessage)
223
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
224
+ // }
225
+ //
226
+ // return filteredResponsePartyList
227
+ // }
228
+
229
+ // const processOraclePartyList = async ({
230
+ // partyList, headers, params, destination, childSpan, proxyEnabled, proxyCache, log, stepState
231
+ // }) => {
232
+ // const callbackEndpointType = partiesUtils.getPartyCbType(params.SubId)
233
+ // const options = partiesUtils.partiesRequestOptionsDto(params)
234
+ //
235
+ // let sentCount = 0 // if sentCount === 0 after sending, should we restart the whole process?
236
+ // const sending = partyList.map(async party => {
237
+ // const { fspId } = party
238
+ // const clonedHeaders = { ...headers }
239
+ // if (!destination) {
240
+ // clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
241
+ // }
242
+ // stepState.inProgress('validateParticipant-6')
243
+ // const schemeParticipant = await participant.validateParticipant(fspId)
244
+ // if (schemeParticipant) {
245
+ // sentCount++
246
+ // log.info('participant is in scheme', { fspId })
247
+ // return participant.sendRequest(clonedHeaders, fspId, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
248
+ // }
249
+ //
250
+ // // If the participant is not in the scheme and proxy routing is enabled,
251
+ // // we should check if there is a proxy for it and send the request to the proxy
252
+ // if (proxyEnabled) {
253
+ // stepState.inProgress('lookupProxyByDfspId-7')
254
+ // const proxyName = await proxyCache.lookupProxyByDfspId(fspId)
255
+ // if (!proxyName) {
256
+ // log.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
257
+ // // todo: delete reference in oracle
258
+ // } else {
259
+ // sentCount++
260
+ // log.info('participant is NOT in scheme, use proxy name', { fspId, proxyName })
261
+ // return participant.sendRequest(clonedHeaders, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
262
+ // }
263
+ // }
264
+ // })
265
+ // await Promise.all(sending)
266
+ // log.verbose('processOraclePartyList is done', { sentCount })
267
+ // // todo: think what if sentCount === 0 here
268
+ // }
269
+
270
+ // const getFilteredProxyList = async ({ config, proxy, proxyEnabled, stepState }) => {
271
+ // stepState.inProgress('getAllProxies-8')
272
+ // if (!proxyEnabled) return []
273
+ //
274
+ // const proxyNames = await Util.proxies.getAllProxiesNames(config.SWITCH_ENDPOINT)
275
+ // return proxyNames.filter(name => name !== proxy)
276
+ // }
277
+
278
+ // const sendErrorCallback = async ({
279
+ // headers, params, requester, childSpan, stepState
280
+ // }) => {
281
+ // stepState.inProgress('sendErrorToParticipant-9')
282
+ // const callbackHeaders = createCallbackHeaders({
283
+ // requestHeaders: headers,
284
+ // partyIdType: params.Type,
285
+ // partyIdentifier: params.ID,
286
+ // endpointTemplate: params.SubId
287
+ // ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
288
+ // : FspEndpointTemplates.PARTIES_PUT_ERROR
289
+ // })
290
+ // const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
291
+ //
292
+ // await participant.sendErrorToParticipant(
293
+ // requester,
294
+ // partiesUtils.errorPartyCbType(params.SubId),
295
+ // fspiopError.toApiErrorObject(config.ERROR_HANDLING),
296
+ // callbackHeaders,
297
+ // params,
298
+ // childSpan
299
+ // )
300
+ // return fspiopError
301
+ // }
302
+
303
+ // const triggerSendToProxiesFlow = async ({
304
+ // proxyNames, headers, params, source, childSpan, proxyCache, log, stepState
305
+ // }) => {
306
+ // stepState.inProgress('setSendToProxiesList-10')
307
+ // const alsReq = partiesUtils.alsRequestDto(source, params)
308
+ // log.info('starting setSendToProxiesList flow: ', { proxyNames, alsReq, proxyCacheTtlSec })
309
+ //
310
+ // const isCached = await proxyCache.setSendToProxiesList(alsReq, proxyNames, proxyCacheTtlSec)
311
+ // if (!isCached) {
312
+ // log.warn('failed to setSendToProxiesList')
313
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
314
+ // }
315
+ //
316
+ // const callbackEndpointType = partiesUtils.getPartyCbType(params.SubId)
317
+ // const options = partiesUtils.partiesRequestOptionsDto(params)
318
+ // stepState.inProgress('sendingProxyRequests-11')
319
+ // const sending = proxyNames.map(
320
+ // proxyName => participant.sendRequest(headers, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
321
+ // .then(({ status, data } = {}) => ({ status, data }))
322
+ // )
323
+ // const results = await Promise.allSettled(sending)
324
+ // const isOk = results.some(result => result.status === 'fulfilled')
325
+ // // If, at least, one request is sent to proxy, we treat the whole flow as successful.
326
+ // // Failed requests should be handled by TTL expired/timeout handler
327
+ // // todo: - think, if we should handle failed requests here (e.g., by calling receivedErrorResponse)
328
+ // log.info('triggerSendToProxiesFlow is done:', { isOk, results, proxyNames, alsReq })
329
+ // stepState.inProgress('allSent-12')
330
+ // if (!isOk) {
331
+ // log.warn('no successful requests sent to proxies')
332
+ // throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.proxyConnectionError)
333
+ // }
334
+ // }
335
+
260
336
  module.exports = getPartiesByTypeAndID
@@ -25,8 +25,11 @@
25
25
 
26
26
  'use strict'
27
27
 
28
- const parties = require('./parties')
28
+ const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
29
+ const { putPartiesByTypeAndID, putPartiesErrorByTypeAndID } = require('./putParties')
29
30
 
30
- exports.getPartiesByTypeAndID = parties.getPartiesByTypeAndID
31
- exports.putPartiesByTypeAndID = parties.putPartiesByTypeAndID
32
- exports.putPartiesErrorByTypeAndID = parties.putPartiesErrorByTypeAndID
31
+ module.exports = {
32
+ getPartiesByTypeAndID,
33
+ putPartiesByTypeAndID,
34
+ putPartiesErrorByTypeAndID
35
+ }
@@ -1,5 +1,31 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ 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
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
24
+
25
+ --------------
26
+ ******/
27
+
1
28
  const { Enum, Util: { Hapi } } = require('@mojaloop/central-services-shared')
2
- const EventSdk = require('@mojaloop/event-sdk')
3
29
  const ErrorHandler = require('@mojaloop/central-services-error-handling')
4
30
 
5
31
  const participant = require('../../models/participantEndpoint/facade')
@@ -20,18 +46,6 @@ const errorPartyCbType = (partySubId) => partySubId
20
46
  ? FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
21
47
  : FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR
22
48
 
23
- const finishSpanWithError = async (childSpan, fspiopError) => {
24
- if (childSpan && !childSpan.isFinished) {
25
- if (fspiopError) {
26
- const state = new EventSdk.EventStateMetadata(EventSdk.EventStatusType.failed, fspiopError.apiErrorCode.code, fspiopError.apiErrorCode.message)
27
- await childSpan.error(fspiopError, state)
28
- await childSpan.finish(fspiopError.message, state)
29
- } else {
30
- await childSpan.finish()
31
- }
32
- }
33
- }
34
-
35
49
  const makePutPartiesErrorPayload = async (config, fspiopError, headers, params) => {
36
50
  const body = fspiopError.toApiErrorObject(config.ERROR_HANDLING)
37
51
  return config.API_TYPE === Hapi.API_TYPES.iso20022
@@ -45,6 +59,12 @@ const alsRequestDto = (sourceId, params) => ({
45
59
  partyId: params.ID
46
60
  })
47
61
 
62
+ const partiesRequestOptionsDto = (params) => ({
63
+ partyIdType: params.Type,
64
+ partyIdentifier: params.ID,
65
+ ...(params.SubId && { partySubIdOrType: params.SubId })
66
+ })
67
+
48
68
  const swapSourceDestinationHeaders = (headers) => {
49
69
  const {
50
70
  [Headers.FSPIOP.SOURCE]: source,
@@ -62,7 +82,7 @@ const swapSourceDestinationHeaders = (headers) => {
62
82
  // change signature to accept object
63
83
  const createErrorHandlerOnSendingCallback = (config, logger) => async (err, headers, params, requester) => {
64
84
  try {
65
- logger.error('error in sending parties callback', err)
85
+ logger.error('error in sending parties callback: ', err)
66
86
  const sendTo = requester || headers[Headers.FSPIOP.SOURCE]
67
87
  const errorCallbackEndpointType = errorPartyCbType(params.SubId)
68
88
  const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err)
@@ -75,7 +95,7 @@ const createErrorHandlerOnSendingCallback = (config, logger) => async (err, head
75
95
  } catch (exc) {
76
96
  // We can't do anything else here- we _must_ handle all errors _within_ this function because
77
97
  // we've already sent a sync response- we cannot throw.
78
- logger.error('failed to handleErrorOnSendingCallback. No further processing!', exc)
98
+ logger.error('failed to handleErrorOnSendingCallback. No further processing! ', exc)
79
99
  }
80
100
  }
81
101
 
@@ -84,8 +104,8 @@ module.exports = {
84
104
  putPartyCbType,
85
105
  errorPartyCbType,
86
106
  makePutPartiesErrorPayload,
87
- finishSpanWithError,
88
107
  createErrorHandlerOnSendingCallback,
89
108
  alsRequestDto,
109
+ partiesRequestOptionsDto,
90
110
  swapSourceDestinationHeaders
91
111
  }
@@ -37,12 +37,12 @@ const Metrics = require('@mojaloop/central-services-metrics')
37
37
 
38
38
  const oracle = require('../../models/oracle/facade')
39
39
  const participant = require('../../models/participantEndpoint/facade')
40
+ const libUtil = require('../../lib/util')
41
+ const Config = require('../../lib/config')
42
+ const { logger } = require('../../lib')
40
43
  const { ERROR_MESSAGES } = require('../../constants')
41
- const logger = require('../../lib').logger.child({ component: 'domain.putParties' })
42
44
 
43
- const Config = require('../../lib/config')
44
- const utils = require('./utils')
45
- const util = require('../../lib/util')
45
+ const partiesUtils = require('./partiesUtils')
46
46
  const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
47
47
 
48
48
  /**
@@ -59,14 +59,13 @@ const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
59
59
  * @param {IProxyCache} [proxyCache] - IProxyCache instance
60
60
  */
61
61
  const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri, cache, proxyCache = undefined) => {
62
+ const components = putPartiesByTypeAndID.name
62
63
  const histTimerEnd = Metrics.getHistogram(
63
- 'putPartiesByTypeAndID',
64
+ components,
64
65
  'Put parties by type and id',
65
66
  ['success']
66
67
  ).startTimer()
67
- const log = logger.child({ params, method: 'putPartiesByTypeAndID' })
68
- const type = params.Type
69
- const partySubId = params.SubId
68
+ const log = logger.child({ params, components })
70
69
  const source = headers[Headers.FSPIOP.SOURCE]
71
70
  const destination = headers[Headers.FSPIOP.DESTINATION]
72
71
  const proxy = headers[Headers.FSPIOP.PROXY]
@@ -75,6 +74,7 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
75
74
 
76
75
  let sendTo
77
76
  let step
77
+
78
78
  try {
79
79
  step = 'validateParticipant-1'
80
80
  const requesterParticipant = await participant.validateParticipant(source)
@@ -91,7 +91,7 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
91
91
  }
92
92
 
93
93
  if (proxyEnabled && proxy) {
94
- const alsReq = utils.alsRequestDto(destination, params) // or source?
94
+ const alsReq = partiesUtils.alsRequestDto(destination, params)
95
95
  step = 'receivedSuccessResponse-2'
96
96
  const isExists = await proxyCache.receivedSuccessResponse(alsReq)
97
97
  if (!isExists) {
@@ -122,21 +122,17 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
122
122
  }
123
123
 
124
124
  const decodedPayload = decodePayload(dataUri, { asParsed: false })
125
- const callbackEndpointType = utils.putPartyCbType(partySubId)
126
- const options = {
127
- partyIdType: type,
128
- partyIdentifier: params.ID,
129
- ...(partySubId && { partySubIdOrType: partySubId })
130
- }
125
+ const callbackEndpointType = partiesUtils.putPartyCbType(params.SubId)
126
+ const options = partiesUtils.partiesRequestOptionsDto(params)
131
127
  step = 'sendRequest-6'
132
128
  await participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.PUT, decodedPayload.body.toString(), options)
133
129
 
134
130
  log.info('parties::putPartiesByTypeAndID::callback was sent', { sendTo })
135
131
  histTimerEnd({ success: true })
136
132
  } catch (err) {
137
- const fspiopError = await utils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
133
+ const fspiopError = await partiesUtils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
138
134
  if (fspiopError) {
139
- util.countFspiopError(fspiopError, { operation: 'putPartiesByTypeAndID', step })
135
+ libUtil.countFspiopError(fspiopError, { operation: components, step })
140
136
  }
141
137
  histTimerEnd({ success: false })
142
138
  }
@@ -156,82 +152,110 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
156
152
  * @param {IProxyCache} [proxyCache] - IProxyCache instance
157
153
  */
158
154
  const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, span, cache, proxyCache = undefined) => {
155
+ const component = putPartiesErrorByTypeAndID.name
159
156
  const histTimerEnd = Metrics.getHistogram(
160
- 'putPartiesErrorByTypeAndID',
157
+ component,
161
158
  'Put parties error by type and id',
162
159
  ['success']
163
160
  ).startTimer()
164
- const log = logger.child({ params, method: 'putPartiesErrorByTypeAndID' })
165
- const partySubId = params.SubId
161
+ const log = logger.child({ params, component })
166
162
  const destination = headers[Headers.FSPIOP.DESTINATION]
167
- const callbackEndpointType = utils.errorPartyCbType(partySubId)
168
163
  const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
164
+ const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
169
165
 
170
- const childSpan = span ? span.getChild('putPartiesErrorByTypeAndID') : undefined
166
+ const childSpan = span ? span.getChild(component) : undefined
167
+ const stepState = libUtil.initStepState()
168
+ log.info('parties::putPartiesErrorByTypeAndID start', { destination, proxy })
171
169
 
172
170
  let sendTo
173
171
  let fspiopError
174
- let step
175
172
 
176
173
  try {
177
- const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
178
174
  if (proxy) {
179
- if (isNotValidPayeeCase(payload)) {
180
- const swappedHeaders = utils.swapSourceDestinationHeaders(headers)
181
- step = 'oracleRequest-1'
182
- await oracle.oracleRequest(swappedHeaders, RestMethods.DELETE, params, null, null, cache)
183
- getPartiesByTypeAndID(swappedHeaders, params, RestMethods.GET, {}, span, cache, proxyCache)
184
- // todo: - think if we need to send errorCallback?
185
- // - or sentCallback after getPartiesByTypeAndID is done
186
- log.info('notValidPayee case - deleted Participants and run getPartiesByTypeAndID:', { proxy, params, payload })
187
- return
188
- }
189
-
190
- const alsReq = utils.alsRequestDto(destination, params) // or source?
191
- step = 'receivedErrorResponse-2'
192
- const isLast = await proxyCache.receivedErrorResponse(alsReq, proxy)
193
- if (!isLast) {
194
- log.info('got NOT last error callback from proxy:', { proxy, alsReq })
175
+ const isDone = await processProxyErrorCallback({
176
+ headers, params, payload, childSpan, cache, proxyCache, log, proxy, destination, stepState
177
+ })
178
+ if (isDone) {
179
+ log.info('putPartiesErrorByTypeAndID proxy callback was processed', { proxy })
180
+ histTimerEnd({ success: true })
195
181
  return
196
182
  }
197
183
  }
198
- step = 'validateParticipant-3'
199
- const destinationParticipant = await participant.validateParticipant(destination)
200
184
 
201
- if (destinationParticipant) {
202
- sendTo = destination
203
- } else {
204
- step = 'lookupProxyByDfspId-4'
205
- const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
206
- if (!proxyName) {
207
- const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
208
- log.warn(errMessage)
209
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
210
- }
211
- sendTo = proxyName
212
- }
213
- const decodedPayload = decodePayload(dataUri, { asParsed: false })
214
- await participant.sendErrorToParticipant(sendTo, callbackEndpointType, decodedPayload.body.toString(), headers, params, childSpan)
185
+ sendTo = await identifyDestinationForErrorCallback({
186
+ destination, proxyCache, proxyEnabled, log, stepState
187
+ })
188
+
189
+ await sendErrorCallbackToParticipant({
190
+ sendTo, headers, params, dataUri, childSpan, stepState
191
+ })
215
192
 
216
193
  log.info('putPartiesErrorByTypeAndID callback was sent', { sendTo })
217
194
  histTimerEnd({ success: true })
218
195
  } catch (err) {
219
- fspiopError = await utils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
196
+ fspiopError = await partiesUtils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
220
197
  if (fspiopError) {
221
- util.countFspiopError(fspiopError, { operation: 'putPartiesErrorByTypeAndID', step })
198
+ libUtil.countFspiopError(fspiopError, { operation: component, step: stepState.step })
222
199
  }
223
200
  histTimerEnd({ success: false })
224
201
  } finally {
225
- await utils.finishSpanWithError(childSpan, fspiopError)
202
+ await libUtil.finishSpanWithError(childSpan, fspiopError)
226
203
  }
227
204
  }
228
205
 
206
+ const processProxyErrorCallback = async ({
207
+ headers, params, payload, childSpan, cache, proxyCache, log, proxy, destination, stepState
208
+ }) => {
209
+ log.verbose('processProxyErrorCallback...')
210
+ let isDone // whether or not to continue putPartiesErrorByTypeAndID
211
+
212
+ if (isNotValidPayeeCase(payload)) {
213
+ stepState.inProgress('notValidPayeeCase-1')
214
+ log.info('notValidPayee case - deleted Participants and run getPartiesByTypeAndID', { proxy, payload })
215
+ const swappedHeaders = partiesUtils.swapSourceDestinationHeaders(headers)
216
+ await oracle.oracleRequest(swappedHeaders, RestMethods.DELETE, params, null, null, cache)
217
+ getPartiesByTypeAndID(swappedHeaders, params, RestMethods.GET, {}, childSpan, cache, proxyCache)
218
+ isDone = true
219
+ } else {
220
+ stepState.inProgress('receivedErrorResponse-2')
221
+ const alsReq = partiesUtils.alsRequestDto(destination, params) // or source?
222
+ const isLast = await proxyCache.receivedErrorResponse(alsReq, proxy)
223
+ log.info(`got${isLast ? '' : 'NOT'} last error callback from proxy`, { proxy, alsReq, isLast })
224
+ isDone = !isLast
225
+ }
226
+ return isDone
227
+ }
228
+
229
+ const identifyDestinationForErrorCallback = async ({
230
+ destination, proxyCache, proxyEnabled, log, stepState
231
+ }) => {
232
+ stepState.inProgress('validateParticipant-3')
233
+ const destinationParticipant = await participant.validateParticipant(destination)
234
+ if (destinationParticipant) return destination
235
+
236
+ stepState.inProgress('lookupProxyDestination-4')
237
+ const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
238
+ if (proxyName) return proxyName
239
+
240
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
241
+ log.warn(errMessage)
242
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
243
+ }
244
+
245
+ const sendErrorCallbackToParticipant = async ({
246
+ sendTo, headers, params, dataUri, childSpan, stepState
247
+ }) => {
248
+ stepState.inProgress('sendErrorToParticipant-5')
249
+ const decodedPayload = decodePayload(dataUri, { asParsed: false })
250
+ const callbackEndpointType = partiesUtils.errorPartyCbType(params.SubId)
251
+ await participant.sendErrorToParticipant(sendTo, callbackEndpointType, decodedPayload.body.toString(), headers, params, childSpan)
252
+ }
253
+
229
254
  function isNotValidPayeeCase (payload) {
230
255
  return payload?.errorInformation?.errorCode === MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
231
256
  }
232
257
 
233
258
  module.exports = {
234
- getPartiesByTypeAndID,
235
259
  putPartiesByTypeAndID,
236
260
  putPartiesErrorByTypeAndID
237
261
  }
@@ -0,0 +1,258 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ 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
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
24
+
25
+ --------------
26
+ ******/
27
+
28
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
29
+ const { Enum } = require('@mojaloop/central-services-shared')
30
+ const { ERROR_MESSAGES } = require('../../../constants')
31
+ const { createCallbackHeaders } = require('../../../lib/headers')
32
+
33
+ const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
34
+ const { Headers, RestMethods } = Enum.Http
35
+
36
+ const proxyCacheTtlSec = 40 // todo: make configurable
37
+
38
+ class GetPartiesService {
39
+ #deps
40
+
41
+ constructor (deps) {
42
+ this.#deps = deps
43
+ this.log = this.#deps.log.child({ component: this.constructor.name })
44
+ this.proxyEnabled = !!(deps.config.PROXY_CACHE_CONFIG?.enabled && deps.proxyCache)
45
+ }
46
+
47
+ async handleRequest () {
48
+ // todo: add logic
49
+ }
50
+
51
+ async validateRequester ({ source, proxy }) {
52
+ this.#deps.stepState.inProgress('validateRequester-0')
53
+ const log = this.log.child({ source, method: 'validateRequester' })
54
+
55
+ const sourceParticipant = await this.#validateParticipant(source)
56
+ if (sourceParticipant) {
57
+ log.debug('source is in scheme')
58
+ return source
59
+ }
60
+
61
+ if (!proxy) {
62
+ const errMessage = ERROR_MESSAGES.sourceFspNotFound
63
+ log.warn(errMessage)
64
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
65
+ }
66
+
67
+ const proxyParticipant = await this.#validateParticipant(proxy)
68
+ if (!proxyParticipant) {
69
+ const errMessage = ERROR_MESSAGES.partyProxyNotFound
70
+ log.warn(errMessage, { proxy })
71
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
72
+ }
73
+
74
+ const isCached = await this.#deps.proxyCache.addDfspIdToProxyMapping(source, proxy)
75
+ // think, what if isCached !== true?
76
+ log.info('source is added to proxyMapping cache:', { proxy, isCached })
77
+ return proxy
78
+ }
79
+
80
+ async forwardRequestToDestination ({ destination, headers, params }) {
81
+ this.#deps.stepState.inProgress('validateDestination-1')
82
+ const log = this.log.child({ method: 'forwardRequestToDestination' })
83
+ let sendTo = destination
84
+
85
+ const destParticipantModel = await this.#validateParticipant(destination)
86
+ if (!destParticipantModel) {
87
+ this.#deps.stepState.inProgress('lookupProxyDestination-2')
88
+ const proxyId = this.proxyEnabled && await this.#deps.proxyCache.lookupProxyByDfspId(destination)
89
+
90
+ if (!proxyId) {
91
+ log.warn('no destination participant, and no dfsp-to-proxy mapping', { destination })
92
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
93
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
94
+ }
95
+ sendTo = proxyId
96
+ }
97
+ // all ok, go ahead and forward the request
98
+ await this.#forwardGetPartiesRequest({ sendTo, headers, params })
99
+ log.info('discovery getPartiesByTypeAndID request was sent', { sendTo })
100
+ }
101
+
102
+ filterOraclePartyList ({ response, params }) {
103
+ // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records. We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
104
+ this.#deps.stepState.inProgress('filterOraclePartyList-5')
105
+ const callbackEndpointType = this.#deps.partiesUtils.getPartyCbType(params.SubId)
106
+ let filteredResponsePartyList
107
+
108
+ switch (callbackEndpointType) {
109
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
110
+ filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
111
+ break
112
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
113
+ filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
114
+ break
115
+ default:
116
+ filteredResponsePartyList = response // Fallback to providing the standard list
117
+ }
118
+
119
+ if (!Array.isArray(filteredResponsePartyList) || !filteredResponsePartyList.length) {
120
+ const errMessage = 'Requested FSP/Party not found'
121
+ this.#deps.log.warn(errMessage)
122
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
123
+ }
124
+
125
+ return filteredResponsePartyList
126
+ }
127
+
128
+ async processOraclePartyList ({ partyList, headers, params, destination }) {
129
+ const log = this.log.child({ method: 'processOraclePartyList' })
130
+
131
+ let sentCount = 0 // if sentCount === 0 after sending, should we restart the whole process?
132
+ const sending = partyList.map(async party => {
133
+ const { fspId } = party
134
+ const clonedHeaders = { ...headers }
135
+ if (!destination) {
136
+ clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
137
+ }
138
+ this.#deps.stepState.inProgress('validateParticipant-6')
139
+ const schemeParticipant = await this.#validateParticipant(fspId)
140
+ if (schemeParticipant) {
141
+ sentCount++
142
+ log.info('participant is in scheme', { fspId })
143
+ return this.#forwardGetPartiesRequest({
144
+ sendTo: fspId,
145
+ headers: clonedHeaders,
146
+ params
147
+ })
148
+ }
149
+
150
+ // If the participant is not in the scheme and proxy routing is enabled,
151
+ // we should check if there is a proxy for it and send the request to the proxy
152
+ if (this.proxyEnabled) {
153
+ this.#deps.stepState.inProgress('lookupProxyByDfspId-7')
154
+ const proxyName = await this.#deps.proxyCache.lookupProxyByDfspId(fspId)
155
+ if (!proxyName) {
156
+ log.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
157
+ // todo: delete reference in oracle
158
+ } else {
159
+ sentCount++
160
+ log.info('participant is NOT in scheme, use proxy name', { fspId, proxyName })
161
+ return this.#forwardGetPartiesRequest({
162
+ sendTo: proxyName,
163
+ headers: clonedHeaders,
164
+ params
165
+ })
166
+ }
167
+ }
168
+ })
169
+ await Promise.all(sending)
170
+ log.verbose('processOraclePartyList is done', { sentCount })
171
+ // todo: think what if sentCount === 0 here
172
+ }
173
+
174
+ async getFilteredProxyList (proxy) {
175
+ this.#deps.stepState.inProgress('getAllProxies-8')
176
+ if (!this.proxyEnabled) return []
177
+
178
+ const proxyNames = await this.#deps.proxies.getAllProxiesNames(this.#deps.config.SWITCH_ENDPOINT)
179
+ this.log.debug('getAllProxiesNames is done', { proxyNames })
180
+ return proxyNames.filter(name => name !== proxy)
181
+ }
182
+
183
+ async triggerSendToProxiesFlow ({ proxyNames, headers, params, source }) {
184
+ const log = this.log.child({ method: 'triggerSendToProxiesFlow' })
185
+ this.#deps.stepState.inProgress('setSendToProxiesList-10')
186
+ const alsReq = this.#deps.partiesUtils.alsRequestDto(source, params)
187
+ log.info('starting setSendToProxiesList flow: ', { proxyNames, alsReq, proxyCacheTtlSec })
188
+
189
+ const isCached = await this.#deps.proxyCache.setSendToProxiesList(alsReq, proxyNames, proxyCacheTtlSec)
190
+ if (!isCached) {
191
+ log.warn('failed to setSendToProxiesList')
192
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
193
+ }
194
+
195
+ this.#deps.stepState.inProgress('sendingProxyRequests-11')
196
+ const sending = proxyNames.map(
197
+ sendTo => this.#forwardGetPartiesRequest({ sendTo, headers, params })
198
+ .then(({ status, data } = {}) => ({ status, data }))
199
+ )
200
+ const results = await Promise.allSettled(sending)
201
+ const isOk = results.some(result => result.status === 'fulfilled')
202
+ // If, at least, one request is sent to proxy, we treat the whole flow as successful.
203
+ // Failed requests should be handled by TTL expired/timeout handler
204
+ // todo: - think, if we should handle failed requests here (e.g., by calling receivedErrorResponse)
205
+ log.info('triggerSendToProxiesFlow is done:', { isOk, results, proxyNames, alsReq })
206
+ this.#deps.stepState.inProgress('allSent-12')
207
+ if (!isOk) {
208
+ log.warn('no successful requests sent to proxies')
209
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.proxyConnectionError)
210
+ }
211
+ }
212
+
213
+ async #validateParticipant (participantId) {
214
+ return this.#deps.participant.validateParticipant(participantId)
215
+ }
216
+
217
+ async #forwardGetPartiesRequest ({ sendTo, headers, params }) {
218
+ this.#deps.stepState.inProgress('forwardRequest-3')
219
+ const callbackEndpointType = this.#deps.partiesUtils.getPartyCbType(params.SubId)
220
+ const options = this.#deps.partiesUtils.partiesRequestOptionsDto(params)
221
+
222
+ return this.#deps.participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.GET, undefined, options, this.#deps.childSpan)
223
+ }
224
+
225
+ // async sendSuccessCallback ({ headers, sendTo }) {
226
+ // return this.#deps.participant.sendRequest(headers, sendTo,)
227
+ // }
228
+
229
+ async sendErrorCallback ({ requester, headers, params }) {
230
+ this.#deps.stepState.inProgress('sendErrorCallback-9')
231
+ const callbackHeaders = createCallbackHeaders({
232
+ requestHeaders: headers,
233
+ partyIdType: params.Type,
234
+ partyIdentifier: params.ID,
235
+ endpointTemplate: params.SubId
236
+ ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
237
+ : FspEndpointTemplates.PARTIES_PUT_ERROR
238
+ })
239
+ const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
240
+
241
+ await this.#deps.participant.sendErrorToParticipant(
242
+ requester,
243
+ this.#deps.partiesUtils.errorPartyCbType(params.SubId),
244
+ fspiopError.toApiErrorObject(this.#deps.config.ERROR_HANDLING),
245
+ callbackHeaders,
246
+ params,
247
+ this.#deps.childSpan
248
+ )
249
+ return fspiopError
250
+ }
251
+
252
+ async sendOracleDiscoveryRequest ({ headers, params, query }) {
253
+ this.#deps.stepState.inProgress('oracleRequest-4')
254
+ return this.#deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.#deps.cache)
255
+ }
256
+ }
257
+
258
+ module.exports = GetPartiesService
@@ -0,0 +1,32 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ 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
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
24
+
25
+ --------------
26
+ ******/
27
+
28
+ const GetPartiesService = require('./GetPartiesService')
29
+
30
+ module.exports = {
31
+ GetPartiesService
32
+ }
@@ -13,7 +13,7 @@ const EventFrameworkUtil = require('@mojaloop/central-services-shared').Util.Eve
13
13
 
14
14
  const LibUtil = require('../../lib/util')
15
15
  const Config = require('../../lib/config')
16
- const partiesUtils = require('../parties/utils')
16
+ const partiesUtils = require('../parties/partiesUtils')
17
17
 
18
18
  const timeoutCallbackDto = async ({ destination, partyId, partyType }) => {
19
19
  const headers = {
@@ -46,7 +46,8 @@ exports.createCallbackHeaders = (params) => {
46
46
  callbackHeaders[Enums.Http.Headers.FSPIOP.DESTINATION] = params.requestHeaders[Enums.Http.Headers.FSPIOP.SOURCE]
47
47
  callbackHeaders[Enums.Http.Headers.FSPIOP.HTTP_METHOD] = Enums.Http.RestMethods.PUT
48
48
  callbackHeaders[Enums.Http.Headers.FSPIOP.URI] = Mustache.render(params.endpointTemplate, {
49
- partyIdType: params.partyIdType, partyIdentifier: params.partyIdentifier
49
+ partyIdType: params.partyIdType,
50
+ partyIdentifier: params.partyIdentifier
50
51
  })
51
52
 
52
53
  return callbackHeaders
package/src/lib/util.js CHANGED
@@ -1,5 +1,33 @@
1
- const util = require('util')
2
- const Path = require('path')
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ 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
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
24
+
25
+ --------------
26
+ ******/
27
+
28
+ const util = require('node:util')
29
+ const Path = require('node:path')
30
+ const EventSdk = require('@mojaloop/event-sdk')
3
31
  const Enum = require('@mojaloop/central-services-shared').Enum
4
32
  const { HeaderValidation, Hapi, rethrow } = require('@mojaloop/central-services-shared').Util
5
33
  const Config = require('../lib/config')
@@ -70,7 +98,30 @@ const countFspiopError = (error, options) => {
70
98
  rethrow.countFspiopError(error, options)
71
99
  }
72
100
 
101
+ // todo: think better name
102
+ const initStepState = (initStep = 'start') => {
103
+ let step = initStep
104
+ return Object.freeze({
105
+ get step () { return step }, // or rename to current ?
106
+ inProgress (nextStep) { step = nextStep }
107
+ })
108
+ }
109
+
110
+ const finishSpanWithError = async (childSpan, fspiopError) => {
111
+ if (childSpan && !childSpan.isFinished) {
112
+ if (fspiopError) {
113
+ const state = new EventSdk.EventStateMetadata(EventSdk.EventStatusType.failed, fspiopError.apiErrorCode.code, fspiopError.apiErrorCode.message)
114
+ await childSpan.error(fspiopError, state)
115
+ await childSpan.finish(fspiopError.message, state)
116
+ } else {
117
+ await childSpan.finish()
118
+ }
119
+ }
120
+ }
121
+
73
122
  module.exports = {
123
+ initStepState,
124
+ finishSpanWithError,
74
125
  getSpanTags,
75
126
  pathForInterface,
76
127
  getStackOrInspect,
@@ -41,8 +41,8 @@ const Metrics = require('@mojaloop/central-services-metrics')
41
41
 
42
42
  const Config = require('../../../../src/lib/config')
43
43
  const Db = require('../../../../src/lib/db')
44
- const partiesDomain = require('../../../../src/domain/parties/parties')
45
- const partiesUtils = require('../../../../src/domain/parties/utils')
44
+ const partiesDomain = require('../../../../src/domain/parties')
45
+ const partiesUtils = require('../../../../src/domain/parties/partiesUtils')
46
46
  const participant = require('../../../../src/models/participantEndpoint/facade')
47
47
  const oracle = require('../../../../src/models/oracle/facade')
48
48
  const oracleEndpointCached = require('../../../../src/models/oracle/oracleEndpointCached')
@@ -36,7 +36,7 @@ jest.mock('@mojaloop/central-services-shared', () => ({
36
36
 
37
37
  const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
38
38
  const { logger } = require('../../../../src/lib')
39
- const utils = require('../../../../src/domain/parties/utils')
39
+ const partiesUtils = require('../../../../src/domain/parties/partiesUtils')
40
40
  const config = require('../../../../src/lib/config')
41
41
  const fixtures = require('../../../fixtures')
42
42
 
@@ -48,7 +48,7 @@ describe('parties utils Tests -->', () => {
48
48
  const headers = fixtures.partiesCallHeadersDto({ source })
49
49
  const params = { ID: '1234', Type: 'MSISDN' }
50
50
 
51
- const handleError = utils.createErrorHandlerOnSendingCallback(isoConfig, logger)
51
+ const handleError = partiesUtils.createErrorHandlerOnSendingCallback(isoConfig, logger)
52
52
  await handleError(err, headers, params, source)
53
53
 
54
54
  expect(mockSendRequest).toHaveBeenCalledTimes(1)
@@ -65,4 +65,29 @@ describe('Util', () => {
65
65
  const expectedApiExtendedAdminMockPath = path.join('interface', 'thirdparty', 'admin_swagger.json')
66
66
  expect(apiExtendedAdminMockPath).toContain(expectedApiExtendedAdminMockPath)
67
67
  })
68
+
69
+ describe('stepState Tests -->', () => {
70
+ it('should initiate stepState with default step', async () => {
71
+ const stepState = Util.initStepState()
72
+ expect(stepState.step).toBe('start')
73
+ })
74
+
75
+ it('should initiate stepState with custom step', async () => {
76
+ const step = 'customStep'
77
+ const stepState = Util.initStepState(step)
78
+ expect(stepState.step).toBe(step)
79
+ })
80
+
81
+ it('should set ongoing step using inProgress() method', async () => {
82
+ const step = 'nextStep'
83
+ const stepState = Util.initStepState()
84
+ stepState.inProgress(step)
85
+ expect(stepState.step).toBe(step)
86
+ })
87
+
88
+ it('should not be able to change internal state directly', async () => {
89
+ const stepState = Util.initStepState()
90
+ expect(() => { stepState.step = 'impossible' }).toThrowError()
91
+ })
92
+ })
68
93
  })