@mojaloop/central-ledger 17.7.8 → 17.8.0-orb.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/.ncurc.yaml +3 -1
- package/.nvmrc +1 -1
- package/.nycrc.yml +16 -1
- package/Dockerfile +9 -10
- package/README.md +39 -9
- package/audit-ci.jsonc +15 -2
- package/config/default.json +17 -1
- package/docker/central-ledger/default.json +9 -0
- package/docker/config-modifier/configs/central-ledger.js +25 -0
- package/docker/env.sh +15 -0
- package/docker/kafka/scripts/provision.sh +4 -1
- package/docker/ml-api-adapter/default.json +4 -1
- package/docker-compose.yml +90 -2
- package/documentation/db/erd-transfer-timeout.png +0 -0
- package/documentation/db/erd-transfer-timeout.txt +81 -0
- package/documentation/fx-implementation/README.md +48 -0
- package/documentation/fx-implementation/assets/fx-position-movements.drawio.svg +4 -0
- package/documentation/fx-implementation/assets/test-scenario.drawio.svg +4 -0
- package/documentation/sequence-diagrams/Handler - FX timeout.plantuml +123 -0
- package/documentation/sequence-diagrams/Handler - FX timeout.png +0 -0
- package/documentation/sequence-diagrams/Handler - timeout.plantuml +81 -0
- package/documentation/sequence-diagrams/Handler - timeout.png +0 -0
- package/documentation/state-diagrams/transfer-ML-spec-states-diagram.png +0 -0
- package/documentation/state-diagrams/transfer-internal-states-diagram.png +0 -0
- package/documentation/state-diagrams/transfer-internal-states.plantuml +75 -0
- package/documentation/state-diagrams/transfer-states.plantuml +13 -0
- package/migrations/310204_transferParticipant-participantId.js +52 -0
- package/migrations/310403_participantPositionChange-participantCurrencyId.js +47 -0
- package/migrations/310404_participantPositionChange-change.js +46 -0
- package/migrations/600010_fxTransferType.js +43 -0
- package/migrations/600011_fxTransferType-indexes.js +38 -0
- package/migrations/600012_fxParticipantCurrencyType.js +43 -0
- package/migrations/600013_fxParticipantCurrencyType-indexes.js +38 -0
- package/migrations/600100_fxTransferDuplicateCheck.js +42 -0
- package/migrations/600110_fxTransferErrorDuplicateCheck.js.js +17 -0
- package/migrations/600200_fxTransfer.js +51 -0
- package/migrations/600201_fxTransfer-indexes.js +40 -0
- package/migrations/600400_fxTransferStateChange.js +46 -0
- package/migrations/600401_fxTransferStateChange-indexes.js +40 -0
- package/migrations/600501_fxWatchList.js +46 -0
- package/migrations/600502_fxWatchList-indexes.js +40 -0
- package/migrations/600600_fxTransferFulfilmentDuplicateCheck.js +43 -0
- package/migrations/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +38 -0
- package/migrations/600700_fxTransferFulfilment.js +47 -0
- package/migrations/600701_fxTransferFulfilment-indexes.js +43 -0
- package/migrations/600800_fxTransferExtension.js +47 -0
- package/migrations/601400_fxTransferTimeout.js +43 -0
- package/migrations/601401_fxTransferTimeout-indexes.js +37 -0
- package/migrations/601500_fxTransferError.js +44 -0
- package/migrations/601501_fxTransferError-indexes.js +37 -0
- package/migrations/610200_fxTransferParticipant.js +52 -0
- package/migrations/610201_fxTransferParticipant-indexes.js +44 -0
- package/migrations/610202_fxTransferParticipant-participantId.js +52 -0
- package/migrations/610403_participantPositionChange-fxTransfer.js +46 -0
- package/migrations/910101_feature904DataMigration.js +46 -52
- package/migrations/910102_feature949DataMigration.js +219 -225
- package/migrations/950104_settlementModel-settlementAccountTypeId.js +15 -20
- package/migrations/950108_participantProxy.js +18 -0
- package/migrations/950109_fxQuote.js +19 -0
- package/migrations/950110_fxQuoteResponse.js +25 -0
- package/migrations/950111_fxQuoteError.js +23 -0
- package/migrations/950113_fxQuoteDuplicateCheck.js +18 -0
- package/migrations/950114_fxQuoteResponseDuplicateCheck.js +21 -0
- package/migrations/950115_fxQuoteConversionTerms.js +36 -0
- package/migrations/950116_fxQuoteConversionTermsExtension.js +21 -0
- package/migrations/950117_fxQuoteResponseConversionTerms.js +39 -0
- package/migrations/950118_fxQuoteResponseConversionTermsExtension.js +21 -0
- package/migrations/950119_fxCharge.js +27 -0
- package/migrations/960100_create_externalParticipant.js +47 -0
- package/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js +50 -0
- package/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +50 -0
- package/package.json +27 -20
- package/seeds/endpointType.js +18 -0
- package/seeds/fxParticipantCurrencyType.js +45 -0
- package/seeds/fxTransferType.js +45 -0
- package/seeds/participant.js +2 -1
- package/seeds/transferParticipantRoleType.js +9 -0
- package/seeds/transferState.js +10 -0
- package/src/api/interface/swagger.json +23 -30
- package/src/api/participants/handler.js +6 -2
- package/src/api/participants/routes.js +8 -7
- package/src/api/root/handler.js +15 -5
- package/src/domain/fx/cyril.js +466 -0
- package/src/domain/fx/index.js +107 -0
- package/src/domain/participant/index.js +108 -1
- package/src/domain/position/abort.js +215 -0
- package/src/domain/position/binProcessor.js +361 -99
- package/src/domain/position/fulfil.js +252 -111
- package/src/domain/position/fx-fulfil.js +138 -0
- package/src/domain/position/fx-prepare.js +280 -0
- package/src/domain/position/fx-timeout-reserved.js +159 -0
- package/src/domain/position/index.js +1 -0
- package/src/domain/position/prepare.js +69 -49
- package/src/domain/position/timeout-reserved.js +162 -0
- package/src/domain/timeout/index.js +26 -2
- package/src/domain/transfer/index.js +22 -5
- package/src/domain/transfer/transform.js +19 -6
- package/src/handlers/admin/handler.js +0 -2
- package/src/handlers/bulk/fulfil/handler.js +5 -5
- package/src/handlers/bulk/get/handler.js +5 -5
- package/src/handlers/bulk/prepare/handler.js +9 -9
- package/src/handlers/bulk/processing/handler.js +8 -7
- package/src/handlers/bulk/shared/validator.js +1 -1
- package/src/handlers/positions/handler.js +23 -10
- package/src/handlers/positions/handlerBatch.js +54 -26
- package/src/handlers/register.js +2 -1
- package/src/handlers/timeouts/handler.js +212 -68
- package/src/handlers/transfers/FxFulfilService.js +387 -0
- package/src/handlers/transfers/createRemittanceEntity.js +106 -0
- package/src/handlers/transfers/dto.js +53 -0
- package/src/handlers/transfers/handler.js +609 -571
- package/src/handlers/transfers/prepare.js +572 -0
- package/src/handlers/transfers/validator.js +75 -14
- package/src/lib/cache.js +1 -1
- package/src/lib/config.js +6 -3
- package/src/lib/healthCheck/subServiceHealth.js +12 -2
- package/src/lib/proxyCache.js +131 -0
- package/src/models/bulkTransfer/facade.js +58 -82
- package/src/models/fxTransfer/duplicateCheck.js +153 -0
- package/src/models/fxTransfer/fxTransfer.js +578 -0
- package/src/models/fxTransfer/fxTransferError.js +53 -0
- package/src/models/fxTransfer/fxTransferExtension.js +41 -0
- package/src/models/fxTransfer/fxTransferTimeout.js +68 -0
- package/src/models/fxTransfer/index.js +15 -0
- package/src/models/fxTransfer/stateChange.js +47 -0
- package/src/models/fxTransfer/watchList.js +49 -0
- package/src/models/ledgerAccountType/ledgerAccountType.js +8 -32
- package/src/models/misc/segment.js +0 -1
- package/src/models/participant/externalParticipant.js +96 -0
- package/src/models/participant/externalParticipantCached.js +148 -0
- package/src/models/participant/facade.js +192 -123
- package/src/models/participant/participant.js +2 -1
- package/src/models/participant/participantCurrency.js +1 -1
- package/src/models/participant/participantPosition.js +2 -8
- package/src/models/position/batch.js +90 -1
- package/src/models/position/facade.js +5 -5
- package/src/models/position/participantPositionChanges.js +68 -0
- package/src/models/settlement/settlementModel.js +5 -23
- package/src/models/transfer/facade.js +806 -447
- package/src/shared/constants.js +52 -0
- package/src/shared/fspiopErrorFactory.js +131 -0
- package/src/shared/logger/index.js +8 -0
- package/src/shared/loggingPlugin.js +43 -0
- package/src/shared/plugins.js +6 -0
- package/src/shared/setup.js +10 -0
- package/test-integration.Dockerfile +1 -1
- package/test.Dockerfile +1 -1
|
@@ -42,6 +42,9 @@ const Decimal = require('decimal.js')
|
|
|
42
42
|
const Config = require('../../lib/config')
|
|
43
43
|
const Participant = require('../../domain/participant')
|
|
44
44
|
const Transfer = require('../../domain/transfer')
|
|
45
|
+
const FxTransferModel = require('../../models/fxTransfer')
|
|
46
|
+
// const TransferStateChangeModel = require('../../models/transfer/transferStateChange')
|
|
47
|
+
const FxTransferStateChangeModel = require('../../models/fxTransfer/stateChange')
|
|
45
48
|
const CryptoConditions = require('../../cryptoConditions')
|
|
46
49
|
const Crypto = require('crypto')
|
|
47
50
|
const base64url = require('base64url')
|
|
@@ -87,9 +90,9 @@ const validatePositionAccountByNameAndCurrency = async function (participantName
|
|
|
87
90
|
return validationPassed
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
const validateDifferentDfsp = (
|
|
93
|
+
const validateDifferentDfsp = (payerFsp, payeeFsp) => {
|
|
91
94
|
if (!Config.ENABLE_ON_US_TRANSFERS) {
|
|
92
|
-
const isPayerAndPayeeDifferent = (
|
|
95
|
+
const isPayerAndPayeeDifferent = (payerFsp.toLowerCase() !== payeeFsp.toLowerCase())
|
|
93
96
|
if (!isPayerAndPayeeDifferent) {
|
|
94
97
|
reasons.push('Payer FSP and Payee FSP should be different, unless on-us tranfers are allowed by the Scheme')
|
|
95
98
|
return false
|
|
@@ -98,8 +101,8 @@ const validateDifferentDfsp = (payload) => {
|
|
|
98
101
|
return true
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
const validateFspiopSourceMatchesPayer = (
|
|
102
|
-
const matched = (headers && headers['fspiop-source'] && headers['fspiop-source'] ===
|
|
104
|
+
const validateFspiopSourceMatchesPayer = (payer, headers) => {
|
|
105
|
+
const matched = (headers && headers['fspiop-source'] && headers['fspiop-source'] === payer)
|
|
103
106
|
if (!matched) {
|
|
104
107
|
reasons.push('FSPIOP-Source header should match Payer')
|
|
105
108
|
return false
|
|
@@ -185,7 +188,11 @@ const validateConditionAndExpiration = async (payload) => {
|
|
|
185
188
|
return true
|
|
186
189
|
}
|
|
187
190
|
|
|
188
|
-
const
|
|
191
|
+
const isAmountValid = (payload, isFx) => isFx
|
|
192
|
+
? validateAmount(payload.sourceAmount) && validateAmount(payload.targetAmount)
|
|
193
|
+
: validateAmount(payload.amount)
|
|
194
|
+
|
|
195
|
+
const validatePrepare = async (payload, headers, isFx = false, determiningTransferCheckResult, proxyObligation) => {
|
|
189
196
|
const histTimerValidatePrepareEnd = Metrics.getHistogram(
|
|
190
197
|
'handlers_transfer_validator',
|
|
191
198
|
'validatePrepare - Metrics for transfer handler',
|
|
@@ -199,15 +206,59 @@ const validatePrepare = async (payload, headers) => {
|
|
|
199
206
|
validationPassed = false
|
|
200
207
|
return { validationPassed, reasons }
|
|
201
208
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
|
|
210
|
+
const initiatingFsp = isFx ? payload.initiatingFsp : payload.payerFsp
|
|
211
|
+
const counterPartyFsp = isFx ? payload.counterPartyFsp : payload.payeeFsp
|
|
212
|
+
|
|
213
|
+
// Check if determining transfers are failed
|
|
214
|
+
if (determiningTransferCheckResult.watchListRecords && determiningTransferCheckResult.watchListRecords.length > 0) {
|
|
215
|
+
// Iterate through determiningTransferCheckResult.watchListRecords
|
|
216
|
+
for (const watchListRecord of determiningTransferCheckResult.watchListRecords) {
|
|
217
|
+
if (isFx) {
|
|
218
|
+
// TODO: Check the transfer state of determiningTransferId
|
|
219
|
+
// const latestTransferStateChange = await TransferStateChangeModel.getByTransferId(watchListRecord.determiningTransferId)
|
|
220
|
+
// if (latestTransferStateChange.transferStateId !== Enum.Transfers.TransferInternalState.RESERVED) {
|
|
221
|
+
// reasons.push('Related Transfer is not in reserved state')
|
|
222
|
+
// validationPassed = false
|
|
223
|
+
// return { validationPassed, reasons }
|
|
224
|
+
// }
|
|
225
|
+
} else {
|
|
226
|
+
// Check the transfer state of commitRequestId
|
|
227
|
+
const latestFxTransferStateChange = await FxTransferStateChangeModel.getByCommitRequestId(watchListRecord.commitRequestId)
|
|
228
|
+
if (latestFxTransferStateChange.transferStateId !== Enum.Transfers.TransferInternalState.RECEIVED_FULFIL_DEPENDENT) {
|
|
229
|
+
reasons.push('Related FX Transfer is not fulfilled')
|
|
230
|
+
validationPassed = false
|
|
231
|
+
return { validationPassed, reasons }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Skip usual validation if preparing a proxy transfer or fxTransfer
|
|
238
|
+
if (!(proxyObligation?.isInitiatingFspProxy || proxyObligation?.isCounterPartyFspProxy)) {
|
|
239
|
+
validationPassed = (
|
|
240
|
+
validateFspiopSourceMatchesPayer(initiatingFsp, headers) &&
|
|
241
|
+
isAmountValid(payload, isFx) &&
|
|
242
|
+
await validateParticipantByName(initiatingFsp) &&
|
|
243
|
+
await validateParticipantByName(counterPartyFsp) &&
|
|
244
|
+
await validateConditionAndExpiration(payload) &&
|
|
245
|
+
validateDifferentDfsp(initiatingFsp, counterPartyFsp)
|
|
246
|
+
)
|
|
247
|
+
} else {
|
|
248
|
+
validationPassed = true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// validate participant accounts from determiningTransferCheckResult
|
|
252
|
+
if (validationPassed && determiningTransferCheckResult) {
|
|
253
|
+
for (const participantCurrency of determiningTransferCheckResult.participantCurrencyValidationList) {
|
|
254
|
+
if (!await validatePositionAccountByNameAndCurrency(participantCurrency.participantName, participantCurrency.currencyId)) {
|
|
255
|
+
validationPassed = false
|
|
256
|
+
break // Exit the loop if validation fails
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
210
260
|
histTimerValidatePrepareEnd({ success: true, funcName: 'validatePrepare' })
|
|
261
|
+
|
|
211
262
|
return {
|
|
212
263
|
validationPassed,
|
|
213
264
|
reasons
|
|
@@ -241,11 +292,21 @@ const validateParticipantTransferId = async function (participantName, transferI
|
|
|
241
292
|
return validationPassed
|
|
242
293
|
}
|
|
243
294
|
|
|
295
|
+
const validateParticipantForCommitRequestId = async function (participantName, commitRequestId) {
|
|
296
|
+
const fxTransferParticipants = await FxTransferModel.fxTransfer.getFxTransferParticipant(participantName, commitRequestId)
|
|
297
|
+
let validationPassed = false
|
|
298
|
+
if (Array.isArray(fxTransferParticipants) && fxTransferParticipants.length > 0) {
|
|
299
|
+
validationPassed = true
|
|
300
|
+
}
|
|
301
|
+
return validationPassed
|
|
302
|
+
}
|
|
303
|
+
|
|
244
304
|
module.exports = {
|
|
245
305
|
validatePrepare,
|
|
246
306
|
validateById,
|
|
247
307
|
validateFulfilCondition,
|
|
248
308
|
validateParticipantByName,
|
|
249
309
|
reasons,
|
|
250
|
-
validateParticipantTransferId
|
|
310
|
+
validateParticipantTransferId,
|
|
311
|
+
validateParticipantForCommitRequestId
|
|
251
312
|
}
|
package/src/lib/cache.js
CHANGED
package/src/lib/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const RC = require('rc')('CLEDG', require('../../config/default.json'))
|
|
1
|
+
const RC = require('parse-strings-in-object')(require('rc')('CLEDG', require('../../config/default.json')))
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
HOSTNAME: RC.HOSTNAME.replace(/\/$/, ''),
|
|
@@ -9,8 +9,8 @@ module.exports = {
|
|
|
9
9
|
MONGODB_USER: RC.MONGODB.USER,
|
|
10
10
|
MONGODB_PASSWORD: RC.MONGODB.PASSWORD,
|
|
11
11
|
MONGODB_DATABASE: RC.MONGODB.DATABASE,
|
|
12
|
-
MONGODB_DEBUG:
|
|
13
|
-
MONGODB_DISABLED:
|
|
12
|
+
MONGODB_DEBUG: RC.MONGODB.DEBUG === true,
|
|
13
|
+
MONGODB_DISABLED: RC.MONGODB.DISABLED === true,
|
|
14
14
|
AMOUNT: RC.AMOUNT,
|
|
15
15
|
EXPIRES_TIMEOUT: RC.EXPIRES_TIMEOUT,
|
|
16
16
|
ERROR_HANDLING: RC.ERROR_HANDLING,
|
|
@@ -23,6 +23,7 @@ module.exports = {
|
|
|
23
23
|
HANDLERS_TIMEOUT_TIMEXP: RC.HANDLERS.TIMEOUT.TIMEXP,
|
|
24
24
|
HANDLERS_TIMEOUT_TIMEZONE: RC.HANDLERS.TIMEOUT.TIMEZONE,
|
|
25
25
|
CACHE_CONFIG: RC.CACHE,
|
|
26
|
+
PROXY_CACHE_CONFIG: RC.PROXY_CACHE,
|
|
26
27
|
KAFKA_CONFIG: RC.KAFKA,
|
|
27
28
|
PARTICIPANT_INITIAL_POSITION: RC.PARTICIPANT_INITIAL_POSITION,
|
|
28
29
|
RUN_MIGRATIONS: !RC.MIGRATIONS.DISABLED,
|
|
@@ -69,5 +70,7 @@ module.exports = {
|
|
|
69
70
|
debug: RC.DATABASE.DEBUG
|
|
70
71
|
},
|
|
71
72
|
API_DOC_ENDPOINTS_ENABLED: RC.API_DOC_ENDPOINTS_ENABLED || false,
|
|
73
|
+
// If this is set to true, payee side currency conversion will not be allowed due to a limitation in the current implementation
|
|
74
|
+
PAYEE_PARTICIPANT_CURRENCY_VALIDATION_ENABLED: (RC.PAYEE_PARTICIPANT_CURRENCY_VALIDATION_ENABLED === true || RC.PAYEE_PARTICIPANT_CURRENCY_VALIDATION_ENABLED === 'true'),
|
|
72
75
|
SETTLEMENT_MODELS: RC.SETTLEMENT_MODELS
|
|
73
76
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
const { statusEnum, serviceName } = require('@mojaloop/central-services-shared').HealthCheck.HealthCheckEnums
|
|
27
27
|
const Logger = require('@mojaloop/central-services-logger')
|
|
28
28
|
const Consumer = require('@mojaloop/central-services-stream').Util.Consumer
|
|
29
|
-
|
|
29
|
+
const ProxyCache = require('../proxyCache')
|
|
30
30
|
const MigrationLockModel = require('../../models/misc/migrationLock')
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -82,7 +82,17 @@ const getSubServiceHealthDatastore = async () => {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
const getSubServiceHealthProxyCache = async () => {
|
|
86
|
+
const proxyCache = ProxyCache.getCache()
|
|
87
|
+
const healthCheck = await proxyCache.healthCheck()
|
|
88
|
+
return {
|
|
89
|
+
name: 'proxyCache',
|
|
90
|
+
status: healthCheck ? statusEnum.OK : statusEnum.DOWN
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
module.exports = {
|
|
86
95
|
getSubServiceHealthBroker,
|
|
87
|
-
getSubServiceHealthDatastore
|
|
96
|
+
getSubServiceHealthDatastore,
|
|
97
|
+
getSubServiceHealthProxyCache
|
|
88
98
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib')
|
|
3
|
+
const { Enum } = require('@mojaloop/central-services-shared')
|
|
4
|
+
const ParticipantService = require('../../src/domain/participant')
|
|
5
|
+
const Config = require('./config.js')
|
|
6
|
+
const { logger } = require('../../src/shared/logger')
|
|
7
|
+
|
|
8
|
+
let proxyCache
|
|
9
|
+
|
|
10
|
+
const init = () => {
|
|
11
|
+
const { type, proxyConfig } = Config.PROXY_CACHE_CONFIG
|
|
12
|
+
proxyCache = createProxyCache(type, proxyConfig)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const connect = async () => {
|
|
16
|
+
return !proxyCache?.isConnected && getCache().connect()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const disconnect = async () => {
|
|
20
|
+
proxyCache?.isConnected && await proxyCache.disconnect()
|
|
21
|
+
proxyCache = null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const reset = async () => {
|
|
25
|
+
await disconnect()
|
|
26
|
+
proxyCache = null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getCache = () => {
|
|
30
|
+
if (!proxyCache) {
|
|
31
|
+
init()
|
|
32
|
+
}
|
|
33
|
+
return proxyCache
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} ProxyOrParticipant - An object containing the inScheme status, proxyId and FSP name
|
|
38
|
+
*
|
|
39
|
+
* @property {boolean} inScheme - Is FSP in the scheme.
|
|
40
|
+
* @property {string|null} proxyId - Proxy, associated with the FSP, if FSP is not in the scheme.
|
|
41
|
+
* @property {string} name - FSP name.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks if dfspId is in scheme or proxy.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} dfspId - The DFSP ID to check.
|
|
48
|
+
* @param {Object} [options] - { validateCurrencyAccounts: boolean, accounts: [ { currency: string, accountType: Enum.Accounts.LedgerAccountType } ] }
|
|
49
|
+
* @returns {ProxyOrParticipant} proxyOrParticipant details
|
|
50
|
+
*/
|
|
51
|
+
const getFSPProxy = async (dfspId, options = null) => {
|
|
52
|
+
logger.debug('Checking if dfspId is in scheme or proxy', { dfspId })
|
|
53
|
+
const participant = await ParticipantService.getByName(dfspId)
|
|
54
|
+
let inScheme = !!participant
|
|
55
|
+
|
|
56
|
+
if (inScheme && options?.validateCurrencyAccounts) {
|
|
57
|
+
logger.debug('Checking if participant currency accounts are active', { dfspId, options, participant })
|
|
58
|
+
let accountsAreActive = false
|
|
59
|
+
for (const account of options.accounts) {
|
|
60
|
+
accountsAreActive = participant.currencyList.some((currAccount) => {
|
|
61
|
+
return (
|
|
62
|
+
currAccount.currencyId === account.currency &&
|
|
63
|
+
currAccount.ledgerAccountTypeId === account.accountType &&
|
|
64
|
+
currAccount.isActive === 1
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
if (!accountsAreActive) break
|
|
68
|
+
}
|
|
69
|
+
inScheme = accountsAreActive
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
inScheme,
|
|
74
|
+
proxyId: !participant ? await getCache().lookupProxyByDfspId(dfspId) : null,
|
|
75
|
+
name: dfspId
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const checkSameCreditorDebtorProxy = async (debtorDfspId, creditorDfspId) => {
|
|
80
|
+
logger.debug('Checking if debtorDfspId and creditorDfspId are using the same proxy', { debtorDfspId, creditorDfspId })
|
|
81
|
+
const [debtorProxyId, creditorProxyId] = await Promise.all([
|
|
82
|
+
getCache().lookupProxyByDfspId(debtorDfspId),
|
|
83
|
+
getCache().lookupProxyByDfspId(creditorDfspId)
|
|
84
|
+
])
|
|
85
|
+
return debtorProxyId && creditorProxyId ? debtorProxyId === creditorProxyId : false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const getProxyParticipantAccountDetails = async (fspName, currency) => {
|
|
89
|
+
logger.debug('Getting account details for fspName and currency', { fspName, currency })
|
|
90
|
+
const proxyLookupResult = await getFSPProxy(fspName)
|
|
91
|
+
if (proxyLookupResult.inScheme) {
|
|
92
|
+
const participantCurrency = await ParticipantService.getAccountByNameAndCurrency(
|
|
93
|
+
fspName,
|
|
94
|
+
currency,
|
|
95
|
+
Enum.Accounts.LedgerAccountType.POSITION
|
|
96
|
+
)
|
|
97
|
+
logger.debug("Account details for fspName's currency", { fspName, currency, participantCurrency })
|
|
98
|
+
return {
|
|
99
|
+
inScheme: true,
|
|
100
|
+
participantCurrencyId: participantCurrency?.participantCurrencyId || null
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
if (proxyLookupResult.proxyId) {
|
|
104
|
+
const participantCurrency = await ParticipantService.getAccountByNameAndCurrency(
|
|
105
|
+
proxyLookupResult.proxyId,
|
|
106
|
+
currency,
|
|
107
|
+
Enum.Accounts.LedgerAccountType.POSITION
|
|
108
|
+
)
|
|
109
|
+
logger.debug('Account details for proxy\'s currency', { proxyId: proxyLookupResult.proxyId, currency, participantCurrency })
|
|
110
|
+
return {
|
|
111
|
+
inScheme: false,
|
|
112
|
+
participantCurrencyId: participantCurrency?.participantCurrencyId || null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
logger.debug('No proxy found for fspName', { fspName })
|
|
116
|
+
return {
|
|
117
|
+
inScheme: false,
|
|
118
|
+
participantCurrencyId: null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
reset, // for testing
|
|
125
|
+
connect,
|
|
126
|
+
disconnect,
|
|
127
|
+
getCache,
|
|
128
|
+
getFSPProxy,
|
|
129
|
+
getProxyParticipantAccountDetails,
|
|
130
|
+
checkSameCreditorDebtorProxy
|
|
131
|
+
}
|
|
@@ -51,25 +51,19 @@ const saveBulkTransferReceived = async (payload, participants, stateReason = nul
|
|
|
51
51
|
|
|
52
52
|
const knex = await Db.getKnex()
|
|
53
53
|
return await knex.transaction(async (trx) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
65
|
-
}
|
|
66
|
-
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
67
|
-
await trx.commit
|
|
68
|
-
return state
|
|
69
|
-
} catch (err) {
|
|
70
|
-
await trx.rollback
|
|
71
|
-
throw err
|
|
54
|
+
await knex('bulkTransfer').transacting(trx).insert(bulkTransferRecord)
|
|
55
|
+
if (payload.extensionList && payload.extensionList.extension) {
|
|
56
|
+
const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
|
|
57
|
+
return {
|
|
58
|
+
bulkTransferId: payload.bulkTransferId,
|
|
59
|
+
key: ext.key,
|
|
60
|
+
value: ext.value
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
72
64
|
}
|
|
65
|
+
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
66
|
+
return state
|
|
73
67
|
})
|
|
74
68
|
} catch (err) {
|
|
75
69
|
Logger.isErrorEnabled && Logger.error(err)
|
|
@@ -95,26 +89,20 @@ const saveBulkTransferProcessing = async (payload, stateReason = null, isValid =
|
|
|
95
89
|
|
|
96
90
|
const knex = await Db.getKnex()
|
|
97
91
|
return await knex.transaction(async (trx) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
110
|
-
}
|
|
111
|
-
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
112
|
-
await trx.commit
|
|
113
|
-
return state
|
|
114
|
-
} catch (err) {
|
|
115
|
-
await trx.rollback
|
|
116
|
-
throw err
|
|
92
|
+
await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
|
|
93
|
+
if (payload.extensionList && payload.extensionList.extension) {
|
|
94
|
+
const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
|
|
95
|
+
return {
|
|
96
|
+
bulkTransferId: payload.bulkTransferId,
|
|
97
|
+
isFulfilment: true,
|
|
98
|
+
key: ext.key,
|
|
99
|
+
value: ext.value
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
117
103
|
}
|
|
104
|
+
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
105
|
+
return state
|
|
118
106
|
})
|
|
119
107
|
} catch (err) {
|
|
120
108
|
Logger.isErrorEnabled && Logger.error(err)
|
|
@@ -138,33 +126,27 @@ const saveBulkTransferErrorProcessing = async (payload, stateReason = null, isVa
|
|
|
138
126
|
|
|
139
127
|
const knex = await Db.getKnex()
|
|
140
128
|
return await knex.transaction(async (trx) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
153
|
-
}
|
|
154
|
-
const returnedInsertIds = await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord).returning('bulkTransferStateChangeId')
|
|
155
|
-
const bulkTransferStateChangeId = returnedInsertIds[0]
|
|
156
|
-
const bulkTransferErrorRecord = {
|
|
157
|
-
bulkTransferStateChangeId,
|
|
158
|
-
errorCode: payload.errorInformation.errorCode,
|
|
159
|
-
errorDescription: payload.errorInformation.errorDescription
|
|
160
|
-
}
|
|
161
|
-
await knex('bulkTransferError').transacting(trx).insert(bulkTransferErrorRecord)
|
|
162
|
-
await trx.commit
|
|
163
|
-
return state
|
|
164
|
-
} catch (err) {
|
|
165
|
-
await trx.rollback
|
|
166
|
-
throw err
|
|
129
|
+
await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
|
|
130
|
+
if (payload.errorInformation.extensionList && payload.errorInformation.extensionList.extension) {
|
|
131
|
+
const bulkTransferExtensionsRecordList = payload.errorInformation.extensionList.extension.map(ext => {
|
|
132
|
+
return {
|
|
133
|
+
bulkTransferId: payload.bulkTransferId,
|
|
134
|
+
isFulfilment: true,
|
|
135
|
+
key: ext.key,
|
|
136
|
+
value: ext.value
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
167
140
|
}
|
|
141
|
+
const returnedInsertIds = await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord).returning('bulkTransferStateChangeId')
|
|
142
|
+
const bulkTransferStateChangeId = returnedInsertIds[0]
|
|
143
|
+
const bulkTransferErrorRecord = {
|
|
144
|
+
bulkTransferStateChangeId,
|
|
145
|
+
errorCode: payload.errorInformation.errorCode,
|
|
146
|
+
errorDescription: payload.errorInformation.errorDescription
|
|
147
|
+
}
|
|
148
|
+
await knex('bulkTransferError').transacting(trx).insert(bulkTransferErrorRecord)
|
|
149
|
+
return state
|
|
168
150
|
})
|
|
169
151
|
} catch (err) {
|
|
170
152
|
Logger.isErrorEnabled && Logger.error(err)
|
|
@@ -188,26 +170,20 @@ const saveBulkTransferAborting = async (payload, stateReason = null) => {
|
|
|
188
170
|
|
|
189
171
|
const knex = await Db.getKnex()
|
|
190
172
|
return await knex.transaction(async (trx) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
203
|
-
}
|
|
204
|
-
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
205
|
-
await trx.commit
|
|
206
|
-
return state
|
|
207
|
-
} catch (err) {
|
|
208
|
-
await trx.rollback
|
|
209
|
-
throw err
|
|
173
|
+
await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
|
|
174
|
+
if (payload.extensionList && payload.extensionList.extension) {
|
|
175
|
+
const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
|
|
176
|
+
return {
|
|
177
|
+
bulkTransferId: payload.bulkTransferId,
|
|
178
|
+
isFulfilment: true,
|
|
179
|
+
key: ext.key,
|
|
180
|
+
value: ext.value
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
await knex.batchInsert('bulkTransferExtension', bulkTransferExtensionsRecordList).transacting(trx)
|
|
210
184
|
}
|
|
185
|
+
await knex('bulkTransferStateChange').transacting(trx).insert(bulkTransferStateChangeRecord)
|
|
186
|
+
return state
|
|
211
187
|
})
|
|
212
188
|
} catch (err) {
|
|
213
189
|
Logger.isErrorEnabled && Logger.error(err)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
2
|
+
const Metrics = require('@mojaloop/central-services-metrics')
|
|
3
|
+
const Db = require('../../lib/db')
|
|
4
|
+
const { logger } = require('../../shared/logger')
|
|
5
|
+
const { TABLE_NAMES } = require('../../shared/constants')
|
|
6
|
+
|
|
7
|
+
const histName = 'model_fx_transfer'
|
|
8
|
+
|
|
9
|
+
const getOneByCommitRequestId = async ({ commitRequestId, table, queryName }) => {
|
|
10
|
+
const histTimerEnd = Metrics.getHistogram(
|
|
11
|
+
histName,
|
|
12
|
+
`${queryName} - Metrics for fxTransfer duplicate check model`,
|
|
13
|
+
['success', 'queryName']
|
|
14
|
+
).startTimer()
|
|
15
|
+
logger.debug('get duplicate record', { commitRequestId, table, queryName })
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = await Db.from(table).findOne({ commitRequestId })
|
|
19
|
+
histTimerEnd({ success: true, queryName })
|
|
20
|
+
return result
|
|
21
|
+
} catch (err) {
|
|
22
|
+
histTimerEnd({ success: false, queryName })
|
|
23
|
+
throw ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const saveCommitRequestIdAndHash = async ({ commitRequestId, hash, table, queryName }) => {
|
|
28
|
+
const histTimerEnd = Metrics.getHistogram(
|
|
29
|
+
histName,
|
|
30
|
+
`${queryName} - Metrics for fxTransfer duplicate check model`,
|
|
31
|
+
['success', 'queryName']
|
|
32
|
+
).startTimer()
|
|
33
|
+
logger.debug('save duplicate record', { commitRequestId, hash, table })
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await Db.from(table).insert({ commitRequestId, hash })
|
|
37
|
+
histTimerEnd({ success: true, queryName })
|
|
38
|
+
return result
|
|
39
|
+
} catch (err) {
|
|
40
|
+
histTimerEnd({ success: false, queryName })
|
|
41
|
+
throw ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @function GetTransferDuplicateCheck
|
|
47
|
+
*
|
|
48
|
+
* @async
|
|
49
|
+
* @description This retrieves the fxTransferDuplicateCheck table record if present
|
|
50
|
+
*
|
|
51
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
52
|
+
*
|
|
53
|
+
* @returns {object} - Returns the record from fxTransferDuplicateCheck table, or throws an error if failed
|
|
54
|
+
*/
|
|
55
|
+
const getFxTransferDuplicateCheck = async (commitRequestId) => {
|
|
56
|
+
const table = TABLE_NAMES.fxTransferDuplicateCheck
|
|
57
|
+
const queryName = `${table}_getFxTransferDuplicateCheck`
|
|
58
|
+
return getOneByCommitRequestId({ commitRequestId, table, queryName })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @function SaveTransferDuplicateCheck
|
|
63
|
+
*
|
|
64
|
+
* @async
|
|
65
|
+
* @description This inserts a record into fxTransferDuplicateCheck table
|
|
66
|
+
*
|
|
67
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
68
|
+
* @param {string} hash - the hash of the fxTransfer request payload
|
|
69
|
+
*
|
|
70
|
+
* @returns {integer} - Returns the database id of the inserted row, or throws an error if failed
|
|
71
|
+
*/
|
|
72
|
+
const saveFxTransferDuplicateCheck = async (commitRequestId, hash) => {
|
|
73
|
+
const table = TABLE_NAMES.fxTransferDuplicateCheck
|
|
74
|
+
const queryName = `${table}_saveFxTransferDuplicateCheck`
|
|
75
|
+
return saveCommitRequestIdAndHash({ commitRequestId, hash, table, queryName })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @function getFxTransferErrorDuplicateCheck
|
|
80
|
+
*
|
|
81
|
+
* @async
|
|
82
|
+
* @description This retrieves the fxTransferErrorDuplicateCheck table record if present
|
|
83
|
+
*
|
|
84
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
85
|
+
*
|
|
86
|
+
* @returns {object} - Returns the record from fxTransferDuplicateCheck table, or throws an error if failed
|
|
87
|
+
*/
|
|
88
|
+
const getFxTransferErrorDuplicateCheck = async (commitRequestId) => {
|
|
89
|
+
const table = TABLE_NAMES.fxTransferErrorDuplicateCheck
|
|
90
|
+
const queryName = `${table}_getFxTransferErrorDuplicateCheck`
|
|
91
|
+
return getOneByCommitRequestId({ commitRequestId, table, queryName })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @function saveFxTransferErrorDuplicateCheck
|
|
96
|
+
*
|
|
97
|
+
* @async
|
|
98
|
+
* @description This inserts a record into fxTransferErrorDuplicateCheck table
|
|
99
|
+
*
|
|
100
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
101
|
+
* @param {string} hash - the hash of the fxTransfer request payload
|
|
102
|
+
*
|
|
103
|
+
* @returns {integer} - Returns the database id of the inserted row, or throws an error if failed
|
|
104
|
+
*/
|
|
105
|
+
const saveFxTransferErrorDuplicateCheck = async (commitRequestId, hash) => {
|
|
106
|
+
const table = TABLE_NAMES.fxTransferErrorDuplicateCheck
|
|
107
|
+
const queryName = `${table}_saveFxTransferErrorDuplicateCheck`
|
|
108
|
+
return saveCommitRequestIdAndHash({ commitRequestId, hash, table, queryName })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @function getFxTransferFulfilmentDuplicateCheck
|
|
113
|
+
*
|
|
114
|
+
* @async
|
|
115
|
+
* @description This retrieves the fxTransferFulfilmentDuplicateCheck table record if present
|
|
116
|
+
*
|
|
117
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
118
|
+
*
|
|
119
|
+
* @returns {object} - Returns the record from fxTransferFulfilmentDuplicateCheck table, or throws an error if failed
|
|
120
|
+
*/
|
|
121
|
+
const getFxTransferFulfilmentDuplicateCheck = async (commitRequestId) => {
|
|
122
|
+
const table = TABLE_NAMES.fxTransferFulfilmentDuplicateCheck
|
|
123
|
+
const queryName = `${table}_getFxTransferFulfilmentDuplicateCheck`
|
|
124
|
+
return getOneByCommitRequestId({ commitRequestId, table, queryName })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @function saveFxTransferFulfilmentDuplicateCheck
|
|
129
|
+
*
|
|
130
|
+
* @async
|
|
131
|
+
* @description This inserts a record into fxTransferFulfilmentDuplicateCheck table
|
|
132
|
+
*
|
|
133
|
+
* @param {string} commitRequestId - the fxTransfer commitRequestId
|
|
134
|
+
* @param {string} hash - the hash of the fxTransfer request payload
|
|
135
|
+
*
|
|
136
|
+
* @returns {integer} - Returns the database id of the inserted row, or throws an error if failed
|
|
137
|
+
*/
|
|
138
|
+
const saveFxTransferFulfilmentDuplicateCheck = async (commitRequestId, hash) => {
|
|
139
|
+
const table = TABLE_NAMES.fxTransferFulfilmentDuplicateCheck
|
|
140
|
+
const queryName = `${table}_saveFxTransferFulfilmentDuplicateCheck`
|
|
141
|
+
return saveCommitRequestIdAndHash({ commitRequestId, hash, table, queryName })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
getFxTransferDuplicateCheck,
|
|
146
|
+
saveFxTransferDuplicateCheck,
|
|
147
|
+
|
|
148
|
+
getFxTransferErrorDuplicateCheck,
|
|
149
|
+
saveFxTransferErrorDuplicateCheck,
|
|
150
|
+
|
|
151
|
+
getFxTransferFulfilmentDuplicateCheck,
|
|
152
|
+
saveFxTransferFulfilmentDuplicateCheck
|
|
153
|
+
}
|