@mojaloop/sdk-scheme-adapter 11.18.8
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/.env.example +140 -0
- package/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.nvmrc +1 -0
- package/.versionrc +15 -0
- package/CHANGELOG.md +118 -0
- package/InboundServer/api.yaml +3594 -0
- package/InboundServer/api_template.yaml +69 -0
- package/InboundServer/handlers.js +940 -0
- package/InboundServer/index.js +205 -0
- package/InboundServer/middlewares.js +426 -0
- package/OAuthTestServer/index.js +66 -0
- package/OAuthTestServer/model.js +70 -0
- package/OutboundServer/api.yaml +2732 -0
- package/OutboundServer/api_interfaces/index.d.ts +117 -0
- package/OutboundServer/api_interfaces/openapi.d.ts +1475 -0
- package/OutboundServer/api_template/components/parameters/bulkQuoteId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/bulkTransferId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/requestToPayTransactionId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/transferId.yaml +9 -0
- package/OutboundServer/api_template/components/responses/accountsCreationCompleted.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationsServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/partiesByIdError404.yaml +9 -0
- package/OutboundServer/api_template/components/responses/partiesByIdSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPaySuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/accountCreationStatus.yaml +18 -0
- package/OutboundServer/api_template/components/schemas/accountsCreationState.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/accountsRequest.yaml +20 -0
- package/OutboundServer/api_template/components/schemas/accountsResponse.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/async2SyncCurrentState.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostRequest.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostResponse.yaml +19 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteResponse.yaml +21 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferResponse.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/errorAccountsResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorAuthorizationsResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorQuotesResponse.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/errorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorSimpleTransfersResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorTransferResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/extensionListEmptiable.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/individualQuote.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualQuoteResult.yaml +28 -0
- package/OutboundServer/api_template/components/schemas/individualTransfer.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualTransferFulfilment.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/individualTransferResult.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/mojaloopError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/mojaloopTransactionRequestState.yaml +2 -0
- package/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quote.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/quoteError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/quotesPostRequest.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +48 -0
- package/OutboundServer/api_template/components/schemas/requestToPayRequest.yaml +39 -0
- package/OutboundServer/api_template/components/schemas/requestToPayResponse.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferRequest.yaml +42 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/simpleTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostRequest.yaml +12 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +11 -0
- package/OutboundServer/api_template/components/schemas/transactionType.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptOTP.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptParty.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptQuote.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/transferFulfilment.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/transferParty.yaml +40 -0
- package/OutboundServer/api_template/components/schemas/transferRequest.yaml +37 -0
- package/OutboundServer/api_template/components/schemas/transferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/transferStatus.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +13 -0
- package/OutboundServer/api_template/health.yaml +12 -0
- package/OutboundServer/api_template/openapi.yaml +55 -0
- package/OutboundServer/api_template/paths/accounts.yaml +26 -0
- package/OutboundServer/api_template/paths/authorizations.yaml +19 -0
- package/OutboundServer/api_template/paths/bulkQuotes.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkQuotes_bulkQuoteId.yaml +24 -0
- package/OutboundServer/api_template/paths/bulkTransfers.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkTransfers_bulkTransferId.yaml +24 -0
- package/OutboundServer/api_template/paths/parties_Type_ID.yaml +20 -0
- package/OutboundServer/api_template/paths/parties_Type_ID_SubId.yaml +22 -0
- package/OutboundServer/api_template/paths/quotes.yaml +20 -0
- package/OutboundServer/api_template/paths/requestToPay.yaml +22 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer.yaml +57 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer_requestToPayTransactionId.yaml +34 -0
- package/OutboundServer/api_template/paths/simpleTransfers.yaml +19 -0
- package/OutboundServer/api_template/paths/transfers.yaml +55 -0
- package/OutboundServer/api_template/paths/transfers_transferId.yaml +58 -0
- package/OutboundServer/handlers.js +622 -0
- package/OutboundServer/index.js +137 -0
- package/OutboundServer/middlewares.js +67 -0
- package/TestServer/api.yaml +62 -0
- package/TestServer/handlers.js +63 -0
- package/TestServer/index.js +215 -0
- package/audit-resolve.json +65 -0
- package/babel.config.js +3 -0
- package/config.js +158 -0
- package/index.d.ts +1 -0
- package/index.js +149 -0
- package/jest.config.js +15 -0
- package/lib/api/index.js +12 -0
- package/lib/cache.js +352 -0
- package/lib/check.js +25 -0
- package/lib/model/AccountsModel.js +396 -0
- package/lib/model/Async2SyncModel.js +283 -0
- package/lib/model/AuthorizationsModel.js +86 -0
- package/lib/model/InboundTransfersModel.js +730 -0
- package/lib/model/OutboundBulkQuotesModel.js +485 -0
- package/lib/model/OutboundBulkTransfersModel.js +479 -0
- package/lib/model/OutboundRequestToPayModel.js +517 -0
- package/lib/model/OutboundRequestToPayTransferModel.js +893 -0
- package/lib/model/OutboundTransfersModel.js +823 -0
- package/lib/model/PartiesModel.js +70 -0
- package/lib/model/ProxyModel/MatchRules/Expression.js +48 -0
- package/lib/model/ProxyModel/MatchRules/Headers.js +65 -0
- package/lib/model/ProxyModel/MatchRules/MatchRule.js +27 -0
- package/lib/model/ProxyModel/MatchRules/Path.js +36 -0
- package/lib/model/ProxyModel/MatchRules/Query.js +65 -0
- package/lib/model/ProxyModel/MatchRules/index.js +19 -0
- package/lib/model/ProxyModel/Route.js +82 -0
- package/lib/model/ProxyModel/configSchema.json +118 -0
- package/lib/model/ProxyModel/index.js +138 -0
- package/lib/model/QuotesModel.js +94 -0
- package/lib/model/TransfersModel.js +81 -0
- package/lib/model/common/BackendError.js +26 -0
- package/lib/model/common/PersistentStateMachine.js +93 -0
- package/lib/model/common/index.js +18 -0
- package/lib/model/index.js +43 -0
- package/lib/model/lib/deferredJob.js +113 -0
- package/lib/model/lib/index.js +9 -0
- package/lib/model/lib/requests/backendRequests.js +227 -0
- package/lib/model/lib/requests/common.js +76 -0
- package/lib/model/lib/requests/index.js +19 -0
- package/lib/model/lib/shared.js +468 -0
- package/lib/randomphrase/index.js +21 -0
- package/lib/randomphrase/words.json +3397 -0
- package/lib/router.js +28 -0
- package/lib/validate.js +205 -0
- package/package.json +102 -0
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +152 -0
- package/test/__mocks__/javascript-state-machine.js +21 -0
- package/test/__mocks__/redis.js +49 -0
- package/test/__mocks__/uuidv4.js +16 -0
- package/test/config/integration.env +136 -0
- package/test/integration/lib/Outbound/authorizations.test.js +58 -0
- package/test/integration/lib/Outbound/data/authorizationsPostRequest.json +43 -0
- package/test/integration/lib/Outbound/data/quotesPostRequest.json +52 -0
- package/test/integration/lib/Outbound/data/transfersPostRequest.json +24 -0
- package/test/integration/lib/Outbound/parties.test.js +28 -0
- package/test/integration/lib/Outbound/quotes.test.js +58 -0
- package/test/integration/lib/Outbound/simpleTransfers.test.js +67 -0
- package/test/integration/lib/cache.test.js +80 -0
- package/test/integration/testEnv.js +7 -0
- package/test/unit/InboundServer.test.js +443 -0
- package/test/unit/TestServer.test.js +394 -0
- package/test/unit/api/accounts/accounts.test.js +128 -0
- package/test/unit/api/accounts/data/postAccountsBody.json +7 -0
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +25 -0
- package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +19 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +17 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +21 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +21 -0
- package/test/unit/api/accounts/utils.js +65 -0
- package/test/unit/api/proxy/data/proxyConfig.yaml +82 -0
- package/test/unit/api/proxy/data/requestBody.json +22 -0
- package/test/unit/api/proxy/data/requestHeaders.json +5 -0
- package/test/unit/api/proxy/data/requestQuery.json +6 -0
- package/test/unit/api/proxy/data/responseBody.json +21 -0
- package/test/unit/api/proxy/data/responseHeaders.json +5 -0
- package/test/unit/api/proxy/proxy.test.js +220 -0
- package/test/unit/api/proxy/utils.js +79 -0
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +21 -0
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +17 -0
- package/test/unit/api/transfers/data/postQuotesBody.json +52 -0
- package/test/unit/api/transfers/data/postTransfersBadBody.json +17 -0
- package/test/unit/api/transfers/data/postTransfersBody.json +24 -0
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +53 -0
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +47 -0
- package/test/unit/api/transfers/data/postTransfersSimpleBody.json +26 -0
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +101 -0
- package/test/unit/api/transfers/data/putPartiesBody.json +20 -0
- package/test/unit/api/transfers/data/putQuotesBody.json +37 -0
- package/test/unit/api/transfers/data/putTransfersBody.json +17 -0
- package/test/unit/api/transfers/transfers.test.js +191 -0
- package/test/unit/api/transfers/utils.js +183 -0
- package/test/unit/api/utils.js +75 -0
- package/test/unit/config.test.js +119 -0
- package/test/unit/data/commonHttpHeaders.json +6 -0
- package/test/unit/data/defaultConfig.json +58 -0
- package/test/unit/data/postQuotesBody.json +52 -0
- package/test/unit/data/putParticipantsBody.json +12 -0
- package/test/unit/data/putPartiesBody.json +20 -0
- package/test/unit/data/testFile.json +29 -0
- package/test/unit/data/testFile.yaml +14 -0
- package/test/unit/inboundApi/data/mockArguments.json +117 -0
- package/test/unit/inboundApi/data/mockTransactionRequest.json +42 -0
- package/test/unit/inboundApi/handlers.test.js +799 -0
- package/test/unit/index.test.js +55 -0
- package/test/unit/lib/cache.test.js +146 -0
- package/test/unit/lib/model/AccountsModel.test.js +121 -0
- package/test/unit/lib/model/AuthorizationsModel.test.js +460 -0
- package/test/unit/lib/model/InboundTransfersModel.test.js +628 -0
- package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +249 -0
- package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +244 -0
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +166 -0
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +245 -0
- package/test/unit/lib/model/OutboundTransfersModel.test.js +836 -0
- package/test/unit/lib/model/PartiesModel.test.js +468 -0
- package/test/unit/lib/model/QuotesModel.test.js +470 -0
- package/test/unit/lib/model/TransfersModel.test.js +474 -0
- package/test/unit/lib/model/common/PersistentStateMachine.test.js +179 -0
- package/test/unit/lib/model/data/authorizationsResponse.json +13 -0
- package/test/unit/lib/model/data/bulkQuoteRequest.json +27 -0
- package/test/unit/lib/model/data/bulkQuoteResponse.json +35 -0
- package/test/unit/lib/model/data/bulkTransferFulfil.json +13 -0
- package/test/unit/lib/model/data/bulkTransferRequest.json +29 -0
- package/test/unit/lib/model/data/defaultConfig.json +47 -0
- package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +42 -0
- package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +22 -0
- package/test/unit/lib/model/data/getTransfersBackendResponse.json +34 -0
- package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +17 -0
- package/test/unit/lib/model/data/mockArguments.json +131 -0
- package/test/unit/lib/model/data/mockTxnRequestsArguments.json +63 -0
- package/test/unit/lib/model/data/notificationToPayee.json +10 -0
- package/test/unit/lib/model/data/payeeParty.json +16 -0
- package/test/unit/lib/model/data/putAuthorizationsResponse.json +10 -0
- package/test/unit/lib/model/data/putQuotesResponse.json +33 -0
- package/test/unit/lib/model/data/putTransfersResponse.json +5 -0
- package/test/unit/lib/model/data/quoteResponse.json +31 -0
- package/test/unit/lib/model/data/requestToPayRequest.json +20 -0
- package/test/unit/lib/model/data/requestToPayTransferRequest.json +27 -0
- package/test/unit/lib/model/data/transactionRequestResponse.json +18 -0
- package/test/unit/lib/model/data/transferFulfil.json +8 -0
- package/test/unit/lib/model/data/transferRequest.json +26 -0
- package/test/unit/lib/model/mockedLibRequests.js +74 -0
- package/test/unit/mockLogger.js +39 -0
- package/test/unit/outboundApi/data/bulkQuoteRequest.json +28 -0
- package/test/unit/outboundApi/data/bulkTransferRequest.json +28 -0
- package/test/unit/outboundApi/data/mockBulkQuoteError.json +45 -0
- package/test/unit/outboundApi/data/mockBulkTransferError.json +48 -0
- package/test/unit/outboundApi/data/mockError.json +41 -0
- package/test/unit/outboundApi/data/mockGetPartiesError.json +4 -0
- package/test/unit/outboundApi/data/mockRequestToPayError.json +32 -0
- package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +39 -0
- package/test/unit/outboundApi/data/requestToPay.json +21 -0
- package/test/unit/outboundApi/data/requestToPayTransferRequest.json +20 -0
- package/test/unit/outboundApi/data/transferRequest.json +21 -0
- package/test/unit/outboundApi/handlers.test.js +986 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* James Bush - james.bush@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const util = require('util');
|
|
14
|
+
const { uuid } = require('uuidv4');
|
|
15
|
+
const StateMachine = require('javascript-state-machine');
|
|
16
|
+
const { Ilp, MojaloopRequests } = require('@mojaloop/sdk-standard-components');
|
|
17
|
+
const shared = require('./lib/shared');
|
|
18
|
+
const { BackendError } = require('./common');
|
|
19
|
+
const PartiesModel = require('./PartiesModel');
|
|
20
|
+
|
|
21
|
+
const transferStateEnum = {
|
|
22
|
+
'WAITING_FOR_PARTY_ACEPTANCE': 'WAITING_FOR_PARTY_ACCEPTANCE',
|
|
23
|
+
'WAITING_FOR_QUOTE_ACCEPTANCE': 'WAITING_FOR_QUOTE_ACCEPTANCE',
|
|
24
|
+
'ERROR_OCCURRED': 'ERROR_OCCURRED',
|
|
25
|
+
'COMPLETED': 'COMPLETED',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Models the state machine and operations required for performing an outbound transfer
|
|
31
|
+
*/
|
|
32
|
+
class OutboundTransfersModel {
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this._cache = config.cache;
|
|
35
|
+
this._logger = config.logger;
|
|
36
|
+
this._requestProcessingTimeoutSeconds = config.requestProcessingTimeoutSeconds;
|
|
37
|
+
this._dfspId = config.dfspId;
|
|
38
|
+
this._expirySeconds = config.expirySeconds;
|
|
39
|
+
this._rejectExpiredQuoteResponses = config.rejectExpiredQuoteResponses;
|
|
40
|
+
this._rejectExpiredTransferFulfils = config.rejectExpiredTransferFulfils;
|
|
41
|
+
this._autoAcceptQuotes = config.autoAcceptQuotes;
|
|
42
|
+
this._autoAcceptParty = config.autoAcceptParty;
|
|
43
|
+
this._useQuoteSourceFSPAsTransferPayeeFSP = config.useQuoteSourceFSPAsTransferPayeeFSP;
|
|
44
|
+
this._checkIlp = config.checkIlp;
|
|
45
|
+
|
|
46
|
+
this._requests = new MojaloopRequests({
|
|
47
|
+
logger: this._logger,
|
|
48
|
+
peerEndpoint: config.peerEndpoint,
|
|
49
|
+
alsEndpoint: config.alsEndpoint,
|
|
50
|
+
quotesEndpoint: config.quotesEndpoint,
|
|
51
|
+
transfersEndpoint: config.transfersEndpoint,
|
|
52
|
+
transactionRequestsEndpoint: config.transactionRequestsEndpoint,
|
|
53
|
+
dfspId: config.dfspId,
|
|
54
|
+
tls: config.tls,
|
|
55
|
+
jwsSign: config.jwsSign,
|
|
56
|
+
jwsSignPutParties: config.jwsSignPutParties,
|
|
57
|
+
jwsSigningKey: config.jwsSigningKey,
|
|
58
|
+
wso2: config.wso2,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this._ilp = new Ilp({
|
|
62
|
+
secret: config.ilpSecret,
|
|
63
|
+
logger: this._logger,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initializes the internal state machine object
|
|
70
|
+
*/
|
|
71
|
+
_initStateMachine (initState) {
|
|
72
|
+
this.stateMachine = new StateMachine({
|
|
73
|
+
init: initState,
|
|
74
|
+
transitions: [
|
|
75
|
+
{ name: 'resolvePayee', from: 'start', to: 'payeeResolved' },
|
|
76
|
+
{ name: 'requestQuote', from: 'payeeResolved', to: 'quoteReceived' },
|
|
77
|
+
{ name: 'executeTransfer', from: 'quoteReceived', to: 'succeeded' },
|
|
78
|
+
{ name: 'getTransfer', to: 'succeeded' },
|
|
79
|
+
{ name: 'error', from: '*', to: 'errored' },
|
|
80
|
+
],
|
|
81
|
+
methods: {
|
|
82
|
+
onTransition: this._handleTransition.bind(this),
|
|
83
|
+
onAfterTransition: this._afterTransition.bind(this),
|
|
84
|
+
onPendingTransition: (transition, from, to) => {
|
|
85
|
+
// allow transitions to 'error' state while other transitions are in progress
|
|
86
|
+
if(transition !== 'error') {
|
|
87
|
+
throw new Error(`Transition requested while another transition is in progress: ${transition} from: ${from} to: ${to}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return this.stateMachine[initState];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Updates the internal state representation to reflect that of the state machine itself
|
|
99
|
+
*/
|
|
100
|
+
_afterTransition() {
|
|
101
|
+
this._logger.log(`State machine transitioned: ${this.data.currentState} -> ${this.stateMachine.state}`);
|
|
102
|
+
this.data.currentState = this.stateMachine.state;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Initializes the transfer model
|
|
108
|
+
*
|
|
109
|
+
* @param data {object} - The inbound API POST /transfers request body
|
|
110
|
+
*/
|
|
111
|
+
async initialize(data) {
|
|
112
|
+
this.data = data;
|
|
113
|
+
|
|
114
|
+
// add a transferId if one is not present e.g. on first submission
|
|
115
|
+
if(!this.data.hasOwnProperty('transferId')) {
|
|
116
|
+
this.data.transferId = uuid();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// initialize the transfer state machine to its starting state
|
|
120
|
+
if(!this.data.hasOwnProperty('currentState')) {
|
|
121
|
+
this.data.currentState = 'start';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if(!this.data.hasOwnProperty('initiatedTimestamp')) {
|
|
125
|
+
this.data.initiatedTimestamp = new Date().toISOString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._initStateMachine(this.data.currentState);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handles state machine transitions
|
|
134
|
+
*/
|
|
135
|
+
async _handleTransition(lifecycle, ...args) {
|
|
136
|
+
this._logger.log(`Transfer ${this.data.transferId} is transitioning from ${lifecycle.from} to ${lifecycle.to} in response to ${lifecycle.transition}`);
|
|
137
|
+
|
|
138
|
+
switch(lifecycle.transition) {
|
|
139
|
+
case 'init':
|
|
140
|
+
// init, just allow the fsm to start
|
|
141
|
+
return;
|
|
142
|
+
|
|
143
|
+
case 'resolvePayee':
|
|
144
|
+
// resolve the payee
|
|
145
|
+
return this._resolvePayee();
|
|
146
|
+
|
|
147
|
+
case 'requestQuote':
|
|
148
|
+
// request a quote
|
|
149
|
+
return this._requestQuote();
|
|
150
|
+
|
|
151
|
+
case 'getTransfer':
|
|
152
|
+
return this._getTransfer();
|
|
153
|
+
|
|
154
|
+
case 'executeTransfer':
|
|
155
|
+
// prepare a transfer and wait for fulfillment
|
|
156
|
+
return this._executeTransfer();
|
|
157
|
+
|
|
158
|
+
case 'error':
|
|
159
|
+
this._logger.log(`State machine is erroring with error: ${util.inspect(args)}`);
|
|
160
|
+
this.data.lastError = args[0] || new Error('unspecified error');
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
default:
|
|
164
|
+
throw new Error(`Unhandled state transition for transfer ${this.data.transferId}: ${util.inspect(args)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Resolves the payee.
|
|
171
|
+
* Starts the payee resolution process by sending a GET /parties request to the switch;
|
|
172
|
+
* then waits for a notification from the cache that the payee has been resolved.
|
|
173
|
+
*/
|
|
174
|
+
async _resolvePayee() {
|
|
175
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
176
|
+
return new Promise(async (resolve, reject) => {
|
|
177
|
+
// listen for resolution events on the payee idType and idValue
|
|
178
|
+
const payeeKey = PartiesModel.channelName({
|
|
179
|
+
type: this.data.to.idType,
|
|
180
|
+
id: this.data.to.idValue,
|
|
181
|
+
subId: this.data.to.idSubValue
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// hook up a subscriber to handle response messages
|
|
185
|
+
const subId = await this._cache.subscribe(payeeKey, (cn, msg, subId) => {
|
|
186
|
+
try {
|
|
187
|
+
let payee = JSON.parse(msg);
|
|
188
|
+
|
|
189
|
+
if(payee.errorInformation) {
|
|
190
|
+
// this is an error response to our GET /parties request
|
|
191
|
+
const err = new BackendError(`Got an error response resolving party: ${util.inspect(payee, { depth: Infinity })}`, 500);
|
|
192
|
+
err.mojaloopError = payee;
|
|
193
|
+
|
|
194
|
+
// cancel the timeout handler
|
|
195
|
+
clearTimeout(timeout);
|
|
196
|
+
return reject(err);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if(!payee.party) {
|
|
200
|
+
// we should never get a non-error response without a party, but just in case...
|
|
201
|
+
// cancel the timeout handler
|
|
202
|
+
clearTimeout(timeout);
|
|
203
|
+
return reject(new Error(`Resolved payee has no party object: ${util.inspect(payee)}`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
payee = payee.party;
|
|
207
|
+
|
|
208
|
+
// cancel the timeout handler
|
|
209
|
+
clearTimeout(timeout);
|
|
210
|
+
|
|
211
|
+
this._logger.push({ payee }).log('Payee resolved');
|
|
212
|
+
|
|
213
|
+
// stop listening for payee resolution messages
|
|
214
|
+
// no need to await for the unsubscribe to complete.
|
|
215
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
216
|
+
this._cache.unsubscribe(payeeKey, subId).catch(e => {
|
|
217
|
+
this._logger.log(`Error unsubscribing (in callback) ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// check we got the right payee and info we need
|
|
221
|
+
if(payee.partyIdInfo.partyIdType !== this.data.to.idType) {
|
|
222
|
+
const err = new Error(`Expecting resolved payee party IdType to be ${this.data.to.idType} but got ${payee.partyIdInfo.partyIdType}`);
|
|
223
|
+
return reject(err);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if(payee.partyIdInfo.partyIdentifier !== this.data.to.idValue) {
|
|
227
|
+
const err = new Error(`Expecting resolved payee party identifier to be ${this.data.to.idValue} but got ${payee.partyIdInfo.partyIdentifier}`);
|
|
228
|
+
return reject(err);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if(payee.partyIdInfo.partySubIdOrType !== this.data.to.idSubValue) {
|
|
232
|
+
const err = new Error(`Expecting resolved payee party subTypeId to be ${this.data.to.idSubValue} but got ${payee.partyIdInfo.partySubIdOrType}`);
|
|
233
|
+
return reject(err);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if(!payee.partyIdInfo.fspId) {
|
|
237
|
+
const err = new Error(`Expecting resolved payee party to have an FSPID: ${util.inspect(payee.partyIdInfo)}`);
|
|
238
|
+
return reject(err);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// now we got the payee, add the details to our data so we can use it
|
|
242
|
+
// in the quote request
|
|
243
|
+
this.data.to.fspId = payee.partyIdInfo.fspId;
|
|
244
|
+
if(payee.partyIdInfo.extensionList) {
|
|
245
|
+
this.data.to.extensionList = payee.partyIdInfo.extensionList.extension;
|
|
246
|
+
}
|
|
247
|
+
if(payee.personalInfo) {
|
|
248
|
+
if(payee.personalInfo.complexName) {
|
|
249
|
+
this.data.to.firstName = payee.personalInfo.complexName.firstName || this.data.to.firstName;
|
|
250
|
+
this.data.to.middleName = payee.personalInfo.complexName.middleName || this.data.to.middleName;
|
|
251
|
+
this.data.to.lastName = payee.personalInfo.complexName.lastName || this.data.to.lastName;
|
|
252
|
+
}
|
|
253
|
+
this.data.to.dateOfBirth = payee.personalInfo.dateOfBirth;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return resolve(payee);
|
|
257
|
+
}
|
|
258
|
+
catch(err) {
|
|
259
|
+
return reject(err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// set up a timeout for the resolution
|
|
264
|
+
const timeout = setTimeout(() => {
|
|
265
|
+
const err = new BackendError(`Timeout resolving payee for transfer ${this.data.transferId}`, 504);
|
|
266
|
+
|
|
267
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
268
|
+
this._cache.unsubscribe(payeeKey, subId).catch(e => {
|
|
269
|
+
this._logger.log(`Error unsubscribing (in timeout handler) ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return reject(err);
|
|
273
|
+
}, this._requestProcessingTimeoutSeconds * 1000);
|
|
274
|
+
|
|
275
|
+
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
276
|
+
// a GET /parties request to the switch
|
|
277
|
+
try {
|
|
278
|
+
const res = await this._requests.getParties(this.data.to.idType, this.data.to.idValue,
|
|
279
|
+
this.data.to.idSubValue);
|
|
280
|
+
this._logger.push({ peer: res }).log('Party lookup sent to peer');
|
|
281
|
+
}
|
|
282
|
+
catch(err) {
|
|
283
|
+
// cancel the timout and unsubscribe before rejecting the promise
|
|
284
|
+
clearTimeout(timeout);
|
|
285
|
+
|
|
286
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
287
|
+
this._cache.unsubscribe(payeeKey, subId).catch(e => {
|
|
288
|
+
this._logger.log(`Error unsubscribing ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return reject(err);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Requests a quote
|
|
299
|
+
* Starts the quote resolution process by sending a POST /quotes request to the switch;
|
|
300
|
+
* then waits for a notification from the cache that the quote response has been received
|
|
301
|
+
*/
|
|
302
|
+
async _requestQuote() {
|
|
303
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
304
|
+
return new Promise(async (resolve, reject) => {
|
|
305
|
+
// create a quote request
|
|
306
|
+
const quote = this._buildQuoteRequest();
|
|
307
|
+
this.data.quoteId = quote.quoteId;
|
|
308
|
+
|
|
309
|
+
// listen for events on the quoteId
|
|
310
|
+
const quoteKey = `qt_${quote.quoteId}`;
|
|
311
|
+
|
|
312
|
+
// hook up a subscriber to handle response messages
|
|
313
|
+
const subId = await this._cache.subscribe(quoteKey, (cn, msg, subId) => {
|
|
314
|
+
try {
|
|
315
|
+
let error;
|
|
316
|
+
let message = JSON.parse(msg);
|
|
317
|
+
|
|
318
|
+
if (message.type === 'quoteResponse') {
|
|
319
|
+
if (this._rejectExpiredQuoteResponses) {
|
|
320
|
+
const now = new Date().toISOString();
|
|
321
|
+
if (now > quote.expiration) {
|
|
322
|
+
const msg = 'Quote response missed expiry deadline';
|
|
323
|
+
error = new BackendError(msg, 504);
|
|
324
|
+
this._logger.error(`${msg}: system time=${now} > expiration time=${quote.expiration}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} else if (message.type === 'quoteResponseError') {
|
|
328
|
+
error = new BackendError(`Got an error response requesting quote: ${util.inspect(message.data, { depth: Infinity })}`, 500);
|
|
329
|
+
error.mojaloopError = message.data;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
this._logger.push({ message }).log(`Ignoring cache notification for quote ${quoteKey}. Unknown message type ${message.type}.`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// cancel the timeout handler
|
|
337
|
+
clearTimeout(timeout);
|
|
338
|
+
|
|
339
|
+
// stop listening for payee resolution messages
|
|
340
|
+
// no need to await for the unsubscribe to complete.
|
|
341
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
342
|
+
this._cache.unsubscribe(quoteKey, subId).catch(e => {
|
|
343
|
+
this._logger.log(`Error unsubscribing (in callback) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
if (error) {
|
|
347
|
+
return reject(error);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const quoteResponseBody = message.data;
|
|
351
|
+
const quoteResponseHeaders = message.headers;
|
|
352
|
+
this._logger.push({ quoteResponseBody }).log('Quote response received');
|
|
353
|
+
|
|
354
|
+
this.data.quoteResponse = quoteResponseBody;
|
|
355
|
+
this.data.quoteResponseSource = quoteResponseHeaders['fspiop-source'];
|
|
356
|
+
|
|
357
|
+
return resolve(quote);
|
|
358
|
+
}
|
|
359
|
+
catch(err) {
|
|
360
|
+
return reject(err);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// set up a timeout for the request
|
|
365
|
+
const timeout = setTimeout(() => {
|
|
366
|
+
const err = new BackendError(`Timeout requesting quote for transfer ${this.data.transferId}`, 504);
|
|
367
|
+
|
|
368
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
369
|
+
this._cache.unsubscribe(quoteKey, subId).catch(e => {
|
|
370
|
+
this._logger.log(`Error unsubscribing (in timeout handler) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return reject(err);
|
|
374
|
+
}, this._requestProcessingTimeoutSeconds * 1000);
|
|
375
|
+
|
|
376
|
+
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
377
|
+
// a POST /quotes request to the switch
|
|
378
|
+
try {
|
|
379
|
+
const res = await this._requests.postQuotes(quote, this.data.to.fspId);
|
|
380
|
+
this._logger.push({ res }).log('Quote request sent to peer');
|
|
381
|
+
}
|
|
382
|
+
catch(err) {
|
|
383
|
+
// cancel the timout and unsubscribe before rejecting the promise
|
|
384
|
+
clearTimeout(timeout);
|
|
385
|
+
|
|
386
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
387
|
+
this._cache.unsubscribe(quoteKey, subId).catch(e => {
|
|
388
|
+
this._logger.log(`Error unsubscribing (in error handler) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
return reject(err);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Constructs a quote request payload based on current state
|
|
399
|
+
*
|
|
400
|
+
* @returns {object} - the quote request object
|
|
401
|
+
*/
|
|
402
|
+
_buildQuoteRequest() {
|
|
403
|
+
let quote = {
|
|
404
|
+
quoteId: uuid(),
|
|
405
|
+
transactionId: this.data.transferId,
|
|
406
|
+
amountType: this.data.amountType,
|
|
407
|
+
amount: {
|
|
408
|
+
currency: this.data.currency,
|
|
409
|
+
amount: this.data.amount
|
|
410
|
+
},
|
|
411
|
+
expiration: this._getExpirationTimestamp()
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
quote.payer = shared.internalPartyToMojaloopParty(this.data.from, this._dfspId);
|
|
415
|
+
quote.payee = shared.internalPartyToMojaloopParty(this.data.to, this.data.to.fspId);
|
|
416
|
+
|
|
417
|
+
quote.transactionType = {
|
|
418
|
+
scenario: this.data.transactionType,
|
|
419
|
+
// TODO: support payee initiated txns?
|
|
420
|
+
initiator: 'PAYER',
|
|
421
|
+
// TODO: defaulting to CONSUMER initiator type should
|
|
422
|
+
// be replaced with a required element on the incoming
|
|
423
|
+
// API request
|
|
424
|
+
initiatorType: this.data.from.type || 'CONSUMER'
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// geocode
|
|
428
|
+
// note
|
|
429
|
+
if(this.data.note) {
|
|
430
|
+
quote.note = this.data.note;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// add extensionList if provided
|
|
434
|
+
if(this.data.quoteRequestExtensions && this.data.quoteRequestExtensions.length > 0) {
|
|
435
|
+
quote.extensionList = {
|
|
436
|
+
extension: this.data.quoteRequestExtensions
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return quote;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Executes a transfer
|
|
446
|
+
* Starts the transfer process by sending a POST /transfers (prepare) request to the switch;
|
|
447
|
+
* then waits for a notification from the cache that the transfer has been fulfilled
|
|
448
|
+
*/
|
|
449
|
+
async _executeTransfer() {
|
|
450
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
451
|
+
return new Promise(async (resolve, reject) => {
|
|
452
|
+
// create a transfer prepare request
|
|
453
|
+
const prepare = this._buildTransferPrepare();
|
|
454
|
+
|
|
455
|
+
// listen for events on the transferId
|
|
456
|
+
const transferKey = `tf_${this.data.transferId}`;
|
|
457
|
+
|
|
458
|
+
const subId = await this._cache.subscribe(transferKey, async (cn, msg, subId) => {
|
|
459
|
+
try {
|
|
460
|
+
let error;
|
|
461
|
+
let message = JSON.parse(msg);
|
|
462
|
+
|
|
463
|
+
if (message.type === 'transferFulfil') {
|
|
464
|
+
if (this._rejectExpiredTransferFulfils) {
|
|
465
|
+
const now = new Date().toISOString();
|
|
466
|
+
if (now > prepare.expiration) {
|
|
467
|
+
const msg = 'Transfer fulfil missed expiry deadline';
|
|
468
|
+
this._logger.error(`${msg}: system time=${now} > expiration=${prepare.expiration}`);
|
|
469
|
+
error = new BackendError(msg, 504);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} else if (message.type === 'transferError') {
|
|
473
|
+
error = new BackendError(`Got an error response preparing transfer: ${util.inspect(message.data, { depth: Infinity })}`, 500);
|
|
474
|
+
error.mojaloopError = message.data;
|
|
475
|
+
} else {
|
|
476
|
+
this._logger.push({ message }).log(`Ignoring cache notification for transfer ${transferKey}. Uknokwn message type ${message.type}.`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// cancel the timeout handler
|
|
481
|
+
clearTimeout(timeout);
|
|
482
|
+
|
|
483
|
+
// stop listening for transfer fulfil messages
|
|
484
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
485
|
+
this._logger.log(`Error unsubscribing (in callback) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (error) {
|
|
489
|
+
return reject(error);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const fulfil = message.data;
|
|
493
|
+
this._logger.push({ fulfil }).log('Transfer fulfil received');
|
|
494
|
+
this.data.fulfil = fulfil;
|
|
495
|
+
|
|
496
|
+
if(this._checkIlp && !this._ilp.validateFulfil(fulfil.fulfilment, this.data.quoteResponse.condition)) {
|
|
497
|
+
throw new Error('Invalid fulfilment received from peer DFSP.');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return resolve(fulfil);
|
|
501
|
+
}
|
|
502
|
+
catch(err) {
|
|
503
|
+
return reject(err);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// set up a timeout for the request
|
|
508
|
+
const timeout = setTimeout(() => {
|
|
509
|
+
const err = new BackendError(`Timeout waiting for fulfil for transfer ${this.data.transferId}`, 504);
|
|
510
|
+
|
|
511
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
512
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
513
|
+
this._logger.log(`Error unsubscribing (in timeout handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
return reject(err);
|
|
517
|
+
}, this._requestProcessingTimeoutSeconds * 1000);
|
|
518
|
+
|
|
519
|
+
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
520
|
+
// a POST /transfers request to the switch
|
|
521
|
+
try {
|
|
522
|
+
const res = await this._requests.postTransfers(prepare, this.data.quoteResponseSource);
|
|
523
|
+
this._logger.push({ res }).log('Transfer prepare sent to peer');
|
|
524
|
+
}
|
|
525
|
+
catch(err) {
|
|
526
|
+
// cancel the timout and unsubscribe before rejecting the promise
|
|
527
|
+
clearTimeout(timeout);
|
|
528
|
+
|
|
529
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
530
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
531
|
+
this._logger.log(`Error unsubscribing (in error handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
return reject(err);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get transfer details by sending GET /transfers request to the switch
|
|
541
|
+
*/
|
|
542
|
+
async _getTransfer() {
|
|
543
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
544
|
+
return new Promise(async (resolve, reject) => {
|
|
545
|
+
const transferKey = `tf_${this.data.transferId}`;
|
|
546
|
+
|
|
547
|
+
// hook up a subscriber to handle response messages
|
|
548
|
+
const subId = await this._cache.subscribe(transferKey, (cn, msg, subId) => {
|
|
549
|
+
try {
|
|
550
|
+
let error;
|
|
551
|
+
let message = JSON.parse(msg);
|
|
552
|
+
|
|
553
|
+
if (message.type === 'transferError') {
|
|
554
|
+
error = new BackendError(`Got an error response retrieving transfer: ${util.inspect(message.data, { depth: Infinity })}`, 500);
|
|
555
|
+
error.mojaloopError = message.data;
|
|
556
|
+
} else if (message.type !== 'transferFulfil') {
|
|
557
|
+
this._logger.push({ message }).log(`Ignoring cache notification for transfer ${transferKey}. Uknokwn message type ${message.type}.`);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// cancel the timeout handler
|
|
562
|
+
clearTimeout(timeout);
|
|
563
|
+
|
|
564
|
+
// stop listening for transfer fulfil messages
|
|
565
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
566
|
+
this._logger.log(`Error unsubscribing (in callback) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
if (error) {
|
|
570
|
+
return reject(error);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const fulfil = message.data;
|
|
574
|
+
this._logger.push({ fulfil }).log('Transfer fulfil received');
|
|
575
|
+
this.data.fulfil = fulfil;
|
|
576
|
+
|
|
577
|
+
return resolve(this.data);
|
|
578
|
+
}
|
|
579
|
+
catch(err) {
|
|
580
|
+
return reject(err);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// set up a timeout for the resolution
|
|
585
|
+
const timeout = setTimeout(() => {
|
|
586
|
+
const err = new BackendError(`Timeout getting transfer ${this.data.transferId}`, 504);
|
|
587
|
+
|
|
588
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
589
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
590
|
+
this._logger.log(`Error unsubscribing (in timeout handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
return reject(err);
|
|
594
|
+
}, this._requestProcessingTimeoutSeconds * 1000);
|
|
595
|
+
|
|
596
|
+
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
597
|
+
// a GET /transfers request to the switch
|
|
598
|
+
try {
|
|
599
|
+
const res = await this._requests.getTransfers(this.data.transferId);
|
|
600
|
+
this._logger.push({ peer: res }).log('Transfer lookup sent to peer');
|
|
601
|
+
}
|
|
602
|
+
catch(err) {
|
|
603
|
+
// cancel the timout and unsubscribe before rejecting the promise
|
|
604
|
+
clearTimeout(timeout);
|
|
605
|
+
|
|
606
|
+
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
607
|
+
this._cache.unsubscribe(transferKey, subId).catch(e => {
|
|
608
|
+
this._logger.log(`Error unsubscribing ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
return reject(err);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Builds a transfer prepare payload from current state
|
|
619
|
+
*
|
|
620
|
+
* @returns {object} - the transfer prepare payload
|
|
621
|
+
*/
|
|
622
|
+
_buildTransferPrepare() {
|
|
623
|
+
let prepare = {
|
|
624
|
+
transferId: this.data.transferId,
|
|
625
|
+
payeeFsp: this.data.to.fspId,
|
|
626
|
+
payerFsp: this._dfspId,
|
|
627
|
+
amount: {
|
|
628
|
+
// We use the transfer currency and amount specified in the quote response
|
|
629
|
+
// rather than the original request. In Forex cases we may have requested
|
|
630
|
+
// a RECEIVE amount in a currency we cannot send. FXP should always give us
|
|
631
|
+
// a quote response with transferAmount in the correct currency.
|
|
632
|
+
currency: this.data.quoteResponse.transferAmount.currency,
|
|
633
|
+
amount: this.data.quoteResponse.transferAmount.amount
|
|
634
|
+
},
|
|
635
|
+
ilpPacket: this.data.quoteResponse.ilpPacket,
|
|
636
|
+
condition: this.data.quoteResponse.condition,
|
|
637
|
+
expiration: this._getExpirationTimestamp()
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
if(this._useQuoteSourceFSPAsTransferPayeeFSP) {
|
|
641
|
+
prepare.payeeFsp = this.data.quoteResponseSource;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// add extensions list if provided
|
|
645
|
+
const { transferRequestExtensions } = this.data;
|
|
646
|
+
if(transferRequestExtensions && transferRequestExtensions.length > 0) {
|
|
647
|
+
prepare.extensionList = {
|
|
648
|
+
extension: transferRequestExtensions,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return prepare;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Returns an ISO-8601 format timestamp n-seconds in the future for expiration of a transfers API object,
|
|
658
|
+
* where n is equal to our config setting "expirySeconds"
|
|
659
|
+
*
|
|
660
|
+
* @returns {string} - ISO-8601 format future expiration timestamp
|
|
661
|
+
*/
|
|
662
|
+
_getExpirationTimestamp() {
|
|
663
|
+
let now = new Date();
|
|
664
|
+
return new Date(now.getTime() + (this._expirySeconds * 1000)).toISOString();
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Returns an object representing the final state of the transfer suitable for the outbound API
|
|
670
|
+
*
|
|
671
|
+
* @returns {object} - Response representing the result of the transfer process
|
|
672
|
+
*/
|
|
673
|
+
getResponse() {
|
|
674
|
+
// we want to project some of our internal state into a more useful
|
|
675
|
+
// representation to return to the SDK API consumer
|
|
676
|
+
let resp = { ...this.data };
|
|
677
|
+
|
|
678
|
+
switch(this.data.currentState) {
|
|
679
|
+
case 'payeeResolved':
|
|
680
|
+
resp.currentState = transferStateEnum.WAITING_FOR_PARTY_ACEPTANCE;
|
|
681
|
+
break;
|
|
682
|
+
|
|
683
|
+
case 'quoteReceived':
|
|
684
|
+
resp.currentState = transferStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE;
|
|
685
|
+
break;
|
|
686
|
+
|
|
687
|
+
case 'succeeded':
|
|
688
|
+
resp.currentState = transferStateEnum.COMPLETED;
|
|
689
|
+
break;
|
|
690
|
+
|
|
691
|
+
case 'errored':
|
|
692
|
+
resp.currentState = transferStateEnum.ERROR_OCCURRED;
|
|
693
|
+
break;
|
|
694
|
+
|
|
695
|
+
default:
|
|
696
|
+
this._logger.log(`Transfer model response being returned from an unexpected state: ${this.data.currentState}. Returning ERROR_OCCURRED state`);
|
|
697
|
+
resp.currentState = transferStateEnum.ERROR_OCCURRED;
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return resp;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Persists the model state to cache for reinstantiation at a later point
|
|
707
|
+
*/
|
|
708
|
+
async _save() {
|
|
709
|
+
try {
|
|
710
|
+
this.data.currentState = this.stateMachine.state;
|
|
711
|
+
const res = await this._cache.set(`transferModel_${this.data.transferId}`, this.data);
|
|
712
|
+
this._logger.push({ res }).log('Persisted transfer model in cache');
|
|
713
|
+
}
|
|
714
|
+
catch(err) {
|
|
715
|
+
this._logger.push({ err }).log('Error saving transfer model');
|
|
716
|
+
throw err;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Loads a transfer model from cache for resumption of the transfer process
|
|
723
|
+
*
|
|
724
|
+
* @param transferId {string} - UUID transferId of the model to load from cache
|
|
725
|
+
*/
|
|
726
|
+
async load(transferId) {
|
|
727
|
+
try {
|
|
728
|
+
const data = await this._cache.get(`transferModel_${transferId}`);
|
|
729
|
+
if(!data) {
|
|
730
|
+
throw new Error(`No cached data found for transferId: ${transferId}`);
|
|
731
|
+
}
|
|
732
|
+
await this.initialize(data);
|
|
733
|
+
this._logger.push({ cache: this.data }).log('Transfer model loaded from cached state');
|
|
734
|
+
}
|
|
735
|
+
catch(err) {
|
|
736
|
+
this._logger.push({ err }).log('Error loading transfer model');
|
|
737
|
+
throw err;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Returns a promise that resolves when the state machine has reached a terminal state
|
|
744
|
+
*/
|
|
745
|
+
async run() {
|
|
746
|
+
try {
|
|
747
|
+
// run transitions based on incoming state
|
|
748
|
+
switch(this.data.currentState) {
|
|
749
|
+
case 'start':
|
|
750
|
+
// next transition is to resolvePayee
|
|
751
|
+
await this.stateMachine.resolvePayee();
|
|
752
|
+
this._logger.log(`Payee resolved for transfer ${this.data.transferId}`);
|
|
753
|
+
if(this.stateMachine.state === 'payeeResolved' && !this._autoAcceptParty) {
|
|
754
|
+
//we break execution here and return the resolved party details to allow asynchronous accept or reject
|
|
755
|
+
//of the resolved party
|
|
756
|
+
await this._save();
|
|
757
|
+
return this.getResponse();
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
760
|
+
|
|
761
|
+
case 'payeeResolved':
|
|
762
|
+
// next transition is to requestQuote
|
|
763
|
+
await this.stateMachine.requestQuote();
|
|
764
|
+
this._logger.log(`Quote received for transfer ${this.data.transferId}`);
|
|
765
|
+
if(this.stateMachine.state === 'quoteReceived' && !this._autoAcceptQuotes) {
|
|
766
|
+
//we break execution here and return the quote response details to allow asynchronous accept or reject
|
|
767
|
+
//of the quote
|
|
768
|
+
await this._save();
|
|
769
|
+
return this.getResponse();
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
|
|
773
|
+
case 'quoteReceived':
|
|
774
|
+
// next transition is executeTransfer
|
|
775
|
+
await this.stateMachine.executeTransfer();
|
|
776
|
+
this._logger.log(`Transfer ${this.data.transferId} has been completed`);
|
|
777
|
+
break;
|
|
778
|
+
|
|
779
|
+
case 'getTransfer':
|
|
780
|
+
await this.stateMachine.getTransfer();
|
|
781
|
+
this._logger.log(`Get transfer ${this.data.transferId} has been completed`);
|
|
782
|
+
break;
|
|
783
|
+
|
|
784
|
+
case 'succeeded':
|
|
785
|
+
// all steps complete so return
|
|
786
|
+
this._logger.log('Transfer completed successfully');
|
|
787
|
+
await this._save();
|
|
788
|
+
return this.getResponse();
|
|
789
|
+
|
|
790
|
+
case 'errored':
|
|
791
|
+
// stopped in errored state
|
|
792
|
+
await this._save();
|
|
793
|
+
this._logger.log('State machine in errored state');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// now call ourslves recursively to deal with the next transition
|
|
798
|
+
this._logger.log(`Transfer model state machine transition completed in state: ${this.stateMachine.state}. Recusring to handle next transition.`);
|
|
799
|
+
return this.run();
|
|
800
|
+
}
|
|
801
|
+
catch(err) {
|
|
802
|
+
this._logger.log(`Error running transfer model: ${util.inspect(err)}`);
|
|
803
|
+
|
|
804
|
+
// as this function is recursive, we dont want to error the state machine multiple times
|
|
805
|
+
if(this.data.currentState !== 'errored') {
|
|
806
|
+
// err should not have a transferState property here!
|
|
807
|
+
if(err.transferState) {
|
|
808
|
+
this._logger.log(`State machine is broken: ${util.inspect(err)}`);
|
|
809
|
+
}
|
|
810
|
+
// transition to errored state
|
|
811
|
+
await this.stateMachine.error(err);
|
|
812
|
+
|
|
813
|
+
// avoid circular ref between transferState.lastError and err
|
|
814
|
+
err.transferState = JSON.parse(JSON.stringify(this.getResponse()));
|
|
815
|
+
await this._save();
|
|
816
|
+
}
|
|
817
|
+
throw err;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
module.exports = OutboundTransfersModel;
|