Skip to content

Commit

Permalink
fix: skip validation when payer and payee are represented by proxy (#…
Browse files Browse the repository at this point in the history
…1086)

* fix: test

* add unit tests

* update saveTransferPrepared

* chore: int tests

* retry count

---------

Co-authored-by: Vijay <vijaya.guthi@infitx.com>
  • Loading branch information
kleyow and vijayg10 authored Aug 26, 2024
1 parent efe0f24 commit 986b3d8
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 61 deletions.
32 changes: 19 additions & 13 deletions src/domain/fx/cyril.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,38 @@ const { fxTransfer, watchList } = require('../../models/fxTransfer')
const Config = require('../../lib/config')
const ProxyCache = require('../../lib/proxyCache')

const checkIfDeterminingTransferExistsForTransferMessage = async (payload) => {
const checkIfDeterminingTransferExistsForTransferMessage = async (payload, proxyObligation) => {
// Does this determining transfer ID appear on the watch list?
const watchListRecords = await watchList.getItemsInWatchListByDeterminingTransferId(payload.transferId)
const determiningTransferExistsInWatchList = (watchListRecords !== null && watchListRecords.length > 0)
// Create a list of participants and currencies to validate against
const participantCurrencyValidationList = []
if (determiningTransferExistsInWatchList) {
// If there's a currency conversion before the transfer is requested, it must be the debtor who did it.
participantCurrencyValidationList.push({
participantName: payload.payeeFsp,
currencyId: payload.amount.currency
})
if (!proxyObligation.isCounterPartyFspProxy) {
participantCurrencyValidationList.push({
participantName: payload.payeeFsp,
currencyId: payload.amount.currency
})
}
} else {
// Normal transfer request or payee side currency conversion
participantCurrencyValidationList.push({
participantName: payload.payerFsp,
currencyId: payload.amount.currency
})
// If it is a normal transfer, we need to validate payeeFsp against the currency of the transfer.
// But its tricky to differentiate between normal transfer and payee side currency conversion.
if (Config.PAYEE_PARTICIPANT_CURRENCY_VALIDATION_ENABLED) {
if (!proxyObligation.isInitiatingFspProxy) {
participantCurrencyValidationList.push({
participantName: payload.payeeFsp,
participantName: payload.payerFsp,
currencyId: payload.amount.currency
})
}
// If it is a normal transfer, we need to validate payeeFsp against the currency of the transfer.
// But its tricky to differentiate between normal transfer and payee side currency conversion.
if (Config.PAYEE_PARTICIPANT_CURRENCY_VALIDATION_ENABLED) {
if (!proxyObligation.isCounterPartyFspProxy) {
participantCurrencyValidationList.push({
participantName: payload.payeeFsp,
currencyId: payload.amount.currency
})
}
}
}
return {
determiningTransferExistsInWatchList,
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/transfers/createRemittanceEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const createRemittanceEntity = (isFx) => {
async checkIfDeterminingTransferExists (payload, proxyObligation) {
return isFx
? cyril.checkIfDeterminingTransferExistsForFxTransferMessage(payload, proxyObligation)
: cyril.checkIfDeterminingTransferExistsForTransferMessage(payload)
: cyril.checkIfDeterminingTransferExistsForTransferMessage(payload, proxyObligation)
},

async getPositionParticipant (payload, determiningTransferCheckResult, proxyObligation) {
Expand Down
4 changes: 3 additions & 1 deletion src/models/transfer/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida
const participantCurrencyRecord = await ParticipantFacade.getByNameAndCurrency(
proxyId, payload.amount.currency, Enum.Accounts.LedgerAccountType.POSITION
)
participants[proxyId].participantCurrencyId = participantCurrencyRecord.participantCurrencyId
// In a regional scheme, the stand-in initiating FSP proxy may not have a participantCurrencyId
// of the target currency of the transfer, so set to null if not found
participants[proxyId].participantCurrencyId = participantCurrencyRecord?.participantCurrencyId
}

if (proxyObligation?.isCounterPartyFspProxy) {
Expand Down
122 changes: 79 additions & 43 deletions test/integration-override/handlers/transfers/handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,69 +156,74 @@ const prepareTestData = async (dataObj) => {
// }

const payer = await ParticipantHelper.prepareData(dataObj.payer.name, dataObj.amount.currency)
const payee = await ParticipantHelper.prepareData(dataObj.payee.name, dataObj.currencies[0], dataObj.currencies[1])
const proxyAR = await ParticipantHelper.prepareData(dataObj.proxyAR.name, dataObj.amount.currency, undefined, undefined, true)
const proxyRB = await ParticipantHelper.prepareData(dataObj.proxyRB.name, dataObj.currencies[0], dataObj.currencies[1], undefined, true)
const fxp = await ParticipantHelper.prepareData(dataObj.fxp.name, dataObj.currencies[0], dataObj.currencies[1])
const proxyAR = await ParticipantHelper.prepareData(dataObj.proxyAR.name, dataObj.amount.currency, undefined, undefined, true)
const proxyRB = await ParticipantHelper.prepareData(dataObj.proxyRB.name, dataObj.currencies[1], undefined, undefined, true)

const payerLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payer.participant.name, {
currency: dataObj.amount.currency,
limit: { value: dataObj.payer.limit }
})
const payeeLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payee.participant.name, {
const fxpPayerLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(fxp.participant.name, {
currency: dataObj.currencies[0],
limit: { value: dataObj.payee.limit }
limit: { value: dataObj.fxp.limit }
})
const payeeLimitAndInitialPositionSecondaryCurrency = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payee.participant.name, {
const fxpPayerLimitAndInitialPositionSecondaryCurrency = await ParticipantLimitHelper.prepareLimitAndInitialPosition(fxp.participant.name, {
currency: dataObj.currencies[1],
limit: { value: dataObj.payee.limit }
limit: { value: dataObj.fxp.limit }
})
const proxyARLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(proxyAR.participant.name, {
currency: dataObj.amount.currency,
limit: { value: dataObj.proxyAR.limit }
})
const proxyRBLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(proxyRB.participant.name, {
currency: dataObj.currencies[0],
limit: { value: dataObj.proxyRB.limit }
})
const proxyRBLimitAndInitialPositionSecondaryCurrency = await ParticipantLimitHelper.prepareLimitAndInitialPosition(proxyRB.participant.name, {
currency: dataObj.currencies[1],
limit: { value: dataObj.proxyRB.limit }
})
const fxpPayerLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(fxp.participant.name, {
currency: dataObj.currencies[0],
limit: { value: dataObj.fxp.limit }
})
const fxpPayerLimitAndInitialPositionSecondaryCurrency = await ParticipantLimitHelper.prepareLimitAndInitialPosition(fxp.participant.name, {
currency: dataObj.currencies[1],
limit: { value: dataObj.fxp.limit }
})

await ParticipantFundsInOutHelper.recordFundsIn(payer.participant.name, payer.participantCurrencyId2, {
currency: dataObj.amount.currency,
amount: 10000
})
await ParticipantFundsInOutHelper.recordFundsIn(proxyAR.participant.name, proxyAR.participantCurrencyId2, {
currency: dataObj.amount.currency,
amount: 10000
})
await ParticipantFundsInOutHelper.recordFundsIn(proxyRB.participant.name, proxyRB.participantCurrencyId2, {
await ParticipantFundsInOutHelper.recordFundsIn(fxp.participant.name, fxp.participantCurrencyId2, {
currency: dataObj.currencies[0],
amount: 10000
})
await ParticipantFundsInOutHelper.recordFundsIn(proxyRB.participant.name, proxyRB.participantCurrencyIdSecondary2, {
await ParticipantFundsInOutHelper.recordFundsIn(fxp.participant.name, fxp.participantCurrencyIdSecondary2, {
currency: dataObj.currencies[1],
amount: 10000
})
await ParticipantFundsInOutHelper.recordFundsIn(fxp.participant.name, fxp.participantCurrencyId2, {
currency: dataObj.currencies[0],
await ParticipantFundsInOutHelper.recordFundsIn(proxyAR.participant.name, proxyAR.participantCurrencyId2, {
currency: dataObj.amount.currency,
amount: 10000
})
await ParticipantFundsInOutHelper.recordFundsIn(fxp.participant.name, fxp.participantCurrencyIdSecondary2, {
await ParticipantFundsInOutHelper.recordFundsIn(proxyRB.participant.name, proxyRB.participantCurrencyId2, {
currency: dataObj.currencies[1],
amount: 10000
})

let payee
let payeeLimitAndInitialPosition
let payeeLimitAndInitialPositionSecondaryCurrency
if (dataObj.crossSchemeSetup) {
payee = await ParticipantHelper.prepareData(dataObj.payee.name, dataObj.currencies[1], undefined)
payeeLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payee.participant.name, {
currency: dataObj.currencies[1],
limit: { value: dataObj.payee.limit }
})
payeeLimitAndInitialPositionSecondaryCurrency = null
} else {
payee = await ParticipantHelper.prepareData(dataObj.payee.name, dataObj.amount.currency, dataObj.currencies[1])
payeeLimitAndInitialPosition = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payee.participant.name, {
currency: dataObj.amount.currency,
limit: { value: dataObj.payee.limit }
})
payeeLimitAndInitialPositionSecondaryCurrency = await ParticipantLimitHelper.prepareLimitAndInitialPosition(payee.participant.name, {
currency: dataObj.currencies[1],
limit: { value: dataObj.payee.limit }
})
}

for (const name of [payer.participant.name, payee.participant.name, proxyAR.participant.name, proxyRB.participant.name, fxp.participant.name]) {
await ParticipantEndpointHelper.prepareData(name, 'FSPIOP_CALLBACK_URL_TRANSFER_POST', `${dataObj.endpoint.base}/transfers`)
await ParticipantEndpointHelper.prepareData(name, 'FSPIOP_CALLBACK_URL_TRANSFER_PUT', `${dataObj.endpoint.base}/transfers/{{transferId}}`)
Expand Down Expand Up @@ -445,7 +450,6 @@ const prepareTestData = async (dataObj) => {
proxyARLimitAndInitialPosition,
proxyRB,
proxyRBLimitAndInitialPosition,
proxyRBLimitAndInitialPositionSecondaryCurrency,
fxp,
fxpPayerLimitAndInitialPosition,
fxpPayerLimitAndInitialPositionSecondaryCurrency
Expand Down Expand Up @@ -895,7 +899,7 @@ Test('Handlers test', async handlersTest => {
Payer DFSP position account must be updated (reserved)`, async (test) => {
const creditor = 'regionalSchemeFXP'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(creditor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -932,7 +936,7 @@ Test('Handlers test', async handlersTest => {
// Create dependent fxTransfer
let creditor = 'regionalSchemeFXP'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(creditor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -992,7 +996,7 @@ Test('Handlers test', async handlersTest => {
Proxy AR position account in source currency must be updated (reserved)`, async (test) => {
const debtor = 'jurisdictionalFspPayerFsp'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(debtor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -1028,7 +1032,7 @@ Test('Handlers test', async handlersTest => {
FXP position account in targeted currency must be updated (reserved)`, async (test) => {
const debtor = 'jurisdictionalFspPayerFsp'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(debtor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -1089,7 +1093,15 @@ Test('Handlers test', async handlersTest => {
Proxy RB position account must be updated (reserved)`, async (test) => {
const debtor = 'jurisdictionalFspPayerFsp'

const td = await prepareTestData(testData)
// Proxy RB and Payee are only set up to deal in XXX currency
const td = await prepareTestData({
...testData,
amount: {
currency: 'XXX',
amount: '100'
},
crossSchemeSetup: true
})
await ProxyCache.getCache().addDfspIdToProxyMapping(debtor, td.proxyRB.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand All @@ -1111,9 +1123,9 @@ Test('Handlers test', async handlersTest => {
topicFilter: 'topic-transfer-position-batch',
action: 'prepare',
// A position prepare message reserving the proxy of ProxyRB on it's XXX participant currency account
keyFilter: td.proxyRB.participantCurrencyIdSecondary.toString()
keyFilter: td.proxyRB.participantCurrencyId.toString()
}), wrapWithRetriesConf.remainingRetries, wrapWithRetriesConf.timeout)
test.ok(positionPrepare[0], 'Position prepare message with key of fxp target currency account found')
test.ok(positionPrepare[0], 'Position prepare message with key of proxyRB target currency account found')
} catch (err) {
test.notOk('Error should not be thrown')
console.error(err)
Expand All @@ -1132,7 +1144,15 @@ Test('Handlers test', async handlersTest => {
Payee DFSP position account must be updated`, async (test) => {
const transferPrepareFrom = 'schemeAPayerFsp'

const td = await prepareTestData(testData)
// Proxy RB and Payee are only set up to deal in XXX currency
const td = await prepareTestData({
...testData,
crossSchemeSetup: true,
amount: {
currency: 'XXX',
amount: '100'
}
})
await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareFrom, td.proxyRB.participant.name)

// Prepare the transfer
Expand Down Expand Up @@ -1199,7 +1219,18 @@ Test('Handlers test', async handlersTest => {
const transferPrepareFrom = 'schemeAPayerFsp'
const transferPrepareTo = 'schemeBPayeeFsp'

const td = await prepareTestData(testData)
// In this particular test, without currency conversion proxyRB and proxyAR
// should have accounts in the same currency. proxyRB default currency is already XXX.
// So configure proxy AR to operate in XXX currency.
const td = await prepareTestData({
...testData,
amount: {
currency: 'XXX',
amount: '100'
},
crossSchemeSetup: true
})

await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareFrom, td.proxyAR.participant.name)
await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareTo, td.proxyRB.participant.name)

Expand Down Expand Up @@ -1268,7 +1299,7 @@ Test('Handlers test', async handlersTest => {
No position changes should happen`, async (test) => {
const debtor = 'jurisdictionalFspPayerFsp'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(debtor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -1331,7 +1362,7 @@ Test('Handlers test', async handlersTest => {
with wrong headers - ABORT VALIDATION`, async (test) => {
const debtor = 'jurisdictionalFspPayerFsp'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(debtor, td.proxyAR.participant.name)

const prepareConfig = Utility.getKafkaConfig(
Expand Down Expand Up @@ -1400,7 +1431,12 @@ Test('Handlers test', async handlersTest => {
const transferPrepareFrom = 'schemeAPayerFsp'
const transferPrepareTo = 'schemeBPayeeFsp'

const td = await prepareTestData(testData)
// In this particular test, with currency conversion, we're assuming that proxyAR and proxyRB
// operate in different currencies. ProxyRB's default currency is XXX, and ProxyAR's default currency is USD.
const td = await prepareTestData({
...testData,
crossSchemeSetup: true
})
await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareFrom, td.proxyAR.participant.name)
await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareTo, td.proxyRB.participant.name)

Expand Down Expand Up @@ -1478,7 +1514,7 @@ Test('Handlers test', async handlersTest => {
const positionFulfil2 = await wrapWithRetries(() => testConsumer.getEventsForFilter({
topicFilter: 'topic-transfer-position-batch',
action: 'commit',
keyFilter: td.proxyRB.participantCurrencyIdSecondary.toString()
keyFilter: td.proxyRB.participantCurrencyId.toString()
}), wrapWithRetriesConf.remainingRetries, wrapWithRetriesConf.timeout)
test.ok(positionFulfil1[0], 'Position fulfil message with key found')
test.ok(positionFulfil2[0], 'Position fulfil message with key found')
Expand All @@ -1498,7 +1534,7 @@ Test('Handlers test', async handlersTest => {
const transferPrepareTo = 'schemeBPayeeFsp'
const fxTransferPrepareTo = 'schemeRFxp'

const td = await prepareTestData(testData)
const td = await prepareTestData({ ...testData, crossSchemeSetup: true })
await ProxyCache.getCache().addDfspIdToProxyMapping(fxTransferPrepareTo, td.proxyAR.participant.name)
await ProxyCache.getCache().addDfspIdToProxyMapping(transferPrepareTo, td.proxyAR.participant.name)

Expand Down
Loading

0 comments on commit 986b3d8

Please sign in to comment.