@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
|
@@ -40,214 +40,86 @@
|
|
|
40
40
|
|
|
41
41
|
const Logger = require('@mojaloop/central-services-logger')
|
|
42
42
|
const EventSdk = require('@mojaloop/event-sdk')
|
|
43
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
44
|
+
const Metrics = require('@mojaloop/central-services-metrics')
|
|
45
|
+
const { Enum, Util } = require('@mojaloop/central-services-shared')
|
|
46
|
+
const { Consumer, Producer } = require('@mojaloop/central-services-stream').Util
|
|
47
|
+
|
|
48
|
+
const { logger } = require('../../shared/logger')
|
|
49
|
+
const { ERROR_MESSAGES } = require('../../shared/constants')
|
|
50
|
+
const Config = require('../../lib/config')
|
|
43
51
|
const TransferService = require('../../domain/transfer')
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
52
|
+
const FxService = require('../../domain/fx')
|
|
53
|
+
// TODO: Can define domain functions instead of accessing model directly from handler
|
|
54
|
+
const FxTransferModel = require('../../models/fxTransfer')
|
|
55
|
+
const TransferObjectTransform = require('../../domain/transfer/transform')
|
|
56
|
+
const Participant = require('../../domain/participant')
|
|
48
57
|
const Validator = require('./validator')
|
|
49
|
-
const
|
|
58
|
+
const FxFulfilService = require('./FxFulfilService')
|
|
59
|
+
|
|
60
|
+
// particular handlers
|
|
61
|
+
const { prepare } = require('./prepare')
|
|
62
|
+
|
|
63
|
+
const { Kafka, Comparators } = Util
|
|
50
64
|
const TransferState = Enum.Transfers.TransferState
|
|
51
65
|
const TransferEventType = Enum.Events.Event.Type
|
|
52
66
|
const TransferEventAction = Enum.Events.Event.Action
|
|
53
|
-
const TransferObjectTransform = require('../../domain/transfer/transform')
|
|
54
|
-
const Metrics = require('@mojaloop/central-services-metrics')
|
|
55
|
-
const Config = require('../../lib/config')
|
|
56
67
|
const decodePayload = Util.StreamingProtocol.decodePayload
|
|
57
|
-
const Comparators = require('@mojaloop/central-services-shared').Util.Comparators
|
|
58
|
-
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
59
|
-
const Participant = require('../../domain/participant')
|
|
60
68
|
|
|
61
69
|
const consumerCommit = true
|
|
62
70
|
const fromSwitch = true
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
* @function TransferPrepareHandler
|
|
66
|
-
*
|
|
67
|
-
* @async
|
|
68
|
-
* @description This is the consumer callback function that gets registered to a topic. This then gets a list of messages,
|
|
69
|
-
* we will only ever use the first message in non batch processing. We then break down the message into its payload and
|
|
70
|
-
* begin validating the payload. Once the payload is validated successfully it will be written to the database to
|
|
71
|
-
* the relevant tables. If the validation fails it is still written to the database for auditing purposes but with an
|
|
72
|
-
* INVALID status. For any duplicate requests we will send appropriate callback based on the transfer state and the hash validation
|
|
73
|
-
*
|
|
74
|
-
* Validator.validatePrepare called to validate the payload of the message
|
|
75
|
-
* TransferService.getById called to get the details of the existing transfer
|
|
76
|
-
* TransferObjectTransform.toTransfer called to transform the transfer object
|
|
77
|
-
* TransferService.prepare called and creates new entries in transfer tables for successful prepare transfer
|
|
78
|
-
* TransferService.logTransferError called to log the invalid request
|
|
79
|
-
*
|
|
80
|
-
* @param {error} error - error thrown if something fails within Kafka
|
|
81
|
-
* @param {array} messages - a list of messages to consume for the relevant topic
|
|
82
|
-
*
|
|
83
|
-
* @returns {object} - Returns a boolean: true if successful, or throws and error if failed
|
|
84
|
-
*/
|
|
85
|
-
const prepare = async (error, messages) => {
|
|
86
|
-
const location = { module: 'PrepareHandler', method: '', path: '' }
|
|
87
|
-
const histTimerEnd = Metrics.getHistogram(
|
|
88
|
-
'transfer_prepare',
|
|
89
|
-
'Consume a prepare transfer message from the kafka topic and process it accordingly',
|
|
90
|
-
['success', 'fspId']
|
|
91
|
-
).startTimer()
|
|
72
|
+
const fulfil = async (error, messages) => {
|
|
92
73
|
if (error) {
|
|
93
|
-
histTimerEnd({ success: false, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
94
74
|
throw ErrorHandler.Factory.reformatFSPIOPError(error)
|
|
95
75
|
}
|
|
96
|
-
let message
|
|
76
|
+
let message
|
|
97
77
|
if (Array.isArray(messages)) {
|
|
98
78
|
message = messages[0]
|
|
99
79
|
} else {
|
|
100
80
|
message = messages
|
|
101
81
|
}
|
|
102
|
-
const parentSpanService = 'cl_transfer_prepare'
|
|
103
82
|
const contextFromMessage = EventSdk.Tracer.extractContextFromMessage(message.value)
|
|
104
|
-
const span = EventSdk.Tracer.createChildSpanFromContext(
|
|
83
|
+
const span = EventSdk.Tracer.createChildSpanFromContext('cl_transfer_fulfil', contextFromMessage)
|
|
105
84
|
try {
|
|
106
|
-
const payload = decodePayload(message.value.content.payload)
|
|
107
|
-
const headers = message.value.content.headers
|
|
108
|
-
const action = message.value.metadata.event.action
|
|
109
|
-
const transferId = payload.transferId
|
|
110
|
-
span.setTags({ transactionId: transferId })
|
|
111
85
|
await span.audit(message, EventSdk.AuditEventAction.start)
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const histTimerDuplicateCheckEnd = Metrics.getHistogram(
|
|
130
|
-
'handler_transfers',
|
|
131
|
-
'prepare_duplicateCheckComparator - Metrics for transfer handler',
|
|
132
|
-
['success', 'funcName']
|
|
133
|
-
).startTimer()
|
|
134
|
-
|
|
135
|
-
const { hasDuplicateId, hasDuplicateHash } = await Comparators.duplicateCheckComparator(transferId, payload, TransferService.getTransferDuplicateCheck, TransferService.saveTransferDuplicateCheck)
|
|
136
|
-
histTimerDuplicateCheckEnd({ success: true, funcName: 'prepare_duplicateCheckComparator' })
|
|
137
|
-
if (hasDuplicateId && hasDuplicateHash) {
|
|
138
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'handleResend'))
|
|
139
|
-
const transfer = await TransferService.getByIdLight(transferId)
|
|
140
|
-
const transferStateEnum = transfer && transfer.transferStateEnumeration
|
|
141
|
-
const eventDetail = { functionality, action: TransferEventAction.PREPARE_DUPLICATE }
|
|
142
|
-
if ([TransferState.COMMITTED, TransferState.ABORTED].includes(transferStateEnum)) {
|
|
143
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'finalized'))
|
|
144
|
-
if (action === TransferEventAction.PREPARE) {
|
|
145
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callback--${actionLetter}1`))
|
|
146
|
-
message.value.content.payload = TransferObjectTransform.toFulfil(transfer)
|
|
147
|
-
message.value.content.uriParams = { id: transferId }
|
|
148
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch })
|
|
149
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
150
|
-
return true
|
|
151
|
-
} else if (action === TransferEventAction.BULK_PREPARE) {
|
|
152
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `validationError1--${actionLetter}2`))
|
|
153
|
-
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST, 'Individual transfer prepare duplicate')
|
|
154
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
155
|
-
throw fspiopError
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'inProgress'))
|
|
159
|
-
if (action === TransferEventAction.BULK_PREPARE) {
|
|
160
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `validationError2--${actionLetter}4`))
|
|
161
|
-
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST, 'Individual transfer prepare duplicate')
|
|
162
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
163
|
-
throw fspiopError
|
|
164
|
-
} else { // action === TransferEventAction.PREPARE
|
|
165
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `ignore--${actionLetter}3`))
|
|
166
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit })
|
|
167
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
168
|
-
return true
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} else if (hasDuplicateId && !hasDuplicateHash) {
|
|
172
|
-
Logger.isErrorEnabled && Logger.error(Util.breadcrumb(location, `callbackErrorModified1--${actionLetter}5`))
|
|
173
|
-
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST)
|
|
174
|
-
const eventDetail = { functionality, action }
|
|
175
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
176
|
-
throw fspiopError
|
|
177
|
-
} else { // !hasDuplicateId
|
|
178
|
-
const { validationPassed, reasons } = await Validator.validatePrepare(payload, headers)
|
|
179
|
-
if (validationPassed) {
|
|
180
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'validationPassed' }))
|
|
181
|
-
try {
|
|
182
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'saveTransfer'))
|
|
183
|
-
await TransferService.prepare(payload)
|
|
184
|
-
} catch (err) {
|
|
185
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInternal1--${actionLetter}6`))
|
|
186
|
-
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
187
|
-
const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err, ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR)
|
|
188
|
-
const eventDetail = { functionality, action: TransferEventAction.PREPARE }
|
|
189
|
-
/**
|
|
190
|
-
* TODO: BULK-Handle at BulkProcessingHandler (not in scope of #967)
|
|
191
|
-
* HOWTO: Stop execution at the `TransferService.prepare`, stop mysql,
|
|
192
|
-
* continue execution to catch block, start mysql
|
|
193
|
-
*/
|
|
194
|
-
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
195
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
196
|
-
throw fspiopError
|
|
197
|
-
}
|
|
198
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic1--${actionLetter}7`))
|
|
199
|
-
functionality = TransferEventType.POSITION
|
|
200
|
-
const eventDetail = { functionality, action }
|
|
201
|
-
// Key position prepare message with payer account id
|
|
202
|
-
const payerAccount = await Participant.getAccountByNameAndCurrency(payload.payerFsp, payload.amount.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
203
|
-
// We route bulk-prepare and prepare messages differently based on the topic configured for it.
|
|
204
|
-
// Note: The batch handler does not currently support bulk-prepare messages, only prepare messages are supported.
|
|
205
|
-
// Therefore, it is necessary to check the action to determine the topic to route to.
|
|
206
|
-
const topicNameOverride =
|
|
207
|
-
action === TransferEventAction.BULK_PREPARE
|
|
208
|
-
? Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.BULK_PREPARE
|
|
209
|
-
: Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.PREPARE
|
|
210
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, messageKey: payerAccount.participantCurrencyId.toString(), topicNameOverride })
|
|
211
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
212
|
-
return true
|
|
213
|
-
} else {
|
|
214
|
-
Logger.isErrorEnabled && Logger.error(Util.breadcrumb(location, { path: 'validationFailed' }))
|
|
215
|
-
try {
|
|
216
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'saveInvalidRequest'))
|
|
217
|
-
await TransferService.prepare(payload, reasons.toString(), false)
|
|
218
|
-
} catch (err) {
|
|
219
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInternal2--${actionLetter}8`))
|
|
220
|
-
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
221
|
-
const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err, ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR)
|
|
222
|
-
const eventDetail = { functionality, action: TransferEventAction.PREPARE }
|
|
223
|
-
/**
|
|
224
|
-
* TODO: BULK-Handle at BulkProcessingHandler (not in scope of #967)
|
|
225
|
-
* HOWTO: For regular transfers this branch may be triggered by sending
|
|
226
|
-
* a transfer in a currency not supported by either dfsp and also stopping
|
|
227
|
-
* mysql at `TransferService.prepare` and starting it after entring catch.
|
|
228
|
-
* Not sure if it will work for bulk, because of the BulkPrepareHandler.
|
|
229
|
-
*/
|
|
230
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
231
|
-
throw fspiopError
|
|
232
|
-
}
|
|
233
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorGeneric--${actionLetter}9`))
|
|
234
|
-
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, reasons.toString())
|
|
235
|
-
await TransferService.logTransferError(transferId, ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR.code, reasons.toString())
|
|
236
|
-
const eventDetail = { functionality, action }
|
|
237
|
-
/**
|
|
238
|
-
* TODO: BULK-Handle at BulkProcessingHandler (not in scope of #967)
|
|
239
|
-
* HOWTO: For regular transfers this branch may be triggered by sending
|
|
240
|
-
* a tansfer in a currency not supported by either dfsp. Not sure if it
|
|
241
|
-
* will be triggered for bulk, because of the BulkPrepareHandler.
|
|
242
|
-
*/
|
|
243
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
244
|
-
throw fspiopError
|
|
86
|
+
const action = message.value.metadata.event.action
|
|
87
|
+
|
|
88
|
+
const functionality = (() => {
|
|
89
|
+
switch (action) {
|
|
90
|
+
case TransferEventAction.COMMIT:
|
|
91
|
+
case TransferEventAction.FX_COMMIT:
|
|
92
|
+
case TransferEventAction.RESERVE:
|
|
93
|
+
case TransferEventAction.FX_RESERVE:
|
|
94
|
+
case TransferEventAction.REJECT:
|
|
95
|
+
case TransferEventAction.FX_REJECT:
|
|
96
|
+
case TransferEventAction.ABORT:
|
|
97
|
+
case TransferEventAction.FX_ABORT:
|
|
98
|
+
return TransferEventType.NOTIFICATION
|
|
99
|
+
case TransferEventAction.BULK_COMMIT:
|
|
100
|
+
case TransferEventAction.BULK_ABORT:
|
|
101
|
+
return TransferEventType.BULK_PROCESSING
|
|
102
|
+
default: return Enum.Events.ActionLetter.unknown
|
|
245
103
|
}
|
|
104
|
+
})()
|
|
105
|
+
logger.info('FulfilHandler start:', { action, functionality })
|
|
106
|
+
|
|
107
|
+
const fxActions = [
|
|
108
|
+
TransferEventAction.FX_COMMIT,
|
|
109
|
+
TransferEventAction.FX_RESERVE,
|
|
110
|
+
TransferEventAction.FX_REJECT,
|
|
111
|
+
TransferEventAction.FX_ABORT,
|
|
112
|
+
TransferEventAction.FX_FORWARDED
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
if (fxActions.includes(action)) {
|
|
116
|
+
return await processFxFulfilMessage(message, functionality, span)
|
|
117
|
+
} else {
|
|
118
|
+
return await processFulfilMessage(message, functionality, span)
|
|
246
119
|
}
|
|
247
120
|
} catch (err) {
|
|
248
|
-
|
|
121
|
+
logger.error(`error in FulfilHandler: ${err?.message}`, { err })
|
|
249
122
|
const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
250
|
-
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}--P0`)
|
|
251
123
|
const state = new EventSdk.EventStateMetadata(EventSdk.EventStatusType.failed, fspiopError.apiErrorCode.code, fspiopError.apiErrorCode.message)
|
|
252
124
|
await span.error(fspiopError, state)
|
|
253
125
|
await span.finish(fspiopError.message, state)
|
|
@@ -259,107 +131,82 @@ const prepare = async (error, messages) => {
|
|
|
259
131
|
}
|
|
260
132
|
}
|
|
261
133
|
|
|
262
|
-
const
|
|
134
|
+
const processFulfilMessage = async (message, functionality, span) => {
|
|
263
135
|
const location = { module: 'FulfilHandler', method: '', path: '' }
|
|
264
136
|
const histTimerEnd = Metrics.getHistogram(
|
|
265
137
|
'transfer_fulfil',
|
|
266
138
|
'Consume a fulfil transfer message from the kafka topic and process it accordingly',
|
|
267
139
|
['success', 'fspId']
|
|
268
140
|
).startTimer()
|
|
269
|
-
if (error) {
|
|
270
|
-
throw ErrorHandler.Factory.reformatFSPIOPError(error)
|
|
271
|
-
}
|
|
272
|
-
let message = {}
|
|
273
|
-
if (Array.isArray(messages)) {
|
|
274
|
-
message = messages[0]
|
|
275
|
-
} else {
|
|
276
|
-
message = messages
|
|
277
|
-
}
|
|
278
|
-
const contextFromMessage = EventSdk.Tracer.extractContextFromMessage(message.value)
|
|
279
|
-
const span = EventSdk.Tracer.createChildSpanFromContext('cl_transfer_fulfil', contextFromMessage)
|
|
280
|
-
try {
|
|
281
|
-
await span.audit(message, EventSdk.AuditEventAction.start)
|
|
282
|
-
const payload = decodePayload(message.value.content.payload)
|
|
283
|
-
const headers = message.value.content.headers
|
|
284
|
-
const type = message.value.metadata.event.type
|
|
285
|
-
const action = message.value.metadata.event.action
|
|
286
|
-
const transferId = message.value.content.uriParams.id
|
|
287
|
-
const kafkaTopic = message.topic
|
|
288
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { method: `fulfil:${action}` }))
|
|
289
141
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
case TransferEventAction.BULK_ABORT: return Enum.Events.ActionLetter.bulkAbort
|
|
298
|
-
default: return Enum.Events.ActionLetter.unknown
|
|
299
|
-
}
|
|
300
|
-
})()
|
|
142
|
+
const payload = decodePayload(message.value.content.payload)
|
|
143
|
+
const headers = message.value.content.headers
|
|
144
|
+
const type = message.value.metadata.event.type
|
|
145
|
+
const action = message.value.metadata.event.action
|
|
146
|
+
const transferId = message.value.content.uriParams.id
|
|
147
|
+
const kafkaTopic = message.topic
|
|
148
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { method: `fulfil:${action}` }))
|
|
301
149
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return TransferEventType.BULK_PROCESSING
|
|
312
|
-
default: return Enum.Events.ActionLetter.unknown
|
|
313
|
-
}
|
|
314
|
-
})()
|
|
315
|
-
|
|
316
|
-
// fulfil-specific declarations
|
|
317
|
-
const isTransferError = action === TransferEventAction.ABORT
|
|
318
|
-
const params = { message, kafkaTopic, decodedPayload: payload, span, consumer: Consumer, producer: Producer }
|
|
319
|
-
|
|
320
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'getById' }))
|
|
321
|
-
|
|
322
|
-
// We fail early and silently to allow timeout handler abort transfer
|
|
323
|
-
// if 'RESERVED' transfer state is sent in with v1.0 content-type
|
|
324
|
-
if (headers['content-type'].split('=')[1] === '1.0' && payload.transferState === TransferState.RESERVED) {
|
|
325
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `failSilentlyforReservedStateWith1.0ContentType--${actionLetter}0`))
|
|
326
|
-
const errorMessage = 'action "RESERVE" is not allowed in fulfil handler for v1.0 clients.'
|
|
327
|
-
Logger.isErrorEnabled && Logger.error(errorMessage)
|
|
328
|
-
!!span && span.error(errorMessage)
|
|
329
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
330
|
-
return true
|
|
150
|
+
const actionLetter = (() => {
|
|
151
|
+
switch (action) {
|
|
152
|
+
case TransferEventAction.COMMIT: return Enum.Events.ActionLetter.commit
|
|
153
|
+
case TransferEventAction.RESERVE: return Enum.Events.ActionLetter.reserve
|
|
154
|
+
case TransferEventAction.REJECT: return Enum.Events.ActionLetter.reject
|
|
155
|
+
case TransferEventAction.ABORT: return Enum.Events.ActionLetter.abort
|
|
156
|
+
case TransferEventAction.BULK_COMMIT: return Enum.Events.ActionLetter.bulkCommit
|
|
157
|
+
case TransferEventAction.BULK_ABORT: return Enum.Events.ActionLetter.bulkAbort
|
|
158
|
+
default: return Enum.Events.ActionLetter.unknown
|
|
331
159
|
}
|
|
160
|
+
})()
|
|
161
|
+
|
|
162
|
+
// We fail early and silently to allow timeout handler abort transfer
|
|
163
|
+
// if 'RESERVED' transfer state is sent in with v1.0 content-type
|
|
164
|
+
if (headers['content-type'].split('=')[1] === '1.0' && payload.transferState === TransferState.RESERVED) {
|
|
165
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `failSilentlyforReservedStateWith1.0ContentType--${actionLetter}0`))
|
|
166
|
+
const errorMessage = 'action "RESERVE" is not allowed in fulfil handler for v1.0 clients.'
|
|
167
|
+
Logger.isErrorEnabled && Logger.error(errorMessage)
|
|
168
|
+
!!span && span.error(errorMessage)
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
332
171
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
172
|
+
// fulfil-specific declarations
|
|
173
|
+
const isTransferError = action === TransferEventAction.ABORT
|
|
174
|
+
const params = { message, kafkaTopic, decodedPayload: payload, span, consumer: Consumer, producer: Producer }
|
|
175
|
+
|
|
176
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'getById' }))
|
|
177
|
+
|
|
178
|
+
const transfer = await TransferService.getById(transferId)
|
|
179
|
+
const transferStateEnum = transfer && transfer.transferStateEnumeration
|
|
180
|
+
|
|
181
|
+
// List of valid actions that Source & Destination headers should be checked
|
|
182
|
+
const validActionsForRouteValidations = [
|
|
183
|
+
TransferEventAction.COMMIT,
|
|
184
|
+
TransferEventAction.RESERVE,
|
|
185
|
+
TransferEventAction.REJECT,
|
|
186
|
+
TransferEventAction.ABORT
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
if (!transfer) {
|
|
190
|
+
Logger.isErrorEnabled && Logger.error(Util.breadcrumb(location, `callbackInternalServerErrorNotFound--${actionLetter}1`))
|
|
191
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError('transfer not found')
|
|
192
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
193
|
+
/**
|
|
194
|
+
* TODO: BULK-Handle at BulkProcessingHandler (not in scope of #967)
|
|
195
|
+
* HOWTO: The list of individual transfers being committed should contain
|
|
196
|
+
* non-existing transferId
|
|
197
|
+
*/
|
|
198
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
199
|
+
throw fspiopError
|
|
200
|
+
|
|
201
|
+
// Lets validate FSPIOP Source & Destination Headers
|
|
202
|
+
// In interscheme scenario, we store proxy fsp id in transferParticipant table and hence we can't compare that data with fspiop headers in fulfil
|
|
203
|
+
} else if (
|
|
204
|
+
validActionsForRouteValidations.includes(action) // Lets only check headers for specific actions that need checking (i.e. bulk should not since its already done elsewhere)
|
|
205
|
+
) {
|
|
206
|
+
// Check if the payerFsp and payeeFsp are proxies and if they are, skip validating headers
|
|
207
|
+
if (
|
|
208
|
+
(headers[Enum.Http.Headers.FSPIOP.SOURCE] && !transfer.payeeIsProxy && (headers[Enum.Http.Headers.FSPIOP.SOURCE].toLowerCase() !== transfer.payeeFsp.toLowerCase())) ||
|
|
209
|
+
(headers[Enum.Http.Headers.FSPIOP.DESTINATION] && !transfer.payerIsProxy && (headers[Enum.Http.Headers.FSPIOP.DESTINATION].toLowerCase() !== transfer.payerFsp.toLowerCase()))
|
|
363
210
|
) {
|
|
364
211
|
/**
|
|
365
212
|
* If fulfilment request is coming from a source not matching transfer payee fsp or destination not matching transfer payer fsp,
|
|
@@ -370,19 +217,22 @@ const fulfil = async (error, messages) => {
|
|
|
370
217
|
let fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'FSP does not match one of the fsp-id\'s associated with a transfer on the Fulfil callback response')
|
|
371
218
|
|
|
372
219
|
// Lets make the error specific if the PayeeFSP IDs do not match
|
|
373
|
-
if (headers[Enum.Http.Headers.FSPIOP.SOURCE].toLowerCase() !== transfer.payeeFsp.toLowerCase()) {
|
|
220
|
+
if (!transfer.payeeIsProxy && (headers[Enum.Http.Headers.FSPIOP.SOURCE].toLowerCase() !== transfer.payeeFsp.toLowerCase())) {
|
|
374
221
|
fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `${Enum.Http.Headers.FSPIOP.SOURCE} does not match payee fsp on the Fulfil callback response`)
|
|
375
222
|
}
|
|
376
223
|
|
|
377
224
|
// Lets make the error specific if the PayerFSP IDs do not match
|
|
378
|
-
if (headers[Enum.Http.Headers.FSPIOP.DESTINATION].toLowerCase() !== transfer.payerFsp.toLowerCase()) {
|
|
225
|
+
if (!transfer.payerIsProxy && (headers[Enum.Http.Headers.FSPIOP.DESTINATION].toLowerCase() !== transfer.payerFsp.toLowerCase())) {
|
|
379
226
|
fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, `${Enum.Http.Headers.FSPIOP.DESTINATION} does not match payer fsp on the Fulfil callback response`)
|
|
380
227
|
}
|
|
381
228
|
|
|
382
229
|
const apiFSPIOPError = fspiopError.toApiErrorObject(Config.ERROR_HANDLING)
|
|
383
230
|
|
|
384
231
|
// Set the event details to map to an ABORT_VALIDATION event targeted to the Position Handler
|
|
385
|
-
const eventDetail = {
|
|
232
|
+
const eventDetail = {
|
|
233
|
+
functionality: TransferEventType.POSITION,
|
|
234
|
+
action: TransferEventAction.ABORT_VALIDATION
|
|
235
|
+
}
|
|
386
236
|
|
|
387
237
|
// Lets handle the abort validation and change the transfer state to reflect this
|
|
388
238
|
const transferAbortResult = await TransferService.handlePayeeResponse(transferId, payload, TransferEventAction.ABORT_VALIDATION, apiFSPIOPError)
|
|
@@ -397,7 +247,7 @@ const fulfil = async (error, messages) => {
|
|
|
397
247
|
// Publish message to Position Handler
|
|
398
248
|
// Key position abort with payer account id
|
|
399
249
|
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
400
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: apiFSPIOPError, eventDetail, fromSwitch, toDestination: transfer.payerFsp, messageKey: payerAccount.participantCurrencyId.toString() })
|
|
250
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: apiFSPIOPError, eventDetail, fromSwitch, toDestination: transfer.payerFsp, messageKey: payerAccount.participantCurrencyId.toString(), hubName: Config.HUB_NAME })
|
|
401
251
|
|
|
402
252
|
/**
|
|
403
253
|
* Send patch notification callback to original payee fsp if they asked for a a patch response.
|
|
@@ -427,319 +277,486 @@ const fulfil = async (error, messages) => {
|
|
|
427
277
|
}
|
|
428
278
|
}
|
|
429
279
|
message.value.content.payload = reservedAbortedPayload
|
|
430
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail: reserveAbortedEventDetail, fromSwitch: true, toDestination: transfer.payeeFsp })
|
|
280
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail: reserveAbortedEventDetail, fromSwitch: true, toDestination: transfer.payeeFsp, hubName: Config.HUB_NAME })
|
|
431
281
|
}
|
|
432
282
|
|
|
433
283
|
throw apiFSPIOPError
|
|
434
284
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'dupCheck' }))
|
|
438
|
-
const histTimerDuplicateCheckEnd = Metrics.getHistogram(
|
|
439
|
-
'handler_transfers',
|
|
440
|
-
'fulfil_duplicateCheckComparator - Metrics for transfer handler',
|
|
441
|
-
['success', 'funcName']
|
|
442
|
-
).startTimer()
|
|
443
|
-
|
|
444
|
-
let dupCheckResult
|
|
445
|
-
if (!isTransferError) {
|
|
446
|
-
dupCheckResult = await Comparators.duplicateCheckComparator(transferId, payload, TransferService.getTransferFulfilmentDuplicateCheck, TransferService.saveTransferFulfilmentDuplicateCheck)
|
|
447
|
-
} else {
|
|
448
|
-
dupCheckResult = await Comparators.duplicateCheckComparator(transferId, payload, TransferService.getTransferErrorDuplicateCheck, TransferService.saveTransferErrorDuplicateCheck)
|
|
449
|
-
}
|
|
450
|
-
const { hasDuplicateId, hasDuplicateHash } = dupCheckResult
|
|
451
|
-
histTimerDuplicateCheckEnd({ success: true, funcName: 'fulfil_duplicateCheckComparator' })
|
|
452
|
-
if (hasDuplicateId && hasDuplicateHash) {
|
|
453
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'handleResend'))
|
|
454
|
-
|
|
455
|
-
// This is a duplicate message for a transfer that is already in a finalized state
|
|
456
|
-
// respond as if we received a GET /transfers/{ID} from the client
|
|
457
|
-
if (transferStateEnum === TransferState.COMMITTED || transferStateEnum === TransferState.ABORTED) {
|
|
458
|
-
message.value.content.payload = TransferObjectTransform.toFulfil(transfer)
|
|
459
|
-
const eventDetail = { functionality, action }
|
|
460
|
-
if (action !== TransferEventAction.RESERVE) {
|
|
461
|
-
if (!isTransferError) {
|
|
462
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackFinalized2--${actionLetter}3`))
|
|
463
|
-
eventDetail.action = TransferEventAction.FULFIL_DUPLICATE
|
|
464
|
-
/**
|
|
465
|
-
* HOWTO: During bulk fulfil use an individualTransfer from a previous bulk fulfil
|
|
466
|
-
*/
|
|
467
|
-
} else {
|
|
468
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackFinalized3--${actionLetter}4`))
|
|
469
|
-
eventDetail.action = TransferEventAction.ABORT_DUPLICATE
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch })
|
|
473
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
474
|
-
return true
|
|
475
|
-
}
|
|
285
|
+
}
|
|
286
|
+
// If execution continues after this point we are sure transfer exists and source matches payee fsp
|
|
476
287
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
*
|
|
484
|
-
* TODO: find a way to trigger this code branch and handle it at BulkProcessingHandler (not in scope of #967)
|
|
485
|
-
*/
|
|
486
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, histTimerEnd })
|
|
487
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
488
|
-
return true
|
|
489
|
-
}
|
|
288
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'dupCheck' }))
|
|
289
|
+
const histTimerDuplicateCheckEnd = Metrics.getHistogram(
|
|
290
|
+
'handler_transfers',
|
|
291
|
+
'fulfil_duplicateCheckComparator - Metrics for transfer handler',
|
|
292
|
+
['success', 'funcName']
|
|
293
|
+
).startTimer()
|
|
490
294
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
295
|
+
let dupCheckResult
|
|
296
|
+
if (!isTransferError) {
|
|
297
|
+
dupCheckResult = await Comparators.duplicateCheckComparator(transferId, payload, TransferService.getTransferFulfilmentDuplicateCheck, TransferService.saveTransferFulfilmentDuplicateCheck)
|
|
298
|
+
} else {
|
|
299
|
+
dupCheckResult = await Comparators.duplicateCheckComparator(transferId, payload, TransferService.getTransferErrorDuplicateCheck, TransferService.saveTransferErrorDuplicateCheck)
|
|
300
|
+
}
|
|
301
|
+
const { hasDuplicateId, hasDuplicateHash } = dupCheckResult
|
|
302
|
+
histTimerDuplicateCheckEnd({ success: true, funcName: 'fulfil_duplicateCheckComparator' })
|
|
303
|
+
if (hasDuplicateId && hasDuplicateHash) {
|
|
304
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, 'handleResend'))
|
|
305
|
+
|
|
306
|
+
// This is a duplicate message for a transfer that is already in a finalized state
|
|
307
|
+
// respond as if we received a GET /transfers/{ID} from the client
|
|
308
|
+
if (transferStateEnum === TransferState.COMMITTED || transferStateEnum === TransferState.ABORTED) {
|
|
309
|
+
message.value.content.payload = TransferObjectTransform.toFulfil(transfer)
|
|
310
|
+
const eventDetail = { functionality, action }
|
|
311
|
+
if (action !== TransferEventAction.RESERVE) {
|
|
312
|
+
if (!isTransferError) {
|
|
313
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackFinalized2--${actionLetter}3`))
|
|
314
|
+
eventDetail.action = TransferEventAction.FULFIL_DUPLICATE
|
|
315
|
+
/**
|
|
316
|
+
* HOWTO: During bulk fulfil use an individualTransfer from a previous bulk fulfil
|
|
317
|
+
*/
|
|
318
|
+
} else {
|
|
319
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackFinalized3--${actionLetter}4`))
|
|
320
|
+
eventDetail.action = TransferEventAction.ABORT_DUPLICATE
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
500
324
|
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
501
325
|
return true
|
|
502
326
|
}
|
|
503
327
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (hasDuplicateId && !hasDuplicateHash) {
|
|
507
|
-
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST)
|
|
508
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorModified2--${actionLetter}7`))
|
|
509
|
-
let action = TransferEventAction.FULFIL_DUPLICATE
|
|
510
|
-
if (isTransferError) {
|
|
511
|
-
action = TransferEventAction.ABORT_DUPLICATE
|
|
512
|
-
}
|
|
513
|
-
|
|
328
|
+
if (transferStateEnum === TransferState.RECEIVED || transferStateEnum === TransferState.RESERVED) {
|
|
329
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `inProgress2--${actionLetter}5`))
|
|
514
330
|
/**
|
|
515
|
-
* HOWTO:
|
|
516
|
-
*
|
|
331
|
+
* HOWTO: Nearly impossible to trigger for bulk - an individual transfer from a bulk needs to be triggered
|
|
332
|
+
* for processing in order to have the fulfil duplicate hash recorded. While it is still in RESERVED state
|
|
333
|
+
* the individual transfer needs to be requested by another bulk fulfil request!
|
|
334
|
+
*
|
|
335
|
+
* TODO: find a way to trigger this code branch and handle it at BulkProcessingHandler (not in scope of #967)
|
|
517
336
|
*/
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
337
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, histTimerEnd, hubName: Config.HUB_NAME })
|
|
338
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
339
|
+
return true
|
|
521
340
|
}
|
|
522
341
|
|
|
523
|
-
//
|
|
342
|
+
// Error scenario - transfer.transferStateEnumeration is in some invalid state
|
|
343
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInvalidTransferStateEnum--${actionLetter}6`))
|
|
344
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(
|
|
345
|
+
`Invalid transferStateEnumeration:(${transferStateEnum}) for event action:(${action}) and type:(${type})`).toApiErrorObject(Config.ERROR_HANDLING)
|
|
346
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
347
|
+
/**
|
|
348
|
+
* HOWTO: Impossible to trigger for individual transfer in a bulk? (not in scope of #967)
|
|
349
|
+
*/
|
|
350
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError, eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
351
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
524
354
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
throw fspiopError
|
|
355
|
+
// ERROR: We have seen a transfer of this ID before, but it's message hash doesn't match
|
|
356
|
+
// the previous message hash.
|
|
357
|
+
if (hasDuplicateId && !hasDuplicateHash) {
|
|
358
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST)
|
|
359
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorModified2--${actionLetter}7`))
|
|
360
|
+
let action = TransferEventAction.FULFIL_DUPLICATE
|
|
361
|
+
if (isTransferError) {
|
|
362
|
+
action = TransferEventAction.ABORT_DUPLICATE
|
|
534
363
|
}
|
|
535
364
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (!validActions.includes(action)) {
|
|
545
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInvalidEventAction--${actionLetter}15`))
|
|
546
|
-
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(`Invalid event action:(${action}) and/or type:(${type})`)
|
|
547
|
-
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
548
|
-
/**
|
|
549
|
-
* TODO: BulkProcessingHandler (not in scope of #967)
|
|
550
|
-
*/
|
|
551
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch })
|
|
552
|
-
throw fspiopError
|
|
553
|
-
}
|
|
365
|
+
/**
|
|
366
|
+
* HOWTO: During bulk fulfil use an individualTransfer from a previous bulk fulfil,
|
|
367
|
+
* but use different fulfilment value.
|
|
368
|
+
*/
|
|
369
|
+
const eventDetail = { functionality, action }
|
|
370
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
371
|
+
throw fspiopError
|
|
372
|
+
}
|
|
554
373
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: apiFSPIOPError, eventDetail, messageKey: payerAccount.participantCurrencyId.toString() })
|
|
374
|
+
// Transfer is not a duplicate, or message hasn't been changed.
|
|
375
|
+
|
|
376
|
+
if (type !== TransferEventType.FULFIL) {
|
|
377
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInvalidEventType--${actionLetter}15`))
|
|
378
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(`Invalid event type:(${type})`)
|
|
379
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
380
|
+
/**
|
|
381
|
+
* TODO: BulkProcessingHandler (not in scope of #967)
|
|
382
|
+
*/
|
|
383
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
384
|
+
throw fspiopError
|
|
385
|
+
}
|
|
568
386
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
387
|
+
const validActions = [
|
|
388
|
+
TransferEventAction.COMMIT,
|
|
389
|
+
TransferEventAction.RESERVE,
|
|
390
|
+
TransferEventAction.REJECT,
|
|
391
|
+
TransferEventAction.ABORT,
|
|
392
|
+
TransferEventAction.BULK_COMMIT,
|
|
393
|
+
TransferEventAction.BULK_ABORT
|
|
394
|
+
]
|
|
395
|
+
if (!validActions.includes(action)) {
|
|
396
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInvalidEventAction--${actionLetter}15`))
|
|
397
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError(`Invalid event action:(${action}) and/or type:(${type})`)
|
|
398
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
399
|
+
/**
|
|
400
|
+
* TODO: BulkProcessingHandler (not in scope of #967)
|
|
401
|
+
*/
|
|
402
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
403
|
+
throw fspiopError
|
|
404
|
+
}
|
|
577
405
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
406
|
+
Util.breadcrumb(location, { path: 'validationCheck' })
|
|
407
|
+
if (payload.fulfilment && !Validator.validateFulfilCondition(payload.fulfilment, transfer.condition)) {
|
|
408
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorInvalidFulfilment--${actionLetter}9`))
|
|
409
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'invalid fulfilment')
|
|
410
|
+
const apiFSPIOPError = fspiopError.toApiErrorObject(Config.ERROR_HANDLING)
|
|
411
|
+
await TransferService.handlePayeeResponse(transferId, payload, TransferEventAction.ABORT_VALIDATION, apiFSPIOPError)
|
|
412
|
+
const eventDetail = { functionality: TransferEventType.POSITION, action: TransferEventAction.ABORT_VALIDATION }
|
|
413
|
+
/**
|
|
414
|
+
* TODO: BulkProcessingHandler (not in scope of #967) The individual transfer is ABORTED by notification is never sent.
|
|
415
|
+
*/
|
|
416
|
+
// Key position validation abort with payer account id
|
|
417
|
+
|
|
418
|
+
const cyrilResult = await FxService.Cyril.processAbortMessage(transferId)
|
|
419
|
+
|
|
420
|
+
params.message.value.content.context = {
|
|
421
|
+
...params.message.value.content.context,
|
|
422
|
+
cyrilResult
|
|
423
|
+
}
|
|
424
|
+
if (cyrilResult.positionChanges.length > 0) {
|
|
425
|
+
const participantCurrencyId = cyrilResult.positionChanges[0].participantCurrencyId
|
|
426
|
+
await Kafka.proceed(
|
|
427
|
+
Config.KAFKA_CONFIG,
|
|
428
|
+
params,
|
|
429
|
+
{
|
|
430
|
+
consumerCommit,
|
|
431
|
+
fspiopError: apiFSPIOPError,
|
|
432
|
+
eventDetail,
|
|
433
|
+
messageKey: participantCurrencyId.toString(),
|
|
434
|
+
topicNameOverride: Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.ABORT,
|
|
435
|
+
hubName: Config.HUB_NAME
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
} else {
|
|
439
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError('Invalid cyril result')
|
|
440
|
+
throw fspiopError
|
|
441
|
+
}
|
|
581
442
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
443
|
+
// const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
444
|
+
// await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: apiFSPIOPError, eventDetail, messageKey: payerAccount.participantCurrencyId.toString(), hubName: Config.HUB_NAME })
|
|
445
|
+
|
|
446
|
+
// emit an extra message - RESERVED_ABORTED if action === TransferEventAction.RESERVE
|
|
447
|
+
if (action === TransferEventAction.RESERVE) {
|
|
448
|
+
// Get the updated transfer now that completedTimestamp will be different
|
|
449
|
+
// TODO: should we just modify TransferService.handlePayeeResponse to
|
|
450
|
+
// return the completed timestamp? Or is it safer to go back to the DB here?
|
|
451
|
+
const transferAbortResult = await TransferService.getById(transferId)
|
|
452
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackReservedAborted--${actionLetter}1`))
|
|
453
|
+
const eventDetail = { functionality: TransferEventType.NOTIFICATION, action: TransferEventAction.RESERVED_ABORTED }
|
|
454
|
+
|
|
455
|
+
// Extract error information
|
|
456
|
+
const errorCode = apiFSPIOPError && apiFSPIOPError.errorInformation && apiFSPIOPError.errorInformation.errorCode
|
|
457
|
+
const errorDescription = apiFSPIOPError && apiFSPIOPError.errorInformation && apiFSPIOPError.errorInformation.errorDescription
|
|
458
|
+
|
|
459
|
+
// TODO: This should be handled by a PATCH /transfers/{id}/error callback in the future FSPIOP v1.2 specification, and instead we should just send the FSPIOP-Error instead! Ref: https://github.com/mojaloop/mojaloop-specification/issues/106.
|
|
460
|
+
const reservedAbortedPayload = {
|
|
461
|
+
transferId: transferAbortResult && transferAbortResult.id,
|
|
462
|
+
completedTimestamp: transferAbortResult && transferAbortResult.completedTimestamp && (new Date(Date.parse(transferAbortResult.completedTimestamp))).toISOString(),
|
|
463
|
+
transferState: TransferState.ABORTED,
|
|
464
|
+
extensionList: { // lets add the extension list to handle the limitation of the FSPIOP v1.1 specification by adding the error cause...
|
|
465
|
+
extension: [
|
|
466
|
+
{
|
|
467
|
+
key: 'cause',
|
|
468
|
+
value: `${errorCode}: ${errorDescription}`
|
|
469
|
+
}
|
|
470
|
+
]
|
|
595
471
|
}
|
|
596
|
-
message.value.content.payload = reservedAbortedPayload
|
|
597
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch: true, toDestination: transfer.payeeFsp })
|
|
598
472
|
}
|
|
599
|
-
|
|
473
|
+
message.value.content.payload = reservedAbortedPayload
|
|
474
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch: true, toDestination: transfer.payeeFsp, hubName: Config.HUB_NAME })
|
|
600
475
|
}
|
|
476
|
+
throw fspiopError
|
|
477
|
+
}
|
|
601
478
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
479
|
+
if (transfer.transferState !== Enum.Transfers.TransferInternalState.RESERVED &&
|
|
480
|
+
transfer.transferState !== Enum.Transfers.TransferInternalState.RESERVED_FORWARDED
|
|
481
|
+
) {
|
|
482
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorNonReservedState--${actionLetter}10`))
|
|
483
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'non-RESERVED transfer state')
|
|
484
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
485
|
+
/**
|
|
486
|
+
* TODO: BulkProcessingHandler (not in scope of #967)
|
|
487
|
+
*/
|
|
488
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
489
|
+
|
|
490
|
+
// emit an extra message - RESERVED_ABORTED if action === TransferEventAction.RESERVE
|
|
491
|
+
if (action === TransferEventAction.RESERVE) {
|
|
492
|
+
// Get the updated transfer now that completedTimestamp will be different
|
|
493
|
+
// TODO: should we just modify TransferService.handlePayeeResponse to
|
|
494
|
+
// return the completed timestamp? Or is it safer to go back to the DB here?
|
|
495
|
+
const transferAborted = await TransferService.getById(transferId) // TODO: remove this once it can be tested
|
|
496
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackReservedAborted--${actionLetter}2`))
|
|
497
|
+
const eventDetail = { functionality: TransferEventType.NOTIFICATION, action: TransferEventAction.RESERVED_ABORTED }
|
|
498
|
+
const reservedAbortedPayload = {
|
|
499
|
+
transferId: transferAborted.id,
|
|
500
|
+
completedTimestamp: Util.Time.getUTCString(new Date(transferAborted.completedTimestamp)), // TODO: remove this once it can be tested
|
|
501
|
+
transferState: TransferState.ABORTED
|
|
502
|
+
}
|
|
503
|
+
message.value.content.payload = reservedAbortedPayload
|
|
504
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch: true, toDestination: transfer.payeeFsp, hubName: Config.HUB_NAME })
|
|
505
|
+
}
|
|
506
|
+
throw fspiopError
|
|
507
|
+
}
|
|
610
508
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
509
|
+
if (transfer.expirationDate <= new Date(Util.Time.getUTCString(new Date()))) {
|
|
510
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorTransferExpired--${actionLetter}11`))
|
|
511
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.TRANSFER_EXPIRED)
|
|
512
|
+
const eventDetail = { functionality, action: TransferEventAction.COMMIT }
|
|
513
|
+
/**
|
|
514
|
+
* TODO: BulkProcessingHandler (not in scope of #967)
|
|
515
|
+
*/
|
|
516
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
517
|
+
|
|
518
|
+
// emit an extra message - RESERVED_ABORTED if action === TransferEventAction.RESERVE
|
|
519
|
+
if (action === TransferEventAction.RESERVE) {
|
|
520
|
+
// Get the updated transfer now that completedTimestamp will be different
|
|
521
|
+
// TODO: should we just modify TransferService.handlePayeeResponse to
|
|
522
|
+
// return the completed timestamp? Or is it safer to go back to the DB here?
|
|
523
|
+
const transferAborted = await TransferService.getById(transferId)
|
|
524
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackReservedAborted--${actionLetter}3`))
|
|
525
|
+
const eventDetail = { functionality: TransferEventType.NOTIFICATION, action: TransferEventAction.RESERVED_ABORTED }
|
|
526
|
+
const reservedAbortedPayload = {
|
|
527
|
+
transferId: transferAborted.id,
|
|
528
|
+
completedTimestamp: Util.Time.getUTCString(new Date(transferAborted.completedTimestamp)),
|
|
529
|
+
transferState: TransferState.ABORTED
|
|
626
530
|
}
|
|
627
|
-
|
|
531
|
+
message.value.content.payload = reservedAbortedPayload
|
|
532
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch: true, hubName: Config.HUB_NAME })
|
|
628
533
|
}
|
|
534
|
+
throw fspiopError
|
|
535
|
+
}
|
|
629
536
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
537
|
+
// Validations Succeeded - process the fulfil
|
|
538
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'validationPassed' }))
|
|
539
|
+
switch (action) {
|
|
540
|
+
case TransferEventAction.COMMIT:
|
|
541
|
+
case TransferEventAction.RESERVE:
|
|
542
|
+
case TransferEventAction.BULK_COMMIT: {
|
|
543
|
+
let topicNameOverride
|
|
544
|
+
if (action === TransferEventAction.COMMIT) {
|
|
545
|
+
topicNameOverride = Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.COMMIT
|
|
546
|
+
} else if (action === TransferEventAction.RESERVE) {
|
|
547
|
+
topicNameOverride = Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.RESERVE
|
|
548
|
+
} else if (action === TransferEventAction.BULK_COMMIT) {
|
|
549
|
+
topicNameOverride = Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.BULK_COMMIT
|
|
550
|
+
}
|
|
638
551
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
completedTimestamp: Util.Time.getUTCString(new Date(transferAborted.completedTimestamp)),
|
|
650
|
-
transferState: TransferState.ABORTED
|
|
552
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic2--${actionLetter}12`))
|
|
553
|
+
await TransferService.handlePayeeResponse(transferId, payload, action)
|
|
554
|
+
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
555
|
+
// Key position fulfil message with payee account id
|
|
556
|
+
const cyrilResult = await FxService.Cyril.processFulfilMessage(transferId, payload, transfer)
|
|
557
|
+
if (cyrilResult.isFx) {
|
|
558
|
+
// const payeeAccount = await Participant.getAccountByNameAndCurrency(transfer.payeeFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
559
|
+
params.message.value.content.context = {
|
|
560
|
+
...params.message.value.content.context,
|
|
561
|
+
cyrilResult
|
|
651
562
|
}
|
|
652
|
-
|
|
653
|
-
|
|
563
|
+
if (cyrilResult.positionChanges.length > 0) {
|
|
564
|
+
const participantCurrencyId = cyrilResult.positionChanges[0].participantCurrencyId
|
|
565
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, messageKey: participantCurrencyId.toString(), topicNameOverride, hubName: Config.HUB_NAME })
|
|
566
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
567
|
+
} else {
|
|
568
|
+
histTimerEnd({ success: false, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
569
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError('Invalid cyril result')
|
|
570
|
+
throw fspiopError
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
const payeeAccount = await Participant.getAccountByNameAndCurrency(transfer.payeeFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
574
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, messageKey: payeeAccount.participantCurrencyId.toString(), topicNameOverride, hubName: Config.HUB_NAME })
|
|
575
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
576
|
+
}
|
|
577
|
+
return true
|
|
578
|
+
}
|
|
579
|
+
// TODO: why do we let this logic get this far? Why not remove it from validActions array above?
|
|
580
|
+
case TransferEventAction.REJECT: {
|
|
581
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic3--${actionLetter}13`))
|
|
582
|
+
const errorMessage = 'action REJECT is not allowed into fulfil handler'
|
|
583
|
+
Logger.isErrorEnabled && Logger.error(errorMessage)
|
|
584
|
+
!!span && span.error(errorMessage)
|
|
585
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
586
|
+
return true
|
|
587
|
+
}
|
|
588
|
+
case TransferEventAction.BULK_ABORT: {
|
|
589
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic4--${actionLetter}14`))
|
|
590
|
+
let fspiopError
|
|
591
|
+
const eInfo = payload.errorInformation
|
|
592
|
+
try { // handle only valid errorCodes provided by the payee
|
|
593
|
+
fspiopError = ErrorHandler.Factory.createFSPIOPErrorFromErrorInformation(eInfo)
|
|
594
|
+
} catch (err) {
|
|
595
|
+
/**
|
|
596
|
+
* TODO: Handling of out-of-range errorCodes is to be introduced to the ml-api-adapter,
|
|
597
|
+
* so that such requests are rejected right away, instead of aborting the transfer here.
|
|
598
|
+
*/
|
|
599
|
+
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
600
|
+
fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'API specification undefined errorCode')
|
|
601
|
+
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
602
|
+
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
603
|
+
// Key position abort with payer account id
|
|
604
|
+
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
605
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, messageKey: payerAccount.participantCurrencyId.toString(), hubName: Config.HUB_NAME })
|
|
606
|
+
throw fspiopError
|
|
654
607
|
}
|
|
608
|
+
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
609
|
+
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
610
|
+
// Key position abort with payer account id
|
|
611
|
+
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
612
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, messageKey: payerAccount.participantCurrencyId.toString(), hubName: Config.HUB_NAME })
|
|
613
|
+
// TODO(2556): I don't think we should emit an extra notification here
|
|
614
|
+
// this is the case where the Payee sent an ABORT, so we don't need to tell them to abort
|
|
655
615
|
throw fspiopError
|
|
656
616
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
617
|
+
case TransferEventAction.ABORT: {
|
|
618
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic4--${actionLetter}14`))
|
|
619
|
+
let fspiopError
|
|
620
|
+
const eInfo = payload.errorInformation
|
|
621
|
+
try { // handle only valid errorCodes provided by the payee
|
|
622
|
+
fspiopError = ErrorHandler.Factory.createFSPIOPErrorFromErrorInformation(eInfo)
|
|
623
|
+
} catch (err) {
|
|
624
|
+
/**
|
|
625
|
+
* TODO: Handling of out-of-range errorCodes is to be introduced to the ml-api-adapter,
|
|
626
|
+
* so that such requests are rejected right away, instead of aborting the transfer here.
|
|
627
|
+
*/
|
|
628
|
+
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
629
|
+
fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'API specification undefined errorCode')
|
|
630
|
+
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
666
631
|
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
667
|
-
// Key position
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
632
|
+
// Key position abort with payer account id
|
|
633
|
+
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
634
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, messageKey: payerAccount.participantCurrencyId.toString(), hubName: Config.HUB_NAME })
|
|
635
|
+
throw fspiopError
|
|
636
|
+
}
|
|
637
|
+
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
638
|
+
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
639
|
+
const cyrilResult = await FxService.Cyril.processAbortMessage(transferId)
|
|
640
|
+
|
|
641
|
+
params.message.value.content.context = {
|
|
642
|
+
...params.message.value.content.context,
|
|
643
|
+
cyrilResult
|
|
644
|
+
}
|
|
645
|
+
if (cyrilResult.positionChanges.length > 0) {
|
|
646
|
+
const participantCurrencyId = cyrilResult.positionChanges[0].participantCurrencyId
|
|
677
647
|
await Kafka.proceed(
|
|
678
648
|
Config.KAFKA_CONFIG,
|
|
679
649
|
params,
|
|
680
650
|
{
|
|
681
651
|
consumerCommit,
|
|
652
|
+
fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING),
|
|
682
653
|
eventDetail,
|
|
683
|
-
messageKey:
|
|
684
|
-
topicNameOverride
|
|
654
|
+
messageKey: participantCurrencyId.toString(),
|
|
655
|
+
topicNameOverride: Config.KAFKA_CONFIG.EVENT_TYPE_ACTION_TOPIC_MAP?.POSITION?.ABORT,
|
|
656
|
+
hubName: Config.HUB_NAME
|
|
685
657
|
}
|
|
686
658
|
)
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
// TODO: why do we let this logic get this far? Why not remove it from validActions array above?
|
|
691
|
-
case TransferEventAction.REJECT: {
|
|
692
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic3--${actionLetter}13`))
|
|
693
|
-
const errorMessage = 'action REJECT is not allowed into fulfil handler'
|
|
694
|
-
Logger.isErrorEnabled && Logger.error(errorMessage)
|
|
695
|
-
!!span && span.error(errorMessage)
|
|
696
|
-
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
697
|
-
return true
|
|
698
|
-
}
|
|
699
|
-
// TODO: why do we let this logic get this far? Why not remove it from validActions array above?
|
|
700
|
-
case TransferEventAction.ABORT:
|
|
701
|
-
case TransferEventAction.BULK_ABORT:
|
|
702
|
-
default: { // action === TransferEventAction.ABORT || action === TransferEventAction.BULK_ABORT // error-callback request to be processed
|
|
703
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `positionTopic4--${actionLetter}14`))
|
|
704
|
-
let fspiopError
|
|
705
|
-
const eInfo = payload.errorInformation
|
|
706
|
-
try { // handle only valid errorCodes provided by the payee
|
|
707
|
-
fspiopError = ErrorHandler.Factory.createFSPIOPErrorFromErrorInformation(eInfo)
|
|
708
|
-
} catch (err) {
|
|
709
|
-
/**
|
|
710
|
-
* TODO: Handling of out-of-range errorCodes is to be introduced to the ml-api-adapter,
|
|
711
|
-
* so that such requests are rejected right away, instead of aborting the transfer here.
|
|
712
|
-
*/
|
|
713
|
-
Logger.isErrorEnabled && Logger.error(`${Util.breadcrumb(location)}::${err.message}`)
|
|
714
|
-
fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'API specification undefined errorCode')
|
|
715
|
-
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
716
|
-
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
717
|
-
// Key position abort with payer account id
|
|
718
|
-
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
719
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, messageKey: payerAccount.participantCurrencyId.toString() })
|
|
720
|
-
throw fspiopError
|
|
721
|
-
}
|
|
722
|
-
await TransferService.handlePayeeResponse(transferId, payload, action, fspiopError.toApiErrorObject(Config.ERROR_HANDLING))
|
|
723
|
-
const eventDetail = { functionality: TransferEventType.POSITION, action }
|
|
724
|
-
// Key position abort with payer account id
|
|
725
|
-
const payerAccount = await Participant.getAccountByNameAndCurrency(transfer.payerFsp, transfer.currency, Enum.Accounts.LedgerAccountType.POSITION)
|
|
726
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, messageKey: payerAccount.participantCurrencyId.toString() })
|
|
727
|
-
// TODO(2556): I don't think we should emit an extra notification here
|
|
728
|
-
// this is the case where the Payee sent an ABORT, so we don't need to tell them to abort
|
|
659
|
+
} else {
|
|
660
|
+
const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError('Invalid cyril result')
|
|
729
661
|
throw fspiopError
|
|
730
662
|
}
|
|
731
663
|
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const processFxFulfilMessage = async (message, functionality, span) => {
|
|
668
|
+
const histTimerEnd = Metrics.getHistogram(
|
|
669
|
+
'fx_transfer_fulfil',
|
|
670
|
+
'Consume a fx fulfil transfer message from the kafka topic and process it accordingly',
|
|
671
|
+
['success', 'fspId']
|
|
672
|
+
).startTimer()
|
|
673
|
+
|
|
674
|
+
const {
|
|
675
|
+
payload,
|
|
676
|
+
headers,
|
|
677
|
+
type,
|
|
678
|
+
action,
|
|
679
|
+
commitRequestId,
|
|
680
|
+
kafkaTopic
|
|
681
|
+
} = FxFulfilService.decodeKafkaMessage(message)
|
|
682
|
+
|
|
683
|
+
const log = logger.child({ commitRequestId, type, action })
|
|
684
|
+
log.info('processFxFulfilMessage start...', { payload })
|
|
685
|
+
|
|
686
|
+
const params = {
|
|
687
|
+
message,
|
|
688
|
+
kafkaTopic,
|
|
689
|
+
span,
|
|
690
|
+
decodedPayload: payload,
|
|
691
|
+
consumer: Consumer,
|
|
692
|
+
producer: Producer
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const fxFulfilService = new FxFulfilService({
|
|
696
|
+
log, Config, Comparators, Validator, FxTransferModel, Kafka, params
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
// Validate event type
|
|
700
|
+
await fxFulfilService.validateEventType(type, functionality)
|
|
701
|
+
|
|
702
|
+
// Validate action
|
|
703
|
+
const validActions = [
|
|
704
|
+
TransferEventAction.FX_RESERVE,
|
|
705
|
+
TransferEventAction.FX_COMMIT,
|
|
706
|
+
// TransferEventAction.FX_REJECT,
|
|
707
|
+
TransferEventAction.FX_ABORT,
|
|
708
|
+
TransferEventAction.FX_FORWARDED
|
|
709
|
+
]
|
|
710
|
+
if (!validActions.includes(action)) {
|
|
711
|
+
const errorMessage = ERROR_MESSAGES.fxActionIsNotAllowed(action)
|
|
712
|
+
log.error(errorMessage)
|
|
713
|
+
span?.error(errorMessage)
|
|
714
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
739
715
|
return true
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const transfer = await fxFulfilService.getFxTransferDetails(commitRequestId, functionality)
|
|
719
|
+
// todo: rename to fxTransfer
|
|
720
|
+
await fxFulfilService.validateHeaders({ transfer, headers, payload })
|
|
721
|
+
|
|
722
|
+
// If execution continues after this point we are sure fxTransfer exists and source matches payee fsp
|
|
723
|
+
const histTimerDuplicateCheckEnd = Metrics.getHistogram(
|
|
724
|
+
'fx_handler_transfers',
|
|
725
|
+
'fxFulfil_duplicateCheckComparator - Metrics for fxTransfer handler',
|
|
726
|
+
['success', 'funcName']
|
|
727
|
+
).startTimer()
|
|
728
|
+
|
|
729
|
+
const dupCheckResult = await fxFulfilService.getDuplicateCheckResult({ commitRequestId, payload })
|
|
730
|
+
histTimerDuplicateCheckEnd({ success: true, funcName: 'fxFulfil_duplicateCheckComparator' })
|
|
731
|
+
|
|
732
|
+
const isDuplicate = await fxFulfilService.checkDuplication({ dupCheckResult, transfer, functionality, action, type })
|
|
733
|
+
if (isDuplicate) {
|
|
734
|
+
log.info('fxTransfer duplication detected, skip further processing')
|
|
735
|
+
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
736
|
+
return true
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Transfer is not a duplicate, or message hasn't been changed.
|
|
740
|
+
|
|
741
|
+
payload.fulfilment && await fxFulfilService.validateFulfilment(transfer, payload)
|
|
742
|
+
await fxFulfilService.validateTransferState(transfer, functionality)
|
|
743
|
+
await fxFulfilService.validateExpirationDate(transfer, functionality)
|
|
744
|
+
|
|
745
|
+
log.info('Validations Succeeded - process the fxFulfil...')
|
|
746
|
+
|
|
747
|
+
switch (action) {
|
|
748
|
+
case TransferEventAction.FX_RESERVE:
|
|
749
|
+
case TransferEventAction.FX_COMMIT: {
|
|
750
|
+
const success = await fxFulfilService.processFxFulfil({ transfer, payload, action })
|
|
751
|
+
log.info('fxFulfil handling is done', { success })
|
|
752
|
+
histTimerEnd({ success, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
753
|
+
return success
|
|
754
|
+
}
|
|
755
|
+
case TransferEventAction.FX_ABORT: {
|
|
756
|
+
const success = await fxFulfilService.processFxAbort({ transfer, payload, action })
|
|
757
|
+
log.info('fxAbort handling is done', { success })
|
|
758
|
+
histTimerEnd({ success, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
759
|
+
return true
|
|
743
760
|
}
|
|
744
761
|
}
|
|
745
762
|
}
|
|
@@ -769,46 +786,66 @@ const getTransfer = async (error, messages) => {
|
|
|
769
786
|
} else {
|
|
770
787
|
message = messages
|
|
771
788
|
}
|
|
789
|
+
const action = message.value.metadata.event.action
|
|
790
|
+
const isFx = action === TransferEventAction.FX_GET
|
|
772
791
|
const contextFromMessage = EventSdk.Tracer.extractContextFromMessage(message.value)
|
|
773
792
|
const span = EventSdk.Tracer.createChildSpanFromContext('cl_transfer_get', contextFromMessage)
|
|
774
793
|
try {
|
|
775
794
|
await span.audit(message, EventSdk.AuditEventAction.start)
|
|
776
795
|
const metadata = message.value.metadata
|
|
777
796
|
const action = metadata.event.action
|
|
778
|
-
const
|
|
797
|
+
const transferIdOrCommitRequestId = message.value.content.uriParams.id
|
|
779
798
|
const kafkaTopic = message.topic
|
|
780
799
|
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { method: `getTransfer:${action}` }))
|
|
781
800
|
|
|
782
801
|
const actionLetter = Enum.Events.ActionLetter.get
|
|
783
802
|
const params = { message, kafkaTopic, span, consumer: Consumer, producer: Producer }
|
|
784
|
-
const eventDetail = { functionality: TransferEventType.NOTIFICATION, action
|
|
803
|
+
const eventDetail = { functionality: TransferEventType.NOTIFICATION, action }
|
|
785
804
|
|
|
786
805
|
Util.breadcrumb(location, { path: 'validationFailed' })
|
|
787
806
|
if (!await Validator.validateParticipantByName(message.value.from)) {
|
|
788
807
|
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `breakParticipantDoesntExist--${actionLetter}1`))
|
|
789
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, histTimerEnd })
|
|
808
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, histTimerEnd, hubName: Config.HUB_NAME })
|
|
790
809
|
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
791
810
|
return true
|
|
792
811
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
812
|
+
if (isFx) {
|
|
813
|
+
const fxTransfer = await FxTransferModel.fxTransfer.getByIdLight(transferIdOrCommitRequestId)
|
|
814
|
+
if (!fxTransfer) {
|
|
815
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorTransferNotFound--${actionLetter}3`))
|
|
816
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.TRANSFER_ID_NOT_FOUND, 'Provided commitRequest ID was not found on the server.')
|
|
817
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
818
|
+
throw fspiopError
|
|
819
|
+
}
|
|
820
|
+
if (!await Validator.validateParticipantForCommitRequestId(message.value.from, transferIdOrCommitRequestId)) {
|
|
821
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorNotFxTransferParticipant--${actionLetter}2`))
|
|
822
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR)
|
|
823
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
824
|
+
throw fspiopError
|
|
825
|
+
}
|
|
826
|
+
Util.breadcrumb(location, { path: 'validationPassed' })
|
|
827
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackMessage--${actionLetter}4`))
|
|
828
|
+
message.value.content.payload = TransferObjectTransform.toFulfil(fxTransfer, true)
|
|
829
|
+
} else {
|
|
830
|
+
const transfer = await TransferService.getByIdLight(transferIdOrCommitRequestId)
|
|
831
|
+
if (!transfer) {
|
|
832
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorTransferNotFound--${actionLetter}3`))
|
|
833
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.TRANSFER_ID_NOT_FOUND, 'Provided Transfer ID was not found on the server.')
|
|
834
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
835
|
+
throw fspiopError
|
|
836
|
+
}
|
|
837
|
+
if (!await Validator.validateParticipantTransferId(message.value.from, transferIdOrCommitRequestId)) {
|
|
838
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackErrorNotTransferParticipant--${actionLetter}2`))
|
|
839
|
+
const fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR)
|
|
840
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, fspiopError: fspiopError.toApiErrorObject(Config.ERROR_HANDLING), eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
841
|
+
throw fspiopError
|
|
842
|
+
}
|
|
843
|
+
Util.breadcrumb(location, { path: 'validationPassed' })
|
|
844
|
+
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackMessage--${actionLetter}4`))
|
|
845
|
+
message.value.content.payload = TransferObjectTransform.toFulfil(transfer)
|
|
805
846
|
}
|
|
806
847
|
|
|
807
|
-
|
|
808
|
-
Util.breadcrumb(location, { path: 'validationPassed' })
|
|
809
|
-
Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `callbackMessage--${actionLetter}4`))
|
|
810
|
-
message.value.content.payload = TransferObjectTransform.toFulfil(transfer)
|
|
811
|
-
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch })
|
|
848
|
+
await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, eventDetail, fromSwitch, hubName: Config.HUB_NAME })
|
|
812
849
|
histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId })
|
|
813
850
|
return true
|
|
814
851
|
} catch (err) {
|
|
@@ -836,13 +873,14 @@ const getTransfer = async (error, messages) => {
|
|
|
836
873
|
*/
|
|
837
874
|
const registerPrepareHandler = async () => {
|
|
838
875
|
try {
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
876
|
+
const { TRANSFER } = TransferEventType
|
|
877
|
+
const { PREPARE } = TransferEventAction
|
|
878
|
+
|
|
879
|
+
const topicName = Kafka.transformGeneralTopicName(Config.KAFKA_CONFIG.TOPIC_TEMPLATES.GENERAL_TOPIC_TEMPLATE.TEMPLATE, TRANSFER, PREPARE)
|
|
880
|
+
const consumeConfig = Kafka.getKafkaConfig(Config.KAFKA_CONFIG, Enum.Kafka.Config.CONSUMER, TRANSFER.toUpperCase(), PREPARE.toUpperCase())
|
|
881
|
+
consumeConfig.rdkafkaConf['client.id'] = topicName
|
|
882
|
+
|
|
883
|
+
await Consumer.createHandler(topicName, consumeConfig, prepare)
|
|
846
884
|
return true
|
|
847
885
|
} catch (err) {
|
|
848
886
|
Logger.isErrorEnabled && Logger.error(err)
|