account-lookup-service 17.4.0-csi-1233.5 → 17.4.1-csi-1300.1
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 +1 -1
- package/src/domain/parties/deps.js +49 -0
- package/src/domain/parties/getPartiesByTypeAndID.js +18 -203
- package/src/domain/parties/index.js +7 -4
- package/src/domain/parties/{utils.js → partiesUtils.js} +36 -16
- package/src/domain/parties/{parties.js → putParties.js} +84 -60
- package/src/domain/parties/services/GetPartiesService.js +291 -0
- package/src/domain/parties/services/index.js +32 -0
- package/src/domain/timeout/dto.js +1 -1
- package/src/lib/headers.js +2 -1
- package/src/lib/util.js +53 -2
- package/test/unit/domain/parties/parties.test.js +2 -2
- package/test/unit/domain/parties/utils.test.js +2 -2
- package/test/unit/lib/util.test.js +25 -0
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.
|
4
|
+
"version": "17.4.1-csi-1300.1",
|
5
5
|
"license": "Apache-2.0",
|
6
6
|
"author": "ModusBox",
|
7
7
|
"contributors": [
|
@@ -0,0 +1,49 @@
|
|
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 { proxies } = require('@mojaloop/central-services-shared').Util
|
29
|
+
const oracle = require('../../models/oracle/facade')
|
30
|
+
const participant = require('../../models/participantEndpoint/facade')
|
31
|
+
const config = require('../../lib/config')
|
32
|
+
const partiesUtils = require('./partiesUtils')
|
33
|
+
|
34
|
+
const createDeps = ({ cache, proxyCache, childSpan, log, stepState }) => Object.freeze({
|
35
|
+
cache,
|
36
|
+
proxyCache,
|
37
|
+
childSpan,
|
38
|
+
log,
|
39
|
+
stepState,
|
40
|
+
config,
|
41
|
+
oracle,
|
42
|
+
participant,
|
43
|
+
proxies,
|
44
|
+
partiesUtils
|
45
|
+
})
|
46
|
+
|
47
|
+
module.exports = {
|
48
|
+
createDeps
|
49
|
+
}
|
@@ -23,51 +23,11 @@
|
|
23
23
|
--------------
|
24
24
|
**********/
|
25
25
|
|
26
|
-
const { Enum, Util } = require('@mojaloop/central-services-shared')
|
27
|
-
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
28
26
|
const Metrics = require('@mojaloop/central-services-metrics')
|
29
|
-
|
30
|
-
const
|
31
|
-
const
|
32
|
-
const
|
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
|
-
}
|
52
|
-
|
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
|
-
}
|
58
|
-
|
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
|
-
}
|
65
|
-
|
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
|
-
}
|
27
|
+
const libUtil = require('../../lib/util')
|
28
|
+
const { logger } = require('../../lib')
|
29
|
+
const { GetPartiesService } = require('./services')
|
30
|
+
const { createDeps } = require('./deps')
|
71
31
|
|
72
32
|
/**
|
73
33
|
* @function getPartiesByTypeAndID
|
@@ -83,177 +43,32 @@ const validateRequester = async ({ source, proxy, proxyCache }) => {
|
|
83
43
|
* @param {IProxyCache} [proxyCache] - IProxyCache instance
|
84
44
|
*/
|
85
45
|
const getPartiesByTypeAndID = async (headers, params, method, query, span, cache, proxyCache = undefined) => {
|
46
|
+
const component = getPartiesByTypeAndID.name
|
86
47
|
const histTimerEnd = Metrics.getHistogram(
|
87
|
-
|
48
|
+
component,
|
88
49
|
'Get party by Type and Id',
|
89
50
|
['success']
|
90
51
|
).startTimer()
|
91
|
-
const
|
92
|
-
const
|
93
|
-
const
|
94
|
-
const partySubId = params.SubId
|
95
|
-
const callbackEndpointType = utils.getPartyCbType(partySubId)
|
96
|
-
const source = headers[Headers.FSPIOP.SOURCE]
|
97
|
-
const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
|
98
|
-
let destination = headers[Headers.FSPIOP.DESTINATION]
|
99
|
-
// see https://github.com/mojaloop/design-authority/issues/79
|
100
|
-
// the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
|
101
|
-
// without consulting any oracles.
|
52
|
+
const childSpan = span ? span.getChild(component) : undefined
|
53
|
+
const log = logger.child({ component, params })
|
54
|
+
const stepState = libUtil.initStepState()
|
102
55
|
|
103
|
-
const
|
104
|
-
|
56
|
+
const deps = createDeps({ cache, proxyCache, childSpan, log, stepState })
|
57
|
+
const service = new GetPartiesService(deps)
|
58
|
+
const results = {}
|
105
59
|
|
106
|
-
let requester
|
107
|
-
let fspiopError
|
108
|
-
let step
|
109
60
|
try {
|
110
|
-
|
111
|
-
|
112
|
-
const options = {
|
113
|
-
partyIdType: type,
|
114
|
-
partyIdentifier: params.ID,
|
115
|
-
...(partySubId && { partySubIdOrType: partySubId })
|
116
|
-
}
|
117
|
-
|
118
|
-
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
|
-
|
136
|
-
histTimerEnd({ success: true })
|
137
|
-
log.info('discovery getPartiesByTypeAndID request was sent to destination', { destination })
|
138
|
-
return
|
139
|
-
}
|
140
|
-
|
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
|
-
}
|
162
|
-
|
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
|
197
|
-
} else {
|
198
|
-
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
|
-
}
|
206
|
-
|
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)
|
221
|
-
} 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
|
-
}
|
246
|
-
}
|
247
|
-
}
|
61
|
+
await service.handleRequest({ headers, params, query, results })
|
62
|
+
log.info('getPartiesByTypeAndID is done')
|
248
63
|
histTimerEnd({ success: true })
|
249
64
|
} catch (err) {
|
250
|
-
fspiopError = await
|
65
|
+
results.fspiopError = await deps.partiesUtils.createErrorHandlerOnSendingCallback(deps.config, log)(err, headers, params, results.requester)
|
251
66
|
histTimerEnd({ success: false })
|
252
|
-
if (fspiopError) {
|
253
|
-
|
67
|
+
if (results.fspiopError) {
|
68
|
+
libUtil.countFspiopError(results.fspiopError, { operation: component, step: stepState.step })
|
254
69
|
}
|
255
70
|
} finally {
|
256
|
-
await
|
71
|
+
await libUtil.finishSpanWithError(childSpan, results.fspiopError)
|
257
72
|
}
|
258
73
|
}
|
259
74
|
|
@@ -25,8 +25,11 @@
|
|
25
25
|
|
26
26
|
'use strict'
|
27
27
|
|
28
|
-
const
|
28
|
+
const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
|
29
|
+
const { putPartiesByTypeAndID, putPartiesErrorByTypeAndID } = require('./putParties')
|
29
30
|
|
30
|
-
exports
|
31
|
-
|
32
|
-
|
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
|
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
|
-
|
64
|
+
components,
|
64
65
|
'Put parties by type and id',
|
65
66
|
['success']
|
66
67
|
).startTimer()
|
67
|
-
const log = logger.child({ params,
|
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 =
|
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 =
|
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
|
133
|
+
const fspiopError = await partiesUtils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
|
138
134
|
if (fspiopError) {
|
139
|
-
|
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
|
-
|
157
|
+
component,
|
161
158
|
'Put parties error by type and id',
|
162
159
|
['success']
|
163
160
|
).startTimer()
|
164
|
-
const log = logger.child({ params,
|
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(
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
202
|
-
|
203
|
-
}
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
196
|
+
fspiopError = await partiesUtils.createErrorHandlerOnSendingCallback(Config, log)(err, headers, params, sendTo)
|
220
197
|
if (fspiopError) {
|
221
|
-
|
198
|
+
libUtil.countFspiopError(fspiopError, { operation: component, step: stepState.step })
|
222
199
|
}
|
223
200
|
histTimerEnd({ success: false })
|
224
201
|
} finally {
|
225
|
-
await
|
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,291 @@
|
|
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 ({ headers, params, query, results }) {
|
48
|
+
const source = headers[Headers.FSPIOP.SOURCE]
|
49
|
+
const proxy = headers[Headers.FSPIOP.PROXY]
|
50
|
+
const destination = headers[Headers.FSPIOP.DESTINATION]
|
51
|
+
// see https://github.com/mojaloop/design-authority/issues/79
|
52
|
+
// the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
|
53
|
+
// without consulting any oracles.
|
54
|
+
this.log.info('handling getParties request', { source, destination, proxy })
|
55
|
+
|
56
|
+
const requester = await this.validateRequester({ source, proxy })
|
57
|
+
results.requester = requester
|
58
|
+
|
59
|
+
if (destination) {
|
60
|
+
await this.forwardRequestToDestination({ destination, headers, params })
|
61
|
+
return
|
62
|
+
}
|
63
|
+
const response = await this.sendOracleDiscoveryRequest({ headers, params, query })
|
64
|
+
|
65
|
+
if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
|
66
|
+
const partyList = this.filterOraclePartyList({ response, params })
|
67
|
+
await this.processOraclePartyList({ partyList, headers, params, destination })
|
68
|
+
return
|
69
|
+
}
|
70
|
+
|
71
|
+
this.log.info('empty partyList form oracle, getting proxyList...', { params })
|
72
|
+
const proxyNames = await this.getFilteredProxyList(proxy)
|
73
|
+
|
74
|
+
if (proxyNames.length) {
|
75
|
+
return this.triggerSendToProxiesFlow({ proxyNames, headers, params, source })
|
76
|
+
}
|
77
|
+
|
78
|
+
results.fspiopError = await this.sendErrorCallback({ requester, headers, params })
|
79
|
+
}
|
80
|
+
|
81
|
+
async validateRequester ({ source, proxy }) {
|
82
|
+
this.#deps.stepState.inProgress('validateRequester-0')
|
83
|
+
const log = this.log.child({ source, method: 'validateRequester' })
|
84
|
+
|
85
|
+
const sourceParticipant = await this.#validateParticipant(source)
|
86
|
+
if (sourceParticipant) {
|
87
|
+
log.debug('source is in scheme')
|
88
|
+
return source
|
89
|
+
}
|
90
|
+
|
91
|
+
if (!this.proxyEnabled || !proxy) {
|
92
|
+
const errMessage = ERROR_MESSAGES.sourceFspNotFound
|
93
|
+
log.warn(errMessage)
|
94
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
95
|
+
}
|
96
|
+
|
97
|
+
const proxyParticipant = await this.#validateParticipant(proxy)
|
98
|
+
if (!proxyParticipant) {
|
99
|
+
const errMessage = ERROR_MESSAGES.partyProxyNotFound
|
100
|
+
log.warn(errMessage, { proxy })
|
101
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
102
|
+
}
|
103
|
+
|
104
|
+
const isCached = await this.#deps.proxyCache.addDfspIdToProxyMapping(source, proxy)
|
105
|
+
// think, what if isCached !== true?
|
106
|
+
log.info('source is added to proxyMapping cache:', { proxy, isCached })
|
107
|
+
return proxy
|
108
|
+
}
|
109
|
+
|
110
|
+
async forwardRequestToDestination ({ destination, headers, params }) {
|
111
|
+
this.#deps.stepState.inProgress('validateDestination-1')
|
112
|
+
const log = this.log.child({ method: 'forwardRequestToDestination' })
|
113
|
+
let sendTo = destination
|
114
|
+
|
115
|
+
const destParticipantModel = await this.#validateParticipant(destination)
|
116
|
+
if (!destParticipantModel) {
|
117
|
+
this.#deps.stepState.inProgress('lookupProxyDestination-2')
|
118
|
+
const proxyId = this.proxyEnabled && await this.#deps.proxyCache.lookupProxyByDfspId(destination)
|
119
|
+
|
120
|
+
if (!proxyId) {
|
121
|
+
log.warn('no destination participant, and no dfsp-to-proxy mapping', { destination })
|
122
|
+
const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
|
123
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
124
|
+
}
|
125
|
+
sendTo = proxyId
|
126
|
+
}
|
127
|
+
// all ok, go ahead and forward the request
|
128
|
+
await this.#forwardGetPartiesRequest({ sendTo, headers, params })
|
129
|
+
log.info('discovery getPartiesByTypeAndID request was sent', { sendTo })
|
130
|
+
}
|
131
|
+
|
132
|
+
filterOraclePartyList ({ response, params }) {
|
133
|
+
// 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:
|
134
|
+
this.#deps.stepState.inProgress('filterOraclePartyList-5')
|
135
|
+
const callbackEndpointType = this.#deps.partiesUtils.getPartyCbType(params.SubId)
|
136
|
+
let filteredResponsePartyList
|
137
|
+
|
138
|
+
switch (callbackEndpointType) {
|
139
|
+
case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
|
140
|
+
filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
|
141
|
+
break
|
142
|
+
case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
|
143
|
+
filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
|
144
|
+
break
|
145
|
+
default:
|
146
|
+
filteredResponsePartyList = response // Fallback to providing the standard list
|
147
|
+
}
|
148
|
+
|
149
|
+
if (!Array.isArray(filteredResponsePartyList) || !filteredResponsePartyList.length) {
|
150
|
+
const errMessage = 'Requested FSP/Party not found'
|
151
|
+
this.#deps.log.warn(errMessage)
|
152
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
153
|
+
}
|
154
|
+
|
155
|
+
return filteredResponsePartyList
|
156
|
+
}
|
157
|
+
|
158
|
+
async processOraclePartyList ({ partyList, headers, params, destination }) {
|
159
|
+
const log = this.log.child({ method: 'processOraclePartyList' })
|
160
|
+
|
161
|
+
let sentCount = 0 // if sentCount === 0 after sending, should we restart the whole process?
|
162
|
+
const sending = partyList.map(async party => {
|
163
|
+
const { fspId } = party
|
164
|
+
const clonedHeaders = { ...headers }
|
165
|
+
if (!destination) {
|
166
|
+
clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
|
167
|
+
}
|
168
|
+
this.#deps.stepState.inProgress('validateParticipant-6')
|
169
|
+
const schemeParticipant = await this.#validateParticipant(fspId)
|
170
|
+
if (schemeParticipant) {
|
171
|
+
sentCount++
|
172
|
+
log.info('participant is in scheme', { fspId })
|
173
|
+
return this.#forwardGetPartiesRequest({
|
174
|
+
sendTo: fspId,
|
175
|
+
headers: clonedHeaders,
|
176
|
+
params
|
177
|
+
})
|
178
|
+
}
|
179
|
+
|
180
|
+
// If the participant is not in the scheme and proxy routing is enabled,
|
181
|
+
// we should check if there is a proxy for it and send the request to the proxy
|
182
|
+
if (this.proxyEnabled) {
|
183
|
+
this.#deps.stepState.inProgress('lookupProxyByDfspId-7')
|
184
|
+
const proxyName = await this.#deps.proxyCache.lookupProxyByDfspId(fspId)
|
185
|
+
if (!proxyName) {
|
186
|
+
log.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
|
187
|
+
// todo: delete reference in oracle
|
188
|
+
} else {
|
189
|
+
sentCount++
|
190
|
+
log.info('participant is NOT in scheme, use proxy name', { fspId, proxyName })
|
191
|
+
return this.#forwardGetPartiesRequest({
|
192
|
+
sendTo: proxyName,
|
193
|
+
headers: clonedHeaders,
|
194
|
+
params
|
195
|
+
})
|
196
|
+
}
|
197
|
+
}
|
198
|
+
})
|
199
|
+
await Promise.all(sending)
|
200
|
+
log.verbose('processOraclePartyList is done', { sentCount })
|
201
|
+
// todo: think what if sentCount === 0 here
|
202
|
+
}
|
203
|
+
|
204
|
+
async getFilteredProxyList (proxy) {
|
205
|
+
this.#deps.stepState.inProgress('getAllProxies-8')
|
206
|
+
if (!this.proxyEnabled) {
|
207
|
+
this.log.warn('proxyCache is not enabled')
|
208
|
+
return []
|
209
|
+
}
|
210
|
+
|
211
|
+
const proxyNames = await this.#deps.proxies.getAllProxiesNames(this.#deps.config.SWITCH_ENDPOINT)
|
212
|
+
this.log.debug('getAllProxiesNames is done', { proxyNames })
|
213
|
+
return proxyNames.filter(name => name !== proxy)
|
214
|
+
}
|
215
|
+
|
216
|
+
async triggerSendToProxiesFlow ({ proxyNames, headers, params, source }) {
|
217
|
+
const log = this.log.child({ method: 'triggerSendToProxiesFlow' })
|
218
|
+
this.#deps.stepState.inProgress('setSendToProxiesList-10')
|
219
|
+
const alsReq = this.#deps.partiesUtils.alsRequestDto(source, params)
|
220
|
+
log.info('starting setSendToProxiesList flow: ', { proxyNames, alsReq, proxyCacheTtlSec })
|
221
|
+
|
222
|
+
const isCached = await this.#deps.proxyCache.setSendToProxiesList(alsReq, proxyNames, proxyCacheTtlSec)
|
223
|
+
if (!isCached) {
|
224
|
+
log.warn('failed to setSendToProxiesList')
|
225
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
|
226
|
+
}
|
227
|
+
|
228
|
+
this.#deps.stepState.inProgress('sendingProxyRequests-11')
|
229
|
+
const sending = proxyNames.map(
|
230
|
+
sendTo => this.#forwardGetPartiesRequest({ sendTo, headers, params })
|
231
|
+
.then(({ status, data } = {}) => ({ status, data }))
|
232
|
+
)
|
233
|
+
const results = await Promise.allSettled(sending)
|
234
|
+
const isOk = results.some(result => result.status === 'fulfilled')
|
235
|
+
// If, at least, one request is sent to proxy, we treat the whole flow as successful.
|
236
|
+
// Failed requests should be handled by TTL expired/timeout handler
|
237
|
+
// todo: - think, if we should handle failed requests here (e.g., by calling receivedErrorResponse)
|
238
|
+
log.info('triggerSendToProxiesFlow is done:', { isOk, results, proxyNames, alsReq })
|
239
|
+
this.#deps.stepState.inProgress('allSent-12')
|
240
|
+
if (!isOk) {
|
241
|
+
log.warn('no successful requests sent to proxies')
|
242
|
+
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.proxyConnectionError)
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
async #validateParticipant (participantId) {
|
247
|
+
return this.#deps.participant.validateParticipant(participantId)
|
248
|
+
}
|
249
|
+
|
250
|
+
async #forwardGetPartiesRequest ({ sendTo, headers, params }) {
|
251
|
+
this.#deps.stepState.inProgress('forwardRequest-3')
|
252
|
+
const callbackEndpointType = this.#deps.partiesUtils.getPartyCbType(params.SubId)
|
253
|
+
const options = this.#deps.partiesUtils.partiesRequestOptionsDto(params)
|
254
|
+
|
255
|
+
return this.#deps.participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.GET, undefined, options, this.#deps.childSpan)
|
256
|
+
}
|
257
|
+
|
258
|
+
// async sendSuccessCallback ({ headers, sendTo }) {
|
259
|
+
// return this.#deps.participant.sendRequest(headers, sendTo,)
|
260
|
+
// }
|
261
|
+
|
262
|
+
async sendErrorCallback ({ requester, headers, params }) {
|
263
|
+
this.#deps.stepState.inProgress('sendErrorCallback-9')
|
264
|
+
const callbackHeaders = createCallbackHeaders({
|
265
|
+
requestHeaders: headers,
|
266
|
+
partyIdType: params.Type,
|
267
|
+
partyIdentifier: params.ID,
|
268
|
+
endpointTemplate: params.SubId
|
269
|
+
? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
|
270
|
+
: FspEndpointTemplates.PARTIES_PUT_ERROR
|
271
|
+
})
|
272
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
|
273
|
+
|
274
|
+
await this.#deps.participant.sendErrorToParticipant(
|
275
|
+
requester,
|
276
|
+
this.#deps.partiesUtils.errorPartyCbType(params.SubId),
|
277
|
+
fspiopError.toApiErrorObject(this.#deps.config.ERROR_HANDLING),
|
278
|
+
callbackHeaders,
|
279
|
+
params,
|
280
|
+
this.#deps.childSpan
|
281
|
+
)
|
282
|
+
return fspiopError
|
283
|
+
}
|
284
|
+
|
285
|
+
async sendOracleDiscoveryRequest ({ headers, params, query }) {
|
286
|
+
this.#deps.stepState.inProgress('oracleRequest-4')
|
287
|
+
return this.#deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.#deps.cache)
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
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/
|
16
|
+
const partiesUtils = require('../parties/partiesUtils')
|
17
17
|
|
18
18
|
const timeoutCallbackDto = async ({ destination, partyId, partyType }) => {
|
19
19
|
const headers = {
|
package/src/lib/headers.js
CHANGED
@@ -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,
|
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
|
-
|
2
|
-
|
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
|
45
|
-
const partiesUtils = require('../../../../src/domain/parties/
|
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
|
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 =
|
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
|
})
|