Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add iso 20022 compatibility #527

Merged
merged 175 commits into from
Jan 27, 2025
Merged
Changes from 1 commit
Commits
Show all changes
175 commits
Select commit Hold shift + click to select a range
f43fc47
feat(3618): added support for POST /fxQuotes and /fxTransfers inbound…
geka-evk Nov 30, 2023
baaea1e
feat(mojaloop/#3655): add support for parties supportedcurrencies & k…
aaronreynoza Nov 30, 2023
e4c3733
fix: unit test
vijayg10 Nov 30, 2023
bbefa17
chore(snapshot): 23.2.0-snapshot.0
vijayg10 Nov 30, 2023
9c1a61c
feat(3654): added FX States and Transitions for OutboundTransfersMode…
geka-evk Dec 1, 2023
b5b6c45
feat(3654): added fxQuote step to outbound model; added inbound fxQuo…
geka-evk Dec 1, 2023
211c138
feat(3654): added fxTransfer flow (#462)
geka-evk Dec 4, 2023
5427670
feat(3654): added subscribeToOneMessageWithTimer(); simplified FX han…
geka-evk Dec 6, 2023
296152f
chore(snapshot): 23.2.0-snapshot.3
vijayg10 Dec 6, 2023
20ba891
feat(mojaloop/#3689): fx quotes changes (#469)
oderayi Mar 8, 2024
060732d
chore(snapshot): 23.2.0-snapshot.4
oderayi Mar 8, 2024
8676532
feat(3784): updated deps
geka-evk Mar 12, 2024
618227f
feat(3784): updated deps
geka-evk Mar 12, 2024
94a76ce
feat(3784): updated from master
geka-evk Mar 12, 2024
d5749f9
feat(3784): updated @mojaloop/central-services-shared version
geka-evk Mar 12, 2024
ed242e5
feat(3784): updated @mojaloop/sdk-standard-components version
geka-evk Mar 12, 2024
710581e
chore: update enabled checks
kleyow Mar 12, 2024
aa8bb60
feat(3784): updated @mojaloop/sdk-standard-components version to v18.…
geka-evk Mar 13, 2024
f2558e6
Merge branch 'feat/fx-impl' of github.com:mojaloop/sdk-scheme-adapter…
geka-evk Mar 13, 2024
50bfb9d
chore(snapshot): 23.5.0-snapshot.0
geka-evk Mar 13, 2024
0463636
chore(snapshot): 23.2.0-snapshot.5
oderayi Mar 15, 2024
cabb328
chore(snapshot): 23.2.0-snapshot.6
oderayi Mar 15, 2024
94a81ab
fix: oauth tls
vijayg10 Mar 18, 2024
57801c9
chore(snapshot): 23.5.0-snapshot.1
vijayg10 Mar 18, 2024
72c62b6
fix: audit
vijayg10 Mar 18, 2024
1e02c90
chore(snapshot): 23.5.0-snapshot.2
vijayg10 Mar 18, 2024
faba4ca
fix: fx jws
vijayg10 Mar 21, 2024
555ecdd
chore(snapshot): 23.5.0-snapshot.3
vijayg10 Mar 21, 2024
1bc8950
chore(snapshot): 23.5.0-snapshot.4
vijayg10 Mar 21, 2024
1ee91b6
feat: configurable generation of ids (#484)
kalinkrustev Jun 10, 2024
568c161
chore(mojaloop/#3909): add unit tests for fx flow (#488)
kleyow Jun 24, 2024
9c16f21
chore(snapshot): 20.7.0-snapshot.17
kleyow Jun 24, 2024
209b5b3
Merge remote-tracking branch 'origin' into feat/fx-impl
kleyow Jun 24, 2024
2be1881
fix
kleyow Jun 24, 2024
1d847e1
audit
kleyow Jun 24, 2024
f2f0681
chore(snapshot): 23.6.0-snapshot.0
kleyow Jun 24, 2024
308430f
chore(mojaloop/#3909): update func test harness and add fx sdk tests …
kleyow Jun 26, 2024
36057c4
fix: fx quotes error
vijayg10 Jul 1, 2024
1c2ddcd
fix: deps
vijayg10 Jul 2, 2024
35c7713
chore(snapshot): 23.6.0-snapshot.1
vijayg10 Jul 2, 2024
fa95843
fix(csi-498): added logs to incoming requests (#491)
geka-evk Aug 19, 2024
ce18210
Merge branch 'feat/fx-impl' of github.com:mojaloop/sdk-scheme-adapter…
oderayi Aug 28, 2024
d64ec2a
chore(snapshot): 23.6.0-snapshot.4
oderayi Aug 28, 2024
fd03a6e
fix(csi-532): added logs for restarting (#492)
geka-evk Sep 4, 2024
c1cb3a8
fix: new public jws keys hot reload (#494)
vijayg10 Sep 5, 2024
17f3802
chore(snapshot): 23.6.0-snapshot.9
vijayg10 Sep 5, 2024
c6b5cad
chore(snapshot): 23.6.0-snapshot.10
vijayg10 Sep 5, 2024
6912693
chore(snapshot): 23.6.0-snapshot.11
vijayg10 Sep 5, 2024
f31f4d8
fix: memory leaks (#497)
kalinkrustev Sep 13, 2024
1287ca4
chore(snapshot): 23.6.0-snapshot.12
kalinkrustev Sep 13, 2024
592657c
chore(snapshot): 23.6.0-snapshot.13
kalinkrustev Sep 13, 2024
22a7ac2
chore(snapshot): 23.6.0-snapshot.14
kalinkrustev Sep 13, 2024
22b82f1
fix: empty supported currencies (#495)
oderayi Sep 18, 2024
07d9dd5
chore(snapshot): 23.6.0-snapshot.15
kalinkrustev Sep 18, 2024
cb15127
feat: add ULID support (#499)
kalinkrustev Oct 1, 2024
8542d2d
feat: IME 115 ADD patch fx transfer (#498)
Ujjwal-Izyane Oct 1, 2024
7703e4b
fix: sonar security hot spots
kalinkrustev Oct 21, 2024
9e39adb
Fix many broken things after updating deps as required by commit hooks.
bushjames Oct 24, 2024
cec835f
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
ba7a030
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
befc280
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
cf2eff4
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
34fdc2c
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
2d35d9e
attempt to fix python3 missing distutil issue for node gyp
bushjames Oct 24, 2024
5b72313
bump deps yet again to fix whining ci.
bushjames Oct 24, 2024
dc2715f
bump deps yet again to fix whining ci.
bushjames Oct 24, 2024
6c36875
bump deps yet again to fix whining ci.
bushjames Oct 24, 2024
8594b68
add exceptions to audit temporarily
bushjames Oct 24, 2024
cc001c1
outbound ISO20022 translation working for all three phases
bushjames Oct 25, 2024
b298ec0
inbound translation from iso20022 to fspiop for parties, quotes and t…
bushjames Oct 25, 2024
fe9b6ee
add ILP version switch to config.
bushjames Oct 25, 2024
dacbcd3
feat(csi-194): updated deps; fixed unit-tests
geka-evk Oct 28, 2024
6d7c3c0
feat(csi-110): updated deps
geka-evk Oct 28, 2024
afb9b6d
feat(csi-110): updated from feat/fx-impl
geka-evk Oct 28, 2024
1217881
feat(csi-110): fixed unit tests
geka-evk Oct 28, 2024
23e15c2
feat(csi-110): fixed unit tests
geka-evk Oct 28, 2024
d55d63e
feat(csi-110): updated deps
geka-evk Oct 28, 2024
e30ed74
feat(csi-110): added ilpFactory
geka-evk Oct 28, 2024
51e1650
feat(csi-110): made Inbound handlers dynamically detect API_TYPE and …
geka-evk Oct 28, 2024
925825e
chore: add context for iso put quote/post transfer
kleyow Oct 28, 2024
6e0f0b1
feat(csi-110): added transformation for ISO POST fxQuotes/fxTransfers
geka-evk Oct 29, 2024
84f33fe
feat(csi-110): added transformation for ISO error callbacks
geka-evk Oct 29, 2024
2b51790
chore(snapshot): 23.6.0-snapshot.17
geka-evk Oct 29, 2024
7e05b0f
chore(snapshot): 23.6.0-snapshot.18
geka-evk Oct 29, 2024
ee9ad3d
feat(csi-110): fixed headers and body validation for ISO requests
geka-evk Oct 29, 2024
ab8f06f
chore(snapshot): 23.6.0-snapshot.19
geka-evk Oct 29, 2024
06315cd
feat(csi-110): updated sdk-standard-components version
geka-evk Oct 30, 2024
b3e1e41
chore(snapshot): 23.6.0-snapshot.20
geka-evk Oct 30, 2024
69d6fd3
feat(csi-110): updated ml-schema-transformer-lib
geka-evk Oct 30, 2024
2759f31
chore(snapshot): 23.6.0-snapshot.21
geka-evk Oct 30, 2024
d17f38b
feat(csi-110): updated ml-schema-transformer-lib
geka-evk Oct 30, 2024
3282db0
chore(snapshot): 23.6.0-snapshot.22
geka-evk Oct 30, 2024
19b810d
feat(csi-110): updated ml-schema-transformer-lib
geka-evk Oct 30, 2024
7c28c98
chore(snapshot): 23.6.0-snapshot.23
geka-evk Oct 30, 2024
70a3f73
feat(csi-110): updated ml-schema-transformer-lib
geka-evk Oct 30, 2024
5b696d3
chore(snapshot): 23.6.0-snapshot.24
geka-evk Oct 30, 2024
572c40f
feat(csi-110): updated ml-schema-transformer-lib and sdk-standard-com…
geka-evk Oct 30, 2024
e19e316
chore(snapshot): 23.6.0-snapshot.25
geka-evk Oct 30, 2024
428eb00
feat(csi-110): updated ml-schema-transformer-lib and sdk-standard-com…
geka-evk Oct 30, 2024
c4b86cd
chore(snapshot): 23.6.0-snapshot.26
geka-evk Oct 30, 2024
1854dd8
chore: store fspiop payloads for websocket test server (#505)
kleyow Oct 30, 2024
d32a8d9
chore(snapshot): 23.6.0-snapshot.27
kalinkrustev Oct 30, 2024
527a5d0
feat: transform iso headers for test server fspiop payload (#507)
oderayi Oct 31, 2024
ef1e90c
chore: update deps (#508)
oderayi Oct 31, 2024
5345906
chore(snapshot): 23.6.0-snapshot.29
kleyow Oct 31, 2024
f5759b5
chore(snapshot): 23.6.0-snapshot.30
kleyow Oct 31, 2024
5e239b5
chore(csi-110): fixed ILP creation logic (#506)
geka-evk Oct 31, 2024
15ccf57
chore: test server fix (#509)
kleyow Oct 31, 2024
200d731
fix: inbound /transactionRequests with only FSPIOP header (#510)
geka-evk Nov 5, 2024
a83cf5d
chore(snapshot): 23.6.0-snapshot.32
geka-evk Nov 5, 2024
ccfaff5
chore(snapshot): 23.6.0-snapshot.33
geka-evk Nov 5, 2024
7939c2c
chore: start using the mojaloop/build orb (#511)
kalinkrustev Nov 5, 2024
04d9aa5
chore(release): [ci skip] 23.6.0-iso.0
Nov 5, 2024
617acad
fix: updated sdk-standard-components
geka-evk Nov 5, 2024
70ed012
chore(release): [ci skip] 23.6.0-iso.1
Nov 5, 2024
a6e1f47
fix: put-transaction-requests-callback (#512)
geka-evk Nov 5, 2024
d0b382a
chore(release): [ci skip] 23.6.0-iso.2
Nov 5, 2024
59ac9bf
chore: updated deps
geka-evk Nov 12, 2024
4f171c8
chore(release): [ci skip] 23.6.0-iso.3
Nov 12, 2024
3dc2a67
feat(csi-128): updated sdk-standard-components with axios impl.; fixe…
geka-evk Nov 20, 2024
676fec1
chore(release): [ci skip] 23.6.0-iso.4
Nov 20, 2024
e0f6a41
feat(csi-303): added support for FX transfer RECEIVE type (#516)
geka-evk Nov 20, 2024
f0847d9
chore(release): [ci skip] 23.6.0-iso.5
Nov 20, 2024
e4e2eff
fix: participants not working (#517)
vijayg10 Nov 20, 2024
378acdd
chore(release): [ci skip] 23.6.0-iso.6
Nov 20, 2024
94c188d
feat(csi-303): added RESOURCE_VERSIONS_STRING (#519)
geka-evk Nov 21, 2024
2ea2299
chore(release): [ci skip] 23.6.0-iso.7
Nov 21, 2024
e7eb678
fix: participant resource versions
vijayg10 Nov 21, 2024
784e051
chore(release): [ci skip] 23.6.0-iso.8
Nov 21, 2024
410771d
feat(csi-927): excluded internal routes from logging (#522)
geka-evk Dec 12, 2024
fc2ab7b
chore(release): [ci skip] 23.6.0-iso.9
Dec 12, 2024
196f827
fix(csi-1023): used updated api-snippets (#523)
geka-evk Dec 13, 2024
4f5d281
chore(release): [ci skip] 23.6.0-iso.10
Dec 13, 2024
0763a93
feat: allow optional dfspId to be in the endpoint path (#526)
kalinkrustev Jan 10, 2025
8e37d8f
chore(release): [ci skip] 23.6.0-iso.11
Jan 10, 2025
d812b71
feat: allow optional dfspId to be in the endpoint path (#528)
kalinkrustev Jan 13, 2025
4655ef6
chore(release): [ci skip] 23.6.0-iso.12
Jan 13, 2025
134736b
fix: handle multiDfsp
kalinkrustev Jan 14, 2025
0afbca5
chore(release): [ci skip] 23.6.0-iso.13
Jan 14, 2025
3d7a3d9
feat(ime-306): fix experience api crashing issue (#525)
Ujjwal-Izyane Jan 14, 2025
579f927
chore(release): [ci skip] 23.6.0-iso.14
Jan 14, 2025
0e27cc6
Merge remote-tracking branch 'origin/master' into minor/iso
kleyow Jan 17, 2025
147e64d
chore: dep update
kleyow Jan 17, 2025
cf9cef1
chore(release): [ci skip] 23.6.0-iso.15
Jan 17, 2025
d18e3a6
chore: add build phase script to package.json
kalinkrustev Jan 19, 2025
f45d550
chore(release): [ci skip] 23.6.0-iso.16
Jan 19, 2025
55ef1f1
feat(ime-5): extension list caching changes (#530)
Ujjwal-Izyane Jan 21, 2025
713d1f9
chore(release): [ci skip] 23.6.0-iso.17
Jan 21, 2025
0bd5d64
chore: address comments
kleyow Jan 23, 2025
edbca54
chore: address comments
kleyow Jan 23, 2025
19d23f2
chore: lock
kleyow Jan 23, 2025
8babf91
chore: lock
kleyow Jan 23, 2025
520438a
chore(release): [ci skip] 23.6.0-iso.18
Jan 23, 2025
19c43b1
fix: restore mem leak fix #497
kalinkrustev Jan 23, 2025
4361db7
chore(release): [ci skip] 23.6.0-iso.19
Jan 23, 2025
6a4c4e9
chore: address comments
kleyow Jan 23, 2025
9e3a2b8
chore: why is test failing again?
kleyow Jan 23, 2025
d8ebf34
chore: fix?
kleyow Jan 23, 2025
ecf1ff2
chore: update snippets with reordered resources
kleyow Jan 24, 2025
fa0cb9d
chore: give down more time
kleyow Jan 24, 2025
01ed1e5
chore: fix
kleyow Jan 24, 2025
7e4a258
chore(release): [ci skip] 23.6.0-iso.20
Jan 24, 2025
4cb9bfb
chore: add message on close
kleyow Jan 24, 2025
27a764e
chore lockfile
kleyow Jan 24, 2025
ec073aa
chore(release): [ci skip] 23.6.0-iso.21
Jan 24, 2025
cb80504
chore: tin foil hat
kleyow Jan 24, 2025
eb1226a
chore(release): [ci skip] 23.6.0-iso.22
Jan 24, 2025
2c8ab01
chore: tin foil hat
kleyow Jan 24, 2025
ce8a451
chore: tin foil hat
kleyow Jan 24, 2025
89bd67f
chore(release): [ci skip] 23.6.0-iso.23
Jan 24, 2025
d619a89
chore: tin foil hat
kleyow Jan 24, 2025
5512581
Merge remote-tracking branch 'origin/minor/iso' into minor/iso
kleyow Jan 24, 2025
3b10a69
chore(release): [ci skip] 23.6.0-iso.24
Jan 24, 2025
4ea51d6
chore: address comments
kleyow Jan 24, 2025
4b43d64
chore(release): [ci skip] 23.6.0-iso.25
Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(3654): added fxQuote step to outbound model; added inbound fxQuo…
…tes handler (#461)

* feat(3618): added support for POST /fxQuotes and /fxTransfers inbound requests

* feat(3618): added support for POST /fxQuotes and /fxTransfers inbound requests

* feat(3618): ignored some vulnerabilities

* feat(3618): added unit-test for postFxQuote model

* feat(3618): added unit-test for postFxQuote model

* feat(3618): added unit-tests for postFxTransfers method

* feat(3618): added FX endpoints to .env

* chore(snapshot): 20.7.0-snapshot.2

* feat(3618): updated @mojaloop/central-services-shared version to support FX resources

* chore(snapshot): 20.7.0-snapshot.3

* feat(3618): disabled @mojaloop/central-services-shared"downgrading" from snapshot version (by dep:update)

* chore(snapshot): 20.7.0-snapshot.4

* feat(3618): added ilp unit-test;  updated Ilp version

* chore(snapshot): 20.7.0-snapshot.5

* feat(3618): updated ilp packet version (with amount)

* chore(snapshot): 20.7.0-snapshot.6

* feat(3654): added FX States and Transitions for OutboundTransfersModel;  small refactoring

* feat(3654): added ErrorMessages; updated deps

* feat(3654): added fxQuote step to outbound model

* feat(3654): added fxQuotes inbound handler

* fix: fxquotes

---------

Co-authored-by: Vijay <vijaya.guthi@infitx.com>
  • Loading branch information
geka-evk and vijayg10 authored Dec 1, 2023
commit b5b6c45b85a77603a2164312a27d50bb3dbe1466
37 changes: 37 additions & 0 deletions modules/api-svc/src/InboundServer/handlers.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ const {
QuotesModel,
TransfersModel,
} = require('../lib/model');
const { CacheKeyPrefixes } = require('../lib/model/common');

const extractBodyHeadersSourceFspId = ctx => ({
sourceFspId: ctx.request.headers['fspiop-source'],
@@ -974,6 +975,36 @@ const postFxQuotes = async (ctx) => {
prepareResponse(ctx);
};

/**
* Create a handler for PUT /fxQuotes/{ID} and PUT /fxQuotes/{ID}/error routes
*
* @param success {boolean} - false is for handling error callback response
*/
const createPutFxQuoteHandler = (success) => async (ctx) => {
const { body, headers } = extractBodyHeadersSourceFspId(ctx);
const { ID } = ctx.state.path.params;

const channel = `${CacheKeyPrefixes.FX_QUOTE_CALLBACK_CHANNEL}_${ID}`;
await ctx.state.cache.publish(channel, {
success,
data: { body, headers },
type: `fxQuoteResponse${success ? '' : 'Error'}`
});

// todo: think, what does it mean in putQuote handler!
kleyow marked this conversation as resolved.
Show resolved Hide resolved
//
// duplicate publication until legacy code refactored
// await QuotesModel.triggerDeferredJob({
// cache: ctx.state.cache,
// message: data,
// args: {
// quoteId
// }
// });

ctx.response.status = ReturnCodes.OK.CODE;
};

const postFxTransfers = async (ctx) => {
const { body, headers, sourceFspId } = extractBodyHeadersSourceFspId(ctx);
const { logger } = ctx.state;
@@ -1081,6 +1112,12 @@ module.exports = {
'/fxQuotes': {
post: postFxQuotes
},
'/fxQuotes/{ID}': {
put: createPutFxQuoteHandler(true)
},
'/fxQuotes/{ID}/error': {
put: createPutFxQuoteHandler(false)
},
'/fxTransfers': {
post: postFxTransfers
}
35 changes: 34 additions & 1 deletion modules/api-svc/src/lib/dto.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/*eslint quote-props: ["error", "as-needed"]*/
const { randomUUID } = require('node:crypto');
const { Directions, SDKStateEnum} = require('./model/common');

const quoteRequestStateDto = (request) => ({
@@ -23,7 +25,38 @@ const fxQuoteRequestStateDto = (request) => ({
initiatedTimestamp: new Date().toISOString(),
});

/**
* @param data {object} - "state" of inbound transaction request
*/
const outboundPostFxQuotePayloadDto = (data) => Object.freeze({
conversionRequestId: randomUUID(),
conversionTerms: {
conversionId: randomUUID(), // should be the same as commitRequestId from fxTransfer
initiatingFsp: data.from.fspId,
counterPartyFsp: data.fxProviders[0], // todo: think if we have several FXPs
amountType: data.amountType,
sourceAmount: {
currency: data.currency,
amount: data.amount
},
targetAmount: {
currency: data.supportedCurrencies[0], // todo: think if we have several currencies
},
expiration: data.fxQuoteExpiration,
}
});

/**
* @param data {object} - "state" of inbound transaction request
*/
const outboundPostFxTransferPayloadDto = (data) => Object.freeze({
commitRequestId: data.transferId, // should be the same as conversionTerms.conversionId from fxQuote
// todo: add other fields
});

module.exports = {
quoteRequestStateDto,
fxQuoteRequestStateDto
fxQuoteRequestStateDto,
outboundPostFxQuotePayloadDto,
outboundPostFxTransferPayloadDto,
};
2 changes: 1 addition & 1 deletion modules/api-svc/src/lib/model/InboundTransfersModel.js
Original file line number Diff line number Diff line change
@@ -1046,7 +1046,7 @@ class InboundTransfersModel {
if (!conversionId) {
throw new Error('No conversionId for making cache key');
}
return `${CacheKeyPrefixes.FX_QUOTE}_${conversionId}`;
return `${CacheKeyPrefixes.FX_QUOTE_INBOUND}_${conversionId}`;
}
}

112 changes: 104 additions & 8 deletions modules/api-svc/src/lib/model/OutboundTransfersModel.js
Original file line number Diff line number Diff line change
@@ -16,11 +16,14 @@ const StateMachine = require('javascript-state-machine');
const { Enum } = require('@mojaloop/central-services-shared');
const { Ilp, MojaloopRequests } = require('@mojaloop/sdk-standard-components');

const dto = require('../dto');
const shared = require('./lib/shared');
const PartiesModel = require('./PartiesModel');
const {
AmountTypes,
BackendError,
CacheKeyPrefixes,
CurrencyConverters,
Directions,
ErrorMessages,
SDKStateEnum,
@@ -347,11 +350,15 @@ class OutboundTransfersModel {
}

if (Array.isArray(payee.supportedCurrencies)) {
if (!payee.supportedCurrencies.length) {
throw new Error(ErrorMessages.noSupportedCurrencies);
}
const needFx = !payee.supportedCurrencies.includes(this.data.currency);
if (needFx && this.data.amountType !== AmountTypes.SEND) {
throw new Error(ErrorMessages.unsupportedFxAmountType);
}
this.data.needFx = needFx;
this.data.supportedCurrencies = payee.supportedCurrencies;
}

return resolve(payee);
@@ -509,15 +516,81 @@ class OutboundTransfersModel {
async _requestServicesFxp() {
this.data.fxProviders = this.getServicesFxpResponse;
// todo: add impl. with real http-request
kleyow marked this conversation as resolved.
Show resolved Hide resolved
if (!this.data.fxProviders?.length) {
throw new Error(ErrorMessages.noFxProviderDetected);
}
return this.data.fxProviders;
}

async _requestFxQuote() {
// todo: add impl.
// 1. build fxQuote payload
// 2. subscribe to cache stream by conversionRequestId (to await fxQuotes callback)
// 2.a - handle error response as well
// 3. send POST fxQuote request to hub
let timer;
let channel;
let subId;

// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
try {
this.data.fxQuoteExpiration = this._getExpirationTimestamp();
const payload = dto.outboundPostFxQuotePayloadDto(this.data);

channel = `${CacheKeyPrefixes.FX_QUOTE_CALLBACK_CHANNEL}_${payload.conversionRequestId}`;

timer = setTimeout(() => {
this.unsubscribeCache(channel, subId);
const errMessage = `Timeout requesting fxQuote for transfer ${this.data.transferId}`;
const err = new BackendError(errMessage, 504);
this._logger.push({ err }).log(`fxQuote payload: ${JSON.stringify(payload)}`);
reject(err);
}, this._requestProcessingTimeoutSeconds * 1000);

subId = await this._cache.subscribe(channel, (cn, msg, subId) => {
try {
clearTimeout(timer);
this.unsubscribeCache(channel, subId);

const message = JSON.parse(msg);

const { body, headers } = message.data;
this._logger.push({ body }).log('fxQuote response received');

if (!message.success) {
const error = new BackendError(`Got an error response requesting fxQuote: ${util.inspect(body, { depth: Infinity })}`, 500);
error.mojaloopError = body;
throw error;
}

if (this._rejectExpiredQuoteResponses) {
const now = new Date().toISOString();
if (now > this.data.fxQuoteExpiration) {
const errMessage = `${ErrorMessages.responseMissedExpiryDeadline} (fxQuote)`;
this._logger.warn(`${errMessage}: system time=${now} > expiration time=${this.data.fxQuoteExpiration}`);
throw new BackendError(errMessage, 504);
}
}

this.data.fxQuoteResponse = {
body,
headers,
};
this.data.fxQuoteResponseSource = headers['fspiop-source']; // todo: check what for we need this

resolve(payload); // todo: think, what should we return at this point
} catch (err) {
this._logger.push({ err }).log(`error in fxQuote cache subscription processing: ${err?.message}`);
reject(err);
}
});

const res = await this._requests.postFxQuotes(payload, payload.conversionTerms.counterPartyFsp);
this.data.fxQuoteRequest = res.originalRequest;
this._logger.push({ res }).log('fxQuote request is sent to hub');
} catch (err) {
this._logger.push({ err }).log(`error in _requestFxQuote: ${err.message}`);
if (timer) clearTimeout(timer);
this.unsubscribeCache(channel, subId);
reject(err);
}
});
}

/**
@@ -675,12 +748,17 @@ class OutboundTransfersModel {
}

// add extensionList if provided
if(this.data.quoteRequestExtensions && this.data.quoteRequestExtensions.length > 0) {
if (this.data.quoteRequestExtensions?.length) {
quote.extensionList = {
extension: this.data.quoteRequestExtensions
};
}

// TODO: re-enable this after updating quoting service
// if (this.data.needFx) {
// quote.converter = CurrencyConverters.PAYER;
// }

return quote;
}

@@ -1108,13 +1186,20 @@ class OutboundTransfersModel {
break;

case States.FX_QUOTE_RECEIVED:
if (!this.data.acceptFxQuote) {
if (!this.data.acceptConversion) {
await this.stateMachine.abort('FX quote rejected by backend');
await this._save();
return this.getResponse();
}
await this.stateMachine.requestQuote();
this._logger.log(`Transfer ${this.data.transferId} has been completed`);
this._logger.log(`Quote received for transfer ${this.data.transferId}`);

if (this.stateMachine.state === States.QUOTE_RECEIVED && !this._autoAcceptQuotes) {
//we break execution here and return the quote response details to allow asynchronous accept or reject
//of the quote
await this._save();
return this.getResponse();
}
break;

case States.QUOTE_RECEIVED:
@@ -1194,6 +1279,17 @@ class OutboundTransfersModel {
throw err;
}
}

async unsubscribeCache(channelKey, subId) {
if (channelKey && subId) {
return this._cache.unsubscribe(channelKey, subId)
.catch(err => {
this._logger.push({ err }).log(`Unsubscribing cache error [${channelKey} ${subId}]: ${err.stack}`);
});
}
}


}

module.exports = OutboundTransfersModel;
13 changes: 12 additions & 1 deletion modules/api-svc/src/lib/model/common/Enums.js
Original file line number Diff line number Diff line change
@@ -43,7 +43,14 @@ const Directions = Object.freeze({
});

const CacheKeyPrefixes = Object.freeze({
FX_QUOTE: 'fxQuote_in'
FX_QUOTE_INBOUND: 'fxQuote_in',
FX_QUOTE_CALLBACK_CHANNEL: 'fxQuote_callback',
FX_TRANSFER_CALLBACK_CHANNEL: 'fxQuote_callback',
});

const CurrencyConverters = Object.freeze({
PAYER: 'PAYER',
PAYEE: 'PAYEE',
});

const States = Object.freeze({
@@ -71,6 +78,9 @@ const Transitions = Object.freeze({
});

const ErrorMessages = Object.freeze({
noFxProviderDetected: 'No FX provider detected',
noSupportedCurrencies: 'No payee supportedCurrencies received',
responseMissedExpiryDeadline: 'Response missed expiry deadline',
unsupportedFxAmountType: 'Unsupported amountType when currency conversion is needed',
});

@@ -82,6 +92,7 @@ const AmountTypes = Object.freeze({
module.exports = {
AmountTypes,
CacheKeyPrefixes,
CurrencyConverters,
Directions,
ErrorMessages,
SDKStateEnum,
Original file line number Diff line number Diff line change
@@ -22,13 +22,14 @@ const PartiesModel = require('~/lib/model').PartiesModel;
const { MojaloopRequests, Logger } = require('@mojaloop/sdk-standard-components');
const StateMachine = require('javascript-state-machine');

const mocks = require('./data/mocks');
const defaultConfig = require('./data/defaultConfig');
const transferRequest = require('./data/transferRequest');
const payeeParty = require('./data/payeeParty');
const quoteResponseTemplate = require('./data/quoteResponse');
const transferFulfil = require('./data/transferFulfil');

const { SDKStateEnum } = require('../../../../src/lib/model/common');
const { SDKStateEnum, CacheKeyPrefixes, States } = require('../../../../src/lib/model/common');
const FSPIOPTransferStateEnum = require('@mojaloop/central-services-shared').Enum.Transfers.TransferState;

const genPartyId = (party) => {
@@ -1576,4 +1577,55 @@ describe('outboundModel', () => {
expect(StateMachine.__instance.state).toBe('quoteReceived');
});

describe('FX flow Tests -->', () => {
let model;

beforeEach(() => {
model = new Model({
cache,
logger,
metricsClient,
...config,
});
});

afterEach(async () => {
jest.clearAllMocks();
});

test('should process callback for POST fxQuotes request', async () => {
model._requests.postFxQuotes = jest.fn(async (payload) => {
// eslint-disable-next-line no-unused-vars
const { conversionRequestId, ...restPayload} = payload;
const channel = `${CacheKeyPrefixes.FX_QUOTE_CALLBACK_CHANNEL}_${payload.conversionRequestId}`;
const cachedCallbackPayload = {
success: true,
data: {
body: {
...restPayload,
condition: 'fxCondition'
},
headers: {},
}
};
await cache.publish(channel, JSON.stringify(cachedCallbackPayload));
return mocks.mockMojaApiResponse();
});

await model.initialize({
...mocks.coreConnectorPostTransfersPayloadDto(),
currentState: States.PAYEE_RESOLVED,
needFx: true,
supportedCurrencies: ['USD']
});
expect(model.data.currentState).toBe(States.PAYEE_RESOLVED);

const result = await model.run();
expect(result).toBeTruthy();
expect(result.fxQuoteRequest).toBeTruthy();
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_CONVERSION_ACCEPTANCE);
expect(model.data.currentState).toBe(States.FX_QUOTE_RECEIVED);
// todo: add more tests
});
});
});
Loading