@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.
Files changed (147) hide show
  1. package/.ncurc.yaml +3 -1
  2. package/.nvmrc +1 -1
  3. package/.nycrc.yml +16 -1
  4. package/Dockerfile +9 -10
  5. package/README.md +39 -9
  6. package/audit-ci.jsonc +15 -2
  7. package/config/default.json +17 -1
  8. package/docker/central-ledger/default.json +9 -0
  9. package/docker/config-modifier/configs/central-ledger.js +25 -0
  10. package/docker/env.sh +15 -0
  11. package/docker/kafka/scripts/provision.sh +4 -1
  12. package/docker/ml-api-adapter/default.json +4 -1
  13. package/docker-compose.yml +90 -2
  14. package/documentation/db/erd-transfer-timeout.png +0 -0
  15. package/documentation/db/erd-transfer-timeout.txt +81 -0
  16. package/documentation/fx-implementation/README.md +48 -0
  17. package/documentation/fx-implementation/assets/fx-position-movements.drawio.svg +4 -0
  18. package/documentation/fx-implementation/assets/test-scenario.drawio.svg +4 -0
  19. package/documentation/sequence-diagrams/Handler - FX timeout.plantuml +123 -0
  20. package/documentation/sequence-diagrams/Handler - FX timeout.png +0 -0
  21. package/documentation/sequence-diagrams/Handler - timeout.plantuml +81 -0
  22. package/documentation/sequence-diagrams/Handler - timeout.png +0 -0
  23. package/documentation/state-diagrams/transfer-ML-spec-states-diagram.png +0 -0
  24. package/documentation/state-diagrams/transfer-internal-states-diagram.png +0 -0
  25. package/documentation/state-diagrams/transfer-internal-states.plantuml +75 -0
  26. package/documentation/state-diagrams/transfer-states.plantuml +13 -0
  27. package/migrations/310204_transferParticipant-participantId.js +52 -0
  28. package/migrations/310403_participantPositionChange-participantCurrencyId.js +47 -0
  29. package/migrations/310404_participantPositionChange-change.js +46 -0
  30. package/migrations/600010_fxTransferType.js +43 -0
  31. package/migrations/600011_fxTransferType-indexes.js +38 -0
  32. package/migrations/600012_fxParticipantCurrencyType.js +43 -0
  33. package/migrations/600013_fxParticipantCurrencyType-indexes.js +38 -0
  34. package/migrations/600100_fxTransferDuplicateCheck.js +42 -0
  35. package/migrations/600110_fxTransferErrorDuplicateCheck.js.js +17 -0
  36. package/migrations/600200_fxTransfer.js +51 -0
  37. package/migrations/600201_fxTransfer-indexes.js +40 -0
  38. package/migrations/600400_fxTransferStateChange.js +46 -0
  39. package/migrations/600401_fxTransferStateChange-indexes.js +40 -0
  40. package/migrations/600501_fxWatchList.js +46 -0
  41. package/migrations/600502_fxWatchList-indexes.js +40 -0
  42. package/migrations/600600_fxTransferFulfilmentDuplicateCheck.js +43 -0
  43. package/migrations/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +38 -0
  44. package/migrations/600700_fxTransferFulfilment.js +47 -0
  45. package/migrations/600701_fxTransferFulfilment-indexes.js +43 -0
  46. package/migrations/600800_fxTransferExtension.js +47 -0
  47. package/migrations/601400_fxTransferTimeout.js +43 -0
  48. package/migrations/601401_fxTransferTimeout-indexes.js +37 -0
  49. package/migrations/601500_fxTransferError.js +44 -0
  50. package/migrations/601501_fxTransferError-indexes.js +37 -0
  51. package/migrations/610200_fxTransferParticipant.js +52 -0
  52. package/migrations/610201_fxTransferParticipant-indexes.js +44 -0
  53. package/migrations/610202_fxTransferParticipant-participantId.js +52 -0
  54. package/migrations/610403_participantPositionChange-fxTransfer.js +46 -0
  55. package/migrations/910101_feature904DataMigration.js +46 -52
  56. package/migrations/910102_feature949DataMigration.js +219 -225
  57. package/migrations/950104_settlementModel-settlementAccountTypeId.js +15 -20
  58. package/migrations/950108_participantProxy.js +18 -0
  59. package/migrations/950109_fxQuote.js +19 -0
  60. package/migrations/950110_fxQuoteResponse.js +25 -0
  61. package/migrations/950111_fxQuoteError.js +23 -0
  62. package/migrations/950113_fxQuoteDuplicateCheck.js +18 -0
  63. package/migrations/950114_fxQuoteResponseDuplicateCheck.js +21 -0
  64. package/migrations/950115_fxQuoteConversionTerms.js +36 -0
  65. package/migrations/950116_fxQuoteConversionTermsExtension.js +21 -0
  66. package/migrations/950117_fxQuoteResponseConversionTerms.js +39 -0
  67. package/migrations/950118_fxQuoteResponseConversionTermsExtension.js +21 -0
  68. package/migrations/950119_fxCharge.js +27 -0
  69. package/migrations/960100_create_externalParticipant.js +47 -0
  70. package/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js +50 -0
  71. package/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +50 -0
  72. package/package.json +27 -20
  73. package/seeds/endpointType.js +18 -0
  74. package/seeds/fxParticipantCurrencyType.js +45 -0
  75. package/seeds/fxTransferType.js +45 -0
  76. package/seeds/participant.js +2 -1
  77. package/seeds/transferParticipantRoleType.js +9 -0
  78. package/seeds/transferState.js +10 -0
  79. package/src/api/interface/swagger.json +23 -30
  80. package/src/api/participants/handler.js +6 -2
  81. package/src/api/participants/routes.js +8 -7
  82. package/src/api/root/handler.js +15 -5
  83. package/src/domain/fx/cyril.js +466 -0
  84. package/src/domain/fx/index.js +107 -0
  85. package/src/domain/participant/index.js +108 -1
  86. package/src/domain/position/abort.js +215 -0
  87. package/src/domain/position/binProcessor.js +361 -99
  88. package/src/domain/position/fulfil.js +252 -111
  89. package/src/domain/position/fx-fulfil.js +138 -0
  90. package/src/domain/position/fx-prepare.js +280 -0
  91. package/src/domain/position/fx-timeout-reserved.js +159 -0
  92. package/src/domain/position/index.js +1 -0
  93. package/src/domain/position/prepare.js +69 -49
  94. package/src/domain/position/timeout-reserved.js +162 -0
  95. package/src/domain/timeout/index.js +26 -2
  96. package/src/domain/transfer/index.js +22 -5
  97. package/src/domain/transfer/transform.js +19 -6
  98. package/src/handlers/admin/handler.js +0 -2
  99. package/src/handlers/bulk/fulfil/handler.js +5 -5
  100. package/src/handlers/bulk/get/handler.js +5 -5
  101. package/src/handlers/bulk/prepare/handler.js +9 -9
  102. package/src/handlers/bulk/processing/handler.js +8 -7
  103. package/src/handlers/bulk/shared/validator.js +1 -1
  104. package/src/handlers/positions/handler.js +23 -10
  105. package/src/handlers/positions/handlerBatch.js +54 -26
  106. package/src/handlers/register.js +2 -1
  107. package/src/handlers/timeouts/handler.js +212 -68
  108. package/src/handlers/transfers/FxFulfilService.js +387 -0
  109. package/src/handlers/transfers/createRemittanceEntity.js +106 -0
  110. package/src/handlers/transfers/dto.js +53 -0
  111. package/src/handlers/transfers/handler.js +609 -571
  112. package/src/handlers/transfers/prepare.js +572 -0
  113. package/src/handlers/transfers/validator.js +75 -14
  114. package/src/lib/cache.js +1 -1
  115. package/src/lib/config.js +6 -3
  116. package/src/lib/healthCheck/subServiceHealth.js +12 -2
  117. package/src/lib/proxyCache.js +131 -0
  118. package/src/models/bulkTransfer/facade.js +58 -82
  119. package/src/models/fxTransfer/duplicateCheck.js +153 -0
  120. package/src/models/fxTransfer/fxTransfer.js +578 -0
  121. package/src/models/fxTransfer/fxTransferError.js +53 -0
  122. package/src/models/fxTransfer/fxTransferExtension.js +41 -0
  123. package/src/models/fxTransfer/fxTransferTimeout.js +68 -0
  124. package/src/models/fxTransfer/index.js +15 -0
  125. package/src/models/fxTransfer/stateChange.js +47 -0
  126. package/src/models/fxTransfer/watchList.js +49 -0
  127. package/src/models/ledgerAccountType/ledgerAccountType.js +8 -32
  128. package/src/models/misc/segment.js +0 -1
  129. package/src/models/participant/externalParticipant.js +96 -0
  130. package/src/models/participant/externalParticipantCached.js +148 -0
  131. package/src/models/participant/facade.js +192 -123
  132. package/src/models/participant/participant.js +2 -1
  133. package/src/models/participant/participantCurrency.js +1 -1
  134. package/src/models/participant/participantPosition.js +2 -8
  135. package/src/models/position/batch.js +90 -1
  136. package/src/models/position/facade.js +5 -5
  137. package/src/models/position/participantPositionChanges.js +68 -0
  138. package/src/models/settlement/settlementModel.js +5 -23
  139. package/src/models/transfer/facade.js +806 -447
  140. package/src/shared/constants.js +52 -0
  141. package/src/shared/fspiopErrorFactory.js +131 -0
  142. package/src/shared/logger/index.js +8 -0
  143. package/src/shared/loggingPlugin.js +43 -0
  144. package/src/shared/plugins.js +6 -0
  145. package/src/shared/setup.js +10 -0
  146. package/test-integration.Dockerfile +1 -1
  147. 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 = (payload) => {
93
+ const validateDifferentDfsp = (payerFsp, payeeFsp) => {
91
94
  if (!Config.ENABLE_ON_US_TRANSFERS) {
92
- const isPayerAndPayeeDifferent = (payload.payerFsp.toLowerCase() !== payload.payeeFsp.toLowerCase())
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 = (payload, headers) => {
102
- const matched = (headers && headers['fspiop-source'] && headers['fspiop-source'] === payload.payerFsp)
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 validatePrepare = async (payload, headers) => {
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
- validationPassed = (validateFspiopSourceMatchesPayer(payload, headers) &&
203
- await validateParticipantByName(payload.payerFsp) &&
204
- await validatePositionAccountByNameAndCurrency(payload.payerFsp, payload.amount.currency) &&
205
- await validateParticipantByName(payload.payeeFsp) &&
206
- await validatePositionAccountByNameAndCurrency(payload.payeeFsp, payload.amount.currency) &&
207
- validateAmount(payload.amount) &&
208
- await validateConditionAndExpiration(payload) &&
209
- validateDifferentDfsp(payload))
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
@@ -74,7 +74,7 @@ const initCache = async function () {
74
74
  }
75
75
 
76
76
  const destroyCache = async function () {
77
- catboxMemoryClient.stop()
77
+ catboxMemoryClient?.stop()
78
78
  catboxMemoryClient = null
79
79
  }
80
80
 
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: (RC.MONGODB.DEBUG === true || RC.MONGODB.DEBUG === 'true'),
13
- MONGODB_DISABLED: (RC.MONGODB.DISABLED === true || RC.MONGODB.DISABLED === 'true'),
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
- try {
55
- await knex('bulkTransfer').transacting(trx).insert(bulkTransferRecord)
56
- if (payload.extensionList && payload.extensionList.extension) {
57
- const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
58
- return {
59
- bulkTransferId: payload.bulkTransferId,
60
- key: ext.key,
61
- value: ext.value
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
- try {
99
- await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
100
- if (payload.extensionList && payload.extensionList.extension) {
101
- const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
102
- return {
103
- bulkTransferId: payload.bulkTransferId,
104
- isFulfilment: true,
105
- key: ext.key,
106
- value: ext.value
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
- try {
142
- await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
143
- if (payload.errorInformation.extensionList && payload.errorInformation.extensionList.extension) {
144
- const bulkTransferExtensionsRecordList = payload.errorInformation.extensionList.extension.map(ext => {
145
- return {
146
- bulkTransferId: payload.bulkTransferId,
147
- isFulfilment: true,
148
- key: ext.key,
149
- value: ext.value
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
- try {
192
- await knex('bulkTransferFulfilment').transacting(trx).insert(bulkTransferFulfilmentRecord)
193
- if (payload.extensionList && payload.extensionList.extension) {
194
- const bulkTransferExtensionsRecordList = payload.extensionList.extension.map(ext => {
195
- return {
196
- bulkTransferId: payload.bulkTransferId,
197
- isFulfilment: true,
198
- key: ext.key,
199
- value: ext.value
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
+ }