@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
|
@@ -13,149 +13,290 @@ const TransferObjectTransform = require('../../domain/transfer/transform')
|
|
|
13
13
|
* @description This is the domain function to process a bin of position-fulfil messages of a single participant account.
|
|
14
14
|
*
|
|
15
15
|
* @param {array} commitReserveFulfilBins - an array containing commit and reserve action bins
|
|
16
|
-
* @param {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {number} accumulatedPositionValue - value of position accumulated so far from previous bin processing
|
|
18
|
+
* @param {number} accumulatedPositionReservedValue - value of position reserved accumulated so far, not used but kept for consistency
|
|
19
|
+
* @param {object} accumulatedTransferStates - object with transfer id keys and transfer state id values. Used to check if transfer is in correct state for processing. Clone and update states for output.
|
|
20
|
+
* @param {object} transferInfoList - object with transfer id keys and transfer info values. Used to pass transfer info to domain function.
|
|
21
|
+
* @param {boolean} changePositions - whether to change positions or not
|
|
20
22
|
* @returns {object} - Returns an object containing accumulatedPositionValue, accumulatedPositionReservedValue, accumulatedTransferStateChanges, accumulatedTransferStates, resultMessages, limitAlarms or throws an error if failed
|
|
21
23
|
*/
|
|
22
24
|
const processPositionFulfilBin = async (
|
|
23
25
|
commitReserveFulfilBins,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
{
|
|
27
|
+
accumulatedPositionValue,
|
|
28
|
+
accumulatedPositionReservedValue,
|
|
29
|
+
accumulatedTransferStates,
|
|
30
|
+
accumulatedFxTransferStates,
|
|
31
|
+
transferInfoList,
|
|
32
|
+
reservedActionTransfers,
|
|
33
|
+
changePositions = true
|
|
34
|
+
}
|
|
29
35
|
) => {
|
|
30
36
|
const transferStateChanges = []
|
|
37
|
+
const fxTransferStateChanges = []
|
|
31
38
|
const participantPositionChanges = []
|
|
32
39
|
const resultMessages = []
|
|
40
|
+
const followupMessages = []
|
|
33
41
|
const accumulatedTransferStatesCopy = Object.assign({}, accumulatedTransferStates)
|
|
42
|
+
const accumulatedFxTransferStatesCopy = Object.assign({}, accumulatedFxTransferStates)
|
|
34
43
|
let runningPosition = new MLNumber(accumulatedPositionValue)
|
|
35
44
|
|
|
36
45
|
for (const binItems of commitReserveFulfilBins) {
|
|
37
46
|
if (binItems && binItems.length > 0) {
|
|
38
47
|
for (const binItem of binItems) {
|
|
39
|
-
let transferStateId
|
|
40
|
-
let reason
|
|
41
|
-
let resultMessage
|
|
42
48
|
const transferId = binItem.message.value.content.uriParams.id
|
|
43
49
|
const payeeFsp = binItem.message.value.from
|
|
44
50
|
const payerFsp = binItem.message.value.to
|
|
45
51
|
const transfer = binItem.decodedPayload
|
|
46
|
-
|
|
47
|
-
Logger.isDebugEnabled && Logger.debug(`accumulatedTransferStates: ${JSON.stringify(accumulatedTransferStates)}`)
|
|
52
|
+
|
|
48
53
|
// Inform payee dfsp if transfer is not in RECEIVED_FULFIL state, skip making any transfer state changes
|
|
49
54
|
if (accumulatedTransferStates[transferId] !== Enum.Transfers.TransferInternalState.RECEIVED_FULFIL) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const headers = { ...binItem.message.value.content.headers }
|
|
53
|
-
headers[Enum.Http.Headers.FSPIOP.DESTINATION] = payeeFsp
|
|
54
|
-
headers[Enum.Http.Headers.FSPIOP.SOURCE] = Enum.Http.Headers.FSPIOP.SWITCH.value
|
|
55
|
-
delete headers['content-length']
|
|
56
|
-
|
|
57
|
-
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(
|
|
58
|
-
`Invalid State: ${accumulatedTransferStates[transferId]} - expected: ${Enum.Transfers.TransferInternalState.RECEIVED_FULFIL}`
|
|
59
|
-
).toApiErrorObject(Config.ERROR_HANDLING)
|
|
60
|
-
const state = Utility.StreamingProtocol.createEventState(
|
|
61
|
-
Enum.Events.EventStatus.FAILURE.status,
|
|
62
|
-
fspiopError.errorInformation.errorCode,
|
|
63
|
-
fspiopError.errorInformation.errorDescription
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
67
|
-
transferId,
|
|
68
|
-
Enum.Kafka.Topics.NOTIFICATION,
|
|
69
|
-
Enum.Events.Event.Action.FULFIL,
|
|
70
|
-
state
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
resultMessage = Utility.StreamingProtocol.createMessage(
|
|
74
|
-
transferId,
|
|
75
|
-
payeeFsp,
|
|
76
|
-
Enum.Http.Headers.FSPIOP.SWITCH.value,
|
|
77
|
-
metadata,
|
|
78
|
-
headers,
|
|
79
|
-
fspiopError,
|
|
80
|
-
{ id: transferId },
|
|
81
|
-
'application/json'
|
|
82
|
-
)
|
|
55
|
+
const resultMessage = _handleIncorrectTransferState(binItem, payeeFsp, transferId, accumulatedTransferStates)
|
|
56
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
83
57
|
} else {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
58
|
+
Logger.isDebugEnabled && Logger.debug(`processPositionFulfilBin::transfer:processingMessage: ${JSON.stringify(transfer)}`)
|
|
59
|
+
Logger.isDebugEnabled && Logger.debug(`accumulatedTransferStates: ${JSON.stringify(accumulatedTransferStates)}`)
|
|
60
|
+
const cyrilResult = binItem.message.value.content.context?.cyrilResult
|
|
61
|
+
if (cyrilResult && cyrilResult.isFx) {
|
|
62
|
+
// This is FX transfer
|
|
63
|
+
// Handle position movements
|
|
64
|
+
// Iterate through positionChanges and handle each position movement, mark as done and publish a position-commit kafka message again for the next item
|
|
65
|
+
// Find out the first item to be processed
|
|
66
|
+
const positionChangeIndex = cyrilResult.positionChanges.findIndex(positionChange => !positionChange.isDone)
|
|
67
|
+
const positionChangeToBeProcessed = cyrilResult.positionChanges[positionChangeIndex]
|
|
68
|
+
let transferStateIdCopy
|
|
69
|
+
if (positionChangeToBeProcessed.isFxTransferStateChange) {
|
|
70
|
+
const { participantPositionChange, fxTransferStateChange, transferStateId, updatedRunningPosition } =
|
|
71
|
+
_handleParticipantPositionChangeFx(runningPosition, positionChangeToBeProcessed.amount, positionChangeToBeProcessed.commitRequestId, accumulatedPositionReservedValue)
|
|
72
|
+
transferStateIdCopy = transferStateId
|
|
73
|
+
runningPosition = updatedRunningPosition
|
|
74
|
+
participantPositionChanges.push(participantPositionChange)
|
|
75
|
+
fxTransferStateChanges.push(fxTransferStateChange)
|
|
76
|
+
accumulatedFxTransferStatesCopy[positionChangeToBeProcessed.commitRequestId] = transferStateId
|
|
77
|
+
const patchMessages = _constructPatchNotificationResultMessage(
|
|
78
|
+
binItem,
|
|
79
|
+
cyrilResult
|
|
80
|
+
)
|
|
81
|
+
for (const patchMessage of patchMessages) {
|
|
82
|
+
resultMessages.push({ binItem, message: patchMessage })
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
const { participantPositionChange, transferStateChange, transferStateId, updatedRunningPosition } =
|
|
86
|
+
_handleParticipantPositionChange(runningPosition, positionChangeToBeProcessed.amount, positionChangeToBeProcessed.transferId, accumulatedPositionReservedValue)
|
|
87
|
+
transferStateIdCopy = transferStateId
|
|
88
|
+
runningPosition = updatedRunningPosition
|
|
89
|
+
participantPositionChanges.push(participantPositionChange)
|
|
90
|
+
transferStateChanges.push(transferStateChange)
|
|
91
|
+
accumulatedTransferStatesCopy[positionChangeToBeProcessed.transferId] = transferStateId
|
|
92
|
+
}
|
|
93
|
+
binItem.result = { success: true }
|
|
94
|
+
cyrilResult.positionChanges[positionChangeIndex].isDone = true
|
|
95
|
+
const nextIndex = cyrilResult.positionChanges.findIndex(positionChange => !positionChange.isDone)
|
|
96
|
+
if (nextIndex === -1) {
|
|
97
|
+
// All position changes are done
|
|
98
|
+
const resultMessage = _constructTransferFulfilResultMessage(binItem, transferId, payerFsp, payeeFsp, transfer, reservedActionTransfers, transferStateIdCopy)
|
|
99
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
100
|
+
} else {
|
|
101
|
+
// There are still position changes to be processed
|
|
102
|
+
// Send position-commit kafka message again for the next item
|
|
103
|
+
const participantCurrencyId = cyrilResult.positionChanges[nextIndex].participantCurrencyId
|
|
104
|
+
const followupMessage = _constructTransferFulfilResultMessage(binItem, transferId, payerFsp, payeeFsp, transfer, reservedActionTransfers, transferStateIdCopy)
|
|
105
|
+
// Pass down the context to the followup message with mutated cyrilResult
|
|
106
|
+
followupMessage.content.context = binItem.message.value.content.context
|
|
107
|
+
followupMessages.push({ binItem, messageKey: participantCurrencyId.toString(), message: followupMessage })
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
const transferAmount = transferInfoList[transferId].amount
|
|
111
|
+
const { participantPositionChange, transferStateChange, transferStateId, updatedRunningPosition } =
|
|
112
|
+
_handleParticipantPositionChange(runningPosition, transferAmount, transferId, accumulatedPositionReservedValue)
|
|
113
|
+
runningPosition = updatedRunningPosition
|
|
114
|
+
binItem.result = { success: true }
|
|
115
|
+
participantPositionChanges.push(participantPositionChange)
|
|
116
|
+
transferStateChanges.push(transferStateChange)
|
|
117
|
+
accumulatedTransferStatesCopy[transferId] = transferStateId
|
|
118
|
+
const resultMessage = _constructTransferFulfilResultMessage(binItem, transferId, payerFsp, payeeFsp, transfer, reservedActionTransfers, transferStateId)
|
|
119
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
128
120
|
}
|
|
129
|
-
participantPositionChanges.push(participantPositionChange)
|
|
130
|
-
binItem.result = { success: true }
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
resultMessages.push({ binItem, message: resultMessage })
|
|
134
|
-
|
|
135
|
-
if (transferStateId) {
|
|
136
|
-
const transferStateChange = {
|
|
137
|
-
transferId,
|
|
138
|
-
transferStateId,
|
|
139
|
-
reason
|
|
140
|
-
}
|
|
141
|
-
transferStateChanges.push(transferStateChange)
|
|
142
|
-
Logger.isDebugEnabled && Logger.debug(`processPositionFulfilBin::transferStateChange: ${JSON.stringify(transferStateChange)}`)
|
|
143
|
-
|
|
144
|
-
accumulatedTransferStatesCopy[transferId] = transferStateId
|
|
145
|
-
Logger.isDebugEnabled && Logger.debug(`processPositionFulfilBin::accumulatedTransferStatesCopy:finalizedTransferState ${JSON.stringify(transferStateId)}`)
|
|
146
121
|
}
|
|
147
122
|
}
|
|
148
123
|
}
|
|
149
124
|
}
|
|
150
125
|
|
|
151
126
|
return {
|
|
152
|
-
accumulatedPositionValue: runningPosition.toNumber(),
|
|
127
|
+
accumulatedPositionValue: changePositions ? runningPosition.toNumber() : accumulatedPositionValue,
|
|
153
128
|
accumulatedTransferStates: accumulatedTransferStatesCopy, // finalized transfer state after fulfil processing
|
|
129
|
+
accumulatedFxTransferStates: accumulatedFxTransferStatesCopy, // finalized transfer state after fx fulfil processing
|
|
154
130
|
accumulatedPositionReservedValue, // not used but kept for consistency
|
|
155
131
|
accumulatedTransferStateChanges: transferStateChanges, // transfer state changes to be persisted in order
|
|
156
|
-
|
|
157
|
-
|
|
132
|
+
accumulatedFxTransferStateChanges: fxTransferStateChanges, // fx-transfer state changes to be persisted in order
|
|
133
|
+
accumulatedPositionChanges: changePositions ? participantPositionChanges : [], // participant position changes to be persisted in order
|
|
134
|
+
notifyMessages: resultMessages, // array of objects containing bin item and result message. {binItem, message}
|
|
135
|
+
followupMessages // array of objects containing bin item, message key and followup message. {binItem, messageKey, message}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const _handleIncorrectTransferState = (binItem, payeeFsp, transferId, accumulatedTransferStates) => {
|
|
140
|
+
// forward same headers from the prepare message, except the content-length header
|
|
141
|
+
// set destination to payeefsp and source to switch
|
|
142
|
+
const headers = { ...binItem.message.value.content.headers }
|
|
143
|
+
headers[Enum.Http.Headers.FSPIOP.DESTINATION] = payeeFsp
|
|
144
|
+
headers[Enum.Http.Headers.FSPIOP.SOURCE] = Config.HUB_NAME
|
|
145
|
+
delete headers['content-length']
|
|
146
|
+
|
|
147
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(
|
|
148
|
+
`Invalid State: ${accumulatedTransferStates[transferId]} - expected: ${Enum.Transfers.TransferInternalState.RECEIVED_FULFIL}`
|
|
149
|
+
).toApiErrorObject(Config.ERROR_HANDLING)
|
|
150
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
151
|
+
Enum.Events.EventStatus.FAILURE.status,
|
|
152
|
+
fspiopError.errorInformation.errorCode,
|
|
153
|
+
fspiopError.errorInformation.errorDescription
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
157
|
+
transferId,
|
|
158
|
+
Enum.Kafka.Topics.NOTIFICATION,
|
|
159
|
+
Enum.Events.Event.Action.FULFIL,
|
|
160
|
+
state
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return Utility.StreamingProtocol.createMessage(
|
|
164
|
+
transferId,
|
|
165
|
+
payeeFsp,
|
|
166
|
+
Config.HUB_NAME,
|
|
167
|
+
metadata,
|
|
168
|
+
headers,
|
|
169
|
+
fspiopError,
|
|
170
|
+
{ id: transferId },
|
|
171
|
+
'application/json'
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const _constructTransferFulfilResultMessage = (binItem, transferId, payerFsp, payeeFsp, transfer, reservedActionTransfers, transferStateId) => {
|
|
176
|
+
// forward same headers from the prepare message, except the content-length header
|
|
177
|
+
const headers = { ...binItem.message.value.content.headers }
|
|
178
|
+
delete headers['content-length']
|
|
179
|
+
|
|
180
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
181
|
+
Enum.Events.EventStatus.SUCCESS.status,
|
|
182
|
+
null,
|
|
183
|
+
null
|
|
184
|
+
)
|
|
185
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
186
|
+
transferId,
|
|
187
|
+
Enum.Kafka.Topics.TRANSFER,
|
|
188
|
+
Enum.Events.Event.Action.COMMIT,
|
|
189
|
+
state
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const resultMessage = Utility.StreamingProtocol.createMessage(
|
|
193
|
+
transferId,
|
|
194
|
+
payerFsp,
|
|
195
|
+
payeeFsp,
|
|
196
|
+
metadata,
|
|
197
|
+
headers,
|
|
198
|
+
transfer,
|
|
199
|
+
{ id: transferId },
|
|
200
|
+
'application/json'
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if (binItem.message.value.metadata.event.action === Enum.Events.Event.Action.RESERVE) {
|
|
204
|
+
resultMessage.content.payload = TransferObjectTransform.toFulfil(
|
|
205
|
+
reservedActionTransfers[transferId]
|
|
206
|
+
)
|
|
207
|
+
resultMessage.content.payload.transferState = transferStateId
|
|
208
|
+
}
|
|
209
|
+
return resultMessage
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const _constructPatchNotificationResultMessage = (binItem, cyrilResult) => {
|
|
213
|
+
const messages = []
|
|
214
|
+
const patchNotifications = cyrilResult.patchNotifications
|
|
215
|
+
for (const patchNotification of patchNotifications) {
|
|
216
|
+
const commitRequestId = patchNotification.commitRequestId
|
|
217
|
+
const fxpName = patchNotification.fxpName
|
|
218
|
+
const fulfilment = patchNotification.fulfilment
|
|
219
|
+
const completedTimestamp = patchNotification.completedTimestamp
|
|
220
|
+
const headers = {
|
|
221
|
+
...binItem.message.value.content.headers,
|
|
222
|
+
'fspiop-source': Config.HUB_NAME,
|
|
223
|
+
'fspiop-destination': fxpName
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const fulfil = {
|
|
227
|
+
conversionState: Enum.Transfers.TransferState.COMMITTED,
|
|
228
|
+
fulfilment,
|
|
229
|
+
completedTimestamp
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
233
|
+
Enum.Events.EventStatus.SUCCESS.status,
|
|
234
|
+
null,
|
|
235
|
+
null
|
|
236
|
+
)
|
|
237
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
238
|
+
commitRequestId,
|
|
239
|
+
Enum.Kafka.Topics.TRANSFER,
|
|
240
|
+
Enum.Events.Event.Action.FX_NOTIFY,
|
|
241
|
+
state
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const resultMessage = Utility.StreamingProtocol.createMessage(
|
|
245
|
+
commitRequestId,
|
|
246
|
+
fxpName,
|
|
247
|
+
Config.HUB_NAME,
|
|
248
|
+
metadata,
|
|
249
|
+
headers,
|
|
250
|
+
fulfil,
|
|
251
|
+
{ id: commitRequestId },
|
|
252
|
+
'application/json'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
messages.push(resultMessage)
|
|
256
|
+
}
|
|
257
|
+
return messages
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const _handleParticipantPositionChange = (runningPosition, transferAmount, transferId, accumulatedPositionReservedValue) => {
|
|
261
|
+
const transferStateId = Enum.Transfers.TransferState.COMMITTED
|
|
262
|
+
// Amounts in `transferParticipant` for the payee are stored as negative values
|
|
263
|
+
const updatedRunningPosition = new MLNumber(runningPosition.add(transferAmount).toFixed(Config.AMOUNT.SCALE))
|
|
264
|
+
|
|
265
|
+
const participantPositionChange = {
|
|
266
|
+
transferId, // Need to delete this in bin processor while updating transferStateChangeId
|
|
267
|
+
transferStateChangeId: null, // Need to update this in bin processor while executing queries
|
|
268
|
+
value: updatedRunningPosition.toNumber(),
|
|
269
|
+
change: transferAmount,
|
|
270
|
+
reservedValue: accumulatedPositionReservedValue
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const transferStateChange = {
|
|
274
|
+
transferId,
|
|
275
|
+
transferStateId,
|
|
276
|
+
reason: undefined
|
|
277
|
+
}
|
|
278
|
+
return { participantPositionChange, transferStateChange, transferStateId, updatedRunningPosition }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const _handleParticipantPositionChangeFx = (runningPosition, transferAmount, commitRequestId, accumulatedPositionReservedValue) => {
|
|
282
|
+
const transferStateId = Enum.Transfers.TransferState.COMMITTED
|
|
283
|
+
// Amounts in `transferParticipant` for the payee are stored as negative values
|
|
284
|
+
const updatedRunningPosition = new MLNumber(runningPosition.add(transferAmount).toFixed(Config.AMOUNT.SCALE))
|
|
285
|
+
|
|
286
|
+
const participantPositionChange = {
|
|
287
|
+
commitRequestId, // Need to delete this in bin processor while updating fxTransferStateChangeId
|
|
288
|
+
fxTransferStateChangeId: null, // Need to update this in bin processor while executing queries
|
|
289
|
+
value: updatedRunningPosition.toNumber(),
|
|
290
|
+
change: transferAmount,
|
|
291
|
+
reservedValue: accumulatedPositionReservedValue
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const fxTransferStateChange = {
|
|
295
|
+
commitRequestId,
|
|
296
|
+
transferStateId,
|
|
297
|
+
reason: null
|
|
158
298
|
}
|
|
299
|
+
return { participantPositionChange, fxTransferStateChange, transferStateId, updatedRunningPosition }
|
|
159
300
|
}
|
|
160
301
|
|
|
161
302
|
module.exports = {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const { Enum } = require('@mojaloop/central-services-shared')
|
|
2
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
3
|
+
const Config = require('../../lib/config')
|
|
4
|
+
const Utility = require('@mojaloop/central-services-shared').Util
|
|
5
|
+
const Logger = require('@mojaloop/central-services-logger')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function processPositionFxFulfilBin
|
|
9
|
+
*
|
|
10
|
+
* @async
|
|
11
|
+
* @description This is the domain function to process a bin of position-fx-fulfil messages of a single participant account.
|
|
12
|
+
*
|
|
13
|
+
* @param {array} binItems - an array of objects that contain a position fx reserve message and its span. {message, span}
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {object} accumulatedFxTransferStates - object with fx transfer id keys and transfer state id values. Used to check if transfer is in correct state for processing. Clone and update states for output.
|
|
16
|
+
* @returns {object} - Returns an object containing accumulatedFxTransferStateChanges, accumulatedFxTransferStates, resultMessages, limitAlarms or throws an error if failed
|
|
17
|
+
*/
|
|
18
|
+
const processPositionFxFulfilBin = async (
|
|
19
|
+
binItems,
|
|
20
|
+
{
|
|
21
|
+
accumulatedFxTransferStates
|
|
22
|
+
}
|
|
23
|
+
) => {
|
|
24
|
+
const fxTransferStateChanges = []
|
|
25
|
+
const resultMessages = []
|
|
26
|
+
const accumulatedFxTransferStatesCopy = Object.assign({}, accumulatedFxTransferStates)
|
|
27
|
+
|
|
28
|
+
if (binItems && binItems.length > 0) {
|
|
29
|
+
for (const binItem of binItems) {
|
|
30
|
+
let transferStateId
|
|
31
|
+
let reason
|
|
32
|
+
let resultMessage
|
|
33
|
+
const commitRequestId = binItem.message.value.content.uriParams.id
|
|
34
|
+
const counterPartyFsp = binItem.message.value.from
|
|
35
|
+
const initiatingFsp = binItem.message.value.to
|
|
36
|
+
const fxTransfer = binItem.decodedPayload
|
|
37
|
+
Logger.isDebugEnabled && Logger.debug(`processPositionFxFulfilBin::fxTransfer:processingMessage: ${JSON.stringify(fxTransfer)}`)
|
|
38
|
+
Logger.isDebugEnabled && Logger.debug(`accumulatedFxTransferStates: ${JSON.stringify(accumulatedFxTransferStates)}`)
|
|
39
|
+
Logger.isDebugEnabled && Logger.debug(`accumulatedFxTransferStates[commitRequestId]: ${accumulatedFxTransferStates[commitRequestId]}`)
|
|
40
|
+
// Inform sender if transfer is not in RECEIVED_FULFIL_DEPENDENT state, skip making any transfer state changes
|
|
41
|
+
if (accumulatedFxTransferStates[commitRequestId] !== Enum.Transfers.TransferInternalState.RECEIVED_FULFIL_DEPENDENT) {
|
|
42
|
+
// forward same headers from the request, except the content-length header
|
|
43
|
+
// set destination to counterPartyFsp and source to switch
|
|
44
|
+
const headers = { ...binItem.message.value.content.headers }
|
|
45
|
+
headers[Enum.Http.Headers.FSPIOP.DESTINATION] = counterPartyFsp
|
|
46
|
+
headers[Enum.Http.Headers.FSPIOP.SOURCE] = Config.HUB_NAME
|
|
47
|
+
delete headers['content-length']
|
|
48
|
+
|
|
49
|
+
// TODO: Confirm if this setting transferStateId to ABORTED_REJECTED is correct. There is no such logic in the fulfil handler.
|
|
50
|
+
transferStateId = Enum.Transfers.TransferInternalState.ABORTED_REJECTED
|
|
51
|
+
reason = 'FxFulfil in incorrect state'
|
|
52
|
+
|
|
53
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(
|
|
54
|
+
`Invalid State: ${accumulatedFxTransferStates[commitRequestId]} - expected: ${Enum.Transfers.TransferInternalState.RECEIVED_FULFIL_DEPENDENT}`
|
|
55
|
+
).toApiErrorObject(Config.ERROR_HANDLING)
|
|
56
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
57
|
+
Enum.Events.EventStatus.FAILURE.status,
|
|
58
|
+
fspiopError.errorInformation.errorCode,
|
|
59
|
+
fspiopError.errorInformation.errorDescription
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
63
|
+
commitRequestId,
|
|
64
|
+
Enum.Kafka.Topics.NOTIFICATION,
|
|
65
|
+
Enum.Events.Event.Action.FX_FULFIL,
|
|
66
|
+
state
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
resultMessage = Utility.StreamingProtocol.createMessage(
|
|
70
|
+
commitRequestId,
|
|
71
|
+
counterPartyFsp,
|
|
72
|
+
Config.HUB_NAME,
|
|
73
|
+
metadata,
|
|
74
|
+
headers,
|
|
75
|
+
fspiopError,
|
|
76
|
+
{ id: commitRequestId },
|
|
77
|
+
'application/json'
|
|
78
|
+
)
|
|
79
|
+
} else {
|
|
80
|
+
// forward same headers from the prepare message, except the content-length header
|
|
81
|
+
const headers = { ...binItem.message.value.content.headers }
|
|
82
|
+
delete headers['content-length']
|
|
83
|
+
|
|
84
|
+
const state = Utility.StreamingProtocol.createEventState(
|
|
85
|
+
Enum.Events.EventStatus.SUCCESS.status,
|
|
86
|
+
null,
|
|
87
|
+
null
|
|
88
|
+
)
|
|
89
|
+
const metadata = Utility.StreamingProtocol.createMetadataWithCorrelatedEvent(
|
|
90
|
+
commitRequestId,
|
|
91
|
+
Enum.Kafka.Topics.TRANSFER,
|
|
92
|
+
Enum.Events.Event.Action.COMMIT,
|
|
93
|
+
state
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
resultMessage = Utility.StreamingProtocol.createMessage(
|
|
97
|
+
commitRequestId,
|
|
98
|
+
initiatingFsp,
|
|
99
|
+
counterPartyFsp,
|
|
100
|
+
metadata,
|
|
101
|
+
headers,
|
|
102
|
+
fxTransfer,
|
|
103
|
+
{ id: commitRequestId },
|
|
104
|
+
'application/json'
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// No need to change the transfer state here for success case.
|
|
108
|
+
|
|
109
|
+
binItem.result = { success: true }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
resultMessages.push({ binItem, message: resultMessage })
|
|
113
|
+
|
|
114
|
+
if (transferStateId) {
|
|
115
|
+
const fxTransferStateChange = {
|
|
116
|
+
commitRequestId,
|
|
117
|
+
transferStateId,
|
|
118
|
+
reason
|
|
119
|
+
}
|
|
120
|
+
fxTransferStateChanges.push(fxTransferStateChange)
|
|
121
|
+
Logger.isDebugEnabled && Logger.debug(`processPositionFxFulfilBin::fxTransferStateChange: ${JSON.stringify(fxTransferStateChange)}`)
|
|
122
|
+
|
|
123
|
+
accumulatedFxTransferStatesCopy[commitRequestId] = transferStateId
|
|
124
|
+
Logger.isDebugEnabled && Logger.debug(`processPositionFxFulfilBin::accumulatedTransferStatesCopy:finalizedFxTransferState ${JSON.stringify(transferStateId)}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
accumulatedFxTransferStates: accumulatedFxTransferStatesCopy, // finalized fx transfer state after fx-fulfil processing
|
|
131
|
+
accumulatedFxTransferStateChanges: fxTransferStateChanges, // fx transfer state changes to be persisted in order
|
|
132
|
+
notifyMessages: resultMessages // array of objects containing bin item and result message. {binItem, message}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
processPositionFxFulfilBin
|
|
138
|
+
}
|