@mojaloop/sdk-scheme-adapter 12.3.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/CHANGELOG.md +11 -0
- package/docker/ml-testing-toolkit/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +2 -2
- package/docker/ml-testing-toolkit/spec_files/rules_callback/default.json +7 -7
- package/docker/ml-testing-toolkit/spec_files/rules_response/default.json +16 -16
- package/docker/ml-testing-toolkit/spec_files/rules_response/default_pisp_rules.json +5 -5
- package/docker/ml-testing-toolkit/spec_files/rules_validation/default.json +10 -10
- package/package.json +1 -1
- package/src/InboundServer/handlers.js +114 -52
- package/src/OutboundServer/api.yaml +54 -3
- package/src/OutboundServer/api_interfaces/openapi.d.ts +24 -3
- package/src/OutboundServer/api_template/components/schemas/accountsResponse.yaml +9 -0
- package/src/OutboundServer/api_template/components/schemas/transferRequest.yaml +3 -0
- package/src/OutboundServer/api_template/components/schemas/transferResponse.yaml +28 -2
- package/src/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +8 -1
- package/src/OutboundServer/handlers.js +1 -1
- package/src/config.js +1 -1
- package/src/lib/model/AccountsModel.js +13 -11
- package/src/lib/model/InboundTransfersModel.js +166 -24
- package/src/lib/model/OutboundRequestToPayModel.js +5 -6
- package/src/lib/model/OutboundRequestToPayTransferModel.js +2 -2
- package/src/lib/model/OutboundTransfersModel.js +261 -56
- package/src/lib/model/PartiesModel.js +1 -1
- package/src/lib/model/common/BackendError.js +28 -4
- package/src/lib/model/common/index.js +2 -1
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +3 -2
- package/test/integration/lib/Outbound/parties.test.js +1 -1
- package/test/unit/InboundServer.test.js +9 -9
- package/test/unit/TestServer.test.js +11 -13
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +11 -3
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +14 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +13 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +18 -0
- package/test/unit/api/accounts/utils.js +15 -1
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +18 -15
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +1 -0
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +9 -0
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +1 -0
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +74 -47
- package/test/unit/api/transfers/utils.js +85 -4
- package/test/unit/data/commonHttpHeaders.json +1 -0
- package/test/unit/inboundApi/handlers.test.js +45 -14
- package/test/unit/lib/model/AccountsModel.test.js +9 -6
- package/test/unit/lib/model/InboundTransfersModel.test.js +210 -30
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +1 -1
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -3
- package/test/unit/lib/model/OutboundTransfersModel.test.js +826 -157
- package/test/unit/lib/model/data/defaultConfig.json +9 -9
- package/test/unit/lib/model/data/mockArguments.json +97 -40
- package/test/unit/lib/model/data/payeeParty.json +13 -11
- package/test/unit/lib/model/data/quoteResponse.json +36 -25
- package/test/unit/lib/model/data/transferFulfil.json +5 -3
|
@@ -1002,6 +1002,12 @@ components:
|
|
|
1002
1002
|
$ref: '#/components/schemas/extensionListEmptiable'
|
|
1003
1003
|
transferRequestExtensions:
|
|
1004
1004
|
$ref: '#/components/schemas/extensionListEmptiable'
|
|
1005
|
+
skipPartyLookup:
|
|
1006
|
+
description: >-
|
|
1007
|
+
Set to true if supplying an FSPID for the payee party and no party
|
|
1008
|
+
resolution is needed. This may be useful is a previous party
|
|
1009
|
+
resolution has been performed.
|
|
1010
|
+
type: boolean
|
|
1005
1011
|
CorrelationId:
|
|
1006
1012
|
title: CorrelationId
|
|
1007
1013
|
type: string
|
|
@@ -1273,8 +1279,24 @@ components:
|
|
|
1273
1279
|
$ref: '#/components/schemas/transferStatus'
|
|
1274
1280
|
quoteId:
|
|
1275
1281
|
$ref: '#/components/schemas/CorrelationId'
|
|
1282
|
+
getPartiesResponse:
|
|
1283
|
+
type: object
|
|
1284
|
+
required:
|
|
1285
|
+
- body
|
|
1286
|
+
properties:
|
|
1287
|
+
body:
|
|
1288
|
+
type: object
|
|
1289
|
+
headers:
|
|
1290
|
+
type: object
|
|
1276
1291
|
quoteResponse:
|
|
1277
|
-
|
|
1292
|
+
type: object
|
|
1293
|
+
required:
|
|
1294
|
+
- body
|
|
1295
|
+
properties:
|
|
1296
|
+
body:
|
|
1297
|
+
$ref: '#/components/schemas/QuotesIDPutResponse'
|
|
1298
|
+
headers:
|
|
1299
|
+
type: object
|
|
1278
1300
|
quoteResponseSource:
|
|
1279
1301
|
type: string
|
|
1280
1302
|
description: >
|
|
@@ -1283,7 +1305,14 @@ components:
|
|
|
1283
1305
|
account in the case of a FOREX transfer. i.e. it may be a FOREX
|
|
1284
1306
|
gateway.
|
|
1285
1307
|
fulfil:
|
|
1286
|
-
|
|
1308
|
+
type: object
|
|
1309
|
+
required:
|
|
1310
|
+
- body
|
|
1311
|
+
properties:
|
|
1312
|
+
body:
|
|
1313
|
+
$ref: '#/components/schemas/TransfersIDPutResponse'
|
|
1314
|
+
headers:
|
|
1315
|
+
type: object
|
|
1287
1316
|
lastError:
|
|
1288
1317
|
description: >
|
|
1289
1318
|
Object representing the last error to occur during a transfer
|
|
@@ -1291,6 +1320,12 @@ components:
|
|
|
1291
1320
|
entity in the scheme or an object representing other types of error
|
|
1292
1321
|
e.g. exceptions that may occur inside the scheme adapter.
|
|
1293
1322
|
$ref: '#/components/schemas/transferError'
|
|
1323
|
+
skipPartyLookup:
|
|
1324
|
+
description: >-
|
|
1325
|
+
Set to true if supplying an FSPID for the payee party and no party
|
|
1326
|
+
resolution is needed. This may be useful is a previous party
|
|
1327
|
+
resolution has been performed.
|
|
1328
|
+
type: boolean
|
|
1294
1329
|
errorResponse:
|
|
1295
1330
|
type: object
|
|
1296
1331
|
properties:
|
|
@@ -1321,7 +1356,14 @@ components:
|
|
|
1321
1356
|
currentState:
|
|
1322
1357
|
$ref: '#/components/schemas/transferStatus'
|
|
1323
1358
|
fulfil:
|
|
1324
|
-
|
|
1359
|
+
type: object
|
|
1360
|
+
required:
|
|
1361
|
+
- body
|
|
1362
|
+
properties:
|
|
1363
|
+
body:
|
|
1364
|
+
$ref: '#/components/schemas/TransfersIDPutResponse'
|
|
1365
|
+
headers:
|
|
1366
|
+
type: object
|
|
1325
1367
|
transferContinuationAcceptParty:
|
|
1326
1368
|
type: object
|
|
1327
1369
|
required:
|
|
@@ -1993,6 +2035,15 @@ components:
|
|
|
1993
2035
|
$ref: '#/components/schemas/accountsCreationState'
|
|
1994
2036
|
lastError:
|
|
1995
2037
|
$ref: '#/components/schemas/transferError'
|
|
2038
|
+
postAccountsResponse:
|
|
2039
|
+
type: object
|
|
2040
|
+
required:
|
|
2041
|
+
- body
|
|
2042
|
+
properties:
|
|
2043
|
+
body:
|
|
2044
|
+
type: object
|
|
2045
|
+
headers:
|
|
2046
|
+
type: object
|
|
1996
2047
|
errorAccountsResponse:
|
|
1997
2048
|
allOf:
|
|
1998
2049
|
- $ref: '#/components/schemas/errorResponse'
|
|
@@ -633,6 +633,8 @@ export interface components {
|
|
|
633
633
|
note?: components["schemas"]["Note"];
|
|
634
634
|
quoteRequestExtensions?: components["schemas"]["extensionListEmptiable"];
|
|
635
635
|
transferRequestExtensions?: components["schemas"]["extensionListEmptiable"];
|
|
636
|
+
/** Set to true if supplying an FSPID for the payee party and no party resolution is needed. This may be useful is a previous party resolution has been performed. */
|
|
637
|
+
skipPartyLookup?: boolean;
|
|
636
638
|
};
|
|
637
639
|
/** Identifier that correlates all messages of the same sequence. The API data type UUID (Universally Unique Identifier) is a JSON String in canonical format, conforming to [RFC 4122](https://tools.ietf.org/html/rfc4122), that is restricted by a regular expression for interoperability reasons. A UUID is always 36 characters long, 32 hexadecimal symbols and 4 dashes (‘-‘). */
|
|
638
640
|
CorrelationId: string;
|
|
@@ -728,12 +730,24 @@ export interface components {
|
|
|
728
730
|
note?: components["schemas"]["Note"];
|
|
729
731
|
currentState?: components["schemas"]["transferStatus"];
|
|
730
732
|
quoteId?: components["schemas"]["CorrelationId"];
|
|
731
|
-
|
|
733
|
+
getPartiesResponse?: {
|
|
734
|
+
body: { [key: string]: unknown };
|
|
735
|
+
headers?: { [key: string]: unknown };
|
|
736
|
+
};
|
|
737
|
+
quoteResponse?: {
|
|
738
|
+
body: components["schemas"]["QuotesIDPutResponse"];
|
|
739
|
+
headers?: { [key: string]: unknown };
|
|
740
|
+
};
|
|
732
741
|
/** FSPID of the entity that supplied the quote response. This may not be the same as the FSPID of the entity which owns the end user account in the case of a FOREX transfer. i.e. it may be a FOREX gateway. */
|
|
733
742
|
quoteResponseSource?: string;
|
|
734
|
-
fulfil?:
|
|
743
|
+
fulfil?: {
|
|
744
|
+
body: components["schemas"]["TransfersIDPutResponse"];
|
|
745
|
+
headers?: { [key: string]: unknown };
|
|
746
|
+
};
|
|
735
747
|
/** Object representing the last error to occur during a transfer process. This may be a Mojaloop API error returned from another entity in the scheme or an object representing other types of error e.g. exceptions that may occur inside the scheme adapter. */
|
|
736
748
|
lastError?: components["schemas"]["transferError"];
|
|
749
|
+
/** Set to true if supplying an FSPID for the payee party and no party resolution is needed. This may be useful is a previous party resolution has been performed. */
|
|
750
|
+
skipPartyLookup?: boolean;
|
|
737
751
|
};
|
|
738
752
|
errorResponse: {
|
|
739
753
|
/** Error code as string. */
|
|
@@ -747,7 +761,10 @@ export interface components {
|
|
|
747
761
|
transferStatusResponse: {
|
|
748
762
|
transferId: components["schemas"]["CorrelationId"];
|
|
749
763
|
currentState: components["schemas"]["transferStatus"];
|
|
750
|
-
fulfil:
|
|
764
|
+
fulfil: {
|
|
765
|
+
body: components["schemas"]["TransfersIDPutResponse"];
|
|
766
|
+
headers?: { [key: string]: unknown };
|
|
767
|
+
};
|
|
751
768
|
};
|
|
752
769
|
transferContinuationAcceptParty: {
|
|
753
770
|
acceptParty: true;
|
|
@@ -1011,6 +1028,10 @@ export interface components {
|
|
|
1011
1028
|
response?: components["schemas"]["accountCreationStatus"];
|
|
1012
1029
|
currentState?: components["schemas"]["accountsCreationState"];
|
|
1013
1030
|
lastError?: components["schemas"]["transferError"];
|
|
1031
|
+
postAccountsResponse?: {
|
|
1032
|
+
body: { [key: string]: unknown };
|
|
1033
|
+
headers?: { [key: string]: unknown };
|
|
1034
|
+
};
|
|
1014
1035
|
};
|
|
1015
1036
|
errorAccountsResponse: components["schemas"]["errorResponse"] & {
|
|
1016
1037
|
executionState: components["schemas"]["accountsResponse"];
|
|
@@ -35,3 +35,6 @@ properties:
|
|
|
35
35
|
$ref: ./extensionListEmptiable.yaml
|
|
36
36
|
transferRequestExtensions:
|
|
37
37
|
$ref: ./extensionListEmptiable.yaml
|
|
38
|
+
skipPartyLookup:
|
|
39
|
+
description: Set to true if supplying an FSPID for the payee party and no party resolution is needed. This may be useful is a previous party resolution has been performed.
|
|
40
|
+
type: boolean
|
|
@@ -39,8 +39,24 @@ properties:
|
|
|
39
39
|
quoteId:
|
|
40
40
|
$ref: >-
|
|
41
41
|
../../../../../node_modules/@mojaloop/api-snippets/fspiop/v1_1/openapi3/components/schemas/CorrelationId.yaml
|
|
42
|
+
getPartiesResponse:
|
|
43
|
+
type: object
|
|
44
|
+
required:
|
|
45
|
+
- body
|
|
46
|
+
properties:
|
|
47
|
+
body:
|
|
48
|
+
type: object
|
|
49
|
+
headers:
|
|
50
|
+
type: object
|
|
42
51
|
quoteResponse:
|
|
43
|
-
|
|
52
|
+
type: object
|
|
53
|
+
required:
|
|
54
|
+
- body
|
|
55
|
+
properties:
|
|
56
|
+
body:
|
|
57
|
+
$ref: './quote.yaml'
|
|
58
|
+
headers:
|
|
59
|
+
type: object
|
|
44
60
|
quoteResponseSource:
|
|
45
61
|
type: string
|
|
46
62
|
description: >
|
|
@@ -48,7 +64,14 @@ properties:
|
|
|
48
64
|
same as the FSPID of the entity which owns the end user account in the
|
|
49
65
|
case of a FOREX transfer. i.e. it may be a FOREX gateway.
|
|
50
66
|
fulfil:
|
|
51
|
-
|
|
67
|
+
type: object
|
|
68
|
+
required:
|
|
69
|
+
- body
|
|
70
|
+
properties:
|
|
71
|
+
body:
|
|
72
|
+
$ref: ./transferFulfilment.yaml
|
|
73
|
+
headers:
|
|
74
|
+
type: object
|
|
52
75
|
lastError:
|
|
53
76
|
description: >
|
|
54
77
|
Object representing the last error to occur during a transfer process.
|
|
@@ -56,3 +79,6 @@ properties:
|
|
|
56
79
|
scheme or an object representing other types of error e.g. exceptions that
|
|
57
80
|
may occur inside the scheme adapter.
|
|
58
81
|
$ref: ./transferError.yaml
|
|
82
|
+
skipPartyLookup:
|
|
83
|
+
description: Set to true if supplying an FSPID for the payee party and no party resolution is needed. This may be useful is a previous party resolution has been performed.
|
|
84
|
+
type: boolean
|
|
@@ -182,7 +182,7 @@ const putTransfers = async (ctx) => {
|
|
|
182
182
|
// load the transfer model from cache and start it running again
|
|
183
183
|
await model.load(ctx.state.path.params.transferId);
|
|
184
184
|
|
|
185
|
-
const response = await model.run();
|
|
185
|
+
const response = await model.run(ctx.request.body);
|
|
186
186
|
|
|
187
187
|
// return the result
|
|
188
188
|
ctx.response.status = 200;
|
package/src/config.js
CHANGED
|
@@ -182,5 +182,5 @@ module.exports = {
|
|
|
182
182
|
// the `transactionId` to retrieve the quote from cache
|
|
183
183
|
allowDifferentTransferTransactionId: env.get('ALLOW_DIFFERENT_TRANSFER_TRANSACTION_ID').default('false').asBool(),
|
|
184
184
|
|
|
185
|
-
pm4mlEnabled: env.get('PM4ML_ENABLED').default('false').asBool()
|
|
185
|
+
pm4mlEnabled: env.get('PM4ML_ENABLED').default('false').asBool(),
|
|
186
186
|
};
|
|
@@ -133,20 +133,23 @@ class AccountsModel {
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
async _executeCreateAccountsRequest(request) {
|
|
136
|
+
const accountRequest = request;
|
|
137
|
+
|
|
136
138
|
// eslint-disable-next-line no-async-promise-executor
|
|
137
139
|
return new Promise(async (resolve, reject) => {
|
|
138
|
-
const requestKey = `ac_${
|
|
140
|
+
const requestKey = `ac_${accountRequest.requestId}`;
|
|
139
141
|
|
|
140
142
|
const subId = await this._cache.subscribe(requestKey, async (cn, msg, subId) => {
|
|
141
143
|
try {
|
|
142
144
|
let error;
|
|
143
|
-
|
|
145
|
+
const message = JSON.parse(msg);
|
|
146
|
+
this._data.postAccountsResponse = message.data;
|
|
144
147
|
|
|
145
148
|
if (message.type === 'accountsCreationErrorResponse') {
|
|
146
|
-
error = new BackendError(`Got an error response creating accounts: ${util.inspect(
|
|
147
|
-
error.mojaloopError =
|
|
149
|
+
error = new BackendError(`Got an error response creating accounts: ${util.inspect(this._data.postAccountsResponse.body)}`, 500);
|
|
150
|
+
error.mojaloopError = this._data.postAccountsResponse.body;
|
|
148
151
|
} else if (message.type !== 'accountsCreationSuccessfulResponse') {
|
|
149
|
-
this._logger.push(
|
|
152
|
+
this._logger.push(util.inspect(this._data.postAccountsResponse)).log(
|
|
150
153
|
`Ignoring cache notification for request ${requestKey}. ` +
|
|
151
154
|
`Unknown message type ${message.type}.`
|
|
152
155
|
);
|
|
@@ -167,7 +170,7 @@ class AccountsModel {
|
|
|
167
170
|
return reject(error);
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
const response =
|
|
173
|
+
const response = this._data.postAccountsResponse;
|
|
171
174
|
this._logger.push({ response }).log('Account creation response received');
|
|
172
175
|
return resolve(response);
|
|
173
176
|
}
|
|
@@ -178,7 +181,7 @@ class AccountsModel {
|
|
|
178
181
|
|
|
179
182
|
// set up a timeout for the request
|
|
180
183
|
const timeout = setTimeout(() => {
|
|
181
|
-
const err = new BackendError(`Timeout waiting for response to account creation request ${
|
|
184
|
+
const err = new BackendError(`Timeout waiting for response to account creation request ${accountRequest.requestId}`, 504);
|
|
182
185
|
|
|
183
186
|
// we dont really care if the unsubscribe fails but we should log it regardless
|
|
184
187
|
this._cache.unsubscribe(requestKey, subId).catch(e => {
|
|
@@ -191,7 +194,7 @@ class AccountsModel {
|
|
|
191
194
|
// now we have a timeout handler and a cache subscriber hooked up we can fire off
|
|
192
195
|
// a POST /participants request to the switch
|
|
193
196
|
try {
|
|
194
|
-
const res = await this._requests.postParticipants(
|
|
197
|
+
const res = await this._requests.postParticipants(accountRequest);
|
|
195
198
|
this._logger.push({ res }).log('Account creation request sent to peer');
|
|
196
199
|
}
|
|
197
200
|
catch(err) {
|
|
@@ -218,11 +221,11 @@ class AccountsModel {
|
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
_buildClientResponse(response) {
|
|
221
|
-
return response.partyList.map(party => ({
|
|
224
|
+
return response.body.partyList.map(party => ({
|
|
222
225
|
idType: party.partyId.partyIdType,
|
|
223
226
|
idValue: party.partyId.partyIdentifier,
|
|
224
227
|
idSubValue: party.partyId.partySubIdOrType,
|
|
225
|
-
...!response.currency && {
|
|
228
|
+
...!response.body.currency && {
|
|
226
229
|
error: {
|
|
227
230
|
statusCode: Errors.MojaloopApiErrorCodes.CLIENT_ERROR.code,
|
|
228
231
|
message: 'Provided currency not supported',
|
|
@@ -295,7 +298,6 @@ class AccountsModel {
|
|
|
295
298
|
resp.currentState = stateEnum.ERROR_OCCURRED;
|
|
296
299
|
break;
|
|
297
300
|
}
|
|
298
|
-
|
|
299
301
|
return resp;
|
|
300
302
|
}
|
|
301
303
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
const util = require('util');
|
|
13
14
|
|
|
14
15
|
const {
|
|
15
16
|
BackendRequests,
|
|
@@ -21,6 +22,7 @@ const {
|
|
|
21
22
|
Errors,
|
|
22
23
|
} = require('@mojaloop/sdk-standard-components');
|
|
23
24
|
const shared = require('./lib/shared');
|
|
25
|
+
const { TransferStateEnum } = require('./common');
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Models the operations required for performing inbound transfers
|
|
@@ -70,6 +72,12 @@ class InboundTransfersModel {
|
|
|
70
72
|
});
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
updateStateWithError(err) {
|
|
76
|
+
this.data.lastError = err;
|
|
77
|
+
this.data.currentState = TransferStateEnum.ERROR_OCCURRED;
|
|
78
|
+
return this._save();
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
/**
|
|
74
82
|
* Queries the backend API for the specified party and makes a callback to the originator with the result
|
|
75
83
|
*/
|
|
@@ -162,7 +170,32 @@ class InboundTransfersModel {
|
|
|
162
170
|
* Asks the backend for a response to an incoming quote request and makes a callback to the originator with
|
|
163
171
|
* the result
|
|
164
172
|
*/
|
|
165
|
-
async quoteRequest(
|
|
173
|
+
async quoteRequest(request, sourceFspId) {
|
|
174
|
+
const quoteRequest = request.body;
|
|
175
|
+
|
|
176
|
+
// keep track of our state.
|
|
177
|
+
// note that instances of this model typically only live as long as it takes to
|
|
178
|
+
// handle an incoming request and send a response asynchronously, but we hold onto
|
|
179
|
+
// some state across async ops
|
|
180
|
+
|
|
181
|
+
this.data = {
|
|
182
|
+
// transferId: this follows the slightly dodgy assumption that transferId will be same as this transactionId.
|
|
183
|
+
// so far this has held in moja implementations but may not always be the case. regardless, future FSPIOP API
|
|
184
|
+
// versions MUST deal with this cleanly so we can expect to eliminate this assumption at some point.
|
|
185
|
+
transferId: quoteRequest.transactionId,
|
|
186
|
+
direction: 'INBOUND',
|
|
187
|
+
quoteRequest: {
|
|
188
|
+
headers: request.headers,
|
|
189
|
+
body: request.body
|
|
190
|
+
},
|
|
191
|
+
currentState: TransferStateEnum.QUOTE_REQUEST_RECEIVED,
|
|
192
|
+
initiatedTimestamp: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// persist the transfer record in the cache. if we crash after this at least we will
|
|
196
|
+
// have a record of the request in the cache.
|
|
197
|
+
await this._save();
|
|
198
|
+
|
|
166
199
|
try {
|
|
167
200
|
const internalForm = shared.mojaloopQuoteRequestToInternal(quoteRequest);
|
|
168
201
|
|
|
@@ -189,19 +222,24 @@ class InboundTransfersModel {
|
|
|
189
222
|
mojaloopResponse.condition = condition;
|
|
190
223
|
|
|
191
224
|
// now store the fulfilment and the quote data against the quoteId in our cache
|
|
192
|
-
|
|
225
|
+
this.data.quote = {
|
|
193
226
|
request: quoteRequest,
|
|
194
227
|
internalRequest: internalForm,
|
|
195
228
|
response: response,
|
|
196
229
|
mojaloopResponse: mojaloopResponse,
|
|
197
230
|
fulfilment: fulfilment
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// now store the quoteResponse data against the quoteId in our cache to be sent as a response to GET /quotes/{ID}
|
|
201
|
-
await this._cache.set(`quoteResponse_${quoteRequest.quoteId}`, mojaloopResponse);
|
|
231
|
+
};
|
|
232
|
+
await this._save();
|
|
202
233
|
|
|
203
234
|
// make a callback to the source fsp with the quote response
|
|
204
|
-
|
|
235
|
+
const res = await this._mojaloopRequests.putQuotes(quoteRequest.quoteId, mojaloopResponse, sourceFspId);
|
|
236
|
+
this.data.quoteResponse = {
|
|
237
|
+
headers: res.originalRequest.headers,
|
|
238
|
+
body: res.originalRequest.body,
|
|
239
|
+
};
|
|
240
|
+
this.data.currentState = TransferStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE;
|
|
241
|
+
await this._save();
|
|
242
|
+
return res;
|
|
205
243
|
}
|
|
206
244
|
catch(err) {
|
|
207
245
|
this._logger.push({ err }).log('Error in quoteRequest');
|
|
@@ -257,7 +295,7 @@ class InboundTransfersModel {
|
|
|
257
295
|
return 'No response from backend';
|
|
258
296
|
}
|
|
259
297
|
|
|
260
|
-
// project our internal quote
|
|
298
|
+
// project our internal quote response into mojaloop quote response form
|
|
261
299
|
const mojaloopResponse = shared.internalTransactionRequestResponseToMojaloop(response);
|
|
262
300
|
|
|
263
301
|
// make a callback to the source fsp with the quote response
|
|
@@ -277,25 +315,36 @@ class InboundTransfersModel {
|
|
|
277
315
|
* Validates an incoming transfer prepare request and makes a callback to the originator with
|
|
278
316
|
* the result
|
|
279
317
|
*/
|
|
280
|
-
async prepareTransfer(
|
|
318
|
+
async prepareTransfer(request, sourceFspId) {
|
|
319
|
+
const prepareRequest = request.body;
|
|
281
320
|
try {
|
|
282
321
|
// retrieve our quote data
|
|
283
|
-
let quote;
|
|
284
|
-
|
|
285
322
|
if (this._allowDifferentTransferTransactionId) {
|
|
286
323
|
const transactionId = this._ilp.getTransactionObject(prepareRequest.ilpPacket).transactionId;
|
|
287
|
-
|
|
324
|
+
this.data = await this._load(transactionId);
|
|
288
325
|
} else {
|
|
289
|
-
|
|
326
|
+
this.data = await this._load(prepareRequest.transferId);
|
|
290
327
|
}
|
|
291
328
|
|
|
292
|
-
|
|
329
|
+
const quote = this.data.quote;
|
|
330
|
+
|
|
331
|
+
if(!this.data || !quote) {
|
|
332
|
+
// If using the sdk-scheme-adapter in place of the deprecated `mojaloop-connector`
|
|
333
|
+
// make sure this is false. Scenarios that use `mojaloop-connector`
|
|
334
|
+
// absolutely requires a previous quote before allowing a transfer to proceed.
|
|
335
|
+
// This is a different to the a typical mojaloop sdk-scheme-adapter setup which allows this as an option.
|
|
336
|
+
|
|
293
337
|
// Check whether to allow transfers without a previous quote.
|
|
294
338
|
if(!this._allowTransferWithoutQuote) {
|
|
295
339
|
throw new Error(`Corresponding quote not found for transfer ${prepareRequest.transferId}`);
|
|
296
340
|
}
|
|
297
341
|
}
|
|
298
342
|
|
|
343
|
+
// persist our state so we have a record if we crash during processing the prepare
|
|
344
|
+
this.data.prepare = request;
|
|
345
|
+
this.data.currentState = TransferStateEnum.PREPARE_RECEIVED;
|
|
346
|
+
await this._save();
|
|
347
|
+
|
|
299
348
|
// Calculate or retrieve fulfilment and condition
|
|
300
349
|
let fulfilment = null;
|
|
301
350
|
let condition = null;
|
|
@@ -313,13 +362,13 @@ class InboundTransfersModel {
|
|
|
313
362
|
throw new Error(`ILP condition in transfer prepare for ${prepareRequest.transferId} does not match quote`);
|
|
314
363
|
}
|
|
315
364
|
|
|
316
|
-
|
|
317
|
-
if (quote && this._rejectTransfersOnExpiredQuotes) {
|
|
365
|
+
if (this._rejectTransfersOnExpiredQuotes) {
|
|
318
366
|
const now = new Date().toISOString();
|
|
319
367
|
const expiration = quote.mojaloopResponse.expiration;
|
|
320
368
|
if (now > expiration) {
|
|
321
369
|
const error = Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.QUOTE_EXPIRED);
|
|
322
370
|
this._logger.error(`Error in prepareTransfer: quote expired for transfer ${prepareRequest.transferId}, system time=${now} > quote time=${expiration}`);
|
|
371
|
+
await this.updateStateWithError(error);
|
|
323
372
|
return this._mojaloopRequests.putTransfersError(prepareRequest.transferId, error, sourceFspId);
|
|
324
373
|
}
|
|
325
374
|
}
|
|
@@ -336,6 +385,7 @@ class InboundTransfersModel {
|
|
|
336
385
|
}
|
|
337
386
|
|
|
338
387
|
this._logger.log(`Transfer accepted by backend returning homeTransactionId: ${response.homeTransactionId} for mojaloop transferId: ${prepareRequest.transferId}`);
|
|
388
|
+
this.data.homeTransactionId = response.homeTransactionId;
|
|
339
389
|
|
|
340
390
|
// create a mojaloop transfer fulfil response
|
|
341
391
|
const mojaloopResponse = {
|
|
@@ -350,10 +400,16 @@ class InboundTransfersModel {
|
|
|
350
400
|
};
|
|
351
401
|
|
|
352
402
|
// make a callback to the source fsp with the transfer fulfilment
|
|
353
|
-
|
|
403
|
+
const res = await this._mojaloopRequests.putTransfers(prepareRequest.transferId, mojaloopResponse,
|
|
354
404
|
sourceFspId);
|
|
355
|
-
|
|
356
|
-
|
|
405
|
+
this.data.fulfil = {
|
|
406
|
+
headers: res.originalRequest.headers,
|
|
407
|
+
body: res.originalRequest.body,
|
|
408
|
+
};
|
|
409
|
+
this.data.currentState = this._reserveNotification ? TransferStateEnum.RESERVED : TransferStateEnum.COMPLETED;
|
|
410
|
+
await this._save();
|
|
411
|
+
return res;
|
|
412
|
+
} catch(err) {
|
|
357
413
|
this._logger.push({ err }).log('Error in prepareTransfer');
|
|
358
414
|
const mojaloopError = await this._handleError(err);
|
|
359
415
|
this._logger.push({ mojaloopError }).log(`Sending error response to ${sourceFspId}`);
|
|
@@ -717,22 +773,108 @@ class InboundTransfersModel {
|
|
|
717
773
|
*/
|
|
718
774
|
async sendNotificationToPayee(body, transferId) {
|
|
719
775
|
try {
|
|
720
|
-
|
|
776
|
+
// load any cached state for this transfer e.g. quote request/response etc...
|
|
777
|
+
this.data = await this._load(transferId);
|
|
778
|
+
|
|
779
|
+
// if we didnt have anything cached, start from scratch
|
|
780
|
+
if(!this.data) {
|
|
781
|
+
this.data = {};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// tag the final notification body on to the state
|
|
785
|
+
this.data.finalNotification = body;
|
|
786
|
+
|
|
787
|
+
if(body.transferState === 'COMMITTED') {
|
|
788
|
+
// if the transfer was successful in the switch, set the overall transfer state to COMPLETED
|
|
789
|
+
this.data.currentState = TransferStateEnum.COMPLETED;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
// if the final notification has anything other than COMMITTED as the final state, set an error
|
|
793
|
+
// in the transfer state.
|
|
794
|
+
this.data.currentState == TransferStateEnum.ERROR_OCCURED;
|
|
795
|
+
this.data.lastError = 'Final notification state not COMMITTED';
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
await this._save();
|
|
799
|
+
|
|
800
|
+
const res = await this._backendRequests.putTransfersNotification(this.data, transferId);
|
|
721
801
|
return res;
|
|
722
802
|
} catch (err) {
|
|
723
|
-
this._logger.push({ err }).log('Error
|
|
803
|
+
this._logger.push({ err }).log('Error notifying backend of final transfer state');
|
|
724
804
|
}
|
|
725
805
|
}
|
|
726
806
|
|
|
727
|
-
async _handleError(err
|
|
807
|
+
async _handleError(err) {
|
|
808
|
+
// by default use a generic server error
|
|
809
|
+
let mojaloopError = (new Errors.MojaloopFSPIOPError(err, null, null, Errors.MojaloopApiErrorCodes.INTERNAL_SERVER_ERROR)).toApiErrorObject();
|
|
728
810
|
if(err instanceof HTTPResponseError) {
|
|
811
|
+
// this is an http response error e.g. from calling DFSP backend
|
|
729
812
|
const e = err.getData();
|
|
730
813
|
if(e.res && e.res.data) {
|
|
731
|
-
|
|
814
|
+
// look for a standard mojaloop error that matches the statusCode
|
|
815
|
+
let mojaloopErrorCode = Errors.MojaloopApiErrorCodeFromCode(`${e.res.data.statusCode}`);
|
|
816
|
+
let errorDescription = e.res.data.message;
|
|
817
|
+
if(mojaloopErrorCode) {
|
|
818
|
+
// use the standard mojaloop error object
|
|
819
|
+
mojaloopError = (new Errors.MojaloopFSPIOPError(err, null, null, mojaloopErrorCode)).toApiErrorObject();
|
|
820
|
+
if(errorDescription) {
|
|
821
|
+
// if the error has a description, use that instead of the default mojaloop description
|
|
822
|
+
// note that the mojaloop API spec allows any string up to 128 utf8 characters to be sent
|
|
823
|
+
// in the errorDescription field.
|
|
824
|
+
mojaloopError.errorInformation.errorDescription = errorDescription;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
// this is a custom error, so construct a mojaloop spec body
|
|
829
|
+
mojaloopError = {
|
|
830
|
+
errorInformation: {
|
|
831
|
+
errorCode: e.res.data.statusCode,
|
|
832
|
+
errorDescription: e.res.data.message,
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
}
|
|
732
836
|
}
|
|
733
837
|
}
|
|
838
|
+
if(this.data) {
|
|
839
|
+
//we have persisted state so update that with this error
|
|
840
|
+
this.data.lastError = {
|
|
841
|
+
originalError: err.stack || util.inspect(err),
|
|
842
|
+
mojaloopError: mojaloopError,
|
|
843
|
+
};
|
|
844
|
+
this.data.currentState = TransferStateEnum.ERROR_OCCURRED;
|
|
845
|
+
await this._save();
|
|
846
|
+
}
|
|
847
|
+
return mojaloopError;
|
|
848
|
+
}
|
|
734
849
|
|
|
735
|
-
|
|
850
|
+
/**
|
|
851
|
+
* Persists the model state to cache for reinstantiation at a later point
|
|
852
|
+
*/
|
|
853
|
+
async _save() {
|
|
854
|
+
try {
|
|
855
|
+
const res = await this._cache.set(`transferModel_in_${this.data.transferId}`, this.data);
|
|
856
|
+
this._logger.push({ res }).log('Persisted transfer model in cache');
|
|
857
|
+
}
|
|
858
|
+
catch(err) {
|
|
859
|
+
this._logger.push({ err }).log('Error saving transfer model');
|
|
860
|
+
throw err;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Loads a transfer model from cache for resumption of the transfer process
|
|
866
|
+
*
|
|
867
|
+
* @param transferId {string} - UUID transferId of the model to load from cache
|
|
868
|
+
*/
|
|
869
|
+
async _load(transferId) {
|
|
870
|
+
try {
|
|
871
|
+
const data = await this._cache.get(`transferModel_in_${transferId}`);
|
|
872
|
+
return data;
|
|
873
|
+
}
|
|
874
|
+
catch(err) {
|
|
875
|
+
this._logger.push({ err }).log('Error loading transfer model');
|
|
876
|
+
throw err;
|
|
877
|
+
}
|
|
736
878
|
}
|
|
737
879
|
}
|
|
738
880
|
|
|
@@ -155,17 +155,16 @@ class OutboundRequestToPayModel {
|
|
|
155
155
|
// hook up a subscriber to handle response messages
|
|
156
156
|
const subId = await this._cache.subscribe(payeeKey, (cn, msg, subId) => {
|
|
157
157
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if(payee.errorInformation) {
|
|
158
|
+
this.data.getPartiesResponse = JSON.parse(msg);
|
|
159
|
+
if(this.data.getPartiesResponse.body.errorInformation) {
|
|
161
160
|
// this is an error response to our GET /parties request
|
|
162
|
-
const err = new BackendError(`Got an error response resolving party: ${util.inspect(
|
|
163
|
-
err.mojaloopError =
|
|
164
|
-
|
|
161
|
+
const err = new BackendError(`Got an error response resolving party: ${util.inspect(this.data.getPartiesResponse.body, { depth: Infinity })}`, 500);
|
|
162
|
+
err.mojaloopError = this.data.getPartiesResponse.body;
|
|
165
163
|
// cancel the timeout handler
|
|
166
164
|
clearTimeout(timeout);
|
|
167
165
|
return reject(err);
|
|
168
166
|
}
|
|
167
|
+
let payee = this.data.getPartiesResponse.body;
|
|
169
168
|
|
|
170
169
|
if(!payee.party) {
|
|
171
170
|
// we should never get a non-error response without a party, but just in case...
|