@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.
Files changed (277) hide show
  1. package/.env.example +140 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc.json +30 -0
  4. package/.nvmrc +1 -0
  5. package/.versionrc +15 -0
  6. package/CHANGELOG.md +118 -0
  7. package/InboundServer/api.yaml +3594 -0
  8. package/InboundServer/api_template.yaml +69 -0
  9. package/InboundServer/handlers.js +940 -0
  10. package/InboundServer/index.js +205 -0
  11. package/InboundServer/middlewares.js +426 -0
  12. package/OAuthTestServer/index.js +66 -0
  13. package/OAuthTestServer/model.js +70 -0
  14. package/OutboundServer/api.yaml +2732 -0
  15. package/OutboundServer/api_interfaces/index.d.ts +117 -0
  16. package/OutboundServer/api_interfaces/openapi.d.ts +1475 -0
  17. package/OutboundServer/api_template/components/parameters/bulkQuoteId.yaml +9 -0
  18. package/OutboundServer/api_template/components/parameters/bulkTransferId.yaml +9 -0
  19. package/OutboundServer/api_template/components/parameters/requestToPayTransactionId.yaml +9 -0
  20. package/OutboundServer/api_template/components/parameters/transferId.yaml +9 -0
  21. package/OutboundServer/api_template/components/responses/accountsCreationCompleted.yaml +5 -0
  22. package/OutboundServer/api_template/components/responses/accountsCreationError.yaml +5 -0
  23. package/OutboundServer/api_template/components/responses/accountsCreationTimeout.yaml +5 -0
  24. package/OutboundServer/api_template/components/responses/authorizationPostSuccess.yaml +5 -0
  25. package/OutboundServer/api_template/components/responses/authorizationsServerError.yaml +5 -0
  26. package/OutboundServer/api_template/components/responses/bulkQuoteBadRequest.yaml +5 -0
  27. package/OutboundServer/api_template/components/responses/bulkQuoteServerError.yaml +5 -0
  28. package/OutboundServer/api_template/components/responses/bulkQuoteSuccess.yaml +5 -0
  29. package/OutboundServer/api_template/components/responses/bulkQuoteTimeout.yaml +5 -0
  30. package/OutboundServer/api_template/components/responses/bulkTransferBadRequest.yaml +5 -0
  31. package/OutboundServer/api_template/components/responses/bulkTransferServerError.yaml +5 -0
  32. package/OutboundServer/api_template/components/responses/bulkTransferSuccess.yaml +5 -0
  33. package/OutboundServer/api_template/components/responses/bulkTransferTimeout.yaml +5 -0
  34. package/OutboundServer/api_template/components/responses/partiesByIdError404.yaml +9 -0
  35. package/OutboundServer/api_template/components/responses/partiesByIdSuccess.yaml +5 -0
  36. package/OutboundServer/api_template/components/responses/quotesPostSuccess.yaml +5 -0
  37. package/OutboundServer/api_template/components/responses/quotesServerError.yaml +5 -0
  38. package/OutboundServer/api_template/components/responses/requestToPaySuccess.yaml +5 -0
  39. package/OutboundServer/api_template/components/responses/requestToPayTransferBadRequest.yaml +5 -0
  40. package/OutboundServer/api_template/components/responses/requestToPayTransferSuccess.yaml +5 -0
  41. package/OutboundServer/api_template/components/responses/simpleTransfersPostSuccess.yaml +5 -0
  42. package/OutboundServer/api_template/components/responses/simpleTransfersServerError.yaml +5 -0
  43. package/OutboundServer/api_template/components/responses/transferBadRequest.yaml +5 -0
  44. package/OutboundServer/api_template/components/responses/transferServerError.yaml +5 -0
  45. package/OutboundServer/api_template/components/responses/transferSuccess.yaml +5 -0
  46. package/OutboundServer/api_template/components/responses/transferTimeout.yaml +5 -0
  47. package/OutboundServer/api_template/components/schemas/accountCreationStatus.yaml +18 -0
  48. package/OutboundServer/api_template/components/schemas/accountsCreationState.yaml +4 -0
  49. package/OutboundServer/api_template/components/schemas/accountsRequest.yaml +20 -0
  50. package/OutboundServer/api_template/components/schemas/accountsResponse.yaml +15 -0
  51. package/OutboundServer/api_template/components/schemas/async2SyncCurrentState.yaml +5 -0
  52. package/OutboundServer/api_template/components/schemas/authorizationsPostRequest.yaml +15 -0
  53. package/OutboundServer/api_template/components/schemas/authorizationsPostResponse.yaml +19 -0
  54. package/OutboundServer/api_template/components/schemas/bulkQuoteErrorResponse.yaml +8 -0
  55. package/OutboundServer/api_template/components/schemas/bulkQuoteRequest.yaml +26 -0
  56. package/OutboundServer/api_template/components/schemas/bulkQuoteResponse.yaml +21 -0
  57. package/OutboundServer/api_template/components/schemas/bulkQuoteStatus.yaml +4 -0
  58. package/OutboundServer/api_template/components/schemas/bulkQuoteStatusResponse.yaml +17 -0
  59. package/OutboundServer/api_template/components/schemas/bulkTransferErrorResponse.yaml +8 -0
  60. package/OutboundServer/api_template/components/schemas/bulkTransferRequest.yaml +26 -0
  61. package/OutboundServer/api_template/components/schemas/bulkTransferResponse.yaml +16 -0
  62. package/OutboundServer/api_template/components/schemas/bulkTransferStatus.yaml +4 -0
  63. package/OutboundServer/api_template/components/schemas/bulkTransferStatusResponse.yaml +17 -0
  64. package/OutboundServer/api_template/components/schemas/errorAccountsResponse.yaml +8 -0
  65. package/OutboundServer/api_template/components/schemas/errorAuthorizationsResponse.yaml +3 -0
  66. package/OutboundServer/api_template/components/schemas/errorQuotesResponse.yaml +9 -0
  67. package/OutboundServer/api_template/components/schemas/errorResponse.yaml +8 -0
  68. package/OutboundServer/api_template/components/schemas/errorSimpleTransfersResponse.yaml +3 -0
  69. package/OutboundServer/api_template/components/schemas/errorTransferResponse.yaml +8 -0
  70. package/OutboundServer/api_template/components/schemas/extensionListEmptiable.yaml +6 -0
  71. package/OutboundServer/api_template/components/schemas/individualQuote.yaml +32 -0
  72. package/OutboundServer/api_template/components/schemas/individualQuoteResult.yaml +28 -0
  73. package/OutboundServer/api_template/components/schemas/individualTransfer.yaml +32 -0
  74. package/OutboundServer/api_template/components/schemas/individualTransferFulfilment.yaml +13 -0
  75. package/OutboundServer/api_template/components/schemas/individualTransferResult.yaml +41 -0
  76. package/OutboundServer/api_template/components/schemas/mojaloopError.yaml +5 -0
  77. package/OutboundServer/api_template/components/schemas/mojaloopTransactionRequestState.yaml +2 -0
  78. package/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +13 -0
  79. package/OutboundServer/api_template/components/schemas/quote.yaml +3 -0
  80. package/OutboundServer/api_template/components/schemas/quoteError.yaml +16 -0
  81. package/OutboundServer/api_template/components/schemas/quotesPostRequest.yaml +13 -0
  82. package/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +48 -0
  83. package/OutboundServer/api_template/components/schemas/requestToPayRequest.yaml +39 -0
  84. package/OutboundServer/api_template/components/schemas/requestToPayResponse.yaml +41 -0
  85. package/OutboundServer/api_template/components/schemas/requestToPayTransferRequest.yaml +42 -0
  86. package/OutboundServer/api_template/components/schemas/requestToPayTransferResponse.yaml +58 -0
  87. package/OutboundServer/api_template/components/schemas/simpleTransferServerError.yaml +5 -0
  88. package/OutboundServer/api_template/components/schemas/simpleTransfersPostRequest.yaml +12 -0
  89. package/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +11 -0
  90. package/OutboundServer/api_template/components/schemas/transactionType.yaml +4 -0
  91. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptOTP.yaml +9 -0
  92. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptParty.yaml +8 -0
  93. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptQuote.yaml +9 -0
  94. package/OutboundServer/api_template/components/schemas/transferError.yaml +16 -0
  95. package/OutboundServer/api_template/components/schemas/transferFulfilment.yaml +3 -0
  96. package/OutboundServer/api_template/components/schemas/transferParty.yaml +40 -0
  97. package/OutboundServer/api_template/components/schemas/transferRequest.yaml +37 -0
  98. package/OutboundServer/api_template/components/schemas/transferResponse.yaml +58 -0
  99. package/OutboundServer/api_template/components/schemas/transferStatus.yaml +6 -0
  100. package/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +13 -0
  101. package/OutboundServer/api_template/health.yaml +12 -0
  102. package/OutboundServer/api_template/openapi.yaml +55 -0
  103. package/OutboundServer/api_template/paths/accounts.yaml +26 -0
  104. package/OutboundServer/api_template/paths/authorizations.yaml +19 -0
  105. package/OutboundServer/api_template/paths/bulkQuotes.yaml +23 -0
  106. package/OutboundServer/api_template/paths/bulkQuotes_bulkQuoteId.yaml +24 -0
  107. package/OutboundServer/api_template/paths/bulkTransfers.yaml +23 -0
  108. package/OutboundServer/api_template/paths/bulkTransfers_bulkTransferId.yaml +24 -0
  109. package/OutboundServer/api_template/paths/parties_Type_ID.yaml +20 -0
  110. package/OutboundServer/api_template/paths/parties_Type_ID_SubId.yaml +22 -0
  111. package/OutboundServer/api_template/paths/quotes.yaml +20 -0
  112. package/OutboundServer/api_template/paths/requestToPay.yaml +22 -0
  113. package/OutboundServer/api_template/paths/requestToPayTransfer.yaml +57 -0
  114. package/OutboundServer/api_template/paths/requestToPayTransfer_requestToPayTransactionId.yaml +34 -0
  115. package/OutboundServer/api_template/paths/simpleTransfers.yaml +19 -0
  116. package/OutboundServer/api_template/paths/transfers.yaml +55 -0
  117. package/OutboundServer/api_template/paths/transfers_transferId.yaml +58 -0
  118. package/OutboundServer/handlers.js +622 -0
  119. package/OutboundServer/index.js +137 -0
  120. package/OutboundServer/middlewares.js +67 -0
  121. package/TestServer/api.yaml +62 -0
  122. package/TestServer/handlers.js +63 -0
  123. package/TestServer/index.js +215 -0
  124. package/audit-resolve.json +65 -0
  125. package/babel.config.js +3 -0
  126. package/config.js +158 -0
  127. package/index.d.ts +1 -0
  128. package/index.js +149 -0
  129. package/jest.config.js +15 -0
  130. package/lib/api/index.js +12 -0
  131. package/lib/cache.js +352 -0
  132. package/lib/check.js +25 -0
  133. package/lib/model/AccountsModel.js +396 -0
  134. package/lib/model/Async2SyncModel.js +283 -0
  135. package/lib/model/AuthorizationsModel.js +86 -0
  136. package/lib/model/InboundTransfersModel.js +730 -0
  137. package/lib/model/OutboundBulkQuotesModel.js +485 -0
  138. package/lib/model/OutboundBulkTransfersModel.js +479 -0
  139. package/lib/model/OutboundRequestToPayModel.js +517 -0
  140. package/lib/model/OutboundRequestToPayTransferModel.js +893 -0
  141. package/lib/model/OutboundTransfersModel.js +823 -0
  142. package/lib/model/PartiesModel.js +70 -0
  143. package/lib/model/ProxyModel/MatchRules/Expression.js +48 -0
  144. package/lib/model/ProxyModel/MatchRules/Headers.js +65 -0
  145. package/lib/model/ProxyModel/MatchRules/MatchRule.js +27 -0
  146. package/lib/model/ProxyModel/MatchRules/Path.js +36 -0
  147. package/lib/model/ProxyModel/MatchRules/Query.js +65 -0
  148. package/lib/model/ProxyModel/MatchRules/index.js +19 -0
  149. package/lib/model/ProxyModel/Route.js +82 -0
  150. package/lib/model/ProxyModel/configSchema.json +118 -0
  151. package/lib/model/ProxyModel/index.js +138 -0
  152. package/lib/model/QuotesModel.js +94 -0
  153. package/lib/model/TransfersModel.js +81 -0
  154. package/lib/model/common/BackendError.js +26 -0
  155. package/lib/model/common/PersistentStateMachine.js +93 -0
  156. package/lib/model/common/index.js +18 -0
  157. package/lib/model/index.js +43 -0
  158. package/lib/model/lib/deferredJob.js +113 -0
  159. package/lib/model/lib/index.js +9 -0
  160. package/lib/model/lib/requests/backendRequests.js +227 -0
  161. package/lib/model/lib/requests/common.js +76 -0
  162. package/lib/model/lib/requests/index.js +19 -0
  163. package/lib/model/lib/shared.js +468 -0
  164. package/lib/randomphrase/index.js +21 -0
  165. package/lib/randomphrase/words.json +3397 -0
  166. package/lib/router.js +28 -0
  167. package/lib/validate.js +205 -0
  168. package/package.json +102 -0
  169. package/test/__mocks__/@mojaloop/sdk-standard-components.js +152 -0
  170. package/test/__mocks__/javascript-state-machine.js +21 -0
  171. package/test/__mocks__/redis.js +49 -0
  172. package/test/__mocks__/uuidv4.js +16 -0
  173. package/test/config/integration.env +136 -0
  174. package/test/integration/lib/Outbound/authorizations.test.js +58 -0
  175. package/test/integration/lib/Outbound/data/authorizationsPostRequest.json +43 -0
  176. package/test/integration/lib/Outbound/data/quotesPostRequest.json +52 -0
  177. package/test/integration/lib/Outbound/data/transfersPostRequest.json +24 -0
  178. package/test/integration/lib/Outbound/parties.test.js +28 -0
  179. package/test/integration/lib/Outbound/quotes.test.js +58 -0
  180. package/test/integration/lib/Outbound/simpleTransfers.test.js +67 -0
  181. package/test/integration/lib/cache.test.js +80 -0
  182. package/test/integration/testEnv.js +7 -0
  183. package/test/unit/InboundServer.test.js +443 -0
  184. package/test/unit/TestServer.test.js +394 -0
  185. package/test/unit/api/accounts/accounts.test.js +128 -0
  186. package/test/unit/api/accounts/data/postAccountsBody.json +7 -0
  187. package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +25 -0
  188. package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +19 -0
  189. package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +17 -0
  190. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +21 -0
  191. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +21 -0
  192. package/test/unit/api/accounts/utils.js +65 -0
  193. package/test/unit/api/proxy/data/proxyConfig.yaml +82 -0
  194. package/test/unit/api/proxy/data/requestBody.json +22 -0
  195. package/test/unit/api/proxy/data/requestHeaders.json +5 -0
  196. package/test/unit/api/proxy/data/requestQuery.json +6 -0
  197. package/test/unit/api/proxy/data/responseBody.json +21 -0
  198. package/test/unit/api/proxy/data/responseHeaders.json +5 -0
  199. package/test/unit/api/proxy/proxy.test.js +220 -0
  200. package/test/unit/api/proxy/utils.js +79 -0
  201. package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +21 -0
  202. package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +17 -0
  203. package/test/unit/api/transfers/data/postQuotesBody.json +52 -0
  204. package/test/unit/api/transfers/data/postTransfersBadBody.json +17 -0
  205. package/test/unit/api/transfers/data/postTransfersBody.json +24 -0
  206. package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +53 -0
  207. package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +47 -0
  208. package/test/unit/api/transfers/data/postTransfersSimpleBody.json +26 -0
  209. package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +101 -0
  210. package/test/unit/api/transfers/data/putPartiesBody.json +20 -0
  211. package/test/unit/api/transfers/data/putQuotesBody.json +37 -0
  212. package/test/unit/api/transfers/data/putTransfersBody.json +17 -0
  213. package/test/unit/api/transfers/transfers.test.js +191 -0
  214. package/test/unit/api/transfers/utils.js +183 -0
  215. package/test/unit/api/utils.js +75 -0
  216. package/test/unit/config.test.js +119 -0
  217. package/test/unit/data/commonHttpHeaders.json +6 -0
  218. package/test/unit/data/defaultConfig.json +58 -0
  219. package/test/unit/data/postQuotesBody.json +52 -0
  220. package/test/unit/data/putParticipantsBody.json +12 -0
  221. package/test/unit/data/putPartiesBody.json +20 -0
  222. package/test/unit/data/testFile.json +29 -0
  223. package/test/unit/data/testFile.yaml +14 -0
  224. package/test/unit/inboundApi/data/mockArguments.json +117 -0
  225. package/test/unit/inboundApi/data/mockTransactionRequest.json +42 -0
  226. package/test/unit/inboundApi/handlers.test.js +799 -0
  227. package/test/unit/index.test.js +55 -0
  228. package/test/unit/lib/cache.test.js +146 -0
  229. package/test/unit/lib/model/AccountsModel.test.js +121 -0
  230. package/test/unit/lib/model/AuthorizationsModel.test.js +460 -0
  231. package/test/unit/lib/model/InboundTransfersModel.test.js +628 -0
  232. package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +249 -0
  233. package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +244 -0
  234. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +166 -0
  235. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +245 -0
  236. package/test/unit/lib/model/OutboundTransfersModel.test.js +836 -0
  237. package/test/unit/lib/model/PartiesModel.test.js +468 -0
  238. package/test/unit/lib/model/QuotesModel.test.js +470 -0
  239. package/test/unit/lib/model/TransfersModel.test.js +474 -0
  240. package/test/unit/lib/model/common/PersistentStateMachine.test.js +179 -0
  241. package/test/unit/lib/model/data/authorizationsResponse.json +13 -0
  242. package/test/unit/lib/model/data/bulkQuoteRequest.json +27 -0
  243. package/test/unit/lib/model/data/bulkQuoteResponse.json +35 -0
  244. package/test/unit/lib/model/data/bulkTransferFulfil.json +13 -0
  245. package/test/unit/lib/model/data/bulkTransferRequest.json +29 -0
  246. package/test/unit/lib/model/data/defaultConfig.json +47 -0
  247. package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +42 -0
  248. package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +22 -0
  249. package/test/unit/lib/model/data/getTransfersBackendResponse.json +34 -0
  250. package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +17 -0
  251. package/test/unit/lib/model/data/mockArguments.json +131 -0
  252. package/test/unit/lib/model/data/mockTxnRequestsArguments.json +63 -0
  253. package/test/unit/lib/model/data/notificationToPayee.json +10 -0
  254. package/test/unit/lib/model/data/payeeParty.json +16 -0
  255. package/test/unit/lib/model/data/putAuthorizationsResponse.json +10 -0
  256. package/test/unit/lib/model/data/putQuotesResponse.json +33 -0
  257. package/test/unit/lib/model/data/putTransfersResponse.json +5 -0
  258. package/test/unit/lib/model/data/quoteResponse.json +31 -0
  259. package/test/unit/lib/model/data/requestToPayRequest.json +20 -0
  260. package/test/unit/lib/model/data/requestToPayTransferRequest.json +27 -0
  261. package/test/unit/lib/model/data/transactionRequestResponse.json +18 -0
  262. package/test/unit/lib/model/data/transferFulfil.json +8 -0
  263. package/test/unit/lib/model/data/transferRequest.json +26 -0
  264. package/test/unit/lib/model/mockedLibRequests.js +74 -0
  265. package/test/unit/mockLogger.js +39 -0
  266. package/test/unit/outboundApi/data/bulkQuoteRequest.json +28 -0
  267. package/test/unit/outboundApi/data/bulkTransferRequest.json +28 -0
  268. package/test/unit/outboundApi/data/mockBulkQuoteError.json +45 -0
  269. package/test/unit/outboundApi/data/mockBulkTransferError.json +48 -0
  270. package/test/unit/outboundApi/data/mockError.json +41 -0
  271. package/test/unit/outboundApi/data/mockGetPartiesError.json +4 -0
  272. package/test/unit/outboundApi/data/mockRequestToPayError.json +32 -0
  273. package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +39 -0
  274. package/test/unit/outboundApi/data/requestToPay.json +21 -0
  275. package/test/unit/outboundApi/data/requestToPayTransferRequest.json +20 -0
  276. package/test/unit/outboundApi/data/transferRequest.json +21 -0
  277. 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;