@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,893 @@
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 requestToPayTransferStateEnum = {
22
+ 'WAITING_FOR_QUOTE_ACCEPTANCE': 'WAITING_FOR_QUOTE_ACCEPTANCE',
23
+ 'WAITING_FOR_OTP_ACCEPTANCE': 'WAITING_FOR_OTP_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 OutboundRequestToPayTransferModel {
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._autoAcceptR2PBusinessQuotes = config.autoAcceptR2PBusinessQuotes;
43
+ this._autoAcceptR2PDeviceQuotes = config.autoAcceptR2PDeviceQuotes;
44
+ this._autoAcceptR2PDeviceOTP = config.autoAcceptR2PDeviceOTP;
45
+ this._useQuoteSourceFSPAsTransferPayeeFSP = config.useQuoteSourceFSPAsTransferPayeeFSP;
46
+ this._checkIlp = config.checkIlp;
47
+
48
+ this._requests = new MojaloopRequests({
49
+ logger: this._logger,
50
+ peerEndpoint: config.peerEndpoint,
51
+ quotesEndpoint: config.quotesEndpoint,
52
+ authorizationsEndpoint: config.authorizationsEndpoint,
53
+ transfersEndpoint: config.transfersEndpoint,
54
+ dfspId: config.dfspId,
55
+ tls: config.tls,
56
+ jwsSign: config.jwsSign,
57
+ jwsSignPutParties: config.jwsSignPutParties,
58
+ jwsSigningKey: config.jwsSigningKey,
59
+ wso2: config.wso2,
60
+ });
61
+
62
+ this._ilp = new Ilp({
63
+ secret: config.ilpSecret,
64
+ logger: this._logger,
65
+ });
66
+ }
67
+ /**
68
+ * Initializes the requestToPayTransfer model
69
+ *
70
+ * @param data {object} - The inbound API POST /requestToPayTransfer request body
71
+ */
72
+ async initialize(data) {
73
+ this.data = data;
74
+
75
+ // add a transferId if one is not present e.g. on first submission
76
+ if(!this.data.hasOwnProperty('transferId')) {
77
+ this.data.transferId = uuid();
78
+ }
79
+
80
+ // initialize the transfer state machine to its starting state
81
+ if(!this.data.hasOwnProperty('currentState')) {
82
+ this.data.currentState = 'start';
83
+ }
84
+
85
+ this._initStateMachine(this.data.currentState);
86
+ }
87
+
88
+
89
+ /**
90
+ * Initializes the internal state machine object
91
+ */
92
+ _initStateMachine (initState) {
93
+ this.stateMachine = new StateMachine({
94
+ init: initState,
95
+ transitions: [
96
+ { name: 'requestQuote', from: 'start', to: 'quoteReceived' },
97
+ { name: 'requestOTP', from: 'quoteReceived', to: 'otpReceived' },
98
+ { name: 'executeTransfer', from: 'otpReceived', to: 'succeeded' },
99
+ { name: 'error', from: '*', to: 'errored' },
100
+ ],
101
+ methods: {
102
+ onTransition: this._handleTransition.bind(this),
103
+ onAfterTransition: this._afterTransition.bind(this),
104
+ onPendingTransition: (transition, from, to) => {
105
+ // allow transitions to 'error' state while other transitions are in progress
106
+ if(transition !== 'error') {
107
+ throw new Error(`Transition requested while another transition is in progress: ${transition} from: ${from} to: ${to}`);
108
+ }
109
+ }
110
+ }
111
+ });
112
+
113
+ return this.stateMachine[initState];
114
+ }
115
+
116
+ /**
117
+ * Returns a promise that resolves when the state machine has reached a terminal state
118
+ */
119
+ async run() {
120
+ try {
121
+ // run transitions based on incoming state
122
+ switch(this.data.currentState) {
123
+ case 'start':
124
+ // next transition is to requestQuote
125
+ await this.stateMachine.requestQuote();
126
+ this._logger.log(`Quote received for transfer ${this.data.transferId}`);
127
+ if(this.stateMachine.state === 'quoteReceived' && this.data.initiatorType === 'BUSINESS' && !this._autoAcceptR2PBusinessQuotes) {
128
+ //we break execution here and return the quote response details to allow asynchronous accept or reject
129
+ //of the quote
130
+ await this._save();
131
+ return this.getResponse();
132
+ }
133
+ break;
134
+
135
+ case 'quoteReceived':
136
+ // next transition is requestOTP
137
+ await this.stateMachine.requestOTP();
138
+ if(this.data.initiatorType !== 'BUSINESS') {
139
+ this._logger.log(`OTP received for transactionId: ${this.data.requestToPayTransactionId} and transferId: ${this.data.transferId}`);
140
+ if(this.stateMachine.state === 'otpReceived' && !this._autoAcceptR2PDeviceOTP) {
141
+ //we break execution here and return the otp response details to allow asynchronous accept or reject
142
+ //of the quote
143
+ await this._save();
144
+ return this.getResponse();
145
+ }
146
+ }
147
+ break;
148
+
149
+ case 'otpReceived':
150
+ // next transition is executeTransfer
151
+ await this.stateMachine.executeTransfer();
152
+ this._logger.log(`Transfer ${this.data.transferId} has been completed`);
153
+ break;
154
+
155
+ case 'succeeded':
156
+ // all steps complete so return
157
+ this._logger.log('Transfer completed successfully');
158
+ await this._save();
159
+ return this.getResponse();
160
+
161
+ case 'errored':
162
+ // stopped in errored state
163
+ this._logger.log('State machine in errored state');
164
+ return;
165
+ }
166
+
167
+ // now call ourslves recursively to deal with the next transition
168
+ this._logger.log(`RequestToPay Transfer model state machine transition completed in state: ${this.stateMachine.state}. Recusring to handle next transition.`);
169
+ return this.run();
170
+ }
171
+ catch(err) {
172
+ this._logger.log(`Error running transfer model: ${util.inspect(err)}`);
173
+
174
+ // as this function is recursive, we dont want to error the state machine multiple times
175
+ if(this.data.currentState !== 'errored') {
176
+ // err should not have a transferState property here!
177
+ if(err.transferState) {
178
+ this._logger.log(`State machine is broken: ${util.inspect(err)}`);
179
+ }
180
+ // transition to errored state
181
+ await this.stateMachine.error(err);
182
+
183
+ // avoid circular ref between transferState.lastError and err
184
+ err.transferState = JSON.parse(JSON.stringify(this.getResponse()));
185
+ }
186
+ throw err;
187
+ }
188
+ }
189
+
190
+
191
+ /**
192
+ * Updates the internal state representation to reflect that of the state machine itself
193
+ */
194
+ _afterTransition() {
195
+ this._logger.log(`State machine transitioned: ${this.data.currentState} -> ${this.stateMachine.state}`);
196
+ this.data.currentState = this.stateMachine.state;
197
+ }
198
+
199
+ /**
200
+ * Handles state machine transitions
201
+ */
202
+ async _handleTransition(lifecycle, ...args) {
203
+ this._logger.log(`Transfer ${this.data.transferId} is transitioning from ${lifecycle.from} to ${lifecycle.to} in response to ${lifecycle.transition}`);
204
+
205
+ switch(lifecycle.transition) {
206
+ case 'init':
207
+ // init, just allow the fsm to start
208
+ return;
209
+
210
+ case 'requestQuote':
211
+ // request a quote
212
+ return this._requestQuote();
213
+
214
+ case 'requestOTP':
215
+ // request an OTP
216
+ return this._requestOTP();
217
+
218
+ case 'executeTransfer':
219
+ // prepare a transfer and wait for fulfillment
220
+ return this._executeTransfer();
221
+
222
+ case 'error':
223
+ this._logger.log(`State machine is erroring with error: ${util.inspect(args)}`);
224
+ this.data.lastError = args[0] || new Error('unspecified error');
225
+ break;
226
+
227
+ default:
228
+ throw new Error(`Unhandled state transition for transfer ${this.data.transferId}: ${util.inspect(args)}`);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * This method is used to communicate back to the Payee that a rejection is being
234
+ * sent because the OTP did not match.
235
+ */
236
+ async rejectRequestToPay() {
237
+ const authResponse = {
238
+ responseType: 'REJECTED'
239
+ };
240
+ await this._requests.putAuthorizations(this.data.requestToPayTransactionId,JSON.stringify(authResponse),this.data.to.fspId);
241
+ const response = {
242
+ status : `${this.data.requestToPayTransactionId} has been REJECTED`
243
+ };
244
+ return JSON.stringify(response);
245
+ }
246
+
247
+
248
+ /**
249
+ * Resolves the payee.
250
+ * Starts the payee resolution process by sending a GET /parties request to the switch;
251
+ * then waits for a notification from the cache that the payee has been resolved.
252
+ */
253
+ async _resolvePayee() {
254
+ // eslint-disable-next-line no-async-promise-executor
255
+ return new Promise(async (resolve, reject) => {
256
+ // listen for resolution events on the payee idType and idValue
257
+ const payeeKey = PartiesModel.channelName({
258
+ type: this.data.to.idType,
259
+ id: this.data.to.idValue,
260
+ subId: this.data.to.idSubValue
261
+ });
262
+
263
+ // hook up a subscriber to handle response messages
264
+ const subId = await this._cache.subscribe(payeeKey, (cn, msg, subId) => {
265
+ try {
266
+ let payee = JSON.parse(msg);
267
+
268
+ if(payee.errorInformation) {
269
+ // this is an error response to our GET /parties request
270
+ const err = new BackendError(`Got an error response resolving party: ${util.inspect(payee)}`, 500);
271
+ err.mojaloopError = payee;
272
+
273
+ // cancel the timeout handler
274
+ clearTimeout(timeout);
275
+ return reject(err);
276
+ }
277
+
278
+ if(!payee.party) {
279
+ // we should never get a non-error response without a party, but just in case...
280
+ // cancel the timeout handler
281
+ clearTimeout(timeout);
282
+ return reject(new Error(`Resolved payee has no party object: ${util.inspect(payee)}`));
283
+ }
284
+
285
+ payee = payee.party;
286
+
287
+ // cancel the timeout handler
288
+ clearTimeout(timeout);
289
+
290
+ this._logger.push({ payee }).log('Payee resolved');
291
+
292
+ // stop listening for payee resolution messages
293
+ // no need to await for the unsubscribe to complete.
294
+ // we dont really care if the unsubscribe fails but we should log it regardless
295
+ this._cache.unsubscribe(payeeKey, subId).catch(e => {
296
+ this._logger.log(`Error unsubscribing (in callback) ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
297
+ });
298
+
299
+ // check we got the right payee and info we need
300
+ if(payee.partyIdInfo.partyIdType !== this.data.to.idType) {
301
+ const err = new Error(`Expecting resolved payee party IdType to be ${this.data.to.idType} but got ${payee.partyIdInfo.partyIdType}`);
302
+ return reject(err);
303
+ }
304
+
305
+ if(payee.partyIdInfo.partyIdentifier !== this.data.to.idValue) {
306
+ const err = new Error(`Expecting resolved payee party identifier to be ${this.data.to.idValue} but got ${payee.partyIdInfo.partyIdentifier}`);
307
+ return reject(err);
308
+ }
309
+
310
+ if(payee.partyIdInfo.partySubIdOrType !== this.data.to.idSubValue) {
311
+ const err = new Error(`Expecting resolved payee party subTypeId to be ${this.data.to.idSubValue} but got ${payee.partyIdInfo.partySubIdOrType}`);
312
+ return reject(err);
313
+ }
314
+
315
+ if(!payee.partyIdInfo.fspId) {
316
+ const err = new Error(`Expecting resolved payee party to have an FSPID: ${util.inspect(payee.partyIdInfo)}`);
317
+ return reject(err);
318
+ }
319
+
320
+ // now we got the payee, add the details to our data so we can use it
321
+ // in the quote request
322
+ this.data.to.fspId = payee.partyIdInfo.fspId;
323
+
324
+ if(payee.personalInfo) {
325
+ if(payee.personalInfo.complexName) {
326
+ this.data.to.firstName = payee.personalInfo.complexName.firstName || this.data.to.firstName;
327
+ this.data.to.middleName = payee.personalInfo.complexName.middleName || this.data.to.middleName;
328
+ this.data.to.lastName = payee.personalInfo.complexName.lastName || this.data.to.lastName;
329
+ }
330
+ this.data.to.dateOfBirth = payee.personalInfo.dateOfBirth;
331
+ }
332
+
333
+ return resolve(payee);
334
+ }
335
+ catch(err) {
336
+ return reject(err);
337
+ }
338
+ });
339
+
340
+ // set up a timeout for the resolution
341
+ const timeout = setTimeout(() => {
342
+ const err = new BackendError(`Timeout resolving payee for transfer ${this.data.transferId}`, 504);
343
+
344
+ // we dont really care if the unsubscribe fails but we should log it regardless
345
+ this._cache.unsubscribe(payeeKey, subId).catch(e => {
346
+ this._logger.log(`Error unsubscribing (in timeout handler) ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
347
+ });
348
+
349
+ return reject(err);
350
+ }, this._requestProcessingTimeoutSeconds * 1000);
351
+
352
+ // now we have a timeout handler and a cache subscriber hooked up we can fire off
353
+ // a GET /parties request to the switch
354
+ try {
355
+ const res = await this._requests.getParties(this.data.to.idType, this.data.to.idValue,
356
+ this.data.to.idSubValue);
357
+ this._logger.push({ peer: res }).log('Party lookup sent to peer');
358
+ }
359
+ catch(err) {
360
+ // cancel the timout and unsubscribe before rejecting the promise
361
+ clearTimeout(timeout);
362
+
363
+ // we dont really care if the unsubscribe fails but we should log it regardless
364
+ this._cache.unsubscribe(payeeKey, subId).catch(e => {
365
+ this._logger.log(`Error unsubscribing ${payeeKey} ${subId}: ${e.stack || util.inspect(e)}`);
366
+ });
367
+
368
+ return reject(err);
369
+ }
370
+ });
371
+ }
372
+
373
+
374
+ /**
375
+ * Requests a quote
376
+ * Starts the quote resolution process by sending a POST /quotes request to the switch;
377
+ * then waits for a notification from the cache that the quote response has been received
378
+ */
379
+ async _requestQuote() {
380
+ // eslint-disable-next-line no-async-promise-executor
381
+ return new Promise(async (resolve, reject) => {
382
+ // create a quote request
383
+ const quote = this._buildQuoteRequest();
384
+ this.data.quoteId = quote.quoteId;
385
+
386
+ // listen for events on the quoteId
387
+ const quoteKey = `qt_${quote.quoteId}`;
388
+
389
+ // hook up a subscriber to handle response messages
390
+ const subId = await this._cache.subscribe(quoteKey, (cn, msg, subId) => {
391
+ try {
392
+ let error;
393
+ let message = JSON.parse(msg);
394
+
395
+ if (message.type === 'quoteResponse') {
396
+ if (this._rejectExpiredQuoteResponses) {
397
+ const now = new Date().toISOString();
398
+ if (now > quote.expiration) {
399
+ const msg = 'Quote response missed expiry deadline';
400
+ error = new BackendError(msg, 504);
401
+ this._logger.error(`${msg}: system time=${now} > expiration time=${quote.expiration}`);
402
+ }
403
+ }
404
+ } else if (message.type === 'quoteResponseError') {
405
+ error = new BackendError(`Got an error response requesting quote: ${util.inspect(message.data)}`, 500);
406
+ error.mojaloopError = message.data;
407
+ }
408
+ else {
409
+ this._logger.push({ message }).log(`Ignoring cache notification for quote ${quoteKey}. Unknown message type ${message.type}.`);
410
+ return;
411
+ }
412
+
413
+ // cancel the timeout handler
414
+ clearTimeout(timeout);
415
+
416
+ // stop listening for payee resolution messages
417
+ // no need to await for the unsubscribe to complete.
418
+ // we dont really care if the unsubscribe fails but we should log it regardless
419
+ this._cache.unsubscribe(quoteKey, subId).catch(e => {
420
+ this._logger.log(`Error unsubscribing (in callback) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
421
+ });
422
+
423
+ if (error) {
424
+ return reject(error);
425
+ }
426
+
427
+ const quoteResponseBody = message.data;
428
+ const quoteResponseHeaders = message.headers;
429
+ this._logger.push({ quoteResponseBody }).log('Quote response received');
430
+
431
+ this.data.quoteResponse = quoteResponseBody;
432
+ this.data.quoteResponseSource = quoteResponseHeaders['fspiop-source'];
433
+
434
+ return resolve(quote);
435
+ }
436
+ catch(err) {
437
+ return reject(err);
438
+ }
439
+ });
440
+
441
+ // set up a timeout for the request
442
+ const timeout = setTimeout(() => {
443
+ const err = new BackendError(`Timeout requesting quote for transfer ${this.data.transferId}`, 504);
444
+
445
+ // we dont really care if the unsubscribe fails but we should log it regardless
446
+ this._cache.unsubscribe(quoteKey, subId).catch(e => {
447
+ this._logger.log(`Error unsubscribing (in timeout handler) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
448
+ });
449
+
450
+ return reject(err);
451
+ }, this._requestProcessingTimeoutSeconds * 1000);
452
+
453
+ // now we have a timeout handler and a cache subscriber hooked up we can fire off
454
+ // a POST /quotes request to the switch
455
+ try {
456
+ const res = await this._requests.postQuotes(quote, this.data.to.fspId);
457
+ this._logger.push({ res }).log('Quote request sent to peer');
458
+ }
459
+ catch(err) {
460
+ // cancel the timout and unsubscribe before rejecting the promise
461
+ clearTimeout(timeout);
462
+
463
+ // we dont really care if the unsubscribe fails but we should log it regardless
464
+ this._cache.unsubscribe(quoteKey, subId).catch(e => {
465
+ this._logger.log(`Error unsubscribing (in error handler) ${quoteKey} ${subId}: ${e.stack || util.inspect(e)}`);
466
+ });
467
+
468
+ return reject(err);
469
+ }
470
+ });
471
+ }
472
+
473
+ /**
474
+ * Sends request for
475
+ * Starts the quote resolution process by sending a POST /quotes request to the switch;
476
+ * then waits for a notification from the cache that the quote response has been received
477
+ */
478
+ async _requestOTP() {
479
+ // eslint-disable-next-line no-async-promise-executor
480
+ return new Promise(async (resolve, reject) => {
481
+
482
+ if( this.data.initiatorType && this.data.initiatorType === 'BUSINESS') return resolve();
483
+
484
+ // listen for events on the quoteId
485
+ const otpKey = `otp_${this.data.requestToPayTransactionId}`;
486
+
487
+ // hook up a subscriber to handle response messages
488
+ const subId = await this._cache.subscribe(otpKey, (cn, msg, subId) => {
489
+ try {
490
+ let otpResponse = JSON.parse(msg);
491
+
492
+ // cancel the timeout handler
493
+ clearTimeout(timeout);
494
+
495
+ // stop listening for payee resolution messages
496
+ // no need to await for the unsubscribe to complete.
497
+ // we dont really care if the unsubscribe fails but we should log it regardless
498
+ this._cache.unsubscribe(otpKey, subId).catch(e => {
499
+ this._logger.log(`Error unsubscribing (in callback) ${otpKey} ${subId}: ${e.stack || util.inspect(e)}`);
500
+ });
501
+
502
+ const otpResponseBody = otpResponse.data;
503
+ this._logger.push({ otpResponseBody }).log('OTP response received');
504
+
505
+ this.data.otpResponse = otpResponseBody;
506
+
507
+ return resolve(otpResponse);
508
+ }
509
+ catch(err) {
510
+ return reject(err);
511
+ }
512
+ });
513
+
514
+ // set up a timeout for the request
515
+ const timeout = setTimeout(() => {
516
+ const err = new BackendError(`Timeout requesting quote for transfer ${this.data.transferId}`, 504);
517
+
518
+ // we dont really care if the unsubscribe fails but we should log it regardless
519
+ this._cache.unsubscribe(otpKey, subId).catch(e => {
520
+ this._logger.log(`Error unsubscribing (in timeout handler) ${otpKey} ${subId}: ${e.stack || util.inspect(e)}`);
521
+ });
522
+
523
+ return reject(err);
524
+ }, this._requestProcessingTimeoutSeconds * 1000);
525
+
526
+ // now we have a timeout handler and a cache subscriber hooked up we can fire off
527
+ // a POST /authorizations request to the switch
528
+ try {
529
+ const res = await this._requests.getAuthorizations(this.data.requestToPayTransactionId,`authenticationType=OTP&retriesLeft=1&amount=${this.data.amount}&currency=${this.data.currency}`,this.data.to.fspId);
530
+ this._logger.push({ res }).log('Authorizations request sent to peer');
531
+ }
532
+ catch(err) {
533
+ // cancel the timout and unsubscribe before rejecting the promise
534
+ clearTimeout(timeout);
535
+
536
+ // we dont really care if the unsubscribe fails but we should log it regardless
537
+ this._cache.unsubscribe(otpKey, subId).catch(e => {
538
+ this._logger.log(`Error unsubscribing (in error handler) ${otpKey} ${subId}: ${e.stack || util.inspect(e)}`);
539
+ });
540
+
541
+ return reject(err);
542
+ }
543
+ });
544
+ }
545
+
546
+
547
+ /**
548
+ * Constructs a quote request payload based on current state
549
+ *
550
+ * @returns {object} - the quote request object
551
+ */
552
+ _buildQuoteRequest() {
553
+ let quote = {
554
+ quoteId: uuid(),
555
+ transactionId: this.data.transferId,
556
+ transactionRequestId: this.data.requestToPayTransactionId,
557
+ amountType: this.data.amountType,
558
+ amount: {
559
+ currency: this.data.currency,
560
+ amount: this.data.amount
561
+ },
562
+ expiration: this._getExpirationTimestamp()
563
+ };
564
+
565
+ quote.payer = shared.internalPartyToMojaloopParty(this.data.from, this._dfspId);
566
+ quote.payee = shared.internalPartyToMojaloopParty(this.data.to, this.data.to.fspId);
567
+
568
+ quote.transactionType = {
569
+ scenario: this.data.scenario,
570
+ initiator: this.data.initiator,
571
+ initiatorType: this.data.initiatorType
572
+ };
573
+
574
+ // geocode
575
+ // note
576
+ if(this.data.note) {
577
+ quote.note = this.data.note;
578
+ }
579
+
580
+ // add extensionList if provided
581
+ if(this.data.quoteRequestExtensions && this.data.quoteRequestExtensions.length > 0) {
582
+ quote.extensionList = {
583
+ extension: this.data.quoteRequestExtensions
584
+ };
585
+ }
586
+
587
+ return quote;
588
+ }
589
+
590
+
591
+ /**
592
+ * Executes a transfer
593
+ * Starts the transfer process by sending a POST /transfers (prepare) request to the switch;
594
+ * then waits for a notification from the cache that the transfer has been fulfilled
595
+ */
596
+ async _executeTransfer() {
597
+ // eslint-disable-next-line no-async-promise-executor
598
+ return new Promise(async (resolve, reject) => {
599
+ // create a transfer prepare request
600
+ const prepare = this._buildTransferPrepare();
601
+
602
+ // listen for events on the transferId
603
+ const transferKey = `tf_${this.data.transferId}`;
604
+
605
+ const subId = await this._cache.subscribe(transferKey, async (cn, msg, subId) => {
606
+ try {
607
+ let error;
608
+ let message = JSON.parse(msg);
609
+
610
+ if (message.type === 'transferFulfil') {
611
+ if (this._rejectExpiredTransferFulfils) {
612
+ const now = new Date().toISOString();
613
+ if (now > prepare.expiration) {
614
+ const msg = 'Transfer fulfil missed expiry deadline';
615
+ this._logger.error(`${msg}: system time=${now} > expiration=${prepare.expiration}`);
616
+ error = new BackendError(msg, 504);
617
+ }
618
+ }
619
+ } else if (message.type === 'transferError') {
620
+ error = new BackendError(`Got an error response preparing transfer: ${util.inspect(message.data)}`, 500);
621
+ error.mojaloopError = message.data;
622
+ } else {
623
+ this._logger.push({ message }).log(`Ignoring cache notification for transfer ${transferKey}. Uknokwn message type ${message.type}.`);
624
+ return;
625
+ }
626
+
627
+ // cancel the timeout handler
628
+ clearTimeout(timeout);
629
+
630
+ // stop listening for transfer fulfil messages
631
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
632
+ this._logger.log(`Error unsubscribing (in callback) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
633
+ });
634
+
635
+ if (error) {
636
+ return reject(error);
637
+ }
638
+
639
+ const fulfil = message.data;
640
+ this._logger.push({ fulfil }).log('Transfer fulfil received');
641
+ this.data.fulfil = fulfil;
642
+
643
+ if(this._checkIlp && !this._ilp.validateFulfil(fulfil.fulfilment, this.data.quoteResponse.condition)) {
644
+ throw new Error('Invalid fulfilment received from peer DFSP.');
645
+ }
646
+
647
+ return resolve(fulfil);
648
+ }
649
+ catch(err) {
650
+ return reject(err);
651
+ }
652
+ });
653
+
654
+ // set up a timeout for the request
655
+ const timeout = setTimeout(() => {
656
+ const err = new BackendError(`Timeout waiting for fulfil for transfer ${this.data.transferId}`, 504);
657
+
658
+ // we dont really care if the unsubscribe fails but we should log it regardless
659
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
660
+ this._logger.log(`Error unsubscribing (in timeout handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
661
+ });
662
+
663
+ return reject(err);
664
+ }, this._requestProcessingTimeoutSeconds * 1000);
665
+
666
+ // now we have a timeout handler and a cache subscriber hooked up we can fire off
667
+ // a POST /transfers request to the switch
668
+ try {
669
+ const res = await this._requests.postTransfers(prepare, this.data.quoteResponseSource);
670
+ this._logger.push({ res }).log('Transfer prepare sent to peer');
671
+ }
672
+ catch(err) {
673
+ // cancel the timout and unsubscribe before rejecting the promise
674
+ clearTimeout(timeout);
675
+
676
+ // we dont really care if the unsubscribe fails but we should log it regardless
677
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
678
+ this._logger.log(`Error unsubscribing (in error handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
679
+ });
680
+
681
+ return reject(err);
682
+ }
683
+ });
684
+ }
685
+
686
+ /**
687
+ * Get transfer details by sending GET /transfers request to the switch
688
+ */
689
+ async _getTransfer() {
690
+ // eslint-disable-next-line no-async-promise-executor
691
+ return new Promise(async (resolve, reject) => {
692
+ const transferKey = `tf_${this.data.transferId}`;
693
+
694
+ // hook up a subscriber to handle response messages
695
+ const subId = await this._cache.subscribe(transferKey, (cn, msg, subId) => {
696
+ try {
697
+ let error;
698
+ let message = JSON.parse(msg);
699
+
700
+ if (message.type === 'transferError') {
701
+ error = new BackendError(`Got an error response retrieving transfer: ${util.inspect(message.data)}`, 500);
702
+ error.mojaloopError = message.data;
703
+ } else if (message.type !== 'transferFulfil') {
704
+ this._logger.push({ message }).log(`Ignoring cache notification for transfer ${transferKey}. Uknokwn message type ${message.type}.`);
705
+ return;
706
+ }
707
+
708
+ // cancel the timeout handler
709
+ clearTimeout(timeout);
710
+
711
+ // stop listening for transfer fulfil messages
712
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
713
+ this._logger.log(`Error unsubscribing (in callback) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
714
+ });
715
+
716
+ if (error) {
717
+ return reject(error);
718
+ }
719
+
720
+ const fulfil = message.data;
721
+ this._logger.push({ fulfil }).log('Transfer fulfil received');
722
+ this.data.fulfil = fulfil;
723
+
724
+ return resolve(this.data);
725
+ }
726
+ catch(err) {
727
+ return reject(err);
728
+ }
729
+ });
730
+
731
+ // set up a timeout for the resolution
732
+ const timeout = setTimeout(() => {
733
+ const err = new BackendError(`Timeout getting transfer ${this.data.transferId}`, 504);
734
+
735
+ // we dont really care if the unsubscribe fails but we should log it regardless
736
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
737
+ this._logger.log(`Error unsubscribing (in timeout handler) ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
738
+ });
739
+
740
+ return reject(err);
741
+ }, this._requestProcessingTimeoutSeconds * 1000);
742
+
743
+ // now we have a timeout handler and a cache subscriber hooked up we can fire off
744
+ // a GET /transfers request to the switch
745
+ try {
746
+ const res = await this._requests.getTransfers(this.data.transferId);
747
+ this._logger.push({ peer: res }).log('Transfer lookup sent to peer');
748
+ }
749
+ catch(err) {
750
+ // cancel the timout and unsubscribe before rejecting the promise
751
+ clearTimeout(timeout);
752
+
753
+ // we dont really care if the unsubscribe fails but we should log it regardless
754
+ this._cache.unsubscribe(transferKey, subId).catch(e => {
755
+ this._logger.log(`Error unsubscribing ${transferKey} ${subId}: ${e.stack || util.inspect(e)}`);
756
+ });
757
+
758
+ return reject(err);
759
+ }
760
+ });
761
+ }
762
+
763
+
764
+ /**
765
+ * Builds a transfer prepare payload from current state
766
+ *
767
+ * @returns {object} - the transfer prepare payload
768
+ */
769
+ _buildTransferPrepare() {
770
+ let prepare = {
771
+ transferId: this.data.transferId,
772
+ payeeFsp: this.data.to.fspId,
773
+ payerFsp: this._dfspId,
774
+ amount: {
775
+ // We use the transfer currency and amount specified in the quote response
776
+ // rather than the original request. In Forex cases we may have requested
777
+ // a RECEIVE amount in a currency we cannot send. FXP should always give us
778
+ // a quote response with transferAmount in the correct currency.
779
+ currency: this.data.quoteResponse.transferAmount.currency,
780
+ amount: this.data.quoteResponse.transferAmount.amount
781
+ },
782
+ ilpPacket: this.data.quoteResponse.ilpPacket,
783
+ condition: this.data.quoteResponse.condition,
784
+ expiration: this._getExpirationTimestamp()
785
+ };
786
+
787
+ if(this._useQuoteSourceFSPAsTransferPayeeFSP) {
788
+ prepare.payeeFsp = this.data.quoteResponseSource;
789
+ }
790
+
791
+ // add extensions list if provided
792
+ const { transferRequestExtensions } = this.data;
793
+ if(transferRequestExtensions && transferRequestExtensions.length > 0) {
794
+ prepare.extensionList = {
795
+ extension: transferRequestExtensions,
796
+ };
797
+ }
798
+
799
+ return prepare;
800
+ }
801
+
802
+
803
+ /**
804
+ * Returns an ISO-8601 format timestamp n-seconds in the future for expiration of a transfers API object,
805
+ * where n is equal to our config setting "expirySeconds"
806
+ *
807
+ * @returns {string} - ISO-8601 format future expiration timestamp
808
+ */
809
+ _getExpirationTimestamp() {
810
+ let now = new Date();
811
+ return new Date(now.getTime() + (this._expirySeconds * 1000)).toISOString();
812
+ }
813
+
814
+
815
+ /**
816
+ * Returns an object representing the final state of the transfer suitable for the outbound API
817
+ *
818
+ * @returns {object} - Response representing the result of the transfer process
819
+ */
820
+ getResponse() {
821
+ // we want to project some of our internal state into a more useful
822
+ // representation to return to the SDK API consumer
823
+ let resp = { ...this.data };
824
+
825
+ switch(this.data.currentState) {
826
+ case 'quoteReceived':
827
+ resp.currentState = requestToPayTransferStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE;
828
+ break;
829
+
830
+ case 'otpReceived':
831
+ resp.currentState = requestToPayTransferStateEnum.WAITING_FOR_OTP_ACCEPTANCE;
832
+ break;
833
+
834
+ case 'succeeded':
835
+ resp.currentState = requestToPayTransferStateEnum.COMPLETED;
836
+ break;
837
+
838
+ case 'errored':
839
+ resp.currentState = requestToPayTransferStateEnum.ERROR_OCCURRED;
840
+ break;
841
+
842
+ default:
843
+ this._logger.log(`Transfer model response being returned from an unexpected state: ${this.data.currentState}. Returning ERROR_OCCURRED state`);
844
+ resp.currentState = requestToPayTransferStateEnum.ERROR_OCCURRED;
845
+ break;
846
+ }
847
+
848
+ return resp;
849
+ }
850
+
851
+
852
+ /**
853
+ * Persists the model state to cache for reinstantiation at a later point
854
+ */
855
+ async _save() {
856
+ try {
857
+ this.data.currentState = this.stateMachine.state;
858
+ const res = await this._cache.set(`requestToPayTransferModel_${this.data.requestToPayTransactionId}`, this.data);
859
+ this._logger.push({ res }).log('Persisted transfer model in cache');
860
+ }
861
+ catch(err) {
862
+ this._logger.push({ err }).log('Error saving transfer model');
863
+ throw err;
864
+ }
865
+ }
866
+
867
+
868
+ /**
869
+ * Loads a transfer model from cache for resumption of the transfer process
870
+ *
871
+ * @param transferId {string} - UUID transferId of the model to load from cache
872
+ */
873
+ async load(requestToPayTransactionId) {
874
+ try {
875
+ const data = await this._cache.get(`requestToPayTransferModel_${requestToPayTransactionId}`);
876
+ if(!data) {
877
+ throw new Error(`No cached data found for requestToPayTransactionId: ${requestToPayTransactionId}`);
878
+ }
879
+ await this.initialize(data);
880
+ this._logger.push({ cache: this.data }).log('RequestToPay Transfer model loaded from cached state');
881
+ }
882
+ catch(err) {
883
+ this._logger.push({ err }).log('Error loading transfer model');
884
+ throw err;
885
+ }
886
+ }
887
+
888
+
889
+
890
+ }
891
+
892
+
893
+ module.exports = OutboundRequestToPayTransferModel;