diff --git a/website/versioned_docs/v1.0.1/sidebar.config.json b/website/versioned_docs/v1.0.1/sidebar.config.json index 183766aa6..6975f24d2 100644 --- a/website/versioned_docs/v1.0.1/sidebar.config.json +++ b/website/versioned_docs/v1.0.1/sidebar.config.json @@ -527,6 +527,102 @@ ], "sidebarDepth": 2 }, + { + "title": "Central Settlements Services", + "collapsable": true, + "children": [ + { + "title": "Settlement Process", + "collapsable": false, + "children": [ + { + "title": "Overview", + "path": "central-settlements/settlement-process/" + }, + { + "title": "Settlement Windows By Params", + "path": "central-settlements/settlement-process/get-settlement-windows-by-params" + }, + { + "title": "Request Settlement By Window", + "path": "central-settlements/settlement-process/get-settlement-window-by-id" + }, + { + "title": "Close Settlement Window", + "path": "central-settlements/settlement-process/post-close-settlement-window" + }, + { + "title": "Create Settlement", + "path": "central-settlements/settlement-process/post-create-settlement" + }, + { + "title": "Request Settlement", + "path": "central-settlements/settlement-process/get-settlement-by-id" + }, + { + "title": "Settlement Transfer Acknowledgment", + "path": "central-settlements/settlement-process/put-settlement-transfer-ack" + }, + { + "title": "Settlement Abort", + "path": "central-settlements/settlement-process/put-settlement-abort" + }, + { + "title": "Request Settlement By SPA", + "path": "central-settlements/settlement-process/get-settlement-by-spa" + }, + { + "title": "Request Settlement By Params", + "path": "central-settlements/settlement-process/get-settlements-by-params" + }, + { + "title": "Gross Settlement Handler", + "path": "central-settlements/settlement-process/gross-settlement-handler-consumer" + }, + { + "title": "Rules Handler", + "path": "central-settlements/settlement-process/rules-handler-consumer" + } + ] + }, + { + "title": "Funds In/Out", + "collapsable": false, + "path": "central-settlements/funds-in-out", + "children": [ + { + "title": "Reconciliation Transfer Prepare", + "path": "central-settlements/funds-in-out/reconciliation-transfer-prepare" + }, + { + "title": "Transfer State and Position Change", + "path": "central-settlements/funds-in-out/transfer-state-and-position-change" + }, + { + "title": "Funds In", + "path": "central-settlements/funds-in-out/funds-in-prepare-reserve-commit" + }, + { + "title": "Funds Out - Prepare & Reserve", + "path": "central-settlements/funds-in-out/funds-out-prepare-reserve" + }, + { + "title": "Funds Out - Commit", + "path": "central-settlements/funds-in-out/funds-out-commit" + }, + { + "title": "Funds Out - Abort", + "path": "central-settlements/funds-in-out/funds-out-abort" + } + ] + }, + { + "title": "OSS Settlement FSD", + "collapsable": false, + "path": "central-settlements/oss-settlement-fsd" + } + ] + }, { "title": "Central Event Processor Service", "collapsable": true, diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/.gitkeep b/website/versioned_docs/v1.0.1/technical/central-settlements/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/README.md b/website/versioned_docs/v1.0.1/technical/central-settlements/README.md new file mode 100644 index 000000000..596744601 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/README.md @@ -0,0 +1,24 @@ +# Central-Settlements Service + +The Central Settlements service is part of the Mojaloop project and deployment. + +* The central settlements service exposes Settlement API to manage the settlements between FSPs and the Central Hub. +* The service manages Settlement Windows and Settlements Event Triggers and provides information about FSPs accounts and settlements. + +## 1. Settlement Process Design + +### 1.1. Architecture overview + +![The Settlements Architecture](./assets/diagrams/architecture/Arch-Mojaloop-Settlements-PI4.svg) + +## 2. Funds In/Out + +Record Funds In and Record Funds Out operations are used respectively to deposit and withdraw funds into participant SETTLEMENT ledgers. The balance of the SETTLEMENT account relates to the NET_DEBIT_CAP set by the switch for every participant of the scheme. NET_DEBIT_CAP value is always lower or equal to the SETTLEMENT value. On the other side, the balance of the participant's POSITION account is limited to the value of the NET_DEBIT_CAP. + +## 3. API Specification + +Refer to **Central Settlements API** in the [API Specifications](../../api/README.md#central-settlements-api) section. + +## 4. Rules Engine + +The rules engine is separate handler within the Central-Settlement handler, that can be deployed if needed. Rules format and way of operation can be found on [Rules Handler . Interchange fees example. File format.](./settlement-process/rules-handler-consume.md) \ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/assets/.gitkeep b/website/versioned_docs/v1.0.1/technical/central-settlements/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/Settlement_ERD.png b/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/Settlement_ERD.png new file mode 100644 index 000000000..d5724815e Binary files /dev/null and b/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/Settlement_ERD.png differ diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/architecture/Arch-Mojaloop-Settlements-PI4.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/architecture/Arch-Mojaloop-Settlements-PI4.svg new file mode 100644 index 000000000..fa626d7bd --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/assets/diagrams/architecture/Arch-Mojaloop-Settlements-PI4.svg @@ -0,0 +1,3 @@ + + +
 Settlements Engine
[Not supported by viewer]
A.3. Store Settlement
B.3. Retrieve Settlement Data
D.3. Store Settlement Acks

[Not supported by viewer]
DB
DB
<alt> E.3 Position Update
Failure Notification

[Not supported by viewer]
Central Services
<font style="font-size: 20px">Central Services</font>
Settlement Adapter
<font style="font-size: 18px">Settlement Adapter</font>
FSP
[Not supported by viewer]
E.3. Notification
[Not supported by viewer]
E.2 Position
Update
Consumed
[Not supported by viewer]
positions
positions<br>
Scheme Settlement Processor
Scheme Settlement Processor<br>
Settlement API
Settlement API
Settlements Handler
Settlements Handler
Hub Operator
[Not supported by viewer]
A.1 Create Settlement
A.4. OK (201)
[Not supported by viewer]
A.2. Create Settlement
B.2. Request Settlement Report
D.2. Process Settlement Acks
[Not supported by viewer]
B.1. Request Settlement Report
B.4. OK (200) - Settlement Report Received
<b>B</b>.1. Request Settlement Report<br><b>B</b>.4. OK (200) - Settlement Report Received<br>
D.1. Send Settlement Acknowledgement
D.5. OK (200) - Provide Settlement Ack Result
<alt> Error (5xx)
[Not supported by viewer]
E.1 Update 
Position

[Not supported by viewer]
Settlement Processes
A. Create Settlement
B. Request Settlement Report
C. Process Settlement
D. Provider Sends through Acks
E. Position Updates from Settlement
[Not supported by viewer]
C. Process Settlement
<b>C</b>. Process Settlement
Settlement Bank
[Not supported by viewer]
E.4 Position change
due to Settlement
[Not supported by viewer]
OK (200)
[Not supported by viewer]
<alt> E.4 Position
change 
failure from
Settlement
[Not supported by viewer]
notifications
notifications
PositionHandler
PositionHandler
DB
DB
Mojaloop Hub
[Not supported by viewer]
Central
Event
Processor
[Not supported by viewer]
Notification
Adapter
/email, ml-api/
[Not supported by viewer]
\ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/README.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/README.md new file mode 100644 index 000000000..1e9340be7 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/README.md @@ -0,0 +1,27 @@ +# Funds In / Out + +## 1. Sequence diagrams + +### 1.1. Reconciliation Transfer Prepare + +- [Sequence Diagram for Reconciliation Transfer Prepare sub-process](reconciliation-transfer-prepare.md) + +### 1.2. Transfer State and Position Change + +- [Sequence Diagram for Transfer State and Position Change Prepare sub-process](transfer-state-and-position-change.md) + +### 1.3. Funds In - Prepare, Reserve, Commit + +- [Sequence Diagram for Funds In](funds-in-prepare-reserve-commit.md) + +### 1.4. Funds Out - Prepare & Reserve + +- [Sequence Diagram for Funds Out Prepare & Reserve](funds-out-prepare-reserve.md) + +### 1.5. Funds Out - Commit + +- [Sequence Diagram for Funds Out Commit](funds-out-commit.md) + +### 1.6. Funds Out - Abort + +- [Sequence Diagram for Funds Out Abort](funds-out-abort.md) diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.plantuml new file mode 100644 index 000000000..76c7c9f27 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.plantuml @@ -0,0 +1,127 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.1.0.a. Reconciliation transfer prepare (reconciliationTransferPrepare) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central Service" #LightYellow + participant TRANSFER_DAO + participant DB +end box + +' start flow +group reconciliationTransferPrepare (payload, trx) + TRANSFER_DAO -> DB: Insert transferDuplicateCheck + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferDuplicateCheck** (transferId, hash, createdDate) + VALUES ({payload.transferId}, hashCode({payload.transferId}), {transactionTimestamp}) + end hnote + + TRANSFER_DAO -> DB: Insert transfer + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transfer** (transferId, amount, currencyId, + ilpCondition, expirationDate, createdDate) + VALUES ({payload.transferId}, {payload.amount.amount}, + {payload.amount.curency}, 0, + {new Date()+Config.INTERNAL_TRANSFER_VALIDITY_SECONDS}, + {transactionTimestamp}) + end hnote + + TRANSFER_DAO -> DB: Retrieve hub reconciliation account + activate DB + hnote over DB #lightyellow + SELECT participantCurrencyId AS reconciliationAccountId + FROM **participantCurrency** + WHERE participantId = 1 + AND currencyId = {payload.amount.currency} + LIMIT 1 + end hnote + deactivate DB + TRANSFER_DAO <-- DB: Return **reconciliationAccountId** + + alt payload.action == 'RECORD_FUNDS_IN' + note right of TRANSFER_DAO #lightgray + **ledgerEntryTypeId** = 'RECORD_FUNDS_IN' + **amount** = payload.amount.amount + end note + else payload.action == 'RECORD_FUNDS_OUT_PREPARE' + note right of TRANSFER_DAO #lightgray + **ledgerEntryTypeId** = 'RECORD_FUNDS_OUT' + **amount** = -payload.amount.amount + end note + end + + TRANSFER_DAO -> DB: Insert transferParticipant records + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferParticipant** + (transferId, participantCurrencyId, transferParticipantRoleTypeId, + ledgerEntryTypeId, amount, createdDate) + VALUES (payload.transferId, reconciliationAccountId, 'HUB', + {ledgerEntryTypeId}, {amount}, {transactionTimestamp}) + + INSERT INTO **transferParticipant** + (transferId, participantCurrencyId, transferParticipantRoleTypeId, + ledgerEntryTypeId, amount, createdDate) + VALUES ({payload.transferId}, {payload.participantCurrencyId}, 'DFSP_SETTLEMENT', + {ledgerEntryTypeId}, {-amount}, {transactionTimestamp}) + end hnote + + TRANSFER_DAO -> DB: Insert transferStateChange record + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferStateChange** + (transferId, transferStateId, reason, createdDate) + VALUES ({payload.transferId}, 'RECEIVED_PREPARE', + {payload.reason}, {transactionTimestamp}) + end hnote + + TRANSFER_DAO -> DB: Save externalReference and extensions + activate DB + deactivate DB + hnote over DB #lightyellow + transferExtension + end hnote +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.svg new file mode 100644 index 000000000..56512040f --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.svg @@ -0,0 +1,267 @@ + + + + + + + + + + + 5.1.0.a. Reconciliation transfer prepare (reconciliationTransferPrepare) + + + + Central Service + + + + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + reconciliationTransferPrepare (payload, trx) + + + + + 1 + + + Insert transferDuplicateCheck + + + + INSERT INTO + + + transferDuplicateCheck + + + (transferId, hash, createdDate) + + + VALUES ({payload.transferId}, hashCode({payload.transferId}), {transactionTimestamp}) + + + + + 2 + + + Insert transfer + + + + INSERT INTO + + + transfer + + + (transferId, amount, currencyId, + + + ilpCondition, expirationDate, createdDate) + + + VALUES ({payload.transferId}, {payload.amount.amount}, + + + {payload.amount.curency}, 0, + + + {new Date()+Config.INTERNAL_TRANSFER_VALIDITY_SECONDS}, + + + {transactionTimestamp}) + + + + + 3 + + + Retrieve hub reconciliation account + + + + SELECT participantCurrencyId AS reconciliationAccountId + + + FROM + + + participantCurrency + + + WHERE participantId = 1 + + + AND currencyId = {payload.amount.currency} + + + LIMIT 1 + + + + + 4 + + + Return + + + reconciliationAccountId + + + + + alt + + + [payload.action == 'RECORD_FUNDS_IN'] + + + + + ledgerEntryTypeId + + + = 'RECORD_FUNDS_IN' + + + amount + + + = + + + payload.amount.amount + + + + [payload.action == 'RECORD_FUNDS_OUT_PREPARE'] + + + + + ledgerEntryTypeId + + + = 'RECORD_FUNDS_OUT' + + + amount + + + = + + + -payload.amount.amount + + + + + 5 + + + Insert transferParticipant records + + + + INSERT INTO + + + transferParticipant + + + (transferId, participantCurrencyId, transferParticipantRoleTypeId, + + + ledgerEntryTypeId, amount, createdDate) + + + VALUES (payload.transferId, reconciliationAccountId, 'HUB', + + + {ledgerEntryTypeId}, {amount}, {transactionTimestamp}) + + + INSERT INTO + + + transferParticipant + + + (transferId, participantCurrencyId, transferParticipantRoleTypeId, + + + ledgerEntryTypeId, amount, createdDate) + + + VALUES ({payload.transferId}, {payload.participantCurrencyId}, 'DFSP_SETTLEMENT', + + + {ledgerEntryTypeId}, {-amount}, {transactionTimestamp}) + + + + + 6 + + + Insert transferStateChange record + + + + INSERT INTO + + + transferStateChange + + + (transferId, transferStateId, reason, createdDate) + + + VALUES ({payload.transferId}, 'RECEIVED_PREPARE', + + + {payload.reason}, {transactionTimestamp}) + + + + + 7 + + + Save externalReference and extensions + + + + transferExtension + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.plantuml new file mode 100644 index 000000000..3c3228d60 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.plantuml @@ -0,0 +1,179 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.1.0.b. Transfer state and position change (transferStateAndPositionChange) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central Service" #LightYellow + participant TRANSFER_DAO + participant DB +end box + +' start flow +group transferStateAndPositionUpdate (param1, trx) + note right of TRANSFER_DAO #lightgray + **param1** = { + transferId: {payload.transferId}, + transferStateId: "enum", + reason: {payload.reason}, + createdDate: {transactionTimestamp}, + drUpdated: "boolean", + crUpdated: "boolean" + } + end note + + TRANSFER_DAO -> DB: Select all required info + activate DB + hnote over DB #lightyellow + SELECT dr.participantCurrencyId AS drAccountId, + dr.amount AS drAmount, drp.participantPositionId AS drPositionId, + drp.value AS drPositionValue, drp.reservedValue AS drReservedValue, + cr.participantCurrencyId AS crAccountId, + cr.amount AS crAmount, crp.participantPositionId AS crPositionId, + crp.value AS crPositionValue, crp.reservedValue AS crReservedValue, + tsc.transferStateId + FROM **transfer** t + JOIN **transferParticipant** dr + ON dr.transferId = t.transferId + AND dr.amount > 0 + JOIN **participantCurrency** drpc + ON drpc.participantCurrencyId = dr.participantCurrencyId + JOIN **participantPosition** drp + ON drp.participantCurrencyId = dr.participantCurrencyId + JOIN **transferParticipant** cr + ON cr.transferId = t.transferId + AND cr.amount < 0 + JOIN **participantCurrency** crpc + ON crpc.participantCurrencyId = dr.participantCurrencyId + JOIN **participantPosition** crp + ON crp.participantCurrencyId = cr.participantCurrencyId + JOIN **transferStateChange** tsc + ON tsc.transferId = t.transferId + WHERE t.transferId = param1.tranferId + AND drpc.ledgerAccountTypeId IN('POSITION', 'SETTLEMENT', ' + HUB_RECONCILIATION', 'HUB_MULTILATERAL_SETTLEMENT') + AND crpc.ledgerAccountTypeId IN('POSITION', 'SETTLEMENT', ' + HUB_RECONCILIATION', 'HUB_MULTILATERAL_SETTLEMENT') + ORDER BY transferStateChangeId DESC + LIMIT 1 + end hnote + TRANSFER_DAO <-- DB: Return **info** + deactivate DB + + opt param1.transferStateId == 'COMMITTED' + TRANSFER_DAO -> DB: Change transfer state + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferStateChange** (transferId, transferStateId, reason, createdDate) + VALUES ({param1.transferId, 'RECEIVED_FULFIL', {param1.reason}, {param1.createdDate}) + end hnote + else param1.transferStateId == 'ABORTED' + TRANSFER_DAO -> DB: Change transfer state + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferStateChange** (transferId, transferStateId, reason, createdDate) + VALUES ({param1.transferId, 'REJECTED', {param1.reason}, {param1.createdDate}) + end hnote + end + + TRANSFER_DAO -> DB: Change transfer state + activate DB + hnote over DB #lightyellow + INSERT INTO **transferStateChange** (transferId, transferStateId, reason, createdDate) + VALUES ({param1.transferId, {param1.transferStateId}, {param1.reason}, {param1.createdDate}) + end hnote + TRANSFER_DAO <-- DB: Return **transferStateChangeId** + deactivate DB + + opt param1.drUpdated == true + opt param1.transferStateId == 'ABORTED' + note right of TRANSFER_DAO #lightgray + info.drAmount = -info.drAmount + end note + end + + TRANSFER_DAO -> DB: Change DR position + activate DB + deactivate DB + hnote over DB #lightyellow + UPDATE **participantPosition** + SET value = {info.drPositionValue + info.drAmount} + WHERE participantPositionId = {info.drPositionId} + + INSERT INTO **participantPositionChange** (participantPositionId, + transferStateChangeId, value, reservedValue, createdDate) + VALUES ({info.drPositionId}, {transferStateChangeId}, + {info.drPositionValue + info.drAmount}, {info.drReservedValue}, + {param1.createdDate}) + end hnote + end + + opt param1.crUpdated == true + opt param1.transferStateId == 'ABORTED' + note right of TRANSFER_DAO #lightgray + info.crAmount = -info.crAmount + end note + end + + TRANSFER_DAO -> DB: Change CR position + activate DB + deactivate DB + hnote over DB #lightyellow + UPDATE **participantPosition** + SET value = {info.crPositionValue + info.crAmount} + WHERE participantPositionId = {info.crPositionId} + + INSERT INTO **participantPositionChange** (participantPositionId, + transferStateChangeId, value, reservedValue, createdDate) + VALUES ({info.crPositionId}, {transferStateChangeId}, + {info.crPositionValue + info.crAmount}, {info.crReservedValue}, + {param1.createdDate}) + end hnote + end + + note right of TRANSFER_DAO #lightgray + **return** { + transferStateChangeId, + drPositionValue: {info.drPositionValue + info.drAmount} + crPositionValue: {info.crPositionValue + info.crAmount} + } + end note +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.svg new file mode 100644 index 000000000..57ab88b3f --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.svg @@ -0,0 +1,473 @@ + + + + + + + + + + + 5.1.0.b. Transfer state and position change (transferStateAndPositionChange) + + + + Central Service + + + + + + + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + transferStateAndPositionUpdate (param1, trx) + + + + + param1 + + + = { + + + transferId: {payload.transferId}, + + + transferStateId: "enum", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: "boolean", + + + crUpdated: "boolean" + + + } + + + + + 1 + + + Select all required info + + + + SELECT dr.participantCurrencyId AS drAccountId, + + + dr.amount AS drAmount, drp.participantPositionId AS drPositionId, + + + drp.value AS drPositionValue, drp.reservedValue AS drReservedValue, + + + cr.participantCurrencyId AS crAccountId, + + + cr.amount AS crAmount, crp.participantPositionId AS crPositionId, + + + crp.value AS crPositionValue, crp.reservedValue AS crReservedValue, + + + tsc.transferStateId + + + FROM + + + transfer + + + t + + + JOIN + + + transferParticipant + + + dr + + + ON dr.transferId = t.transferId + + + AND dr.amount > 0 + + + JOIN + + + participantCurrency + + + drpc + + + ON drpc.participantCurrencyId = dr.participantCurrencyId + + + JOIN + + + participantPosition + + + drp + + + ON drp.participantCurrencyId = dr.participantCurrencyId + + + JOIN + + + transferParticipant + + + cr + + + ON cr.transferId = t.transferId + + + AND cr.amount < 0 + + + JOIN + + + participantCurrency + + + crpc + + + ON crpc.participantCurrencyId = dr.participantCurrencyId + + + JOIN + + + participantPosition + + + crp + + + ON crp.participantCurrencyId = cr.participantCurrencyId + + + JOIN + + + transferStateChange + + + tsc + + + ON tsc.transferId = t.transferId + + + WHERE t.transferId = param1.tranferId + + + AND drpc.ledgerAccountTypeId IN('POSITION', 'SETTLEMENT', ' + + + HUB_RECONCILIATION', 'HUB_MULTILATERAL_SETTLEMENT') + + + AND crpc.ledgerAccountTypeId IN('POSITION', 'SETTLEMENT', ' + + + HUB_RECONCILIATION', 'HUB_MULTILATERAL_SETTLEMENT') + + + ORDER BY transferStateChangeId DESC + + + LIMIT 1 + + + + + 2 + + + Return + + + info + + + + + opt + + + [param1.transferStateId == 'COMMITTED'] + + + + + 3 + + + Change transfer state + + + + INSERT INTO + + + transferStateChange + + + (transferId, transferStateId, reason, createdDate) + + + VALUES ({param1.transferId, 'RECEIVED_FULFIL', {param1.reason}, {param1.createdDate}) + + + + [param1.transferStateId == 'ABORTED'] + + + + + 4 + + + Change transfer state + + + + INSERT INTO + + + transferStateChange + + + (transferId, transferStateId, reason, createdDate) + + + VALUES ({param1.transferId, 'REJECTED', {param1.reason}, {param1.createdDate}) + + + + + 5 + + + Change transfer state + + + + INSERT INTO + + + transferStateChange + + + (transferId, transferStateId, reason, createdDate) + + + VALUES ({param1.transferId, {param1.transferStateId}, {param1.reason}, {param1.createdDate}) + + + + + 6 + + + Return + + + transferStateChangeId + + + + + opt + + + [param1.drUpdated == true] + + + + + opt + + + [param1.transferStateId == 'ABORTED'] + + + + + info.drAmount = -info.drAmount + + + + + 7 + + + Change DR position + + + + UPDATE + + + participantPosition + + + SET value = {info.drPositionValue + info.drAmount} + + + WHERE participantPositionId = {info.drPositionId} + + + INSERT INTO + + + participantPositionChange + + + (participantPositionId, + + + transferStateChangeId, value, reservedValue, createdDate) + + + VALUES ({info.drPositionId}, {transferStateChangeId}, + + + {info.drPositionValue + info.drAmount}, {info.drReservedValue}, + + + {param1.createdDate}) + + + + + opt + + + [param1.crUpdated == true] + + + + + opt + + + [param1.transferStateId == 'ABORTED'] + + + + + info.crAmount = -info.crAmount + + + + + 8 + + + Change CR position + + + + UPDATE + + + participantPosition + + + SET value = {info.crPositionValue + info.crAmount} + + + WHERE participantPositionId = {info.crPositionId} + + + INSERT INTO + + + participantPositionChange + + + (participantPositionId, + + + transferStateChangeId, value, reservedValue, createdDate) + + + VALUES ({info.crPositionId}, {transferStateChangeId}, + + + {info.crPositionValue + info.crAmount}, {info.crReservedValue}, + + + {param1.createdDate}) + + + + + return + + + { + + + transferStateChangeId, + + + drPositionValue: {info.drPositionValue + info.drAmount} + + + crPositionValue: {info.crPositionValue + info.crAmount} + + + } + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.plantuml new file mode 100644 index 000000000..a8e45e4a2 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.plantuml @@ -0,0 +1,150 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.1.1. Record Funds In (recordFundsIn) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Central Service\n Admin API" as CS_ADMIN_API +collections "Admin-Transfer-Topic" as TOPIC_ADMIN_TRANSFER +control "Admin Event Handler" as ADMIN_HANDLER +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Central Service" #LightYellow + participant CS_ADMIN_API + participant TOPIC_ADMIN_TRANSFER + participant ADMIN_HANDLER + participant TRANSFER_DAO + participant DB +end box + +' start flow +activate ADMIN_HANDLER +group Record Funds In + note right of OPERATOR #yellow + { + "transferId": , + "externalReference": "string", + "action": "recordFundsIn", + "amount": { + "amount": , + "currency": "string" + + }, + "reason": "string", + "extensionList": { + "extension": [ + { + "key": "string", + "value": "string" + } + ] + } + } + end note + OPERATOR -> CS_ADMIN_API: POST - /participants/{name}/accounts/{id} + activate CS_ADMIN_API + + note right of CS_ADMIN_API #yellow + Message: + { + id: + from: "dfspName", + to: "Hub", + type: "application/json" + content: { + headers: , + payload: + }, + metadata: { + event: { + id: , + responseTo: "", + type: "transfer", + action: "recordFundsIn", + createdAt: , + state: "" + } + }, + pp: "" + } + end note + CS_ADMIN_API -> TOPIC_ADMIN_TRANSFER: Route & Publish Prepare event\nError code: 2003 + activate TOPIC_ADMIN_TRANSFER + TOPIC_ADMIN_TRANSFER <-> TOPIC_ADMIN_TRANSFER: Ensure event is replicated\nas configured (ACKS=all)\nError code: 2003 + TOPIC_ADMIN_TRANSFER --> CS_ADMIN_API: Respond replication acks\nhave been received + deactivate TOPIC_ADMIN_TRANSFER + CS_ADMIN_API ---> OPERATOR: Respond HTTP - 202 (Accepted) + deactivate CS_ADMIN_API + + TOPIC_ADMIN_TRANSFER <- ADMIN_HANDLER: Consume message + ADMIN_HANDLER -> TRANSFER_DAO: Prepare transfer + activate TRANSFER_DAO + group DB TRANSACTION + group Reconciliation Transfer Prepare + ||| + ref over TRANSFER_DAO, DB:**reconciliationTransferPrepare**\n(payload, trx)\n + end + + group Reconciliation Transfer Reserve + ||| + ref over TRANSFER_DAO, DB: **transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "RESERVED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: true,\n crUpdated: false\n}, trx)\n + end + + group Reconciliation Transfer Commit + TRANSFER_DAO -> DB: Insert transferFulfilment record + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferFulfilment** + (transferFulfilmentId, transferId, ilpFulfilment, + completedDate, isValid, settlementWindowId, createdDate) + VALUES ({Uuid()}, {payload.transferId}, 0, {transactionTimestamp}, + 1, NULL, {transactionTimestamp}) + end hnote + ||| + ref over TRANSFER_DAO, DB:**transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "COMMITTED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: false,\n crUpdated: true\n}, trx)\n + end + end + ADMIN_HANDLER <-- TRANSFER_DAO: Finished await + deactivate TRANSFER_DAO +end +deactivate ADMIN_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.svg new file mode 100644 index 000000000..c83a1ab9b --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.svg @@ -0,0 +1,426 @@ + + + + + + + + + + + 5.1.1. Record Funds In (recordFundsIn) + + + + Central HUB + + + + Central Service + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Central Service + + + Admin API + + + + + Central Service + + + Admin API + + + + + + + Admin-Transfer-Topic + + + + + Admin-Transfer-Topic + + + Admin Event Handler + + + + + Admin Event Handler + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Record Funds In + + + + + { + + + "transferId": <uuid>, + + + "externalReference": "string", + + + "action": "recordFundsIn", + + + "amount": { + + + "amount": <decimal>, + + + "currency": "string" + + + }, + + + "reason": "string", + + + "extensionList": { + + + "extension": [ + + + { + + + "key": "string", + + + "value": "string" + + + } + + + ] + + + } + + + } + + + + + 1 + + + POST - /participants/{name}/accounts/{id} + + + + + Message: + + + { + + + id: <payload.transferId> + + + from: "dfspName", + + + to: "Hub", + + + type: "application/json" + + + content: { + + + headers: <payloadHeaders>, + + + payload: <payloadMessage> + + + }, + + + metadata: { + + + event: { + + + id: <uuid>, + + + responseTo: "", + + + type: "transfer", + + + action: "recordFundsIn", + + + createdAt: <timestamp>, + + + state: "" + + + } + + + }, + + + pp: "" + + + } + + + + + 2 + + + Route & Publish Prepare event + + + Error code: + + + 2003 + + + + + 3 + + + Ensure event is replicated + + + as configured (ACKS=all) + + + Error code: + + + 2003 + + + + + 4 + + + Respond replication acks + + + have been received + + + + + 5 + + + Respond HTTP - 202 (Accepted) + + + + + 6 + + + Consume message + + + + + 7 + + + Prepare transfer + + + + + DB TRANSACTION + + + + + Reconciliation Transfer Prepare + + + + + ref + + + reconciliationTransferPrepare + + + (payload, trx) + + + + + Reconciliation Transfer Reserve + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "RESERVED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: true, + + + crUpdated: false + + + }, trx) + + + + + Reconciliation Transfer Commit + + + + + 8 + + + Insert transferFulfilment record + + + + INSERT INTO + + + transferFulfilment + + + (transferFulfilmentId, transferId, ilpFulfilment, + + + completedDate, isValid, settlementWindowId, createdDate) + + + VALUES ({Uuid()}, {payload.transferId}, 0, {transactionTimestamp}, + + + 1, NULL, {transactionTimestamp}) + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "COMMITTED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: false, + + + crUpdated: true + + + }, trx) + + + + + 9 + + + Finished await + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.plantuml new file mode 100644 index 000000000..c6e914484 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.plantuml @@ -0,0 +1,157 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.2.1. Record Funds Out - Prepare & Reserve (recordFundsOutPrepareReserve) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Central Service\n Admin API" as CS_ADMIN_API +collections "Admin-Transfer-Topic" as TOPIC_ADMIN_TRANSFER +control "Admin Event Handler" as ADMIN_HANDLER +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Central Service" #LightYellow + participant CS_ADMIN_API + participant TOPIC_ADMIN_TRANSFER + participant ADMIN_HANDLER + participant TRANSFER_DAO + participant DB +end box + +' start flow +activate ADMIN_HANDLER +group Record Funds Out - Prepare & Reserve + note right of OPERATOR #yellow + { + "transferId": , + "externalReference": "string", + "action": "recordFundsOutPrepareReserve", + "amount": { + "amount": , + "currency": "string" + + }, + "reason": "string", + "extensionList": { + "extension": [ + { + "key": "string", + "value": "string" + } + ] + } + } + end note + OPERATOR -> CS_ADMIN_API: POST - /participants/{name}/accounts/{id} + activate CS_ADMIN_API + + note right of CS_ADMIN_API #yellow + Message: + { + id: + from: "Hub", + to: "dfspName", + type: "application/json" + content: { + headers: , + payload: + }, + metadata: { + event: { + id: , + responseTo: "", + type: "transfer", + action: "recordFundsOutPrepareReserve", + createdAt: , + state: "" + } + }, + pp: "" + } + end note + CS_ADMIN_API -> TOPIC_ADMIN_TRANSFER: Route & Publish Prepare event\nError code: 2003 + activate TOPIC_ADMIN_TRANSFER + TOPIC_ADMIN_TRANSFER <-> TOPIC_ADMIN_TRANSFER: Ensure event is replicated\nas configured (ACKS=all)\nError code: 2003 + TOPIC_ADMIN_TRANSFER --> CS_ADMIN_API: Respond replication acks\nhave been received + deactivate TOPIC_ADMIN_TRANSFER + CS_ADMIN_API ---> OPERATOR: Respond HTTP - 202 (Accepted) + deactivate CS_ADMIN_API + + TOPIC_ADMIN_TRANSFER <- ADMIN_HANDLER: Consume message + ADMIN_HANDLER -> TRANSFER_DAO: Prepare transfer\nand reserve + activate TRANSFER_DAO + group DB TRANSACTION + group Reconciliation Transfer Prepare + ||| + ref over TRANSFER_DAO, DB: **reconciliationTransferPrepare**\n(payload, trx)\n + end + + group Reconciliation Transfer Reserve + ||| + ref over TRANSFER_DAO, DB: **transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "RESERVED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: true,\n crUpdated: false\n}, trx)\n + end + + ||| + opt transferStateAndPositionUpdate.crPositionValue > 0 + note right of TRANSFER_DAO #lightgray + payload.reason = 'Aborted due to insufficient funds' + end note + + group Reconciliation Transfer Abort + TRANSFER_DAO -> DB: Insert transferFulfilment record + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferFulfilment** + (transferFulfilmentId, transferId, ilpFulfilment, + completedDate, isValid, settlementWindowId, createdDate) + VALUES ({Uuid()}, {payload.transferId}, 0, {transactionTimestamp}, + 1, NULL, {transactionTimestamp}) + end hnote + ||| + ref over TRANSFER_DAO, DB: **transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "ABORTED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: true,\n crUpdated: false\n}, trx)\n + end + end + end + ADMIN_HANDLER <-- TRANSFER_DAO: Finished await + deactivate TRANSFER_DAO +end +deactivate ADMIN_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.svg new file mode 100644 index 000000000..a22743415 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.svg @@ -0,0 +1,443 @@ + + + + + + + + + + + 5.2.1. Record Funds Out - Prepare & Reserve (recordFundsOutPrepareReserve) + + + + Central HUB + + + + Central Service + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Central Service + + + Admin API + + + + + Central Service + + + Admin API + + + + + + + Admin-Transfer-Topic + + + + + Admin-Transfer-Topic + + + Admin Event Handler + + + + + Admin Event Handler + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Record Funds Out - Prepare & Reserve + + + + + { + + + "transferId": <uuid>, + + + "externalReference": "string", + + + "action": "recordFundsOutPrepareReserve", + + + "amount": { + + + "amount": <decimal>, + + + "currency": "string" + + + }, + + + "reason": "string", + + + "extensionList": { + + + "extension": [ + + + { + + + "key": "string", + + + "value": "string" + + + } + + + ] + + + } + + + } + + + + + 1 + + + POST - /participants/{name}/accounts/{id} + + + + + Message: + + + { + + + id: <payload.transferId> + + + from: "Hub", + + + to: "dfspName", + + + type: "application/json" + + + content: { + + + headers: <payloadHeaders>, + + + payload: <payloadMessage> + + + }, + + + metadata: { + + + event: { + + + id: <uuid>, + + + responseTo: "", + + + type: "transfer", + + + action: "recordFundsOutPrepareReserve", + + + createdAt: <timestamp>, + + + state: "" + + + } + + + }, + + + pp: "" + + + } + + + + + 2 + + + Route & Publish Prepare event + + + Error code: + + + 2003 + + + + + 3 + + + Ensure event is replicated + + + as configured (ACKS=all) + + + Error code: + + + 2003 + + + + + 4 + + + Respond replication acks + + + have been received + + + + + 5 + + + Respond HTTP - 202 (Accepted) + + + + + 6 + + + Consume message + + + + + 7 + + + Prepare transfer + + + and reserve + + + + + DB TRANSACTION + + + + + Reconciliation Transfer Prepare + + + + + ref + + + reconciliationTransferPrepare + + + (payload, trx) + + + + + Reconciliation Transfer Reserve + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "RESERVED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: true, + + + crUpdated: false + + + }, trx) + + + + + opt + + + [transferStateAndPositionUpdate.crPositionValue > 0] + + + + + payload.reason = 'Aborted due to insufficient funds' + + + + + Reconciliation Transfer Abort + + + + + 8 + + + Insert transferFulfilment record + + + + INSERT INTO + + + transferFulfilment + + + (transferFulfilmentId, transferId, ilpFulfilment, + + + completedDate, isValid, settlementWindowId, createdDate) + + + VALUES ({Uuid()}, {payload.transferId}, 0, {transactionTimestamp}, + + + 1, NULL, {transactionTimestamp}) + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "ABORTED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: true, + + + crUpdated: false + + + }, trx) + + + + + 9 + + + Finished await + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.plantuml new file mode 100644 index 000000000..a22129bbf --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.plantuml @@ -0,0 +1,125 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.2.2. Record Funds Out Commit (recordFundsOutCommit) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Central Service\n Admin API" as CS_ADMIN_API +collections "Admin-Transfer-Topic" as TOPIC_ADMIN_TRANSFER +control "Admin Event Handler" as ADMIN_HANDLER +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Central Service" #LightYellow + participant CS_ADMIN_API + participant TOPIC_ADMIN_TRANSFER + participant ADMIN_HANDLER + participant TRANSFER_DAO + participant DB +end box + +' start flow +activate ADMIN_HANDLER +group Record Funds Out Commit + note right of OPERATOR #yellow + { + "action": "recordFundsOutCommit", + "reason": "string" + } + end note + OPERATOR -> CS_ADMIN_API: PUT - /participants/{name}/accounts/\n {id}/transfers/{transferId} + activate CS_ADMIN_API + + note right of CS_ADMIN_API #yellow + Message: + { + id: + from: "Hub", + to: "dfspName", + type: "application/json" + content: { + headers: , + payload: + }, + metadata: { + event: { + id: , + responseTo: "", + type: "transfer", + action: "recordFundsOutCommit", + createdAt: , + state: "" + } + }, + pp: "" + } + end note + CS_ADMIN_API -> TOPIC_ADMIN_TRANSFER: Route & Publish Prepare event\nError code: 2003 + activate TOPIC_ADMIN_TRANSFER + TOPIC_ADMIN_TRANSFER <-> TOPIC_ADMIN_TRANSFER: Ensure event is replicated\nas configured (ACKS=all)\nError code: 2003 + TOPIC_ADMIN_TRANSFER --> CS_ADMIN_API: Respond replication acks\nhave been received + deactivate TOPIC_ADMIN_TRANSFER + CS_ADMIN_API ---> OPERATOR: Respond HTTP - 202 (Accepted) + deactivate CS_ADMIN_API + + TOPIC_ADMIN_TRANSFER <- ADMIN_HANDLER: Consume message + ADMIN_HANDLER -> TRANSFER_DAO: Commit transfer + activate TRANSFER_DAO + group DB TRANSACTION + group Reconciliation Transfer Commit + TRANSFER_DAO -> DB: Insert transferFulfilment record + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferFulfilment** + (transferFulfilmentId, transferId, ilpFulfilment, + completedDate, isValid, settlementWindowId, createdDate) + VALUES ({transferFulfilmentId}, {payload.transferId}, 0, {transactionTimestamp}, + 1, NULL, {transactionTimestamp}) + end hnote + ||| + ref over TRANSFER_DAO, DB: **transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "COMMITTED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: false,\n crUpdated: true\n}, trx)\n + end + end + ADMIN_HANDLER <-- TRANSFER_DAO: Finished await + deactivate TRANSFER_DAO +end +deactivate ADMIN_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.svg new file mode 100644 index 000000000..fd276ff59 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.svg @@ -0,0 +1,334 @@ + + + + + + + + + + + 5.2.2. Record Funds Out Commit (recordFundsOutCommit) + + + + Central HUB + + + + Central Service + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Central Service + + + Admin API + + + + + Central Service + + + Admin API + + + + + + + Admin-Transfer-Topic + + + + + Admin-Transfer-Topic + + + Admin Event Handler + + + + + Admin Event Handler + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Record Funds Out Commit + + + + + { + + + "action": "recordFundsOutCommit", + + + "reason": "string" + + + } + + + + + 1 + + + PUT - /participants/{name}/accounts/ + + + {id}/transfers/{transferId} + + + + + Message: + + + { + + + id: <payload.transferId> + + + from: "Hub", + + + to: "dfspName", + + + type: "application/json" + + + content: { + + + headers: <payloadHeaders>, + + + payload: <payloadMessage> + + + }, + + + metadata: { + + + event: { + + + id: <uuid>, + + + responseTo: "", + + + type: "transfer", + + + action: "recordFundsOutCommit", + + + createdAt: <timestamp>, + + + state: "" + + + } + + + }, + + + pp: "" + + + } + + + + + 2 + + + Route & Publish Prepare event + + + Error code: + + + 2003 + + + + + 3 + + + Ensure event is replicated + + + as configured (ACKS=all) + + + Error code: + + + 2003 + + + + + 4 + + + Respond replication acks + + + have been received + + + + + 5 + + + Respond HTTP - 202 (Accepted) + + + + + 6 + + + Consume message + + + + + 7 + + + Commit transfer + + + + + DB TRANSACTION + + + + + Reconciliation Transfer Commit + + + + + 8 + + + Insert transferFulfilment record + + + + INSERT INTO + + + transferFulfilment + + + (transferFulfilmentId, transferId, ilpFulfilment, + + + completedDate, isValid, settlementWindowId, createdDate) + + + VALUES ({transferFulfilmentId}, {payload.transferId}, 0, {transactionTimestamp}, + + + 1, NULL, {transactionTimestamp}) + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "COMMITTED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: false, + + + crUpdated: true + + + }, trx) + + + + + 9 + + + Finished await + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.plantuml new file mode 100644 index 000000000..0372380da --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.plantuml @@ -0,0 +1,125 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 5.2.3. Record Funds Out Abort (recordFundsOutAbort) + +autonumber + +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Central Service\n Admin API" as CS_ADMIN_API +collections "Admin-Transfer-Topic" as TOPIC_ADMIN_TRANSFER +control "Admin Event Handler" as ADMIN_HANDLER +entity "Transfer DAO" as TRANSFER_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Central Service" #LightYellow + participant CS_ADMIN_API + participant TOPIC_ADMIN_TRANSFER + participant ADMIN_HANDLER + participant TRANSFER_DAO + participant DB +end box + +' start flow +activate ADMIN_HANDLER +group Record Funds Out Abort + note right of OPERATOR #yellow + { + "action": "recordFundsOutAbort", + "reason": "string" + } + end note + OPERATOR -> CS_ADMIN_API: PUT - /participants/{name}/accounts/\n {id}/transfers/{transferId} + activate CS_ADMIN_API + + note right of CS_ADMIN_API #yellow + Message: + { + id: + from: "Hub", + to: "dfspName", + type: "application/json" + content: { + headers: , + payload: + }, + metadata: { + event: { + id: , + responseTo: "", + type: "transfer", + action: "recordFundsOutAbort", + createdAt: , + state: "" + } + }, + pp: "" + } + end note + CS_ADMIN_API -> TOPIC_ADMIN_TRANSFER: Route & Publish Prepare event\nError code: 2003 + activate TOPIC_ADMIN_TRANSFER + TOPIC_ADMIN_TRANSFER <-> TOPIC_ADMIN_TRANSFER: Ensure event is replicated\nas configured (ACKS=all)\nError code: 2003 + TOPIC_ADMIN_TRANSFER --> CS_ADMIN_API: Respond replication acks\nhave been received + deactivate TOPIC_ADMIN_TRANSFER + CS_ADMIN_API ---> OPERATOR: Respond HTTP - 202 (Accepted) + deactivate CS_ADMIN_API + + TOPIC_ADMIN_TRANSFER <- ADMIN_HANDLER: Consume message + ADMIN_HANDLER -> TRANSFER_DAO: Abort transfer + activate TRANSFER_DAO + group DB TRANSACTION + group Reconciliation Transfer Abort + TRANSFER_DAO -> DB: Insert transferFulfilment record + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **transferFulfilment** + (transferFulfilmentId, transferId, ilpFulfilment, + completedDate, isValid, settlementWindowId, createdDate) + VALUES ({transferFulfilmentId}, {payload.transferId}, 0, {transactionTimestamp}, + 1, NULL, {transactionTimestamp}) + end hnote + ||| + ref over TRANSFER_DAO, DB: **transferStateAndPositionUpdate**\n({\n transferId: {payload.transferId},\n transferStateId: "ABORTED",\n reason: {payload.reason},\n createdDate: {transactionTimestamp},\n drUpdated: true,\n crUpdated: false\n}, trx)\n + end + end + ADMIN_HANDLER <-- TRANSFER_DAO: Finished await + deactivate TRANSFER_DAO +end +deactivate ADMIN_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.svg new file mode 100644 index 000000000..8db2df0c4 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.svg @@ -0,0 +1,334 @@ + + + + + + + + + + + 5.2.3. Record Funds Out Abort (recordFundsOutAbort) + + + + Central HUB + + + + Central Service + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Central Service + + + Admin API + + + + + Central Service + + + Admin API + + + + + + + Admin-Transfer-Topic + + + + + Admin-Transfer-Topic + + + Admin Event Handler + + + + + Admin Event Handler + + + + + Transfer DAO + + + + + Transfer DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Record Funds Out Abort + + + + + { + + + "action": "recordFundsOutAbort", + + + "reason": "string" + + + } + + + + + 1 + + + PUT - /participants/{name}/accounts/ + + + {id}/transfers/{transferId} + + + + + Message: + + + { + + + id: <payload.transferId> + + + from: "Hub", + + + to: "dfspName", + + + type: "application/json" + + + content: { + + + headers: <payloadHeaders>, + + + payload: <payloadMessage> + + + }, + + + metadata: { + + + event: { + + + id: <uuid>, + + + responseTo: "", + + + type: "transfer", + + + action: "recordFundsOutAbort", + + + createdAt: <timestamp>, + + + state: "" + + + } + + + }, + + + pp: "" + + + } + + + + + 2 + + + Route & Publish Prepare event + + + Error code: + + + 2003 + + + + + 3 + + + Ensure event is replicated + + + as configured (ACKS=all) + + + Error code: + + + 2003 + + + + + 4 + + + Respond replication acks + + + have been received + + + + + 5 + + + Respond HTTP - 202 (Accepted) + + + + + 6 + + + Consume message + + + + + 7 + + + Abort transfer + + + + + DB TRANSACTION + + + + + Reconciliation Transfer Abort + + + + + 8 + + + Insert transferFulfilment record + + + + INSERT INTO + + + transferFulfilment + + + (transferFulfilmentId, transferId, ilpFulfilment, + + + completedDate, isValid, settlementWindowId, createdDate) + + + VALUES ({transferFulfilmentId}, {payload.transferId}, 0, {transactionTimestamp}, + + + 1, NULL, {transactionTimestamp}) + + + + + ref + + + transferStateAndPositionUpdate + + + ({ + + + transferId: {payload.transferId}, + + + transferStateId: "ABORTED", + + + reason: {payload.reason}, + + + createdDate: {transactionTimestamp}, + + + drUpdated: true, + + + crUpdated: false + + + }, trx) + + + + + 9 + + + Finished await + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-in-prepare-reserve-commit.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-in-prepare-reserve-commit.md new file mode 100644 index 000000000..64288328a --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-in-prepare-reserve-commit.md @@ -0,0 +1,8 @@ +# Funds In - Prepare, Reserve, Commit + +Funds In operation performs all the stages of the transfer (prepare, reserve and commit) at once. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.1-in.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-abort.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-abort.md new file mode 100644 index 000000000..ad93a4b73 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-abort.md @@ -0,0 +1,8 @@ +# Funds Out - Abort + +Funds out abort of a prepared reconciliation transfer. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.3-out-abort.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-commit.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-commit.md new file mode 100644 index 000000000..5a10b0a3d --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-commit.md @@ -0,0 +1,8 @@ +# Funds Out - Commit + +Funds out commit of a prepared reconciliation transfer. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.2-out-commit.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-prepare-reserve.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-prepare-reserve.md new file mode 100644 index 000000000..5fb1ad08e --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/funds-out-prepare-reserve.md @@ -0,0 +1,9 @@ +# Funds Out - Prepare & Reserve + +Funds Out operation performs the prepare and reserve stages of the transfer at once. But the commit of the transfer or the abort operation are separate processes. + +## Sequence Diagram + + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.2.1-out-prepare-reserve.plantuml" %} +{% enduml %} \ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/reconciliation-transfer-prepare.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/reconciliation-transfer-prepare.md new file mode 100644 index 000000000..b4c76a93f --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/reconciliation-transfer-prepare.md @@ -0,0 +1,8 @@ +# Reconciliation Transfer Prepare + +Design for Reconciliation Transfer Prepare sub-process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.a-reconciliationTransferPrepare.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/transfer-state-and-position-change.md b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/transfer-state-and-position-change.md new file mode 100644 index 000000000..65188d67f --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/funds-in-out/transfer-state-and-position-change.md @@ -0,0 +1,8 @@ +# Transfer State and Position Change + +Design for Transfer State and Position Change sub-process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/funds-in-out/assets/diagrams/sequence/seq-recfunds-5.1.0.b-transferStateAndPositionChange.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/oss-settlement-fsd.md b/website/versioned_docs/v1.0.1/technical/central-settlements/oss-settlement-fsd.md new file mode 100644 index 000000000..58b38fc9d --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/oss-settlement-fsd.md @@ -0,0 +1,1723 @@ +--- +title: "OSS Settlement \n \nBusiness Requirements Document" +--- + +Introduction +============ + +This document describes the requirements for changes to the functionality of the +OSS Mojaloop switch to support: + +1. The immediate requirement for Mowali of being able to settle according to + different timetables for different currencies. + +2. The requirement for TIPS to be able to settle gross via a pooled account. + +3. The requirement for TIPS to use the settlement process for interchange fees. + +4. The requirement for Mojaloop to support the evolving Settlement API + +References +========== + +The following external references are used in this document: + +| Reference | Document | +| :--- | :--- | +| 1 | [Open API for FSP Interoperability Specification](http://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html) | +| 2 | [Settlement API Interface Definition](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json)| +| 3 | [Current administration API definition](https://mojaloop.io/documentation/api/central-ledger-api-specification.html)| +| | | + +Versions +======== + +| Version | Description | Author | Date | Changes tracked | +|:---------|:------------------|:------------------|:------------|:-----------------| +| 1.0 | Baseline version | Michael Richards | 2 Dec 2019 | No | +| | | | | | + +Definition of terms +=================== + +Settlement model +---------------- + +A settlement model defines a kind of settlement within the scheme. Settlement +models can be defined by scheme administrators, and have the following +characteristics: + +1. They can support settlement either gross or net settlements. If gross, then + a settlement is executed after each transfer is completed. If net, a group + of transfers is settled together. + +2. They can support either multilateral or bilateral settlements. If + settlements are multilateral, each participant settles with the scheme for + the net of its transfers which are included in the settlement. If they are + multilateral, each participant settles separately with each of the other + participants and the scheme is not a party to the settlement. + +3. They can support either deferred or immediate settlements. If settlements + are immediate, they are actioned immediately they are completed. If they are + deferred, they are actioned after a period of delay. + +4. They can support continuous or discontinuous settlements. Discontinuous + settlements require a formal approval from a resource outside the system to + confirm that they have been completed. Continuous settlements can be + approved from within the switch, provided that the criteria for approval are + met. + +5. They can require liquidity support or not. + +Ledger account type +------------------- + +A ledger account type defines a type of internal account. Current values are: + +1. POSITION. Accounts of this type are used for provisioning transfers + +2. SETTLEMENT. Accounts of this type are intended to be reflections of + Settlement Accounts held at the Settlement Bank + +3. HUB_RECONCILIATION. Each implementation of the switch has one account of + this type for each currency supported by the switch, owned by the virtual + DFSP which represents the Hub in the implementation. The account is used + during FUNDS IN/OUT operations to represent movements in participants’ + SETTLEMENT accounts. + +4. HUB_MULTILATERAL_SETTLEMENT. Each implementation of the switch has one + account of this type for each currency supported by the switch, owned by the + virtual DFSP which represents the Hub in the implementation. This account + type is used to record counterparty information for net amounts paid to or + from participants as part of settlements. Entries in this account represent + balancing values corresponding with the net value in a participant’s + POSITION account for a given settlement. + +5. HUB_FEE. Accounts of this type represent accounts into which fees are + collected, or from which they are disbursed. (Not implemented) + +Ledger entry type +----------------- + +A ledger entry type categorises the type of funds which are the content of a +transfer and which are due to or from a participant as a consequence of that +transfer. The current values are: + +1. PRINCIPLE_VALUE. This designates the funds moved as the content of a + transfer in the Interoperability API Specification sense of that term (i.e. + the **amount.amount** item in the **Transaction** object described in + [Section 7.4.17](https://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html#7317-ilpcondition) of the Interoperability API specification[1](https://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html).) It should be + spelt “principal.” + +2. INTERCHANGE_FEE. This designates fees paid between participants in the + system. (Not implemented) + +3. HUB_FEE. This designates fees paid between participants and the scheme. (Not + implemented) + +4. POSITION_DEPOSIT. This is used to designate funds transfers which are + intended to increase the value of the position in a particular ledger + account. (Not implemented) + +5. POSITION_WITHDRAWAL. This is used to designate funds transfers which are + intended to reduce the value of the position in a particular ledger account. + (Not implemented) + +6. SETTLEMENT_NET_RECIPIENT. This is used to designate funds which a + participant is owed by the counterparty as part of a settlement (in a + multilateral settlement model, the counterparty will be the hub; in a + bilateral settlement model, it will be another party.) + +7. SETTLEMENT_NET_SENDER. This is used to designate funds which a participant + owes to the counterparty as part of a settlement (in a multilateral + settlement model, the counterparty will be the hub; in a bilateral + settlement model, it will be another party.) + +8. SETTLEMENT_NET_ZERO. This is used to generate formal records where a + participant’s net position in a settlement is zero. + +9. RECORD_FUNDS_IN. This is used to record funds deposited by a participant to + the SETTLEMENT account. + +10. RECORD_FUNDS_OUT. This is used to record funds withdrawn by a participant + from the SETTLEMENT account. + +The last seven of these types relate to the internal structure of the switch and +should be neither visible to nor modifiable by a scheme. The first three fall +into a category which is expected to be extensible by the scheme. The table does +not currently have an indicator to mark this distinction, but one is proposed in +Section TODO. + +Participant role types +---------------------- + +A participant role type defines the role that a particular participant is +playing in a given transaction. The current values of this table are: + +1. PAYER_DFSP. The participant is the debtor in a transfer. + +2. PAYEE_DFSP. The participant is a creditor in a transfer. + +3. HUB. The participant is representing the scheme, and may be either the + creditor or the debtor in a transaction. + +4. DFSP_SETTLEMENT. The participant represents the settlement account of a + participant in the scheme. It may be either the creditor or the debtor in a + transaction. It is used during FUNDS IN/OUT operations. This is used for + entries whose counterparty is an entry in the HUB account. + +5. DFSP_POSITION. The participant represents the position of a participant in + the scheme. It may be either the creditor or the debtor in a transaction. It + is used during the settlement process. This is used for entries whose + counterparty is an entry in the HUB account. + +Settlement states +----------------- + +A settlement may be in a number of possible states. The states currently +supported are: + +1. PENDING_SETTLEMENT. A new settlement consisting of one or more settlement + windows has been created. The net amounts due to or from each participant + have been calculated and a report detailing these net amounts has been + produced. + +2. PS_TRANSFERS_RECORDED: all the calculated net amounts for the settlement + have been converted into transfers between the creditor and debtor accounts. + It is applied for each settlement entry and at settlement level, when all + entries are recorded. This state is not applied to settlement windows. + +3. PS_TRANSFERS_RESERVED: all the funds required for settlement have been + reserved (debit amounts only.) It is applied for each settlement entry and + at settlement level, when all entries are reserved. + +4. PS_TRANSFERS_COMMITTED: all the credit amounts required as part of the + settlement have been committed. It is applied for each settlement entry and + at settlement level, when all entries are committed. + +5. SETTLING: It is used only at settlement level if all accounts are not yet + settled. + +6. SETTLED: This state is applied to settlement accounts in + PS_TRANSFERS_COMMITTED state. When applied to even one account, the + settlement will transit to SETTLING state. When all accounts in a window are + SETTLED, the settlement window will change from PENDING_SETTLEMENT to + SETTLED, while the settlement is still in SETTLING state. When all accounts + from all windows are SETTLED, the settlement will be changed to SETTLED. It + is possible for a settlement to change directly from PS_TRANSFERS_COMMITTED + to SETTLED if all accounts are settled with one request. + +7. ABORTED: the settlement could not be completed and should be rolled back. It + is currently only possible to abort a settlement if the state is one of: + PENDING_SETTLEMENT, PS_TRANSFERS_RECORDED or PS_TRANSFERS_RESERVED. After + having even one account in PS_TRANSFERS_COMMITTED state (even if the + settlement as a whole is still in PS_TRANSFERS_RESERVED), a request to abort + the settlement is rejected. It should be noted that this prevents abortion + in cases such as the default of a participant midway through the settlement + process. + +The present data model contains a foreign key to the enumeration table +containing settlement states from the +**settlementParticipantCurrencyStateChange** table, which maps onto a table +which contains a record for each participant/currency/account type combination. + +Assumption: this means that the states appropriate to a settlement are also to +be applied to the individual elements of a settlement. As a consequence, the +descriptions should be taken to refer to individual settlement amounts as well +as to the settlement as a whole. + +Settlement window states +------------------------ + +A settlement is made up of one or more settlement windows. Each settlement +window has a state associated with it. The current values of this table are as +follows: + +1. OPEN: the settlement window can have ledger movements added to it. + +2. CLOSED: the settlement window cannot have any additional ledger movements + added to it. It is available for settlement. + +3. PENDING_SETTLEMENT: the settlement window’s contents have been included in a + settlement request, but this request is a request for deferred settlement + which has not yet been completed. + +4. SETTLED: the scheme has confirmed that the obligations incurred by + participants as a consequence of the settlement to which this settlement + window belongs have been met by those participants and the settlement is + therefore complete. + +5. ABORTED: the settlement to which this settlement window belongs did not + complete. The settlement window is available to be included in another + settlement. + +Transfer states +--------------- + +Each transaction which passes through the system can be assigned one of a fixed +number of states. The current values of this state are: + +1. RECEIVED_PREPARE: the switch has received the transaction request. + +2. RESERVED: the switch has reserved the funds required to cover the + transaction. + +3. RECEIVED_FULFIL: the switch has received the fulfilment request, and the + transaction has been assigned to a settlement window + +4. COMMITTED: the transaction has been committed. + +5. FAILED: the transaction has failed in some unspecified way and has been + aborted. (Not implemented) + +6. RESERVED_TIMEOUT: the transaction has timed out while in the reserved state + and has been aborted. + +7. RECEIVED_REJECT: the transaction has been rejected by the payee DFSP and + should be aborted. + +8. ABORTED_REJECTED: the transaction has been aborted by the switch as a + consequence of its having been rejected by the payee DFSP. + +9. RECEIVED_ERROR: the transaction was not received correctly by the payee DFSP + and should be aborted. + +10. ABORTED_ERROR: the transaction has been aborted by the switch as a + consequence of its not having been received correctly. + +11. EXPIRED_PREPARED: the transaction has expired during the prepare process and + has been aborted. + +12. EXPIRED_RESERVED: the transaction has timed out during the reservation + process and has been aborted. + +13. INVALID: the transaction has failed the switch’s internal validation process + and has been aborted. + +Transfers and transactions +-------------------------- + +Transfers define a movement of funds in the system. There is a table in the +switch which has this name. Some entries in this table are the consequence of +external movements which are generated by scheme participants and processed by +the switch; others are internally generated by the switch (e.g. to record net +movements of funds associated with settlements.) + +It may be a source of confusion that, although “transfers” is a term used e.g. +in the Interoperability API specification to designate the movement of funds +between participants, it is used as the name of a table in the switch which +stores other types of funds movement as well. + +This document will therefore adopt the following convention: + +- “Transfers” refers to the content of instructions issued using the + **/transfers** resource of the Interoperability API definition[1](http://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html). (see [Section 6.7](http://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html#67-api-resource-transfers) of the Interoperability API definition[1](http://mojaloop.io/mojaloop-specification/documents/API%20Definition%20v1.0.html).) + +- “Transactions” refers to all movements of funds which are tracked by the + switch. + +In scope and out of scope +========================== + +In scope +-------- + +The following functional items are in scope: + +1. Requesting a settlement by currency + +2. Requesting a settlement by currency and settlement model + +Out of scope +------------ + +Business rules +============== + +How do things happen now? +------------------------- + +This section describes how the current settlement process works. + +### Categorisation + +The process leading to settlement is initially defined by entries in the +*participantCurrency* table. This table holds a record for each combination of: + +1. Participant + +2. Currency + +3. Ledger account type (see Section 2.2 above.) + +### Recording positions + +Each entry in this table has a corresponding entry in the *participantPosition* +table. This table stores the current committed and reserved positions for each +of the combinations described above. Each change to the positions in this table +is documented by an entry in the *participantPositionChange* table, which refers +the change back to the *transfer* table and thence to the authoritative record +of transactions. + +### Assigning transactions to settlement windows + +As each transfer is fulfilled, a record is created for it in the +**transferFulfilment** table. This record contains a link to the currently open +settlement window. + +Records may be created for transactions which are not transfers. Non-transfer +transactions have their ilpFulfilment set to 0. + +The settlement API [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) contains a resource +(**closeSettlementWindow**) to allow an administrator to close a settlement +window and create a new one. The resource takes a settlement window ID as part +of the query string, together with a string describing the status to be assigned +and a string describing the reason for the change of status. In-line +documentation for the resource states: “If the settlementWindow is open, it can +be closed and a new window is created. If it is already closed, return an error +message. Returns the new settlement window.” + +Settlement windows can only be passed a status of CLOSED using this resource. + +### Initiating a settlement + +The initiation of a settlement is initiated by making a call triggered by the +scheme making a **POST** to the **/settlements** resource in the settlement API. +The caller gives a reason for the settlement request. This is a synchronous +call, whose return is described in Section 4.1.5 below. Internally, the +initiation is marked by the creation of a record in the **settlement** table and +the association of a number of settlement window records in the +**settlementWindow** table with the settlement created. This says: I want this +settlement to contain these settlement windows. Each of the settlement windows +selected to form part of this settlement must have the status CLOSED or ABORTED. +When selected to form part of a settlement, the status of each window should be +changed to PENDING_SETTLEMENT. The status of the settlement itself should also +be set to PENDING_SETTLEMENT. + +### Calculating the content of a settlement + +When a settlement has been requested and the windows which it will contain have +been selected, the switch produces an aggregation of the content of the proposed +settlement at the following levels: + +1. Settlement window (from **settlementWindow**) + +2. Participant (from **participantCurrency**) + +3. Currency (from **participantCurrency**) + +4. Account type (from **participantCurrency**) + +5. Participant role type (from **transferParticipant**) + +6. Ledger entry type (from **transferParticipant**) + +This query can be provisioned from the database FK links from **settlement** to +**settlementwindow**, to **transferFulfilment** (all fulfilled transfers), to +**transfer**, to **transferParticipant**. The amount is taken from the +**transferParticipant** table. Correct! + +Only transfers are included in a settlement. This is implied by the first +aggregation criterion above (Settlement window) and the fact that only transfers +are assigned to the current OPEN window when a **transferFulfilment** record is +created. All COMMITTED transactions should have an entry in the +**transferFulfilment** table. + +The results of this query are used to construct a number of records in the +**settlementParticipantCurrency** table, representing the net amount due to or +from each participant and currency in the settlement as a consequence of the +settlement. For a given settlement, these records are segmented by: Correct! + +1. Participant (from **participantCurrency**) + +2. Currency (from **participantCurrency**) + +3. Account type (from **participantCurrency)** + +This report is used as the basis of the information returned from the initial +**POST** to the **/createSettlement** resource of the settlement API (see +Section 4.1.4 above.) + +Bilateral settlements are not currently implemented. + +### Creating position records for the settlement + +A record is inserted in the **transfer** table for each net amount calculated in +the previous step when the settlement account transitions from +PENDING_SETTLEMENT to PS_TRANSFERS_RECORDED. Please note that PS stands for +PENDING_SETTLEMENT. Each of these records will have the account type SETTLEMENT. +The ledger entry type will be SETTLEMENT_NET_SENDER, SETTLEMENT_NET_RECIPIENT or +SETTLEMENT_NET_ZERO depending on whether the participant owes money to the +scheme, is owed money by the scheme or is neutral in the settlement, +respectively. The transfer participant type is HUB for the Hub participant and +DFSP_POSITION for the DFSP participant. The account type is imposed by the +participant currency – for the Hub participant it is the +HUB_MULTILATERAL_SETTLEMENT and for the DFSP participant it is the POSITION +account. This enables the switch to reset the participant’s position when the +window enters the PS_TRANSFERS_COMMITTED state. + +### Progress of the settlement + +As the scheme verifies that participants have settled the amounts due from them, +the scheme administrator can update the switch with this information. + +#### Updating the status of settlement windows + +Three methods of performing this update are supported. Each of these methods is +discussed in more detail below. + +##### **updateSettlementById** + +An administrator may issue a **PUT** to the **/updateSettlementById** resource +on the settlement API, giving the settlement ID of the settlement they wish to +update as part of the query (e.g. **PUT +/updateSettlementById/settlements/123.**) The content of the request is as +follows: + +1. A state to be assigned to the participants required. The state is + constrained to be either ABORTED or INVALID + +2. A reason for the change of state. + +3. An external reference for the change. + +4. An array of participants to which the status is to be applied. The following + information is given for each participant: + + 1. The ID of the participant. This is the internal Mojaloop ID of the + participant. + + 2. An array of *accounts*. The content of each account is as follows: + + 1. An ID. The description characterises this as the participant’s + currency ID QUESTION: Is this correct? It is an integer, where a + VARCHAR(3) would be expected. + + 2. A reason. A string which presumably can contain anything. + + 3. The state of the settlement for the account. This is not constrained + by an enum, but is a simple string. It is not clear how this status + relates to the overall state given in Item 1 above. + + 4. An external reference for the change in state. + +> A call to this API resource may contain *either* items 1-3 above *or* an +> array of accounts as specified in item 4 above, but not both. If it contains +> items 1-3 above, then all the items must be present. If these rules are +> breached, then the switch will reject the request. + +##### **updateSettlementBySettlementParticipant** + +An administrator may issue a **PUT** to the +**/updateSettlementBySettlementParticipant** resource on the settlement API, +giving the settlement ID and the participant ID of the parts of the settlement +they wish to update as part of the query (e.g. **PUT +/updateSettlementByParticipant/settlements/123/participants/56789.**) The +content of the request is as follows: + +1. An array of state changes, whose content is as follows: + + 1. The currency ID whose status is to be changed. This is an integer, where + a VARCHAR(3) would be expected. + + 2. A reason for the state change. + + 3. The state requested. This is a string, where an enumeration would be + expected. + + 4. An external reference for the change in state. + +Note that this is an array with the same structure as that described in item 4 +of Section 5.1.7.1 above, although it is defined separately in the API +definition. + +##### **updateSettlementBySettlementParticipantAccount** + +An administrator may issue a **PUT** to the +**/updateSettlementBySettlementParticipantAccount** resource on the settlement +API, giving the settlement ID, the participant ID and the account ID of the part +of the settlement they wish to update as part of the query (e.g. **PUT +/updateSettlementByParticipant/settlements/123/participants/56789/accounts/1.**) +The content of the request is as follows: + +1. The state requested. This is a string, where an enumeration would be + expected. + +2. A reason. A string which presumably can contain anything. + +3. An external reference for the change in state. + +Note that this is a structure with most of the same members as the structure +defined in Item 1 of Section 5.1.7.2 above, although the items appear in a +different order. + +#### How changes in settlement window state are processed + +The action taken in response to these calls depends on the status assigned to +the account. In any case, a record is created in the +**settlementParticipantCurrencyStateChange** table, giving the state identifier, +the reason and an external reference for the change. + +There is a sequence of steps defined for a settlement. Each step must be +followed in order, and no steps may be omitted. The sequence of steps is +hard-coded into the application and is as follows: + +1. PENDING_SETTLEMENT + +2. PS_TRANSFERS_RECORDED + +3. PS_TRANSFERS_RESERVED + +4. PS_TRANSFERS_COMMITTED + +5. SETTLED + +A settlement can be aborted provided no account in the settlement has reached a +status of PS_TRANSFERS_COMMITTED. + +The following actions are taken on each change of status: + +##### PENDING_SETTLEMENT to PS_TRANSFERS_RECORDED + +A record is generated in the **transfer** table to record the change in state. +The parties to this transfer are the POSITION account type and the +HUB_MULTILATERAL_SETTLEMENT type. If the participant account is a net creditor +for the settlement, then the ledger entry type will be set to +SETTLEMENT_NET_RECIPIENT. If the participant account is a net debtor for the +settlement, then the ledger entry type will be set to SETTLEMENT_NET_SENDER. If +the participant account is neutral for the settlement, then the ledger entry +type will be set to SETTLEMENT_NET_ZERO. + +A record is also created in the **transferstatechange** table with the +RECEIVED_PREPARE state, which means that no positions have been changed. + +When the last participating account is changed to PS_TRANSFERS_RECORDED, then +the settlement’s status is also changed to PS_TRANSFERS_RECORDED. + +If an administrator attempts to change an account’s state to +PS_TRANSFERS_RECORDED and either the settlement’s state or the settlement +account’s state is not PENDING_SETTLEMENT or PS_TRANSFERS_RECORDED, then the +request will be rejected. + +##### PS_TRANSFERS_RECORDED to PS_TRANSFERS_RESERVED + +A new record is created in the **transferstatechange** table for the transfer +that was created in section 5.1.7.2 above. This record will have a status of +RESERVED. If the participant is a net creditor as a result of the settlement, +then a record will also be created in the participantPositionChange table if the +account being reserved is for a net creditor in the settlement, as defined in +Section 5.1.7.2 above. The Net Debit Cap is checked at this point, and if the +current position exceeds the Net Debit Cap, then the Net Debit Cap is +automatically adjusted by the amount of the net credit due to the account as +part of the settlement and the participant is sent a notification of the new Net +Debit Cap value and its currency. + +When the last participating account is changed to PS_TRANSFERS_RESERVED, then +the settlement’s status is also changed to PS_TRANSFERS_RESERVED. + +If an administrator attempts to change an account’s state to +PS_TRANSFERS_RESERVED and either the settlement’s state or the settlement +account’s state is not PS_TRANSFERS_RECORDED or PS_TRANSFERS_RESERVED, then the +request will be rejected. + +##### PS_TRANSFERS_RESERVED to PS_TRANSFERS_COMMITTED + +A new record is created in the **transferstatechange** table for the transfer +that was created in section 5.1.7.2 above. This record will have a status of +COMMITTED. If the participant is a net debtor as a result of the settlement, +then a record will also be created in the participantPositionChange table if the +account being reserved is for a net debtor in the settlement, as defined in +Section 5.1.7.2 above. + +When the last participating account is changed to PS_TRANSFERS_COMMITTED, then +the settlement’s status is also changed to PS_TRANSFERS_COMMITTED. + +If an administrator attempts to change an account’s state to +PS_TRANSFERS_COMMITTED and either the settlement’s state or the settlement +account’s state is not to PS_TRANSFERS_RESERVED or PS_TRANSFERS_COMMITTED, then +the request will be rejected. + +##### PS_TRANSFERS_COMMITTED to SETTLED + +When the first account is changed to a status of SETTLED, the settlement’s state +is changed to SETTLING. + +When the last participating account is changed to SETTLED, then the settlement’s +status is also changed to SETTLED. + +If an administrator attempts to change an account’s state to SETTLED and either +the settlement’s state or the settlement account’s state is not to +PS_TRANSFERS_COMMITTED or SETTLED, then the request will be rejected. + +### Aborting the settlement + +If there is any failure in the scheme’s process for recovering the amounts due +from participants in a settlement, the scheme can update the switch with this +information by issuing a **PUT** to the **/updateSettlementById** resource of +the settlement API and setting the **state** value of the content of the message +to ABORTED. A **PUT** call on the **/updateSettlementById** resource is the only +method which may be used for ABORTING a settlement. If any account information +is given in the call, then neither the **state** nor the **reason** nor the +**externalReference** fields may be set. if the **state** value is set at the +top level of the call, then the **reason** field must also be set, and the +request will be rejected if any account information is given in the call. + +If an attempt is made to abort a settlement, and any of the accounts in the +settlement have the status PS_TRANSFERS_COMMITTED or SETTLED, then the request +will be rejected. + +When a call is received, a new record is created in the +**settlementParticipantCurrencyStateChange** table, and is given the appropriate +status based on the status reported by the caller. Depending on the update that +was received, this may also require the status of the transaction and that of +the participant position to be updated. No, it is done at settlement level for +the entire settlement and all entries in settlementParticipantCurrency are +affected. + +Note: if the settlement is bilateral, then there is no obvious reason to abort +the entire settlement if one interchange fails. We should think about this use +case and how we would want to represent it. This document does not consider this +use case. + +Aborting a settlement comprises the following steps: + +1. The status of the transfers created in Section 5.1.6 above should be changed + to ABORTED. + +2. A new record is added to the **settlementParticipantCurrencyStateChange** + table for each of the participant records in the settlement. This record has + a status of ABORTED, and the **currentStateChangeId** column in the + **settlementParticipantCurrency** table for that participant record is + changed to point to this new record. + +3. A new record is added to the **settlementWindowStateChange** table for each + settlement window in the settlement. This record has a status of ABORTED, + and the **currentStateChangeId** column in the settlementWindow table for + that window is changed to point to this new record. + +4. A new record is added to the **settlementStateChange** table for the + settlement. This record has a status of ABORTED, and the + **currentStateChangeId** column in the **settlement** table is changed to + point to this new record. + +5. If positions have been reserved for net creditors as a result of the + settlement (see Section 5.1.7.3 above,) then a balancing entry will be + created in the **participantPositionChange** table to reverse the + reservation of funds. This action does not at present reverse any change to + the account’s Net Debit Cap that may have been made as a consequence of this + reservation. + +6. Any records created in the **transfers** table (see Section 5.1.7.2 above) + will have their state changed in two steps by adding records to the + **transferStateChange** table. The first step will change the transfer state + to REJECTED. The second step will change the state to ABORTED. + +Question: should there be/is there a time-out after which a settlement will be +aborted if it has not completed? If there is, how is it set? No, there isn’t +timeout on a settlement level. But when transfers are prepared (for +PS_TRANSFERS_RECORED) expiration is set on a transfer level. Its value is +controlled by a Config.TRANSFER_VALIDITY_SECONDS, which currently defaults to +432000 seconds, which equals 5 days. It is big enough to avoid expiration. +Still, if that happens, it would leave the data in an unrecoverable by the API +state. This is very good point and should be certainly addressed with the next +increment! + +Recording the deposit of funds +------------------------------ + +As participants are informed of their liabilities under the settlement, it is +expected that they will deposit funds in their settlement account to cover those +liabilities. These activities are recorded via the central ledger administration +interface resource **recordFundsIn**. + +This action is called through a POST to the administration interface, giving the +name of the participant and the account to be credited in the form POST +/participants/{participantName}/accounts/{accountId} (e.g. **POST +/participants/myDfsp/accounts/1234**) The content of this message is as follows. + +1. transferId: a UUID to be used to identify the transfer in the switch. + +2. externalReference: a reference used to identify the transfer for the + administrator + +3. action: this should be set to “recordFundsIn” for recording funds in. + +4. reason: the reason why the transfer is being made. + +5. amount: the amount of the transfer. + + 1. amount: the actual amount being transferred + + 2. currency: the ISO 4217 code of the currency of the deposit. + +6. extensionList: a series of key/value pairs which are used to carry + additional information + +When an administrator records that a participant has deposited funds to an +account, the amount deposited is recorded in an entry in the **transfer** table. +The parties to the transfer are recorded by entries in the +**transferParticipant** table, with a ledger account type of the type requested +in the POST for the participant and HUB_RECONCILIATION for the balancing entry. +For deposits, the participant account will be the creditor and the +HUB_RECONCILIATION account the debtor. The application will currently reject +requests to this interface which do not have a ledger account type of +SETTLEMENT. + +A deposit goes through the following changes of state: + +1. A record is created for the transfer in the **transferStateChange** table + with a state of RECEIVED_PREPARE. + +2. Next, a record is created in the **transferStateChange** table with the + state RESERVED. This also creates a record in the + **participantPositionChange** table to record the reservation of funds in + the HUB_RECONCILIATION account, and the **participantPosition** table’s + value for that account is updated. + +3. Finally, a record is created in the **transferStateChange** table with the + state COMMITTED. After this act, records are created in the + **participantPositionChange** table for the creditor account to record the + completion of the deposit, and the appropriate record in the + **participantPosition** table has its balance updated. + +These changes of state are simply chained together in sequence. There is no +interval or trigger between the steps. + +This activity has no direct effect on the settlement process or on the Net Debit +Cap. + +Recording the withdrawal of funds +--------------------------------- + +At various times, participants may wish to withdraw funds from their settlement +accounts: for instance, if they are long-term net beneficiaries of transfers and +are building up a surplus of liquidity. These activities are recorded via a +two-phase process. In the first phase, the funds for the proposed withdrawal are +reserved via the central ledger administration interface resource +**recordFundsOutPrepareReserve**. In the second phase, the withdrawal is +committed via the **recprdFundsOutCommit** resource or aborted through the +**recordFundsOutAbort** resource. + +These activities are defined below. + +### **recordFundsOutPrepareReserve** + +This action is called through a POST to the administration interface, giving the +name of the participant and the account to be credited (e.g. **POST +/participants/myDfsp/accounts/1234**) The content of this message is as follows. + +1. transferId: a UUID to be used to identify the transfer in the switch. + +2. externalReference: a reference used to identify the transfer for the + administrator + +3. action: this should be set to “recordFundsOutPrepareReserve” for recording + funds withdrawals. + +4. reason: the reason why the transfer is being made. + +5. amount: the amount of the transfer. + + 1. amount: the actual amount being transferred + + 2. currency: the ISO 4217 code of the currency of the deposit. + +6. extensionList: a series of key/value pairs which are used to carry + additional information + +When an administrator records that a participant has requested the withdrawal of +funds from an account, the amount to be withdrawn is recorded in an entry in the +**transfer** table. The parties to the transfer are recorded by entries in the +**transferParticipant** table, with a ledger account type of the type requested +in the POST for the participant and HUB_RECONCILIATION for the balancing entry. +For withdrawals, the participant account will be the debtor and the +HUB_RECONCILIATION account the creditor. The application will currently reject +requests to this interface which do not have a ledger account type of +SETTLEMENT. + +Reservation of a withdrawal goes through the following changes of state: + +1. A record is created for the transfer in the **transferStateChange** table + with a state of RECEIVED_PREPARE. + +2. Next, a record is created in the **transferStateChange** table with the + state RESERVED. This also creates a record in the + **participantPositionChange** table to record the reservation of funds in + the participant’s settlement account, and the **participantPosition** + table’s value for that account is updated. + +These changes of state are simply chained together in sequence. There is no +interval or trigger between the steps.**recordFundsOutCommit** + +This action is called through a POST to the administration interface, giving the +name of the participant and the account to be credited (e.g. **POST +/participants/myDfsp/accounts/1234**) The content of this message is as follows. + +1. transferId: a UUID to be used to tie the commit to the preceding reservation + in the switch. + +2. externalReference: a reference used to identify the transfer for the + administrator + +3. action: this should be set to “recordFundsOutCommit” for recording funds + commitments. + +4. reason: the reason why the transfer is being made. + +5. amount: the amount of the transfer. + + 1. amount: the actual amount being transferred + + 2. currency: the ISO 4217 code of the currency of the deposit. + +6. extensionList: a series of key/value pairs which are used to carry + additional information + +When an administrator records that a participant wants to commit the withdrawal +of funds from an account, the original entry in the **transfer** table is +identified. The parties to the transfer are recorded by entries in the +**transferParticipant** table, with a ledger account type of the type requested +in the POST for the participant and HUB_RECONCILIATION for the balancing entry. +For withdrawals, the participant account will be the debtor and the +HUB_RECONCILIATION account the creditor. The application will currently reject +requests to this interface which do not have a ledger account type of +SETTLEMENT. + +Commitment of a withdrawal goes through the following changes of state: a record +is created in the **transferStateChange** table with the state COMMITTED. After +this act, records are created in the **participantPositionChange** table for the +creditor account to record the completion of the deposit, and the appropriate +record in the **participantPosition** table has its balance updated. These +changes of state are simply chained together in sequence. There is no interval +or trigger between the steps. + +This activity has no direct effect on the settlement process or on the Net Debit +Cap. + +Proposed enhancements +===================== + +This section describes the enhancements to the existing OSS settlement process +(described in Section 4.1 above) which are proposed. Each enhancement is shown +in a separate section and, where there are dependencies between enhancements, +these are listed in the enhancement’s description. + +Request settlement by currency [EPIC] +------------------------------------- + +The following changes are required to support settling separately for different +currencies. + +### Database changes + +The following changes are required to support multi-currency settlement. + +#### Addition of a settlementWindowContent table [Story \#1] + +A new table will be added to the database. The name of this table will be +**settlementWindowContent**. The table will contain an entry for each item of +content in a given settlement window, broken down by ledger account type and +currency. The full column structure of the table is as follows: + +| **Column name** | **Description** | **Attributes** | +|-----------------------------------------|---------------------------------------------------|---------------------------------------------------------------------------------------| +| **settlementWindowContentId** | Auto-generated key for the record. | BIGINT(20). Unsigned, not null, primary key, autoincrement | +| **settlementWindowId** | The settlement window that the record belongs to. | BIGINT(20). Unsigned, not null. Foreign Key to **settlementWindow** | +| **ledgerAccountTypeId** | The ledger account that the record refers to. | INT(10). Unsigned, not null. Foreign key to **ledgerAccountType** | +| **currencyId** | The currency that the record refers to. | VARCHAR(3). Not null. Foreign key to **currency**. | +| **createdDate** | The date and time when the record was created. | DATETIME. Not null. Defaults to CURRENT_TIMESTAMP. | +| **currentStateChangeId** | The current state of this entry. | BIGINT(20). Unsigned. Foreign key to **settlementWindowContentStateChange** | + +  + +#### Addition of a settlementWindowContentStateChange table [Story \#2] + +A new table will be added to the database. The name of this table will be +**settlementWindowContentStateChange**. The table will track changes to the +status of entries in the **settlementWindowContent** table. The full column +structure of the table is as follows: + +| **Column name** | **Description** | **Attributes** | +|------------------------------------------|---------------------------------------------------------------------|----------------------------------------------------------------------------| +| **settlementWindowContentStateChangeId** | Auto-generated key for the record. | BIGINT(20). Unsigned, not null, primary key, autoincrement | +| **settlementWindowContentId** | The settlement window content record whose status is being tracked. | BIGINT(20). Unsigned, not null. Foreign Key to **settlementWindowContent** | +| **settlementWindowStateId** | The record’s status. | VARCHAR(50). Not null. Foreign key to **settlementWindowState** | +| **reason** | An optional field giving the reason for the state being set. | VARCHAR(512). | +| **createdDate** | The date and time when the record was created | DATETIME. Not null. Defaults to CURRENT_TIMESTAMP | + +  + +#### Changes to the settlementTransferParticipant table [Story \#3] + +The name of the **settlementTransferParticipant** table should be changed to +**settlementContentAggregation**. + +The column structure of the **settlementContentAggregation** table should be +modified as follows: + +1. Remove the following foreign keys from the table: + 1. settlementtransferparticipant_settlementid_foreign + 2. settlementtransferparticipant_settlementwindowid_foreign +2. Remove the following columns from the table: + 1. settlementId + 2. settlementWindowId +3. Add the following column to the table: + 1. Column name: **settlementWindowContentId** + 2. Attributes: BIGINT(20), unsigned, not null +4. Add the following foreign key to the table: + 1. Name: settlementContentAggregation_settlementwindowcontent_foreign + 2. Child column **settlementWindowContentId** + 3. Refers to table: **settlementWindowContent** + 4. Refers to column: **settlementWindowContentId** + +All database scripts which use the **settlementTransferParticipant** table will +be changed to accommodate the new name and structure of the table. [Story \#8] + +Queries to filter the records in the **settlementContentAggregation** table +required for a settlement in a particular currency will need to join across from +that table to the **participantCurrency** table using the +**participantCurrencyId** column to ascertain the currency to which a particular +row refers. The **currencyId** column is held in the **participantCurrency** +table. [Story \#9] + +#### Add structures relating to settlement models [Story \#4] + +In order to support the specification of settlement models, which will include +currencies, the database changes specified in Section 6.2.1.1 below should be +added to the database. + +Settlement models describing the settlement types required for a given +implementation will be developed and tested.**[Story \#4a]** + +#### Change to settlement table [Story \#5] + +The **settlement** table in the central ledger database needs to be modified to +add a *settlementModel* column. This column should have the following +characteristics: + +- The column should be required (NOT NULL) and unsigned. +- The column’s data type should be integer(10) +- The column should be defined as a foreign key reference to the +*settlementModelId* field of the **settlementModel** table. + +When this change is applied to an existing database, a settlement model to +describe the default settlement should be created. The settlementCurrencyId +column in this model should be left blank (= all currencies.) The +settlementModel column in all existing records in the settlement table should be +set to point to this model’s ID. A script to apply this change should be +created, tested and stored in the repository. + +### Changes to processing + +The following changes to the processing code are required to support +multi-currency settlement. + +#### Change to code supporting closeSettlementWindow resource + +The existing API [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) provides a single function +(**closeSettlementWindow**) to manage settlement windows. This function allows +its user to select a settlement window by ID number and to input a new status +for the window and a reason for that status. + +When a settlement window is closed, the code supporting this activity should +perform two functions, as follows. These functions should be performed in the +background and without impacting system performance. + +##### Generate records in the settlementWindowContent table [Story \#1095] + +The code should generate a record in the **settlementWindowContent** table for +each ledger entry type/currency combination found in the transfers in the +settlement window. This information can be obtained from the following query: +``` +SELECT DISTINCT + @mySettlementWindowId, pc.ledgerAccountTypeId, pc.currencyId +FROM transferFulfilment tf +INNER JOIN transferParticipant tp + ON tp.transferId = tf.transferId +INNER JOIN participantCurrency pc + ON pc.participantCurrencyId = tp.participantCurrencyId +WHERE tf.settlementWindowId = @mySettlementWindowId; +``` + +##### Generate records in the settlementContentAggregation table [Story \#1095] + +The code should calculate the aggregate values for all transfers which form part +of that settlement window and store them in the **settlementContentAggregation** +table. Aggregates should be produced for the following segmentation: + +1. Participant +2. Currency +3. Ledger account type +4. Participant role type +5. Ledger entry type + +The following query will perform this function for a given settlement window: + +``` +INSERT INTO settlementContentAggregation + (settlementWindowContentId, participantCurrencyId, + transferParticipantRoleTypeId, ledgerEntryTypeId, amount) +SELECT swc.settlementWindowContentId, pc.participantCurrencyId, + tp.transferParticipantRoleTypeId, tp.ledgerEntryTypeId, SUM(tp.amount) +FROM transferFulfilment tf +INNER JOIN transferParticipant tp + ON tf.transferId = tp.transferId +INNER JOIN participantCurrency pc + ON pc.participantCurrencyId = tp.participantCurrencyId +INNER JOIN settlementWindowContent swc + ON swc.settlementWindowId = tf.settlementWindowId +  AND swc.ledgerAccountTypeId = pc.ledgerAccountTypeId + AND swc.currencyId = pc.currencyId +WHERE tf.settlementWindowId = @mySettlementWindowId +GROUP BY swc.settlementWindowContentId, pc.participantCurrencyId, + tp.transferParticipantRoleTypeId, tp.ledgerEntryTypeId; +``` + +#### createSettlement + +The parameters for the **createSettlement** resource should be extended to +include the settlement model for which the settlement is required. + +When the settlement is created, the settlement model for which the settlement is +required should be added to the new row in the **settlement** table. [Story \#1097] + +When a settlement is requested, the switch should check that the settlement +model for which settlement is being requested requires NET settlement and not +GROSS settlement. If the requested settlement model requires GROSS settlement, +then the request should be rejected with an error. + +The verification procedures associated with the **createSettlement** resource +should be changed to check that some of the settlement windows associated with +the proposed settlement contain entries for the settlement model requested. If +there are no entries for the settlement model requested for any of the +settlement windows requested, then an error should be returned and the +settlement request rejected. [Story \#1096] + +#### Updating the progress of a settlement + +When the status for a settlement participant is changed to SETTLED, this will result in +changes to the status of all the records in the **settlementContentAggregation** +table for the given participant, identified by the compound key: settlementId + participantCurrencyId. + +[Story \#16] + +The code should then check to see if, as a result of the updates to the records +in **settlementContentAggregation**, all records for a given combination of +settlement window, ledger account type and currency have the same status. If +they have, then the appropriate record in the **settlementWindowContent** table +should be updated to have that status. + +The code should then check to see if all records in the +**settlementWindowContent** table for a given settlement window have the same +status. If they have, then the appropriate record in the **settlementWindow** + +Support continuous gross settlement +----------------------------------- + +Continuous Gross Settlement is a settlement model in which each transaction is +settled as soon as it is fulfilled. The following changes are required to +implement this change. + +### Database changes + +The database changes shown in Section 8 below should be implemented. These can +be summarised as follows: + +#### Changes to support the settlement model + +A number of new tables are required to define a settlement model and to store +the enumerations for its definition types. This comprises the following tables +in the ERD: + +- settlementDelay + +- settlementInterchange + +- settlementModel + +In addition, the **settlementmodel** table has foreign keys to two existing +tables, as follows: + +1. A foreign key to the **currency** table to enable the settlement model to + settle only scheme accounts in a particular currency. If this entry is + blank, this should be interpreted as saying that the settlement model + settles all currencies that are not specified as being settled by other + settlement models for the same account type. + +2. A foreign key to the **ledgerAccountType** table. This specifies that the + settlement model settles accounts of this type. + +#### Changes to the **ledgeraccounttype** table + +The current **ledgeraccounttype** table stores a number of entries for account +types which should not be used for settlements – for instance, +HUB_MULTILATERAL_SETTLEMENT. A column should therefore be added to the +**ledgeraccounttype** table to indicate whether the account type can be attached +to a settlement model or not. This column is called **settleable**, and is +specified as a Boolean value, NOT NULL and with a default of FALSE. + +Of the existing ledger account types, PRINCIPAL_VALUE, INTERCHANGE_FEE and +HUB_FEE should be marked as capable of being attached to settlement models (this +value should be TRUE.) For all other ledger account types, the column should be +set to FALSE. If new ledger account types are added to this table via the +eventual settlement API, then they should have a value of TRUE. + +#### Changes to the **ledgerentrytype** table + +Implementation of the TIPS settlement model requires an explicit association +between ledger entry types and the account types in which they should appear. +Since each ledger entry type should only appear in a single account type, though +multiple ledger entry types may appear in a given account type, this is modelled +by including a foreign key reference to the **ledgeraccounttype** table as a +column in the **ledgerentrytype** table. It should be noted that this applies +only to ledger entry types which are associated with a settlement model, as +described in Section 6.2.1.2 above. The assignment of existing entries in the +database would be as follows: + +| Ledger Entry Type | Ledger Account Type | +|-------------------|---------------------------| +| PRINCIPAL_VALUE | POSITION | +| INTERCHANGE_FEE | INTERCHANGE_FEE | +| HUB_FEE | HUB_FEE | +| | | + +#### Changes to the **settlement** table + +The **settlement** table should have a column added to it to allow the +settlement model to be used in settling it to be specified. The column should be +called *settlementModelId*. It should have the same data type as the equivalent +field in the **settlementModel** table, and should be a foreign key into that +table. It should be defined as not null. + +#### Changes to the **settlementWindow** table + +As well as settlements, individual settlement windows will need to be assigned +to settlement models. The **settlementWindow** table should therefore have a +column added to it to allow the settlement model to be used in settling it to be +specified. The column should be called *settlementModelId*. It should have the +same data type as the equivalent field in the **settlementModel** table, and +should be a foreign key into that table. It should be defined as not null. + +This concludes the list of schema changes required to support the TIPS +settlement models + +### API changes + +API Support for the settlement functionality required for TIPS will include the +following functions: + +#### Close a settlement window + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a +**closeSettlementWindow** resource. This function allows its user to select a +settlement window by ID number and to input a new status for the window and a +reason for that status. This resource will retain its current signature, but the +code supporting it needs to change as follows. + +When a settlement window is closed, the system should create a new settlement +window with the same settlement model as the newly closed settlement window, and +should make this the active window. + +When a settlement window is closed, the code supporting this activity should +calculate the aggregate values for all transfers which form part of that +settlement window and store them in the **settlementTransferParticipant** table. +Aggregates should be produced for the following segmentation: + +1. Participant + +2. Currency + +3. Ledger account type + +4. Participant role type + +5. Ledger entry type + +The following example code will produce the aggregations required for a given +settlement window (identified as \@MyWindow in the example): + +SELECT + +S.settlementId + +, W.settlementWindowId + +, P.participantCurrencyId + +, P.transferParticipantRoleTypeId + +, P.ledgerEntryTypeId + +, SUM(P.amount) + +, CURRENT_TIMESTAMP AS createdDate + +FROM + +settlementWindow W INNER JOIN settlementModel M ON W.settlementModelId = +M.idsettlementModel + +INNER JOIN settlementSettlementWindow S ON W.settlementWindowId = +S.settlementWindowId + +INNER JOIN ledgerAccountType L ON M.idsettlementModel = L.settlementModelId + +INNER JOIN transferFulfilment F ON W.settlementWindowId = F.settlementWindowId + +INNER JOIN transferParticipant P ON F.transferId = P.TransferId + +INNER JOIN participantCurrency PC ON P.participantCurrencyId = +PC.participantCurrencyId + +AND PC.ledgerAccountTypeId = L.ledgerAccountTypeId + +WHERE + +W.settlementWindowId = \@MyWindow + +AND + +(PC.currencyId = M.settlementcurrencyId OR M.settlementcurrencyId IS NULL) + +GROUP BY + +S.settlementId + +, W.settlementWindowId + +, P.participantCurrencyId + +, P.transferParticipantRoleTypeId + +, P.ledgerEntryTypeId; + +#### Getting information about a settlement window + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a +**getSettlementWindowById** resource. This function allows its user to obtain +information about a settlement window by giving the ID that was returned when +the settlement window was created. + +This call returns a **SettlementWindow** object. This object will need to be +extended to include the name of the settlement model to which the settlement +window belongs. + +#### Getting information about settlement windows using parameters + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a +**getSettlementWindowsByParams** resource. This function allows its user to +obtain information about all settlement windows which meet the criteria +specified by the user. The following changes will need to be made to this call: + +1. The parameters supported by the call will need to be extended to allow a + user to request settlement windows by settlement model. The user should be + able to enter the name of a settlement model. + +2. This call returns an array of **SettlementWindow** objects. It is assumed + that these objects will have been changed by the changes specified in + Section 6.2.2.2 above, and that no further processing will be required for + this call. + +#### Requesting a settlement + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a **createSettlement** +resource. This function allows its user to request a settlement for a given set +of settlement windows, which are passed to the resource as parameters. The +following changes will need to be made to this call: + +The parameters for the **createSettlement** resource are defined in the +**SettlementEventPayload** object. This object will need to have a parameter +added to specify the settlement model which is to be settled. The parameter +should be called *settlementModel*, and it should be a string. It should be +required. + +The following validation should be performed on this parameter when the request +is received via the API: + +1. The content of the parameter is a case-insensitive match for an active entry + in the **settlementmodel** table, ignoring whitespace. + +2. The *settlementtypeid* column in the record in the selected settlement model + should not point to a record in the **settlementdelay** table whose + *settlementDelayName* value is “IMMEDIATE”. If it does, the request should + be rejected and an error message returned to the caller. + +The id of the settlement model requested should be stored in the +settlementModelId column of the row created in the **settlement** table to +describe the settlement. + +#### Returning the status of a settlement + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a **getSettlementById** +resource. This function allows its user to obtain information about a settlement +by giving the ID that was returned when the settlement was created. + +Information about settlements is returned in a **Settlement** object. A +parameter should be added to the Settlement object to record the settlement +model which is being settled by the settlement. + +The parameter should be called *settlementModel*, and it should be a string. It +should be required. + +**Note**: when a settlement is requested, a reason is given; but the +**Settlement** object does not contain the reason. It might be worth including +this in the definition of the **Settlement** object. + +#### Getting information about settlements using parameters + +The existing API definition[2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a +**getSettlementsByParams** resource. This function allows its user to obtain +information about all settlements by giving the ID that was returned when the +settlement was created. The following changes will need to be made to this call: + +1. This call returns an array of **Settlement** objects. It is assumed that + these objects will have been changed by the changes specified in Section + 6.2.2.5 above, and that no further processing will be required for this + call. + +#### Getting information about a specific participant in a settlement + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports a +**getSettlementBySettlementParticipant** resource. This function allows its user +to obtain information about a settlement which meet the criteria specified by +the user. The following changes will need to be made to this call: + +1. This call returns a **Settlement** object. It is assumed that this object + will have been changed by the changes specified in Section 6.2.2.5 above, + and that no further processing will be required for this call. + +#### Updating a specific settlement + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports an +**updateSettlementById** resource. This function allows its user to update +information about a settlement by giving the ID that was returned when the +settlement was created. + +This call returns a **Settlement** object. It is assumed that this object will +have been changed by the changes specified in Section 6.2.2.5 above, and that no +further processing will be required for this call. + +#### Updating a settlement for a specific participant + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports an +**updateSettlementBySettlementParticipant** resource. This function allows its +user to update information about a settlement by giving the ID that was returned +when the settlement was created, and the participant whose information is to be +updated. + +This call returns a **Settlement** object. It is assumed that this object will +have been changed by the changes specified in Section 6.2.2.5 above, and that no +further processing will be required for this call. + +#### Updating a settlement for a specific participant and account + +The existing API definition [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json) supports an +**updateSettlementBySettlementParticipantAccount** resource. This function +allows its user to update information about a settlement by giving the ID that +was returned when the settlement was created, and the participant and account +whose information is to be updated. + +This call returns a **Settlement** object. It is assumed that this object will +have been changed by the changes specified in Section 6.2.2.5 above, and that no +further processing will be required for this call. + +#### Recording the deposit of funds by a participant + +The existing administration API [3](https://mojaloop.io/documentation/api/central-ledger-api-specification.html) contains functions to enable +an administrator to record a deposit made by a participant to an account. This +process is described in Section 5.2 above. The API should be changed to align it +with the structures used in the existing settlement API [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json). In addition, the signature of the API should be extended to allow the +administrator to specify the account type that will be updated by the deposit. + +#### Recording the withdrawal of funds by a participant + +The existing administration API [3](https://mojaloop.io/documentation/api/central-ledger-api-specification.html) contains functions to enable +an administrator to record a withdrawal made by a participant from an account. +This process is described in Section 5.3 above. The API should be changed to +align it with the structures used in the existing settlement API [2](https://github.com/mojaloop/central-settlement/blob/master/src/interface/swagger.json). In addition, the signature of the API should be extended to allow +the administrator to specify the account type that will be updated by the +withdrawal. + +#### New resource: openSettlementWindow + +In the current architecture, an instance of each settlement window is created +when the instance is set up, and subsequent settlement windows are created by +closing the current settlement window. In the new settlement management +structure, we will need the ability to create new settlement windows where no +predecessors exist: for instance, when an administrator decides to settle a +particular currency using a different settlement model. + +### Processing changes + +The following processing changes are required to implement the changes required +to support continuous gross settlement. + +#### Attributing ledger entries to the correct ledger account type + +When a ledger entry is created, it should be assigned to the ledger account type +specified in the *ledgeraccounttypeid* column of the row in the +**ledgerentrytype** table appropriate to the ledger entry type which is being +created. + +For example: if a normal entry representing a transfer is being created, it will +have a ledger entry type of PRINCIPAL_VALUE. It should be assigned to the +POSITION account type (the default position at present.) This implies that the +match described in Section 6.2.1.3 above has been implemented. + +Question: how does the switch decide whether or when to construct a record in +the **participantPositionChange** table? How would it be possible to select the +ledger account type to which the position refers? + +Processing interchange fees +---------------------------- + +In order to support the scheme model implemented by TIPS, we need to generate +and settle liabilities incurred as a consequence of making transfers between +particular types of customer. The general form of this rule is as follows: + +- If the transaction is a wallet-to-wallet P2P transaction, then the receiver + DFSP pays the sender DFSP 0.6% of the amount of the transaction. + +- No interchange fees are levied for on-us transactions. + +The business decisions around this requirement are: + +1. The definition of whether or not a payee account is a wallet will be + returned by the payee DFSP as part of customer discovery. The mechanism by + which this is implemented is outside the scope of this document. + +2. Interchange fees will be captured by the switch when the transfers which + incur them are completed. + +3. Interchange fees will have the ledger entry type INTERCHANGE_FEE and will be + recorded in accounts whose type is INTERCHANGE_FEE. + +4. Interchange fees will be settled multilaterally, net and deferred. It is + expected that this settlement will take place monthly. + +5. Interchange fees do not require liability cover by participants. + +This functionality will be implemented as a partial instance of a general +process for defining and executing rules, and for taking actions based on the +outcome of evaluation of a rule. For this particular case, we propose the +changes described in the following sections. + +### Part 1: Run script for batch update of interchange fees when settlement window is closed. + +Include stories to manage account type definition + +### Part 2: Add reservation on prepare using script and modify fulfilment to fulfil all ledger types for which an entry has been made + +### Evaluating a rule + +The process of evaluating a rule is based on the following assumptions: + +1. There will be a standard form of rule evaluation with the following + structure: + + 1. A transaction object will be passed as the parameter to the rule + evaluation function. + + 2. The rule evaluation itself will use a complex if statement. + + 3. If the rule evaluates to TRUE, then an action should be executed as + described in Section 6.3.2 below. + +An example of a rule function to evaluate a TIPS interchange fee rule could be: + +function evaluateInterchangeFee (transaction) { + +if( + +(transaction.payee.fspId.toLowerCase() != transaction.payer.fspId.toLowerCase()) + +&& (transaction.extensionList[“payerAccountType”].toLowerCase() == +"Wallet".toLowerCase() + +&& transaction.extensionList[“payeeAccountType”].toLowerCase() == +"Wallet".toLowerCase()) + +&& (transaction.transactionType.scenario.toLowerCase() == +"TRANSFER".toLowerCase() + +&& transaction.transactionType.initiator.toLowerCase() == "PAYER".toLowerCase() + +&& transaction.transactionType.initiatorType.toLowerCase() == +"CONSUMER".toLowerCase()) + +) { + +// Do some good stuff + +}; + +}; + +### Taking action after evaluating a rule + +If a rule evaluates to TRUE as described in Section 6.3.1 above, then +appropriate action should be taken. In the case of the immediate example of +interchange fees, the action taken should be to add two entries to the +participants’ interchange fee accounts, on recording the debit from the payee of +the interchange fee amount and the other recording the credit to the payer of +the interchange fee amount. + +A simple yet general way of supporting actions of this type is to define a class +(which might be called ruleAction) and adding methods to it to represent the +actions to be taken. The rule evaluation function can then instantiate the class +and call the appropriate function. + +In the case of the interchange fees, we would define an action called +addLedgerEntry, with the following parameters: + +1. The transfer ID for which the ledger entry is being created + +2. The ledger entry type to be used + +3. The currency in which the amount is denominated + +4. The amount of the fee + +5. The FSP ID of the credit party + +6. The FSP ID of the debit party + +This might appear in the rule evaluation function as: + +myAction.addLedgerEntry(transaction.transactionId, + +transaction.transactionId, + +"INTERCHANGE_FEE“, + +transaction.currency, + +transaction.amount\*0.006, + +transaction.payer.fspId, + +transaction.payee.fspId); + +### Providing a generic framework for rule evaluation + +Finally, we will need to provide a generic framework to trigger the evaluation +of rules. This should be an array of evaluation functions, which are triggered +when the status of a transfer changes to FULFILLED. + +Process transfers for continuous gross settlement [EPIC] +------------------------------------------------- + +When a settlement model specifies that an account is to be settled immediate gross, then each ledger entry which is of a type belonging to that scheme account should be settled immediately. This immediate settlement should have the following characteristics: + +- It should be performed by a process which is forensically logged. +- It should be performed immediately, so that participants can check their current position against the transfers that comprise it. +- It should be aggregated to settlement window level, so that the checks which are currently performed on the overall status of a settlement window will continue to work. + +The following sections describe the changes that are required to process transfers for accounts which are settled immediate gross. + +### Database changes + +The following changes are required to the database to implement transfer processing for continuous gross settlement + +#### Addition of a new table to store changes in state + +A new table should be added to store changes in state for ledger entries for individual transfers. The name of this table should be **transferParticipantStateChange**. Its column structure should be as follows: + +1. The unique key to the record. Column name: **transferParticipantStateChangeId**; type: unsigned BIGINT; not nullable; primary key +2. The record in **TransferParticipant** whose state change this record marks. Column name: **transferParticipantId**; type: unsigned BIGINT; not nullable; foreign key to the **transferParticipantId** column of the **transferParticipant** table. +3. The current state of the record in **transferParticipant** to which this state record refers. Column name: **settlementWindowStateId**; data type VARCHAR(50); not nullable; foreign key to the **settlementWindowStateId** column of the **settlementWindowState** table. +4. An explanation of the state change. Column name: **reason**; type: VARCHAR(512); nullable. +5. The date and time when the change was recorded. Column name: **createdDate**; type DATETIME; not nullable; default value **CURRENT_TIMESTAMP**. + +#### Changes to the TransferParticipant table + +No changes to the **transferParticipant** table are required. The relationship between records in the **transferParticipant** table and records in the **transferParticipantStateChange** table is managed via the **transferParticipantId** column in the **transferParticipantStateChange** table. + +#### Changes to the settlementModel table + +Existing implementations have functionality which automatically adjusts participants' positions when settlements are completed. In order to support backwards compatibility for these implementations, the settlement model will be expanded to allow automated position adjustment to be switched off and on. + +This functionality will be managed through a new column in the **settlementModel** table. The name of the column will be **adjustPosition**. Its type will be TINYINT(1), and it should not be nullable. It should have a default value of zero (FALSE). + +### Processing changes + +The following changes to processing are required to support immediate settlement of gross ledger entries. + + +#### Generating entries in settlementContentAggregation + +The following changes to the process that creates aggregation records in the **settlementContentAggregation** table are required. + +1. The aggregation process for a settlement window may not be performed if there are any records in the **transferParticipant** table which belong to the settlement window to be aggregated (as defined by joining the **transferParticipant** records to the matching records in the **transferFulfilment** table on the **transferId** column in both tables) and which do not have any corresponding entries in the **transferParticipantStateChange** table. This test is performed via a LEFT OUTER JOIN relationship between the **transferParticipantStateChange** table and the **transferParticipant** table, using the foregin key relation between the **transferParticipantId** columns in the **transferParticipant** table and the **transferParticipantStateChange** table. +2. In the discussion which follows, the current status of a record in **transferParticipant** is defined as: the status of the record in the **transferParticipantStateChange** table which is keyed to the record in **transferParticipant** and which has the latest value in the **createdDate** column of the **transferParticipantStateChange** table. +3. When there are no records in **transferParticipant** which meet the blocking criteria described in step 1 above, then all records belonging to the settlement window which has just been closed, and which currently have the status OPEN, should have their status set to CLOSED. This means: a record should be added to the **transferParticipantStateChange** table for the qualifying **transferParticipant** record whose status is CLOSED, and the **currentStateChangeId** column for the qualifying **transferParticipant** record should be set to point to the newly created record. +4. When aggregating records for insertion into the **settlementContentAggregation** table, if all the records in the **transferParticipant** table which are to be aggregated into a single record in the **settlementContentAggregation** table have the same value in their **currentStateChangeId** column, then the value of the **currentStateId** column in the newly created record in the **settlementContentAggregation** table should be set as follows. The value of the **currentStateId** column in the newly created record in the **settlementContentAggregation** table should be set to the shared value in the constituent records from the **transferParticipant** table, except in the following case: if the shared value in the constituent records from the **transferParticipant** table is OPEN, then the value of the **currentStateId** column should be set to the value CLOSED. + +#### Marking transfers as settled + +The following additional processes are required in order to mark ledger entries which are settled immediate gross as having been settled. + +##### Queueing transfers for settlement processing + +When a transfer is completed, a record is generated in the **transferFulfilment** table. As part of the process that generates this record, the transfer should be placed on a Kafka stream for immediate settlement processing. + +##### Processing settlements + +A new service should be developed for processing gross (i.e. per-transfer) settlements. The requirements for this service are as follows: +1. It should enable an auditor to verify that a given transfer has been settled using the agreed process +2. It should allow transfer settlement to be recorded either internally, using an automatic process, or externally, exporting the information for each transfer to be settled to a configurable endpoint. +3. It should not delay processing of the transfer itself + +The characteristics of the service should be as follows: + +1. Pick a transfer from the Kafka stream holding transfers awaiting settlement processing. There is no requirement for sequence preservation, so this service can pick up multiple transfer entries if this would accelerate processing. +2. For each record in the **transferParticipant** table which belongs to the transfer *and* whose **ledgerEntryType** column specifies a ledger entry type which belongs to a settlement model which is settled both GROSS and IMMEDIATE, the service should generate consecutive records in the **transferParticipantStateChange** table with the values: CLOSED, PENDING_SETTLEMENT, and SETTLED, in that order. The **currentStateChangeId** column for the record in the **transferParticipant** table should be set to point to the record in the **transferParticipantStateChange** table whose value is SETTLED. +3. For each record in the **transferParticipant** table which belongs to the transfer *and* whose **ledgerEntryType** column specifies a ledger entry type which belongs to a settlement model which is settled both GROSS and IMMEDIATE *and* where the settlement model has an export endpoint configured, the process should export the information relating to the entry that is being settled to the endpoint specified in an agreed format. The format to be used, the means of specifying the endpoint to be addressed, and the process by which exports are generated and acknowledged, are not specified at this time. +4. For all other records in the **transferParticipant** table which belong to the transfer, the service should generate a record in the **transferParticipantStateChange** table with a value of OPEN. The **currentStateChangeId** column for the record in the **transferParticipant** table should be set to point to the record in the **transferParticipantStateChange** table which was created. + +#### Updating status values for net settlements + +When the status is updated for a participant in a settlement which belongs to a settlement model which is not settled both GROSS and IMMEDIATE, then the constituent records for that participant in the settlement in the **transferParticipant** table need to be updated. The rules for this are: + +1. When the settlement is created, all the records in **transferParticipant** which belong to a transfer which belongs to a window which belongs to the settlement being created (i.e. which are contained in the inner join between **transferParticipant**, **transferfulfilment** (on **transferId**) and **settlementSettlementWindow** (on **settlementWindowId**) for the settlement Id which is being created) should have a record created in **settlementContentAggregationStateChange** with the **settlementWindowStateId** column set to PENDING_SETTLEMENT. +2. When a participant's settlement status is updated to SETTLED in **settlementParticipantCurrency**, then all the records in **transferParticipant** for settlement windows which belong to that settlement, and whose participant and currency IDs match the participant and currency of the records in **settlementParticipantCurrency** which have been updated, should have their status set to SETTLED. + +#### Gross settlement and position management + +If gross settlement is enabled for a settlement model and that settlement model also has its **adjustPosition** flag set to TRUE, then an adjustment to both participants' positions should be made. This should be done in the following way: + +1. For each record in the **transferParticipant** table which is being settled, create a record in the **participantPositionChange** table with the following characteristics: + a. The **participantPositionId** column should be set to the value of the **participantPositionId** column in the **participantPosition** table for the record whose **participantCurrencyId** field is the same as that of the record in the **transferParticipant** table which has been settled. + b. The **transferStateChangeId** column should be set to the value of the **transferStateChangeId** column for the record in the **transferStateChange** table whose **transferId** column is the same as the value of the **transferId** column in the **transferParticipant** table for the record which is being settled, and which has the latet value in its **createdDate** column. + c. The **value** column should be set to the **amount** column in the **transferParticipant** table for the record which is being settled. + d. The **reservedValue** column should be set to zero. + e. The **createdDate** column should be set to the current date and time. +2. The record in the **participantPosition** table whose **participantCurrencyId** field matches that of the record in the **transferParticipant** table which has been settled should have the **amount** column of the corresponding record in the **transferParticipant** table added to its **value** column. + +Domain class diagram +==================== + +ERD +=== + +The following ERD describes the new data structures required to model settlements. + +![](/mojaloop-technical-overview/central-settlements/assets/diagrams/Settlement_ERD.png) + +Enumerations +------------ + +The following enumerations are required to support the new ERD: +``` +DELETE FROM settlementGranularity; +INSERT INTO settlementGranularity (name) +VALUES ('GROSS'), ('NET'); + +DELETE FROM settlementInterchange; +INSERT INTO settlementInterchange (name) +VALUES ('BILATERAL'), ('MULTILATERAL'); + +DELETE FROM settlementDelay; +INSERT INTO settlementDelay(name) +VALUES ('IMMEDIATE'), ('DEFERRED'); +``` diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/README.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/README.md new file mode 100644 index 000000000..1cf8e627b --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/README.md @@ -0,0 +1,111 @@ +# Settlement Process + +## 1. Database Design + +### Notes: + +- `settlementWindow` - a table where all settlement windows are stored; +- `settlementWindowStateChange` - stores information regarding settlement windows state; +- `settlement` - keeps data regarding all settlements; +- `settlementContentAggregation` - contains aggregated values for a settlement, participant currency, role type and ledger entry type grouping in a settlement; +- `settlementModel` - contains the configured settlement models for the switch; +- `settlementWindowContent` - contains an entry for each item of content in a given settlement window, broken down by ledger account type and currency; +- `settlementWindowContentStateChange` - tracks settlement window content state changes; +- `settlementSettlementWindow` - association table for settlements and settlement windows, providing connection many-to-many; +- `settlementStateChange` - tracks the settlement state change; +- `settlementTransferParticipant` - this table is used for staging data for all transfers which are to be included in a settlement; +- `settlementParticipantCurrency` - stores grouped information by participant and currency. For calculation of netAmount, the summarized data from `settlementTransferParticipant` is used; +- `settlementParticipantCurrencyStateChange` - used to track the state change of each individual settlement participant account. + +The remaining tables in the below ERD are either transfer-specific (gray) or lookup (blue) and are included as direct dependencies to depict the relation between the settlement tables and transfer specific entities. + +![Central Settlements. Service ERD](./assets/entities/central-settlements-db-schema.png) + +* [Central Settlements Service DBeaver ERD](./assets/entities/central-settlements-db-schema-dbeaver.erd) + +## 2. Sequence diagrams + +### 2.1. Settlement Windows By Params + +Used for acquiring information regarding Settlement Windows. E.g.: +1. Find the ID of the current OPEN window by querying by state and later use the information for closing that window; +2. Find all CLOSED and/or ABORTED windows to be used for creating a settlement; +3. Other reporting needs. +- [Sequence Diagram for Get Settlement Windows by Parameters](get-settlement-windows-by-params.md) + +### 2.2. Settlement Windows By Params + +Used for acquiring settlement window information when ID is present. +- [Sequence Diagram for Request Settlement Window by Id](get-settlement-window-by-id.md) + +### 2.3. Close Settlement Window + +There is always one open settlement window which groups all ongoing transfers. This functionality is used to close the currently opened window and create the next one. The operations starts on the API and then the Deferred handler consumes a message after the validations are passed and prepares the Settlement Window Content and Settlement Content Aggregation records for the settlement process. +- [Sequence Diagram for Close Settlement Window](post-close-settlement-window.md) + +### 2.4. Create Settlement + +The creation of settlement is possible when at least one OPEN or ABORTED window is provided. The data from all transfers in all provided windows is summarized and as a result the settlement amount is calculated per participant and currency. Depending on its sign we distinct 3 types of participants in regards to the newly created settlement: SETTLEMENT_NET_RECIPIENT, SETTLEMENT_NET_SENDER and SETTLEMENT_NET_ZERO. Newly generated id of type bigint is returned as a response together it all other information. +- [Sequence Diagram for Trigger Settlement Event](post-create-settlement.md) + +### 2.5. Request Settlement + +This endpoint is used for acquiring information regarding a settlement and all included windows and accounts/positions. The ID from the previous request is being utilized for that purpose. +- [Sequence Diagram for Get Settlement by Id](get-settlement-by-id.md) + +### 2.6. Settlement Transfer Acknowledgment + +It is used to advance the settlement through all the states initiated with the creation and finilized by settle or abort. The actual state flow is: +- PENDING_SETTLEMENT: The net settlement report for this window has been taken, with the parameter set to indicate that settlement is to be processed; +- PS_TRANSFERS_RECORDED: Record transfer entries against the Position Account and the Multi-lateral Net Settlement Account, these are the "multi-lateral net  settlement transfers" (MLNS transfers). An identifier might be provided to be past to the reference bank; +- PS_TRANSFERS_RESERVED: All the debit entries for the MLNS transfers are reserved; +- PS_TRANSFERS_COMMITTED: All the credit entries for the MLNS transfers are committed. An identifier might be received and recorded from the Settlement bank to allow reconciliation; +- SETTLING: If all accounts are not yet SETTLED, the Status of the settlement is moved to SETTLING. Note: applies only on settlement level; +- SETTLED: Final state when all outstanding accounts are SETTLED, the entire Settlement is moved to SETTLED. + +[Sequence Diagram for Acknowledgement of Settlement Transfer](put-settlement-transfer-ack.md) + +### 2.7. Settlement Abort + +- ABORTED: Final state when the settlement is not possible. Please, note the settlement might be aborted up to when no account/position has been marked as PS_TRANSFERS_COMMITTED. Also there is no possibility to mark an individual account as ABORTED, but rahter the entire settlement is ABORTED. After performing such operation there is possibility to create a new settlement by including only non-problematic ABORTED accounts + +[Sequence Diagram for Settlement Abort](put-settlement-abort.md) + +### 2.8. Request Settlement By SPA + +Used to request drill-down information regarding a settlement, participant and account. Even though participant and account are optional, the order settlement/{id}/participant/{id}/account/{id} is mandatory. + +- [Sequence Diagram for Get Settlement by Settlement/Participant/Account](get-settlement-by-spa.md) + +### 2.9. Request Settlements By Params + +This endpoint enables advanced reporting capabilities. + +- [Sequence Diagram for Query Settlements by Parameters](get-settlements-by-params.md) + +### 2.10 Gross Settlement Handler + +This handler executes after each transfer is committed and performs the following operations on success: + Handle the updating of the POSITION and SETTLEMENT accounts for participants involved in a transfer where there is a settlement model defined as immediate and gross on the POSITION account to facilitate RTCGS (Real-Time Continuous Gross Settlement) per transfer. + +This is done by consuming events of the notification topic. + +- [Sequence Diagram for Gross Settlement Handler](gross-settlement-handler-consume.md) + +### 2.11 Rules Handler + +This handler executes after each transfer is committed and performs the following operations on success: + Execute the rules defined by the scripts in the SCRIPTS_FOLDER. The rules are validated for valid headers before loading. + +This is done by consuming events of the notification topic. + +- [Sequence Diagram for Rules Handler](rules-handler-consume.md) + +### 2.12 Deferred Handler + +This handler executes after close settlement window operation has been received and validated: + + Handle the updating of the participant accounts involved in the transfers for the settlement window that is closed. The process then continues with settlement event trigger. + +This is done by consuming events of the notification topic, that are emitted by the service after close settlement window command has been sent and validated. +- [Sequence Diagram for Close Settlement Window](post-close-settlement-window.md) \ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.plantuml new file mode 100644 index 000000000..fc837adab --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.plantuml @@ -0,0 +1,204 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Neal Donnan + * Shashikant Hirugade + -------------- + ******'/ + +@startuml +' declare title +title Gross Settlement Handler Consume (Success) +autonumber +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors + +collections "topic-notification" as TOPIC_NOTIFICATION +control "Gross Settlement Handler" as SETTLEMENT_HANDLER +control "Gross Settlement Service" as SETTLEMENT_SERVICE +database "central_ledger" as DB +entity "Settlement DAO" as SETTLEMENT_DAO + +box "Settlement Service" #LightGreen + participant TOPIC_NOTIFICATION + participant SETTLEMENT_HANDLER + participant SETTLEMENT_SERVICE + participant SETTLEMENT_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +activate SETTLEMENT_HANDLER +group Gross Settlement Handler Consume (Success) + alt Consume Single Message + TOPIC_NOTIFICATION <- SETTLEMENT_HANDLER: Consume notification event message + activate TOPIC_NOTIFICATION + deactivate TOPIC_NOTIFICATION + group Validate Message + SETTLEMENT_HANDLER <-> SETTLEMENT_HANDLER: Validate event - Rule: message has payload\nError codes: 2001 + end + opt action == 'COMMIT' + + group Retry (default 3 retries) + group DB TRANSACTION: settle transfer + SETTLEMENT_HANDLER -> SETTLEMENT_SERVICE: Process fulfil message + SETTLEMENT_SERVICE -> SETTLEMENT_DAO: Get Gross settlement model + SETTLEMENT_DAO -> DB: Get settlement model records + activate DB + deactivate DB + hnote over DB #lightyellow + SELECT settlementModel.* + FROM **settlementModel** + INNER JOIN `participantCurrency` AS `pc` ON `pc`.`currencyId` = `settlementModel`.`currencyId` + AND `pc`.`ledgerAccountTypeId` `settlementModel`.`ledgerAccountTypeId` + INNER JOIN `transferParticipant` AS `tp` ON `tp`.`participantCurrencyId` = `pc`.`participantCurrencyId` + INNER JOIN settlementGranularity AS `g` ON `g`.`settlementGranularityId` = `settlementModel`.`settlementGranularityId` + WHERE `tp`.`transferId`, {transferId} + AND `g`.`name`, {settlementGranularityName} + AND `settlementModel`.`isActive`, {1}; + end hnote + SETTLEMENT_DAO <-- DB: Gross settlement model result + SETTLEMENT_SERVICE <-- SETTLEMENT_DAO: Gross settlement model result + group Validate settlement model + SETTLEMENT_SERVICE <-> SETTLEMENT_SERVICE: Valid Gross settlement model with given currency does not exist + SETTLEMENT_SERVICE <-> DB: Get all settlement models + alt Check if NET settlement model with the transfer currency exists + SETTLEMENT_SERVICE <-> SETTLEMENT_SERVICE: Return true + else + SETTLEMENT_SERVICE <-> SETTLEMENT_SERVICE: filter all settlement models by currencyId === null and granularityType === GROSS + SETTLEMENT_SERVICE <-> SETTLEMENT_SERVICE: Return default GROSS settlement model + end + end + SETTLEMENT_SERVICE -> SETTLEMENT_DAO: Settle transfer if CGS + SETTLEMENT_DAO -> DB: Insert transferParticipant entries + activate DB + deactivate DB + hnote over DB #lightyellow + insert into **`transferParticipant`** (transferID, participantCurrencyId, transferParticipantRoleTypeId, ledgerEntryTypeId, + amount) + select `TP`.`transferId`, + `TP`.`participantCurrencyId`, + `TP`.`transferParticipantRoleTypeId`, + `TP`.`ledgerEntryTypeId`, + `TP`.`amount` * -1 + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + union + select `TP`.`transferId`, + `PC1`.`participantCurrencyId`, + `TP`.`transferParticipantRoleTypeId`, + `TP`.`ledgerEntryTypeId`, + `TP`.`amount` + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + inner join `participantCurrency` as `PC1` + on `PC1`.`currencyId` = `PC`.`currencyId` and `PC1`.`participantId` = `PC`.`participantId` and + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')); + end hnote + SETTLEMENT_DAO -> DB: Update participantPosition records + activate DB + deactivate DB + hnote over DB #lightyellow + update **`participantPosition`** as `PP` + inner join (select `PC`.`participantCurrencyId`, `TP`.`Amount` + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` + on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + union + select `PC1`.`participantCurrencyId`, `TP`.`amount` + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` + on `M`.`ledgerAccountTypeId` = `PC`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + inner join `participantCurrency` as `PC1` + on `PC1`.`currencyId` = `PC`.`currencyId` and + `PC1`.`participantId` = `PC`.`participantId` and + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS'))) + AS TR ON PP.participantCurrencyId = TR.ParticipantCurrencyId + set `value` = `PP`.`value` - `TR`.`amount`; + end hnote + SETTLEMENT_DAO -> DB: Insert participantPositionChange records + activate DB + deactivate DB + hnote over DB #lightyellow + insert into **`participantPositionChange`** (participantPositionId, transferStateChangeId, value, reservedValue) + select `PP`.`participantPositionId`, `TSC`.`transferStateChangeId`, `PP`.`value`, `PP`.`reservedValue` + from `participantPosition` as `PP` + inner join (select `PC`.`participantCurrencyId` + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` + on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + union + select `PC1`.`participantCurrencyId` + from `transferParticipant` as `TP` + inner join `participantCurrency` as `PC` + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + inner join `settlementModel` as `M` + on `PC`.`ledgerAccountTypeId` = `PC`.`ledgerAccountTypeId` + inner join `settlementGranularity` as `G` + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + inner join `participantCurrency` as `PC1` on `PC1`.`currencyId` = `PC`.`currencyId` and + `PC1`.`participantId` = `PC`.`participantId` and + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS'))) AS TR + ON PP.participantCurrencyId = TR.ParticipantCurrencyId + inner join `transferStateChange` as `TSC` + on `TSC`.`transferID` = {transferId} and `TSC`.`transferStateId` = 'SETTLED'; + end hnote + end + end + end + else Consume Batch Messages + note left of SETTLEMENT_HANDLER #lightblue + To be delivered by future story + end note + end +end +deactivate SETTLEMENT_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.svg new file mode 100644 index 000000000..5d560d536 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-gross-settlement-handler.svg @@ -0,0 +1,567 @@ + + + + + + + + + + + Gross Settlement Handler Consume (Success) + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + + + topic-notification + + + + + topic-notification + + + Gross Settlement Handler + + + + + Gross Settlement Handler + + + + + Gross Settlement Service + + + + + Gross Settlement Service + + + + + Settlement DAO + + + + + Settlement DAO + + + + + central_ledger + + + + + central_ledger + + + + + + + + Gross Settlement Handler Consume (Success) + + + + + alt + + + [Consume Single Message] + + + + + 1 + + + Consume notification event message + + + + + Validate Message + + + + + 2 + + + Validate event - Rule: message has payload + + + Error codes: + + + 2001 + + + + + opt + + + [action == 'COMMIT'] + + + + + Retry (default 3 retries) + + + + + DB TRANSACTION: settle transfer + + + + + 3 + + + Process fulfil message + + + + + 4 + + + Get Gross settlement model + + + + + 5 + + + Get settlement model records + + + + SELECT settlementModel.* + + + FROM + + + settlementModel + + + INNER JOIN `participantCurrency` AS `pc` ON `pc`.`currencyId` = `settlementModel`.`currencyId` + + + AND `pc`.`ledgerAccountTypeId` `settlementModel`.`ledgerAccountTypeId` + + + INNER JOIN `transferParticipant` AS `tp` ON `tp`.`participantCurrencyId` = `pc`.`participantCurrencyId` + + + INNER JOIN settlementGranularity AS `g` ON `g`.`settlementGranularityId` = `settlementModel`.`settlementGranularityId` + + + WHERE `tp`.`transferId`, {transferId} + + + AND `g`.`name`, {settlementGranularityName} + + + AND `settlementModel`.`isActive`, {1}; + + + + + 6 + + + Gross settlement model result + + + + + 7 + + + Gross settlement model result + + + + + Validate settlement model + + + + + 8 + + + Valid Gross settlement model with given currency does not exist + + + + + 9 + + + Get all settlement models + + + + + alt + + + [Check if NET settlement model with the transfer currency exists] + + + + + 10 + + + Return true + + + + + + 11 + + + filter all settlement models by currencyId === null and granularityType === GROSS + + + + + 12 + + + Return default GROSS settlement model + + + + + 13 + + + Settle transfer if CGS + + + + + 14 + + + Insert transferParticipant entries + + + + insert into + + + `transferParticipant` + + + (transferID, participantCurrencyId, transferParticipantRoleTypeId, ledgerEntryTypeId, + + + amount) + + + select `TP`.`transferId`, + + + `TP`.`participantCurrencyId`, + + + `TP`.`transferParticipantRoleTypeId`, + + + `TP`.`ledgerEntryTypeId`, + + + `TP`.`amount` * -1 + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + + + union + + + select `TP`.`transferId`, + + + `PC1`.`participantCurrencyId`, + + + `TP`.`transferParticipantRoleTypeId`, + + + `TP`.`ledgerEntryTypeId`, + + + `TP`.`amount` + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + inner join `participantCurrency` as `PC1` + + + on `PC1`.`currencyId` = `PC`.`currencyId` and `PC1`.`participantId` = `PC`.`participantId` and + + + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')); + + + + + 15 + + + Update participantPosition records + + + + update + + + `participantPosition` + + + as `PP` + + + inner join (select `PC`.`participantCurrencyId`, `TP`.`Amount` + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` + + + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` + + + on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` + + + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + + + union + + + select `PC1`.`participantCurrencyId`, `TP`.`amount` + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` + + + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` + + + on `M`.`ledgerAccountTypeId` = `PC`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` + + + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + inner join `participantCurrency` as `PC1` + + + on `PC1`.`currencyId` = `PC`.`currencyId` and + + + `PC1`.`participantId` = `PC`.`participantId` and + + + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS'))) + + + AS TR ON PP.participantCurrencyId = TR.ParticipantCurrencyId + + + set `value` = `PP`.`value` - `TR`.`amount`; + + + + + 16 + + + Insert participantPositionChange records + + + + insert into + + + `participantPositionChange` + + + (participantPositionId, transferStateChangeId, value, reservedValue) + + + select `PP`.`participantPositionId`, `TSC`.`transferStateChangeId`, `PP`.`value`, `PP`.`reservedValue` + + + from `participantPosition` as `PP` + + + inner join (select `PC`.`participantCurrencyId` + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` + + + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` + + + on `PC`.`ledgerAccountTypeId` = `M`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` + + + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS')) + + + union + + + select `PC1`.`participantCurrencyId` + + + from `transferParticipant` as `TP` + + + inner join `participantCurrency` as `PC` + + + on `TP`.`participantCurrencyId` = `PC`.`participantCurrencyId` + + + inner join `settlementModel` as `M` + + + on `PC`.`ledgerAccountTypeId` = `PC`.`ledgerAccountTypeId` + + + inner join `settlementGranularity` as `G` + + + on `M`.`settlementGranularityId` = `G`.`settlementGranularityId` + + + inner join `participantCurrency` as `PC1` on `PC1`.`currencyId` = `PC`.`currencyId` and + + + `PC1`.`participantId` = `PC`.`participantId` and + + + `PC1`.`ledgerAccountTypeId` = `M`.`settlementAccountTypeId` + + + where (`TP`.`transferId` = {transferId} and (`G`.`name` = 'GROSS'))) AS TR + + + ON PP.participantCurrencyId = TR.ParticipantCurrencyId + + + inner join `transferStateChange` as `TSC` + + + on `TSC`.`transferID` = {transferId} and `TSC`.`transferStateId` = 'SETTLED'; + + + + [Consume Batch Messages] + + + + + To be delivered by future story + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.plantuml new file mode 100644 index 000000000..88ff1491c --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.plantuml @@ -0,0 +1,219 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Neal Donnan + * Valentin Genev + -------------- + ******'/ + +@startuml +' declare title +title Rules Handler Consume (Success) +autonumber +' Actor Keys: +' boundary - APIs/Interfaces, etc +' collections - Kafka Topics +' control - Kafka Consumers +' entity - Database Access Objects +' database - Database Persistance Store + +' declare actors + +collections "topic-notification" as TOPIC_NOTIFICATION +control "Rules Handler" as RULES_HANDLER +database "central_ledger" as DB +entity "Scripts Loader" as SCRIPTS_LOADER +entity "Script Engine" as SCRIPT_ENGINE +entity "Transaction DAO" as TRANSACTION_DAO +entity "Settlement DAO" as SETTLEMENT_DAO +entity "Central Ledger DAO" as CENTRAL_LEDGER_DAO + +box "Settlement Service" #LightGreen + participant TOPIC_NOTIFICATION + participant RULES_HANDLER + participant SCRIPTS_LOADER + participant SCRIPT_ENGINE + participant TRANSACTION_DAO + participant SETTLEMENT_DAO + participant CENTRAL_LEDGER_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +activate RULES_HANDLER +group Rules Handler Consume (Success) + group Load Scripts + RULES_HANDLER -> SCRIPTS_LOADER: load scripts (yaml file) (scriptDirectory) + activate SCRIPTS_LOADER + loop script + SCRIPTS_LOADER -> SCRIPTS_LOADER: validate script type, action and status against enums; validate script is valid JS; validate start and end timestamps; Create a map ScriptMap[type][action][result] + end + SCRIPTS_LOADER --> RULES_HANDLER: create script map + note right of RULES_HANDLER #yellow + example: + { + scriptType: { + scriptAction: { + scriptStatus: {[ + { + name: "interchangeFeeCalculation", + startTime: "2020-06-01T00:00:00.000Z", + endTime: "2100-12-31T23:59:59.999Z", + script: [vm.Script] + } + ]} + } + } + } + end note + deactivate SCRIPTS_LOADER + end + group Process Single Message + TOPIC_NOTIFICATION <- RULES_HANDLER: Consume notification event message + activate TOPIC_NOTIFICATION + deactivate TOPIC_NOTIFICATION + group Validate Message + RULES_HANDLER <-> RULES_HANDLER: Validate event - Rule: message has payload\nError codes: 2001 + end + alt Rule found + RULES_HANDLER <-> RULES_HANDLER: check if ScriptMap[type][action][result] has valid script assigned + group Execute Scripts + RULES_HANDLER -> SCRIPTS_LOADER: execute scripts + activate SCRIPTS_LOADER + loop script + SCRIPTS_LOADER -> SCRIPT_ENGINE: execute(script, message payload) + activate SCRIPT_ENGINE + SCRIPT_ENGINE -> TRANSACTION_DAO: Get transaction by transfer Id + activate TRANSACTION_DAO + TRANSACTION_DAO -> DB: retrieve ILP packet from ilpPacket + activate DB + DB --> TRANSACTION_DAO: ilpPacket row + deactivate DB + TRANSACTION_DAO --> SCRIPT_ENGINE: ilpPacket row + SCRIPT_ENGINE -> TRANSACTION_DAO: Get transaction object + TRANSACTION_DAO -> TRANSACTION_DAO: decode ILP packet + TRANSACTION_DAO --> SCRIPT_ENGINE: Transaction object + SCRIPT_ENGINE -> SCRIPT_ENGINE: execute script in sandbox + deactivate + SCRIPT_ENGINE --> SCRIPTS_LOADER: Ledger entries + deactivate SCRIPT_ENGINE + SCRIPTS_LOADER -> SCRIPTS_LOADER: Merge results + end + SCRIPTS_LOADER --> RULES_HANDLER: script results + deactivate SCRIPTS_LOADER + end + + alt Has ledger entries + group DB TRANSACTION: Validate and Insert ledger entries + RULES_HANDLER -> SETTLEMENT_DAO: Insert valid ledger entries + SETTLEMENT_DAO -> DB: Get the records to insert + activate DB + hnote over DB #lightyellow + select {transferId} AS transferId, + `PC`.`participantCurrencyId`, + IFNULL(`T1`.`transferparticipantroletypeId`, + `T2`.`transferparticipantroletypeId`) as `transferParticipantRoleTypeId`, + `E`.`ledgerEntryTypeId`, + CASE `P`.`name` + WHEN {ledgerEntry.payerFspId} THEN {ledgerEntry.amount} + WHEN {ledgerEntry.payeeFspId} THEN {ledgerEntry.amount * -1} + ELSE 0 + END AS `amount` + from `participantCurrency` as `PC` + inner join `participant` as `P` on `P`.`participantId` = `PC`.`participantId` + inner join `ledgerEntryType` as `E` on `E`.`LedgerAccountTypeId` = `PC`.`LedgerAccountTypeId` + left outer join `transferParticipantRoleType` as `T1` on `P`.`name` = {ledgerEntry.payerFspId} and `T1`.`name` = 'PAYER_DFSP' + left outer join `transferParticipantRoleType` as `T2` on `P`.`name` = {ledgerEntry.payerFspId} and `T2`.`name` = 'PAYEE_DFSP' + where `E`.`name` = {ledgerEntry.ledgerEntryTypeId} + and `P`.`name` in ({ledgerEntry.payerFspId}, {ledgerEntry.payeeFspId}) + and `PC`.`currencyId` = {ledgerEntry.currency}; + end hnote + SETTLEMENT_DAO <-- DB: recordsToInsert + deactivate DB + + alt Has records to insert + SETTLEMENT_DAO -> DB: Insert transferParticipant records + activate DB + deactivate DB + hnote over DB #lightyellow + insert into **transferParticipant** + + end hnote + SETTLEMENT_DAO -> DB: Update positions + activate DB + deactivate DB + loop transferParticipant records + hnote over DB #lightyellow + update **`participantPosition`** + set `value` = `value` + {record.amount} + where `participantCurrencyId` = {record.participantCurrencyId}; + end hnote + end + SETTLEMENT_DAO -> DB: Get transferStateChange record + activate DB + hnote over DB #lightyellow + select `transferStateChangeId` + from **`transferStateChange`** + where `transferId` = {transferId} + and `transferStateId` = 'COMMITTED'; + end hnote + SETTLEMENT_DAO <-- DB: transferStateChange record + deactivate DB + SETTLEMENT_DAO -> DB: Get participantPosition record + activate DB + hnote over DB #lightyellow + select `participantPositionId`, `value`, `reservedValue` + from **`participantPosition`** + where `participantCurrencyId` = {recordsToInsert[0].participantCurrencyId} + OR `participantCurrencyId` = {recordsToInsert[1].participantCurrencyId}; + end hnote + SETTLEMENT_DAO <-- DB: participantPosition record + deactivate DB + SETTLEMENT_DAO -> DB: Insert participantPositionChange records + activate DB + deactivate DB + hnote over DB #lightyellow + insert into **`participantPositionChange`** + select `participantPositionId`, {transferStateChangeId}, `value`, `reservedValue` + from `participantPosition` + where `participantCurrencyId` = {transferParticipantRecord1.participantCurrencyId} + or `participantCurrencyId` = {transferParticipantRecord2.participantCurrencyId}; + end hnote + else No records found + SETTLEMENT_DAO <-> SETTLEMENT_DAO: Error + end + else + SETTLEMENT_DAO <-> SETTLEMENT_DAO: Rollback on Error + end + else + RULES_HANDLER <-> RULES_HANDLER: exit + end + + + else + RULES_HANDLER <-> RULES_HANDLER: exit + end + end +end +deactivate RULES_HANDLER +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.svg new file mode 100644 index 000000000..1a26ed085 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-rules-handler.svg @@ -0,0 +1,642 @@ + + + + + + + + + + + Rules Handler Consume (Success) + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + + + + + topic-notification + + + + + topic-notification + + + Rules Handler + + + + + Rules Handler + + + + + Scripts Loader + + + + + Scripts Loader + + + + + Script Engine + + + + + Script Engine + + + + + Transaction DAO + + + + + Transaction DAO + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Ledger DAO + + + + + Central Ledger DAO + + + + + central_ledger + + + + + central_ledger + + + + + + + + Rules Handler Consume (Success) + + + + + Load Scripts + + + + + 1 + + + load scripts (yaml file) (scriptDirectory) + + + + + loop + + + [script] + + + + + 2 + + + validate script type, action and status against enums; validate script is valid JS; validate start and end timestamps; Create a map ScriptMap[type][action][result] + + + + + 3 + + + create script map + + + + + example: + + + { + + + scriptType: { + + + scriptAction: { + + + scriptStatus: {[ + + + { + + + name: "interchangeFeeCalculation", + + + startTime: "2020-06-01T00:00:00.000Z", + + + endTime: "2100-12-31T23:59:59.999Z", + + + script: [vm.Script] + + + } + + + ]} + + + } + + + } + + + } + + + + + Process Single Message + + + + + 4 + + + Consume notification event message + + + + + Validate Message + + + + + 5 + + + Validate event - Rule: message has payload + + + Error codes: + + + 2001 + + + + + alt + + + [Rule found] + + + + + 6 + + + check if ScriptMap[type][action][result] has valid script assigned + + + + + Execute Scripts + + + + + 7 + + + execute scripts + + + + + loop + + + [script] + + + + + 8 + + + execute(script, message payload) + + + + + 9 + + + Get transaction by transfer Id + + + + + 10 + + + retrieve ILP packet from ilpPacket + + + + + 11 + + + ilpPacket row + + + + + 12 + + + ilpPacket row + + + + + 13 + + + Get transaction object + + + + + 14 + + + decode ILP packet + + + + + 15 + + + Transaction object + + + + + 16 + + + execute script in sandbox + + + + + 17 + + + Ledger entries + + + + + 18 + + + Merge results + + + + + 19 + + + script results + + + + + alt + + + [Has ledger entries] + + + + + DB TRANSACTION: Validate and Insert ledger entries + + + + + 20 + + + Insert valid ledger entries + + + + + 21 + + + Get the records to insert + + + + select {transferId} AS transferId, + + + `PC`.`participantCurrencyId`, + + + IFNULL(`T1`.`transferparticipantroletypeId`, + + + `T2`.`transferparticipantroletypeId`) as `transferParticipantRoleTypeId`, + + + `E`.`ledgerEntryTypeId`, + + + CASE `P`.`name` + + + WHEN {ledgerEntry.payerFspId} THEN {ledgerEntry.amount} + + + WHEN {ledgerEntry.payeeFspId} THEN {ledgerEntry.amount * -1} + + + ELSE 0 + + + END AS `amount` + + + from `participantCurrency` as `PC` + + + inner join `participant` as `P` on `P`.`participantId` = `PC`.`participantId` + + + inner join `ledgerEntryType` as `E` on `E`.`LedgerAccountTypeId` = `PC`.`LedgerAccountTypeId` + + + left outer join `transferParticipantRoleType` as `T1` on `P`.`name` = {ledgerEntry.payerFspId} and `T1`.`name` = 'PAYER_DFSP' + + + left outer join `transferParticipantRoleType` as `T2` on `P`.`name` = {ledgerEntry.payerFspId} and `T2`.`name` = 'PAYEE_DFSP' + + + where `E`.`name` = {ledgerEntry.ledgerEntryTypeId} + + + and `P`.`name` in ({ledgerEntry.payerFspId}, {ledgerEntry.payeeFspId}) + + + and `PC`.`currencyId` = {ledgerEntry.currency}; + + + + + 22 + + + recordsToInsert + + + + + alt + + + [Has records to insert] + + + + + 23 + + + Insert transferParticipant records + + + + insert into + + + transferParticipant + + + + + 24 + + + Update positions + + + + + loop + + + [transferParticipant records] + + + + update + + + `participantPosition` + + + set `value` = `value` + {record.amount} + + + where `participantCurrencyId` = {record.participantCurrencyId}; + + + + + 25 + + + Get transferStateChange record + + + + select `transferStateChangeId` + + + from + + + `transferStateChange` + + + where `transferId` = {transferId} + + + and `transferStateId` = 'COMMITTED'; + + + + + 26 + + + transferStateChange record + + + + + 27 + + + Get participantPosition record + + + + select `participantPositionId`, `value`, `reservedValue` + + + from + + + `participantPosition` + + + where `participantCurrencyId` = {recordsToInsert[0].participantCurrencyId} + + + OR `participantCurrencyId` = {recordsToInsert[1].participantCurrencyId}; + + + + + 28 + + + participantPosition record + + + + + 29 + + + Insert participantPositionChange records + + + + insert into + + + `participantPositionChange` + + + select `participantPositionId`, {transferStateChangeId}, `value`, `reservedValue` + + + from `participantPosition` + + + where `participantCurrencyId` = {transferParticipantRecord1.participantCurrencyId} + + + or `participantCurrencyId` = {transferParticipantRecord2.participantCurrencyId}; + + + + [No records found] + + + + + 30 + + + Error + + + + + + 31 + + + Rollback on Error + + + + + + 32 + + + exit + + + + + + 33 + + + exit + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.plantuml new file mode 100644 index 000000000..54cafaa6c --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.plantuml @@ -0,0 +1,355 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.1. Trigger Settlement Event (createSettlement) +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Trigger Settlement Event +activate OPERATOR + note right of OPERATOR #yellow + { + "settlementModel": "string", + "reason": "string", + "settlementWindows": [ + { + "id": 1, + }, + { + "id": 2, + } + ] + } + end note + OPERATOR -> SSAPI: POST - /settlements + activate SSAPI + + SSAPI-> SETTLE_DAO: Request settlementModel\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve settlementModel + activate DB + hnote over DB #lightyellow + SELECT sg.name settlementGranularity, si.name settlementInterchange, + sd.name settlementDelay, sm.ledgerAccountTypeId, + sm.currencyId, sm.requireLiquidityCheck + FROM **settlementModel** sm + JOIN **settlementGranularity** sg + ON sg.settlementGranularityId = sm.settlementGranularityId + JOIN **settlementInterchange** si + ON si.settlementInterchangeId = sm.settlementInterchangeId + JOIN **settlementDelay** sd + ON sd.settlementDelayId = sm.settlementDelayId + WHERE name = {settlementModelName} + AND isActive = 1 + end hnote + SETTLE_DAO <-- DB: Return data + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlementModelData (smd)** + deactivate SETTLE_DAO + + break smd.settlementGranularity != 'NET' ||\nsmd.settlementInterchange != 'MULTILATERAL' ||\nsmd.settlementDelay != 'DEFERRED' + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Invalid settlement model" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + end + + SSAPI-> SETTLE_DAO: Request settlementWindow(s)\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve settlementWindow(s) + activate DB + hnote over DB #lightyellow + SELECT DISTINCT sw.settlementWindowId, sw.currentStateChangeId, sw.createdDate + FROM **settlementWindow** sw + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + JOIN **settlementWindowContent** swc + ON swc.settlementWindowId = sw.settlementWindowId + JOIN **settlementWindowContentStateChange** swcsc + ON swcsc.settlementWindowContentStateChangeId = sw.currentStateChangeId + WHERE sw.settlementWindowId IN {payload.settlementWindows.idList} + AND swc.ledgerAccountType = smd.ledgerAccountType + AND swc.currencyId = ISNULL(smd.currencyId, swc.currencyId) + AND swsc.settlementWindowStateId IN ('CLOSED', 'ABORTED', 'PENDING_SETTLEMENT') + AND swcsc.settlementWindowStateId IN ('CLOSED', 'ABORTED') + end hnote + SETTLE_DAO <-- DB: Return data + deactivate DB + SSAPI <-- SETTLE_DAO: Return **windowsData** + deactivate SETTLE_DAO + + break payload.settlementWindows.length != windowsData.length + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Inapplicable windows found: ${windowId1}, ${windowId2}, ..." + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + end + + note right of SSAPI #lightgray + All preliminary validations succeeded + end note + + group Main processing + SSAPI ->SETTLE_DAO: Create settlement\nError code: 2001 + activate SETTLE_DAO + group DB TRANSACTION + note right of SETTLE_DAO #lightgray + let **transactionTimestamp** = now() + end note + + SETTLE_DAO -> DB: Insert new settlement + activate DB + hnote over DB #lightyellow + INSERT INTO **settlement** (reason, createdDate) + VALUES ({payload.reason}, {transactionTimestamp}) + end hnote + SETTLE_DAO <-- DB: Return **settlementId** + deactivate DB + + SETTLE_DAO -> DB: Associate settlement windows with the settlement + activate DB + SETTLE_DAO -> DB: Retrieve SettlementWindowContent id List + opt Settlement Model is Default + SETTLE_DAO -> SETTLE_DAO: filter out the SettlementWindowContent id List\n to exclude content that is not settled by default settlement model + end + hnote over DB #lightyellow + INSERT INTO **settlementSettlementWindow** (settlementId, settlementWindowId, createdDate) + VALUES ({settlementId}, {payload.settlementWindows.idList}, {transactionTimestamp}) + end hnote + deactivate DB + + SETTLE_DAO -> DB: Bind to settlement + activate DB + hnote over DB #lightyellow + **settlementWindowContent** + .settlementId + **settlementContentAggregation** + .settlementId + .currentStateId + end hnote + deactivate DB + + SETTLE_DAO -> DB: Change state to 'PENDING_SETTLEMENT' + activate DB + hnote over DB #lightyellow + transferParticipantStateChange + transferParticipant + settlementWindowContentStateChange + settlementWindowContent + end hnote + deactivate DB + + SETTLE_DAO -> DB: Aggregate settlement net amounts + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementParticipantCurrency** + (settlementId, participantCurrencyId, netAmount, createdDate) + SELECT settlementId, participantCurrencyId, SUM(amount), {transactionTimestamp} + JOIN **settlementContentAggregation** + WHERE settlementId = {settlementId} + GROUP BY settlementId, participantCurrencyId + end hnote + SETTLE_DAO <-- DB: Return inserted **settlementParticipantCurrencyIdList** + deactivate DB + + SETTLE_DAO -> DB: Insert initial settlement accounts state 'PENDING_SETTLEMENT' + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementParticipantCurrencyStateChange** + (settlementParticipantCurrencyId, settlementStateId, reason, createdDate) + VALUES ({settlementParticipantCurrencyIdList}, 'PENDING_SETTLEMENT', + {payload.reason}, {transactionTimestamp}) + end hnote + SETTLE_DAO <-- DB: Return inserted **settlementParticipantCurrencyStateChangeIdList** + deactivate DB + SETTLE_DAO -> SETTLE_DAO: Merge settlementParticipantCurrencyStateChangeIdList\nto settlementParticipantCurrencyIdList in order to\nissue the following update in one knex command + + SETTLE_DAO -> DB: Update pointers to current state change ids + activate DB + hnote over DB #lightyellow + UPDATE **settlementParticipantCurrency** + SET currentStateChangeId = {settlementParticipantCurrencyStateChangeIdList} + WHERE settlementParticipantCurrencyId = {settlementParticipantCurrencyIdList} + end hnote + deactivate DB + + loop foreach w in windowsData + opt if w.currentStateChangeId IN ('CLOSED', 'ABORTED') + SETTLE_DAO -> DB: Insert new state for settlementWindow 'PENDING_SETTLEMENT' + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementWindowStateChange** + (settlementWindowId, settlementWindowStateId, reason, createdDate) + VALUES ({w.settlementWindowId}, 'PENDING_SETTLEMENT', + {payload.reason}, {transactionTimestamp}) + end hnote + SETTLE_DAO <-- DB: Return inserted **settlementWindowStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointers to current state change ids + activate DB + hnote over DB #lightyellow + UPDATE **settlementWindow** + SET currentStateChangeId = {settlementWindowStateChangeId} + WHERE settlementWindowId = {w.settlementWindowId} + end hnote + deactivate DB + end + end + + SETTLE_DAO -> DB: Insert initial state for settlement 'PENDING_SETTLEMENT' + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementStateChange** + (settlementId, settlementStateId, reason, createdDate) + VALUES ({settlementId}, ‘PENDING_SETTLEMENT’, + {payload.reason}, {transactionTimestamp}) + end hnote + SETTLE_DAO <-- DB: Return **settlementStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointer to current state change id + activate DB + hnote over DB #lightyellow + UPDATE **settlement** + SET currentStateChangeId = {settlementStateChangeId} + WHERE settlementId = {settlementId} + end hnote + deactivate DB + end + + SETTLE_DAO -> DB: Retrieve all content + activate DB + hnote over DB #lightyellow + settlementWindowContent + settlementWindowContentStateChange + ledgerAccountType + currency + settlementWindow + settlementWindowStateChange + end hnote + SETTLE_DAO <-- DB: Return **settlementWindowContentReport** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Use previous result to produce settlementWindowsData (**swd**) array + + SETTLE_DAO -> DB: Select account data for response + activate DB + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, spc.netAmount, pc.currencyId + FROM **settlementParticipantCurrency** spc + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementId = {settlementId} + end hnote + SETTLE_DAO <-- DB: Return **accountData** + deactivate DB + + SSAPI <-- SETTLE_DAO: Construct and return result + deactivate SETTLE_DAO + note left of SSAPI #yellow + { + "id": settlementId, + "state": "PENDING_SETTLEMENT", + "settlementWindows": [ + { + "id": swd[m].id, + "state": swd[m].state, + "reason": swd[m].reason, + "createdDate": swd[m].createdDate, + "changedDate": swd[m].changedDate, + "content": [ + { + "id": swd[m].content[n].settlementWindowContentId, + "state": swd[m].content[n].settlementWindowStateId, + "ledgerAccountType": swd[m].content[n].ledgerAccountType, + "currencyId": swd[m].content[n].currencyId, + "createdDate": swd[m].content[n].createdDate, + "changedDate": swd[m].content[n].changedDate + } + ] + } + ], + "participants": [ + { + "id": accountData.participantId, + "accounts": [ + { + "id": accountData.participantCurrencyId, + "state": "PENDING_SETTLEMENT", + "reason": payload.reason, + "netSettlementAmount": { + "amount": accountData.netAmount, + "currency": accountData.currencyId + } + } + ] + } + ] + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 201 (Created) + end + deactivate SSAPI + deactivate OPERATOR +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.svg new file mode 100644 index 000000000..7a99625e7 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.svg @@ -0,0 +1,1060 @@ + + + + + + + + + + + 6.2.1. Trigger Settlement Event (createSettlement) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Trigger Settlement Event + + + + + { + + + "settlementModel": "string", + + + "reason": "string", + + + "settlementWindows": [ + + + { + + + "id": 1, + + + }, + + + { + + + "id": 2, + + + } + + + ] + + + } + + + + + 1 + + + POST - /settlements + + + + + 2 + + + Request settlementModel + + + Error code: + + + 2001 + + + + + 3 + + + Retrieve settlementModel + + + + SELECT sg.name settlementGranularity, si.name settlementInterchange, + + + sd.name settlementDelay, sm.ledgerAccountTypeId, + + + sm.currencyId, sm.requireLiquidityCheck + + + FROM + + + settlementModel + + + sm + + + JOIN + + + settlementGranularity + + + sg + + + ON sg.settlementGranularityId = sm.settlementGranularityId + + + JOIN + + + settlementInterchange + + + si + + + ON si.settlementInterchangeId = sm.settlementInterchangeId + + + JOIN + + + settlementDelay + + + sd + + + ON sd.settlementDelayId = sm.settlementDelayId + + + WHERE name = {settlementModelName} + + + AND isActive = 1 + + + + + 4 + + + Return data + + + + + 5 + + + Return + + + settlementModelData (smd) + + + + + break + + + [smd.settlementGranularity != 'NET' || + + + smd.settlementInterchange != 'MULTILATERAL' || + + + smd.settlementDelay != 'DEFERRED'] + + + + + Log ERROR event + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Invalid settlement model" + + + } + + + } + + + + + 6 + + + Respond HTTP - 4xx (Client error) + + + + + 7 + + + Request settlementWindow(s) + + + Error code: + + + 2001 + + + + + 8 + + + Retrieve settlementWindow(s) + + + + SELECT DISTINCT sw.settlementWindowId, sw.currentStateChangeId, sw.createdDate + + + FROM + + + settlementWindow + + + sw + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + JOIN + + + settlementWindowContent + + + swc + + + ON swc.settlementWindowId = sw.settlementWindowId + + + JOIN + + + settlementWindowContentStateChange + + + swcsc + + + ON swcsc.settlementWindowContentStateChangeId = sw.currentStateChangeId + + + WHERE sw.settlementWindowId IN {payload.settlementWindows.idList} + + + AND swc.ledgerAccountType = smd.ledgerAccountType + + + AND swc.currencyId = ISNULL(smd.currencyId, swc.currencyId) + + + AND swsc.settlementWindowStateId IN ('CLOSED', 'ABORTED', 'PENDING_SETTLEMENT') + + + AND swcsc.settlementWindowStateId IN ('CLOSED', 'ABORTED') + + + + + 9 + + + Return data + + + + + 10 + + + Return + + + windowsData + + + + + break + + + [payload.settlementWindows.length != windowsData.length] + + + + + Log ERROR event + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Inapplicable windows found: ${windowId1}, ${windowId2}, ..." + + + } + + + } + + + + + 11 + + + Respond HTTP - 4xx (Client error) + + + + + All preliminary validations succeeded + + + + + Main processing + + + + + 12 + + + Create settlement + + + Error code: + + + 2001 + + + + + DB TRANSACTION + + + + + let + + + transactionTimestamp + + + = now() + + + + + 13 + + + Insert new settlement + + + + INSERT INTO + + + settlement + + + (reason, createdDate) + + + VALUES ({payload.reason}, {transactionTimestamp}) + + + + + 14 + + + Return + + + settlementId + + + + + 15 + + + Associate settlement windows with the settlement + + + + + 16 + + + Retrieve SettlementWindowContent id List + + + + + opt + + + [Settlement Model is Default] + + + + + 17 + + + filter out the SettlementWindowContent id List + + + to exclude content that is not settled by default settlement model + + + + INSERT INTO + + + settlementSettlementWindow + + + (settlementId, settlementWindowId, createdDate) + + + VALUES ({settlementId}, {payload.settlementWindows.idList}, {transactionTimestamp}) + + + + + 18 + + + Bind to settlement + + + + settlementWindowContent + + + .settlementId + + + settlementContentAggregation + + + .settlementId + + + .currentStateId + + + + + 19 + + + Change state to 'PENDING_SETTLEMENT' + + + + transferParticipantStateChange + + + transferParticipant + + + settlementWindowContentStateChange + + + settlementWindowContent + + + + + 20 + + + Aggregate settlement net amounts + + + + INSERT INTO + + + settlementParticipantCurrency + + + (settlementId, participantCurrencyId, netAmount, createdDate) + + + SELECT settlementId, participantCurrencyId, SUM(amount), {transactionTimestamp} + + + JOIN + + + settlementContentAggregation + + + WHERE settlementId = {settlementId} + + + GROUP BY settlementId, participantCurrencyId + + + + + 21 + + + Return inserted + + + settlementParticipantCurrencyIdList + + + + + 22 + + + Insert initial settlement accounts state 'PENDING_SETTLEMENT' + + + + INSERT INTO + + + settlementParticipantCurrencyStateChange + + + (settlementParticipantCurrencyId, settlementStateId, reason, createdDate) + + + VALUES ({settlementParticipantCurrencyIdList}, 'PENDING_SETTLEMENT', + + + {payload.reason}, {transactionTimestamp}) + + + + + 23 + + + Return inserted + + + settlementParticipantCurrencyStateChangeIdList + + + + + 24 + + + Merge settlementParticipantCurrencyStateChangeIdList + + + to settlementParticipantCurrencyIdList in order to + + + issue the following update in one knex command + + + + + 25 + + + Update pointers to current state change ids + + + + UPDATE + + + settlementParticipantCurrency + + + SET currentStateChangeId = {settlementParticipantCurrencyStateChangeIdList} + + + WHERE settlementParticipantCurrencyId = {settlementParticipantCurrencyIdList} + + + + + loop + + + [foreach w in windowsData] + + + + + opt + + + [if w.currentStateChangeId IN ('CLOSED', 'ABORTED')] + + + + + 26 + + + Insert new state for settlementWindow 'PENDING_SETTLEMENT' + + + + INSERT INTO + + + settlementWindowStateChange + + + (settlementWindowId, settlementWindowStateId, reason, createdDate) + + + VALUES ({w.settlementWindowId}, 'PENDING_SETTLEMENT', + + + {payload.reason}, {transactionTimestamp}) + + + + + 27 + + + Return inserted + + + settlementWindowStateChangeId + + + + + 28 + + + Update pointers to current state change ids + + + + UPDATE + + + settlementWindow + + + SET currentStateChangeId = {settlementWindowStateChangeId} + + + WHERE settlementWindowId = {w.settlementWindowId} + + + + + 29 + + + Insert initial state for settlement 'PENDING_SETTLEMENT' + + + + INSERT INTO + + + settlementStateChange + + + (settlementId, settlementStateId, reason, createdDate) + + + VALUES ({settlementId}, ‘PENDING_SETTLEMENT’, + + + {payload.reason}, {transactionTimestamp}) + + + + + 30 + + + Return + + + settlementStateChangeId + + + + + 31 + + + Update pointer to current state change id + + + + UPDATE + + + settlement + + + SET currentStateChangeId = {settlementStateChangeId} + + + WHERE settlementId = {settlementId} + + + + + 32 + + + Retrieve all content + + + + settlementWindowContent + + + settlementWindowContentStateChange + + + ledgerAccountType + + + currency + + + settlementWindow + + + settlementWindowStateChange + + + + + 33 + + + Return + + + settlementWindowContentReport + + + + + 34 + + + Use previous result to produce settlementWindowsData ( + + + swd + + + ) array + + + + + 35 + + + Select account data for response + + + + SELECT pc.participantId, spc.participantCurrencyId, spc.netAmount, pc.currencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementId = {settlementId} + + + + + 36 + + + Return + + + accountData + + + + + 37 + + + Construct and return result + + + + + { + + + "id": settlementId, + + + "state": "PENDING_SETTLEMENT", + + + "settlementWindows": [ + + + { + + + "id": swd[m].id, + + + "state": swd[m].state, + + + "reason": swd[m].reason, + + + "createdDate": swd[m].createdDate, + + + "changedDate": swd[m].changedDate, + + + "content": [ + + + { + + + "id": swd[m].content[n].settlementWindowContentId, + + + "state": swd[m].content[n].settlementWindowStateId, + + + "ledgerAccountType": swd[m].content[n].ledgerAccountType, + + + "currencyId": swd[m].content[n].currencyId, + + + "createdDate": swd[m].content[n].createdDate, + + + "changedDate": swd[m].content[n].changedDate + + + } + + + ] + + + } + + + ], + + + "participants": [ + + + { + + + "id": accountData.participantId, + + + "accounts": [ + + + { + + + "id": accountData.participantCurrencyId, + + + "state": "PENDING_SETTLEMENT", + + + "reason": payload.reason, + + + "netSettlementAmount": { + + + "amount": accountData.netAmount, + + + "currency": accountData.currencyId + + + } + + + } + + + ] + + + } + + + ] + + + } + + + + + 38 + + + Respond HTTP - 201 (Created) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.plantuml new file mode 100644 index 000000000..527d09f20 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.plantuml @@ -0,0 +1,209 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.2. Query Settlements by Parameters (getSettlementsByParams) + +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #LightPink + participant OPERATOR +end box + +box "Settlement Service" #LightGreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #LightYellow + participant DB +end box + +' start flow +group Query Settlements by Parameters + activate OPERATOR + note right of OPERATOR #Yellow + **Params:** + ?[state={settlementStateId}] + [&fromDateTime={fromDateTime}] + [&toDateTime={toDateTime}] + [¤cy={currencyId}] + [&settlementWindowId={settlementWindowId}] + [&fromSettlementWindowDateTime={fromSettlementWindowDateTime}] + [&toSettlementWindowDateTime={toSettlementWindowDateTime}] + [&participantId={participantId}] + [&accountId={participantCurrencyId}] + end note + + OPERATOR -> SSAPI: GET - /settlements + activate SSAPI + SSAPI -> SETTLE_DAO: Retrieve settlements\nError code: 2001 + activate SETTLE_DAO + + SETTLE_DAO -> DB: Retrieve requested data + activate DB + hnote over DB #lightyellow + SELECT **//DISTINCT//** s.settlementId, ssc.settlementStateId, + ssw.settlementWindowId, swsc.settlementWindowStateId, swsc.reason + settlementWindowReason, sw.createdDate, swsc.createdDate changedDate, + pc.participantId, spc.participantCurrencyId, spcsc.reason + accountReason, spcsc.settlementStateId accountState, spc.netAmount + accountAmount, pc.currencyId accountCurrency + FROM **settlement** s + JOIN **settlementStateChange** ssc + ON ssc.settlementStateChangeId = s.currentStateChangeId + JOIN **settlementSettlementWindow** ssw + ON ssw.settlementId = s.settlementId + JOIN **settlementWindow** sw + ON sw.settlementWindowId = ssw.settlementWindowId + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + JOIN **//settlementWindowContent//** swc + ON swc.settlementWindowId = sw.settlementWindowId + JOIN **//settlementWindowContentAggregation//** swca + ON swca.settlementWindowContentId = swc.settlementWindowContentId + JOIN **settlementParticipantCurrency** spc + ON spc.settlementId = s.settlementId + AND spc.participantCurrencyId = swca.participantCurrencyId + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE [ssc.settlementStateId = {settlementStateId}] + [AND s.createdDate >= {fromDateTime}] + [AND s.createdDate <= {toDateTime}] + [AND pc.currencyId = {currencyId}] + [AND sw.settlementWindowId = //{settlementWindowId}//] + [AND sw.createdDate >= {fromSettlementWindowDateTime}] + [AND sw.createdDate <= {toSettlementWindowDateTime}] + [AND pc.participantId = {participantId}] + [AND spc.participantCurrencyId = {participantCurrencyId}] + end hnote + SETTLE_DAO <-- DB: Return data + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlementsData** + deactivate SETTLE_DAO + alt Settlement(s) found + note right of SSAPI #lightgray + let settlements = {} + let settlement + let participant + end note + loop settlementsData + note right of SSAPI #lightgray + if (!settlements[settlementsData.settlementId]) { + settlements[settlementsData.settlementId] = { + "id: settlementsData.settlementId, + "state": settlementsData.settlementStateId + } + } + settlement = settlements[settlementsData.settlementId] + if (!settlement.settlementWindows[settlementsData.settlementWindowId]) { + settlement.settlementWindows[settlementsData.settlementWindowId] = { + "id": settlementsData.settlementWindowId, + "state": settlementsData.settlementWindowStateId, + "reason": settlementsData.settlementWindowReason, + "createdDate": settlementsData.createdDate, + "changedDate": settlementsData.changedDate + } + } + if (!settlement.participants[settlementsData.participantId]) { + settlement.participants[settlementsData.participantId] = { + "id": settlementsData.participantId + } + } + participant = settlement.participants[settlementsData.participantId] + participant.accounts[settlementsData.accountId] = { + "id": settlementsData.participantCurrencyId, + "state": settlementsData.accountState, + "reason": settlementsData.accountReason, + "netSettlementAmount": { + "amount": settlementsData.accountAmount, + "currency": settlementsData.accountCurrency + } + } + end note + end + SSAPI -> SSAPI: Transform **settlements** map to array + deactivate SETTLE_DAO + note left of SSAPI #yellow + [ + { + "id": settlementId, + "state": settlementStateId, + "settlementWindows": [ + { + "id": settlementWindowId, + "state": settlementWindowStateId, + "reason": settlementWindowReason, + "createdDate": createdDate, + "changedDate": changedDate + } + ], + "participants": [ + { + "id": participantId, + "accounts": [ + { + "id": participantCurrencyId, + "state": accountState, + "reason": accountReason, + "netSettlementAmount": { + "amount": accountAmount, + "currency": accountCurrency + } + } + ] + } + ] + } + ] + end note + OPERATOR <-- SSAPI: Respond HTTP - 200 (OK) + else + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Client error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + deactivate OPERATOR + end +end +@enduml \ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.svg new file mode 100644 index 000000000..f47d21e98 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.svg @@ -0,0 +1,609 @@ + + + + + + + + + + + 6.2.2. Query Settlements by Parameters (getSettlementsByParams) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Query Settlements by Parameters + + + + + Params: + + + ?[state={settlementStateId}] + + + [&fromDateTime={fromDateTime}] + + + [&toDateTime={toDateTime}] + + + [&currency={currencyId}] + + + [&settlementWindowId={settlementWindowId}] + + + [&fromSettlementWindowDateTime={fromSettlementWindowDateTime}] + + + [&toSettlementWindowDateTime={toSettlementWindowDateTime}] + + + [&participantId={participantId}] + + + [&accountId={participantCurrencyId}] + + + + + 1 + + + GET - /settlements + + + + + 2 + + + Retrieve settlements + + + Error code: + + + 2001 + + + + + 3 + + + Retrieve requested data + + + + SELECT + + + DISTINCT + + + s.settlementId, ssc.settlementStateId, + + + ssw.settlementWindowId, swsc.settlementWindowStateId, swsc.reason + + + settlementWindowReason, sw.createdDate, swsc.createdDate changedDate, + + + pc.participantId, spc.participantCurrencyId, spcsc.reason + + + accountReason, spcsc.settlementStateId accountState, spc.netAmount + + + accountAmount, pc.currencyId accountCurrency + + + FROM + + + settlement + + + s + + + JOIN + + + settlementStateChange + + + ssc + + + ON ssc.settlementStateChangeId = s.currentStateChangeId + + + JOIN + + + settlementSettlementWindow + + + ssw + + + ON ssw.settlementId = s.settlementId + + + JOIN + + + settlementWindow + + + sw + + + ON sw.settlementWindowId = ssw.settlementWindowId + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + JOIN + + + settlementWindowContent + + + swc + + + ON swc.settlementWindowId = sw.settlementWindowId + + + JOIN + + + settlementWindowContentAggregation + + + swca + + + ON swca.settlementWindowContentId = swc.settlementWindowContentId + + + JOIN + + + settlementParticipantCurrency + + + spc + + + ON spc.settlementId = s.settlementId + + + AND spc.participantCurrencyId = swca.participantCurrencyId + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE [ssc.settlementStateId = {settlementStateId}] + + + [AND s.createdDate >= {fromDateTime}] + + + [AND s.createdDate <= {toDateTime}] + + + [AND pc.currencyId = {currencyId}] + + + [AND sw.settlementWindowId = + + + {settlementWindowId} + + + ] + + + [AND sw.createdDate >= {fromSettlementWindowDateTime}] + + + [AND sw.createdDate <= {toSettlementWindowDateTime}] + + + [AND pc.participantId = {participantId}] + + + [AND spc.participantCurrencyId = {participantCurrencyId}] + + + + + 4 + + + Return data + + + + + 5 + + + Return + + + settlementsData + + + + + alt + + + [Settlement(s) found] + + + + + let settlements = {} + + + let settlement + + + let participant + + + + + loop + + + [settlementsData] + + + + + if (!settlements[settlementsData.settlementId]) { + + + settlements[settlementsData.settlementId] = { + + + "id: settlementsData.settlementId, + + + "state": settlementsData.settlementStateId + + + } + + + } + + + settlement = settlements[settlementsData.settlementId] + + + if (!settlement.settlementWindows[settlementsData.settlementWindowId]) { + + + settlement.settlementWindows[settlementsData.settlementWindowId] = { + + + "id": settlementsData.settlementWindowId, + + + "state": settlementsData.settlementWindowStateId, + + + "reason": settlementsData.settlementWindowReason, + + + "createdDate": settlementsData.createdDate, + + + "changedDate": settlementsData.changedDate + + + } + + + } + + + if (!settlement.participants[settlementsData.participantId]) { + + + settlement.participants[settlementsData.participantId] = { + + + "id": settlementsData.participantId + + + } + + + } + + + participant = settlement.participants[settlementsData.participantId] + + + participant.accounts[settlementsData.accountId] = { + + + "id": settlementsData.participantCurrencyId, + + + "state": settlementsData.accountState, + + + "reason": settlementsData.accountReason, + + + "netSettlementAmount": { + + + "amount": settlementsData.accountAmount, + + + "currency": settlementsData.accountCurrency + + + } + + + } + + + + + 6 + + + Transform + + + settlements + + + map to array + + + + + [ + + + { + + + "id": settlementId, + + + "state": settlementStateId, + + + "settlementWindows": [ + + + { + + + "id": settlementWindowId, + + + "state": settlementWindowStateId, + + + "reason": settlementWindowReason, + + + "createdDate": createdDate, + + + "changedDate": changedDate + + + } + + + ], + + + "participants": [ + + + { + + + "id": participantId, + + + "accounts": [ + + + { + + + "id": participantCurrencyId, + + + "state": accountState, + + + "reason": accountReason, + + + "netSettlementAmount": { + + + "amount": accountAmount, + + + "currency": accountCurrency + + + } + + + } + + + ] + + + } + + + ] + + + } + + + ] + + + + + 7 + + + Respond HTTP - 200 (OK) + + + + + + Log ERROR event + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Client error description" + + + } + + + } + + + + + 8 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.plantuml new file mode 100644 index 000000000..0cd3845b6 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.plantuml @@ -0,0 +1,282 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.3. Get Settlement By Settlement, Participant and Account (getSettlementBySPA) +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Get Settlement By Settlement, Participant and Account +activate OPERATOR + alt + OPERATOR -> SSAPI: GET - /settlements/{settlementId}/\nparticipants/{participantId} + else + OPERATOR -> SSAPI: GET - /settlements/{settlementId}/\nparticipants/{participantId}/\naccounts/{accountId} + end + + activate SSAPI + + note right of SSAPI #lightgray + let settlementFound = false + let participantFoundInSettlement = false + let accountProvided = accountId > 0 + let participantAndAccountMatched = !accountProvided + let accountFoundInSettlement = !accountProvided + end note + SSAPI-> SETTLE_DAO: Request settlement state\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve settlement + activate DB + hnote over DB #lightyellow + SELECT s.settlementId, ssc.settlementStateId, s.reason, s.createdDate + FROM **settlement** s + JOIN **settlementStateChange** ssc + ON ssc.settlementStateChangeId = s.currentStateChangeId + WHERE s.settlementId = {id} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlement** + deactivate SETTLE_DAO + note right of SSAPI #lightgray + if (settlement.settlementId) { + **settlementFound** = true + } + end note + + opt settlementFound + SSAPI-> SETTLE_DAO: Check participant\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Check exists + activate DB + hnote over DB #lightyellow + SELECT settlementParticipantCurrencyId + FROM **settlementParticipantCurrency** spc + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementId = {id} + AND pc.participantId = {participantId} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlementParticipantCurrencyIdList** + deactivate SETTLE_DAO + note right of SSAPI #lightgray + if (settlementParticipantCurrencyIdList.length > 0) { + **participantFoundInSettlement** = true + } + end note + + opt participantFoundInSettlement && accountProvided + SSAPI-> SETTLE_DAO: Check participant\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Check exists + activate DB + hnote over DB #lightyellow + SELECT participantCurrencyId + JOIN **participantCurrency** + WHERE participantCurrencyId = {accountId} + AND participantId = {participantId} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **account** + deactivate SETTLE_DAO + note right of SSAPI #lightgray + if (account) { + **participantAndAccountMatched** = true + } + end note + + opt participantAndAccountMatched + SSAPI-> SETTLE_DAO: Check account in settlement\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Check exists + activate DB + hnote over DB #lightyellow + SELECT settlementParticipantCurrencyId + FROM **settlementParticipantCurrency** + WHERE spc.settlementId = {id} + AND pc.participantCurrencyId = {accountId} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlementParticipantCurrencyId** + deactivate SETTLE_DAO + + note right of SSAPI #lightgray + if (settlementParticipantCurrencyId) { + **accountFoundInSettlement** = true + } + end note + end + end + end + + alt settlementFound && participantFoundInSettlement && participantAndAccountMatched && accountFoundInSettlement + SSAPI-> SETTLE_DAO: Request settlement windows\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve windows + activate DB + alt accountProvided + hnote over DB #lightyellow + SELECT **//DISTINCT//** sw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementSettlementWindow** ssw + JOIN **settlementWindow** sw + ON sw.settlementWindowId = ssw.settlementWindowId + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + JOIN **//settlementWindowContent//** swc + ON swc.settlementWindowId = sw.settlementWindowId + JOIN **//settlementWindowContentAggregation//** swca + ON swca.settlementWindowContentId = swc.settlementWindowContentId + AND swca.participantCurrencyId = //{accountId}// + WHERE ssw.settlementId = {id} + end hnote + else + hnote over DB #lightyellow + SELECT DISTINCT sw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementSettlementWindow** ssw + JOIN **settlementWindow** sw + ON sw.settlementWindowId = ssw.settlementWindowId + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + JOIN **//settlementWindowContent//** swc + ON swc.settlementWindowId = sw.settlementWindowId + JOIN **//settlementWindowContentAggregation//** swca + ON swca.settlementWindowContentId = swc.settlementWindowContentId + AND swca.participantCurrencyId IN( + SELECT participantCurrencyId + FROM participantCurrency + WHERE participantId = //{participantId}// + ) + WHERE ssw.settlementId = {id} + end hnote + end + deactivate DB + SSAPI <-- SETTLE_DAO: Return **windows** + deactivate SETTLE_DAO + + SSAPI-> SETTLE_DAO: Request settlement accounts\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve accounts + activate DB + alt accountProvided + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + spcsc.reason, spc.netAmount, pc.currencyId + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementParticipantCurrencyId = {settlementParticipantCurrencyId} + end hnote + else + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + spcsc.reason, spc.netAmount, pc.currencyId + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementParticipantCurrencyId IN {settlementParticipantCurrencyIdList} + end hnote + end + deactivate DB + SSAPI <-- SETTLE_DAO: Return **accounts** + deactivate SETTLE_DAO + + note left of SSAPI #yellow + { + "id": settlement.settlementId, + "state": settlement.settlementStateId, + "settlementWindows": [ + [ + { + "id": window.settlementWindowId, + "reason": window.reason, + "state": window.settlementWindowStateId, + "createdDate": window.createdDate, + "changedDate": window.changedDate + } + ] + ], + "participants": [ + { + "id": account.participantId, + "accounts": [ + { + "id": account.participantCurrencyId, + "reason": account.reason, + "state": account.settlementStateId, + "netSettlementAmount": { + "amount": account.netAmount, + "currency": account.currencyId + } + } + ] + } + ] + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 200 (OK) + else !settlementFound || !participantFoundInSettlement || !participantAndAccountMatched || !accountFoundInSettlement + note right of SSAPI #lightgray + Log ERROR event (based on the failure) + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Client error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + deactivate OPERATOR + end +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.svg new file mode 100644 index 000000000..47915f13b --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.svg @@ -0,0 +1,916 @@ + + + + + + + + + + + 6.2.3. Get Settlement By Settlement, Participant and Account (getSettlementBySPA) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Get Settlement By Settlement, Participant and Account + + + + + alt + + + + + 1 + + + GET - /settlements/{settlementId}/ + + + participants/{participantId} + + + + + + 2 + + + GET - /settlements/{settlementId}/ + + + participants/{participantId}/ + + + accounts/{accountId} + + + + + let settlementFound = false + + + let participantFoundInSettlement = false + + + let accountProvided = accountId > 0 + + + let participantAndAccountMatched = !accountProvided + + + let accountFoundInSettlement = !accountProvided + + + + + 3 + + + Request settlement state + + + Error code: + + + 2001 + + + + + 4 + + + Retrieve settlement + + + + SELECT s.settlementId, ssc.settlementStateId, s.reason, s.createdDate + + + FROM + + + settlement + + + s + + + JOIN + + + settlementStateChange + + + ssc + + + ON ssc.settlementStateChangeId = s.currentStateChangeId + + + WHERE s.settlementId = {id} + + + + + 5 + + + Return + + + settlement + + + + + if (settlement.settlementId) { + + + settlementFound + + + = true + + + } + + + + + opt + + + [settlementFound] + + + + + 6 + + + Check participant + + + Error code: + + + 2001 + + + + + 7 + + + Check exists + + + + SELECT settlementParticipantCurrencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementId = {id} + + + AND pc.participantId = {participantId} + + + + + 8 + + + Return + + + settlementParticipantCurrencyIdList + + + + + if (settlementParticipantCurrencyIdList.length > 0) { + + + participantFoundInSettlement + + + = true + + + } + + + + + opt + + + [participantFoundInSettlement && accountProvided] + + + + + 9 + + + Check participant + + + Error code: + + + 2001 + + + + + 10 + + + Check exists + + + + SELECT participantCurrencyId + + + JOIN + + + participantCurrency + + + WHERE participantCurrencyId = {accountId} + + + AND participantId = {participantId} + + + + + 11 + + + Return + + + account + + + + + if (account) { + + + participantAndAccountMatched + + + = true + + + } + + + + + opt + + + [participantAndAccountMatched] + + + + + 12 + + + Check account in settlement + + + Error code: + + + 2001 + + + + + 13 + + + Check exists + + + + SELECT settlementParticipantCurrencyId + + + FROM + + + settlementParticipantCurrency + + + WHERE spc.settlementId = {id} + + + AND pc.participantCurrencyId = {accountId} + + + + + 14 + + + Return + + + settlementParticipantCurrencyId + + + + + if (settlementParticipantCurrencyId) { + + + accountFoundInSettlement + + + = true + + + } + + + + + alt + + + [settlementFound && participantFoundInSettlement && participantAndAccountMatched && accountFoundInSettlement] + + + + + 15 + + + Request settlement windows + + + Error code: + + + 2001 + + + + + 16 + + + Retrieve windows + + + + + alt + + + [accountProvided] + + + + SELECT + + + DISTINCT + + + sw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementSettlementWindow + + + ssw + + + JOIN + + + settlementWindow + + + sw + + + ON sw.settlementWindowId = ssw.settlementWindowId + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + JOIN + + + settlementWindowContent + + + swc + + + ON swc.settlementWindowId = sw.settlementWindowId + + + JOIN + + + settlementWindowContentAggregation + + + swca + + + ON swca.settlementWindowContentId = swc.settlementWindowContentId + + + AND swca.participantCurrencyId = + + + {accountId} + + + WHERE ssw.settlementId = {id} + + + + + SELECT DISTINCT sw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementSettlementWindow + + + ssw + + + JOIN + + + settlementWindow + + + sw + + + ON sw.settlementWindowId = ssw.settlementWindowId + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + JOIN + + + settlementWindowContent + + + swc + + + ON swc.settlementWindowId = sw.settlementWindowId + + + JOIN + + + settlementWindowContentAggregation + + + swca + + + ON swca.settlementWindowContentId = swc.settlementWindowContentId + + + AND swca.participantCurrencyId IN( + + + SELECT participantCurrencyId + + + FROM participantCurrency + + + WHERE participantId = + + + {participantId} + + + ) + + + WHERE ssw.settlementId = {id} + + + + + 17 + + + Return + + + windows + + + + + 18 + + + Request settlement accounts + + + Error code: + + + 2001 + + + + + 19 + + + Retrieve accounts + + + + + alt + + + [accountProvided] + + + + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + + + spcsc.reason, spc.netAmount, pc.currencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementParticipantCurrencyId = {settlementParticipantCurrencyId} + + + + + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + + + spcsc.reason, spc.netAmount, pc.currencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementParticipantCurrencyId IN {settlementParticipantCurrencyIdList} + + + + + 20 + + + Return + + + accounts + + + + + { + + + "id": settlement.settlementId, + + + "state": settlement.settlementStateId, + + + "settlementWindows": [ + + + [ + + + { + + + "id": window.settlementWindowId, + + + "reason": window.reason, + + + "state": window.settlementWindowStateId, + + + "createdDate": window.createdDate, + + + "changedDate": window.changedDate + + + } + + + ] + + + ], + + + "participants": [ + + + { + + + "id": account.participantId, + + + "accounts": [ + + + { + + + "id": account.participantCurrencyId, + + + "reason": account.reason, + + + "state": account.settlementStateId, + + + "netSettlementAmount": { + + + "amount": account.netAmount, + + + "currency": account.currencyId + + + } + + + } + + + ] + + + } + + + ] + + + } + + + + + 21 + + + Respond HTTP - 200 (OK) + + + + [!settlementFound || !participantFoundInSettlement || !participantAndAccountMatched || !accountFoundInSettlement] + + + + + Log ERROR event (based on the failure) + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Client error description" + + + } + + + } + + + + + 22 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.plantuml new file mode 100644 index 000000000..37f9694bd --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.plantuml @@ -0,0 +1,159 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.4. Get Settlement By Id (getSettlementById) +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Get Settlement By Id +activate OPERATOR + OPERATOR -> SSAPI: GET - /settlements/{id} + activate SSAPI + + SSAPI-> SETTLE_DAO: Request settlement state\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve settlement + activate DB + hnote over DB #lightyellow + SELECT s.settlementId, ssc.settlementStateId, s.reason, s.createdDate + FROM **settlement** s + JOIN **settlementStateChange** ssc + ON ssc.settlementStateChangeId = s.currentStateChangeId + WHERE s.settlementId = {id} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlement** + deactivate SETTLE_DAO + + alt settlement found + SSAPI-> SETTLE_DAO: Request settlement windows\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve windows + activate DB + hnote over DB #lightyellow + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementSettlementWindow** ssw + JOIN **settlementWindow** sw + ON sw.settlementWindowId = ssw.settlementWindowId + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + WHERE ssw.settlementId = {id} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **windows** + deactivate SETTLE_DAO + + SSAPI-> SETTLE_DAO: Request settlement accounts\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Retrieve accounts + activate DB + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + spcsc.reason, spc.netAmount, pc.currencyId + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementId = {id} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **accounts** + deactivate SETTLE_DAO + + note left of SSAPI #yellow + { + "id": settlement.settlementId, + "state": settlement.settlementStateId, + "settlementWindows": [ + [ + { + "id": window.settlementWindowId, + "reason": window.reason, + "state": window.settlementWindowStateId, + "createdDate": window.createdDate, + "changedDate": window.changedDate + } + ] + ], + "participants": [ + { + "id": account.participantId, + "accounts": [ + { + "id": account.participantCurrencyId, + "reason": account.reason, + "state": account.settlementStateId, + "netSettlementAmount": { + "amount": account.netAmount, + "currency": account.currencyId + } + } + ] + } + ] + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 200 (OK) + else + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Client error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + deactivate OPERATOR + end +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.svg new file mode 100644 index 000000000..460c5a1ac --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.svg @@ -0,0 +1,442 @@ + + + + + + + + + + + 6.2.4. Get Settlement By Id (getSettlementById) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Get Settlement By Id + + + + + 1 + + + GET - /settlements/{id} + + + + + 2 + + + Request settlement state + + + Error code: + + + 2001 + + + + + 3 + + + Retrieve settlement + + + + SELECT s.settlementId, ssc.settlementStateId, s.reason, s.createdDate + + + FROM + + + settlement + + + s + + + JOIN + + + settlementStateChange + + + ssc + + + ON ssc.settlementStateChangeId = s.currentStateChangeId + + + WHERE s.settlementId = {id} + + + + + 4 + + + Return + + + settlement + + + + + alt + + + [settlement found] + + + + + 5 + + + Request settlement windows + + + Error code: + + + 2001 + + + + + 6 + + + Retrieve windows + + + + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementSettlementWindow + + + ssw + + + JOIN + + + settlementWindow + + + sw + + + ON sw.settlementWindowId = ssw.settlementWindowId + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + WHERE ssw.settlementId = {id} + + + + + 7 + + + Return + + + windows + + + + + 8 + + + Request settlement accounts + + + Error code: + + + 2001 + + + + + 9 + + + Retrieve accounts + + + + SELECT pc.participantId, spc.participantCurrencyId, spcsc.settlementStateId, + + + spcsc.reason, spc.netAmount, pc.currencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementId = {id} + + + + + 10 + + + Return + + + accounts + + + + + { + + + "id": settlement.settlementId, + + + "state": settlement.settlementStateId, + + + "settlementWindows": [ + + + [ + + + { + + + "id": window.settlementWindowId, + + + "reason": window.reason, + + + "state": window.settlementWindowStateId, + + + "createdDate": window.createdDate, + + + "changedDate": window.changedDate + + + } + + + ] + + + ], + + + "participants": [ + + + { + + + "id": account.participantId, + + + "accounts": [ + + + { + + + "id": account.participantCurrencyId, + + + "reason": account.reason, + + + "state": account.settlementStateId, + + + "netSettlementAmount": { + + + "amount": account.netAmount, + + + "currency": account.currencyId + + + } + + + } + + + ] + + + } + + + ] + + + } + + + + + 11 + + + Respond HTTP - 200 (OK) + + + + + + Log ERROR event + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Client error description" + + + } + + + } + + + + + 12 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.plantuml new file mode 100644 index 000000000..66e9c65b0 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.plantuml @@ -0,0 +1,584 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.5. Acknowledgement of Settlement Transfer (updateSettlementById) +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Acknowledgement of Settlement Transfer + activate OPERATOR + note right of OPERATOR #yellow + { + "participants": [ + { + "id": 1 + "accounts" : [ + { "id": 1, "state": "PENDING_SETTLEMENT", "reason": }, + { "id": 2, "state": "PS_TRANSFERS_RECORDED", "reason": , "externalReference": }, + { "id": 3, "state": "PS_TRANSFERS_RESERVED", "reason": }, + { "id": 4, "state": "PS_TRANSFERS_COMMITTED", "reason": , "externalReference": }, + { "id": 5, "state": "SETTLED", "reason": } + ] + }, + { + "id": 2 + "accounts" : [ + { "id": 6, "state": "SETTLED", "reason": } + ] + } + ] + } + end note + + OPERATOR -> SSAPI: PUT - /settlement/{id} + activate SSAPI + SSAPI -> SETTLE_DAO: updateSettlementById routine\nError code: 2001 + activate SETTLE_DAO + group DB TRANSACTION + SETTLE_DAO -> DB: Retrieve settlement information + activate DB + hnote over DB #lightyellow + SELECT s.settlementId, ssc.settlementStateId, + ssc.reason, ssc.createdDate, sm.autoPositionReset + FROM **settlement** s + JOIN **settlementStateChange** ssc + ON ssc.settlementStateChangeId = s.currentStateChangeId + JOIN **settlementModel** sm + ON sm.settlementModelId = s.settlementModelId + WHERE s.settlementId = {id} + FOR UPDATE + end hnote + SETTLE_DAO <-- DB: Return **settlementData** + deactivate DB + + SETTLE_DAO -> DB: Retrive settlement accounts information + activate DB + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, + spcsc.settlementStateId, spcsc.reason, + spcsc.createdDate, spc.netAmount, pc.currencyId, + spc.settlementParticipantCurrencyId AS key + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = + spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementId = {id} + FOR UPDATE + end hnote + SETTLE_DAO <-- DB: Return **settlementAccountsList** + deactivate DB + + ||| + note right of SETTLE_DAO #lightblue + All objects below are for the purpose of the syncronous request. + If at some point, Node process memory limit is reached, we may decide to: + A. Limit the amount of transfers per window and windows per settlement or + B. Move to asyncronous processing where we don't need these objects + end note + note right of SETTLE_DAO #lightgray + Available raw datasets from DB: + **settlementData** contains information about settlement and its current state/reason + **settlementAccountsList** holds information about all accounts and their current state/reason + + Local variables and objects: + **settlementAccounts**: { // (derived from settlementAccountsList) + pendingSettlementCount: , // count of accounts in PENDING_SETTLEMENT state + psTransfersRecordedCount: , // count of accounts in PS_TRANSFERS_RECORDED state + psTransfersReservedCount: , // count of accounts in PS_TRANSFERS_RESERVED state + psTransfersCommittedCount: , // count of accounts in PS_TRANSFERS_COMMITTED state + settledCount: , // count of accounts in SETTLED state + abortedCount: // count of accounts in ABORTED state + unknownCount: , + settledIdList: , + changedIdList: + } + **settlementAccountsInit** copy of previous object to be preserved for comparission at the end + **allAccounts**: { // same as previous but accessed by account id (derived from settlementAccountsList) + participantCurrencyId_key: { // number used to access the object in map-like style + id: participantCurrencyId, + state: settlementStateId, + reason: reason, + createdDate: createdDate, + netSettlementAmount: { + amount: netAmount, + currency: currencyId + }, + participantId: participantId, // could be used to reconstruct allParticipants + key: key // will be used to insert new state for settlementParticipantCurrency + } + } + let **transactionTimestamp** = now() + end note + ||| + SETTLE_DAO -> SETTLE_DAO: Declare and initialize variables + note right of SETTLE_DAO #lightgray + let settlementAccounts = { + pendingSettlementCount: 0, + psTransfersRecordedCount: 0, + psTransfersReservedCount: 0, + psTransfersCommittedCount: 0, + settledCount: 0, + abortedCount: 0, + unknownCount: 0 + } + ' let allParticipants = {} // declare map + let allAccounts = {} // declare map + let pid // participantId + let aid // accountId (participantCurrencyId) + let state + end note + + loop settlementAccountsList as account + SETTLE_DAO -> SETTLE_DAO: Populate **allAccounts** + ' and **allParticipants** + note right of SETTLE_DAO #lightgray + pid = account.participantId + aid = account.participantCurrencyId + state = account.settlementStateId + + allAccounts[aid] = { + id: aid, + state, + reason: account.reason, + createDate: account.createdDate, + netSettlementAmount: { + amount: account.netAmount, + currency: account.currencyId + }, + participantId: pid, + key: account.key + } + ' allParticipants[pid] = allParticipants[pid] ? allParticipants[pid] : {id: pid, accounts: {}} + ' allParticipants[pid].accounts[aid] = allAccounts[aid] + end note + + SETTLE_DAO -> SETTLE_DAO: Populate **settlementAccounts** + alt state == 'PENDING_SETTLEMENT' + note right of SETTLE_DAO #lightgray + settlementAccounts.pendingSettlementCount++ + end note + else state == 'PS_TRANSFERS_RECORDED' + note right of SETTLE_DAO #lightgray + settlementAccounts.psTransfersRecordedCount++ + end note + else state == 'PS_TRANSFERS_RESERVED' + note right of SETTLE_DAO #lightgray + settlementAccounts.psTransfersReservedCount++ + end note + else state == 'PS_TRANSFERS_COMMITTED' + note right of SETTLE_DAO #lightgray + settlementAccounts.psTransfersCommittedCount++ + end note + else state == 'SETTLED' + note right of SETTLE_DAO #lightgray + settlementAccounts.settledCount++ + end note + else state == 'ABORTED' + note right of SETTLE_DAO #lightgray + settlementAccounts.abortedCount++ + end note + else default + note right of SETTLE_DAO #lightgray + settlementAccounts.unknownCount++ + end note + end + end + SETTLE_DAO -> SETTLE_DAO: Make a copy of settlementAccounts into **settlementAccountsInit** + note right of SETTLE_DAO #lightgray + settlementAccountsInit = Object.assign({}, settlementAccounts) + end note + ||| + note right of SETTLE_DAO #lightgray + Available objects after the setup: + **settlementAccounts** is used for tracing settlement state and state transition allowance + **allAccounts** is helper object, same as previous, providing direct access to account by id + + Now we are ready to process the **payload**: + **participants** = [] // part of the response object that lists the affected participants and respective accounts + **settlementParticipantCurrencyStateChange** = [] // array to collect inserts to the table + **settlementParticipantCurrencySettledList** = [] // array to collect settled accounts + **processedAccounts** = [] // array to log processed accounts and restrict subsequent processing + end note + + loop let participant IN payload.participants + SETTLE_DAO -> SETTLE_DAO: Loop payload for each **participantPayload** + note right of SETTLE_DAO #lightgray + let participantPayload = payload.participants[participant] + participants.push({id: participantPayload.id, accounts: []}) + let pi = participants.length - 1 + participant = participants[pi] + end note + + loop let account IN participantPayload.accounts + SETTLE_DAO -> SETTLE_DAO: Loop payload for each **accountPayload** + note right of SETTLE_DAO #lightgray + let accountPayload = participantPayload.accounts[account] + end note + alt allAccounts[accountPayload.id] == undefined + SETTLE_DAO -> SETTLE_DAO: If the account doesn't match the settlement + note right of SETTLE_DAO #lightgray + participant.accounts.push({ + id: accountPayload.id, + errorInformation: { + errorCode: 3000, + errorDescription: 'Account not found' + } + }) + end note + else participantPayload.id != allAccounts[accountPayload.id].participantId + SETTLE_DAO -> SETTLE_DAO: If the account doesn't match the participant + note right of SETTLE_DAO #lightgray + participant.accounts.push({ + id: accountPayload.id, + errorInformation: { + errorCode: 3000, + errorDescription: 'Participant and account mismatch' + } + }) + end note + else processedAccounts.indexOf(accountPayload.id) > -1 + SETTLE_DAO -> SETTLE_DAO: If the account has been previosly processed (duplicated in the payload) + note right of SETTLE_DAO #lightgray + participant.accounts.push({ + id: accountPayload.id, + state: allAccounts[accountPayload.id].state, + reason: allAccounts[accountPayload.id].reason, + createdDate: allAccounts[accountPayload.id].createdDate, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + errorInformation: { + errorCode: 3000, + errorDescription: 'Account already processed once' + } + }) + end note + else allAccounts[account.id].state == accountPayload.state // allowed + SETTLE_DAO -> SETTLE_DAO: Same-state reason amendment is always allowed + note right of SETTLE_DAO #lightgray + processedAccounts.push(accountPayload.id) + participant.accounts.push({ + id: accountPayload.id, + state: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + createdDate: transactionTimestamp, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + }) + settlementParticipantCurrencyStateChange.push({ + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + settlementStateId: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference + }) + allAccounts[accountPayload.id].reason = accountPayload.reason + allAccounts[accountPayload.id].createdDate = currentTimestamp + end note + else settlementData.state == 'PENDING_SETTLEMENT' && accountPayload.state == 'PS_TRANSFERS_RECORDED' + else settlementData.state == 'PS_TRANSFERS_RECORDED' && accountPayload.state == 'PS_TRANSFERS_RESERVED' + else settlementData.state == 'PS_TRANSFERS_RESERVED' && accountPayload.state == 'PS_TRANSFERS_COMMITTED' + else settlementData.state == 'PS_TRANSFERS_COMMITTED' || settlementData.state == 'SETTLING' && accountPayload.state == 'SETTLED' + note right of SETTLE_DAO #lightgray + **Note**: Since we previously checked same-state, here we don't need to match + allAccounts[account.id].state == settlementData.state. + end note + + SETTLE_DAO -> SETTLE_DAO: Settlement acknowledgement + note right of SETTLE_DAO #lightgray + processedAccounts.push(accountPayload.id) + participant.accounts.push({ + id: accountPayload.id, + state: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + createdDate: transactionTimestamp, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + }) + settlementParticipantCurrencyStateChange.push({ + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + settlementStateId: accountPayload.state, + reason: accountPayload.reason, + externalReference: accountPayload.externalReference, + settlementTransferId: Uuid() -- only for PS_TRANSFERS_RECORDED + }) + if (accountPayload.state == 'PS_TRANSFERS_RECORDED') { + settlementAccounts.pendingSettlementCount-- + settlementAccounts.psTransfersRecordedCount++ + } else if (accountPayload.state == 'PS_TRANSFERS_RESERVED') { + settlementAccounts.psTransfersRecordedCount-- + settlementAccounts.psTransfersReservedCount++ + } else if (accountPayload.state == 'PS_TRANSFERS_COMMITTED') { + settlementAccounts.psTransfersReservedCount-- + settlementAccounts.psTransfersCommittedCount++ + } else if (accountPayload.state == 'SETTLED') { + settlementParticipantCurrencySettledIdList.push(allAccounts[accountPayload.id].key) + settlementAccounts.psTransfersCommittedCount-- + settlementAccounts.settledCount++ + settlementAccounts.settledIdList.push(accountPayload.id) + } + settlementAccounts.changedIdList.push(accountPayload.id) + allAccounts[accountPayload.id].state = accountPayload.state + allAccounts[accountPayload.id].reason = accountPayload.reason + allAccounts[accountPayload.id].externalReference = accountPayload.externalReference + allAccounts[accountPayload.id].createdDate = currentTimestamp + end note + else + SETTLE_DAO -> SETTLE_DAO: All other state transitions are not permitted + note right of SETTLE_DAO #lightgray + participant.accounts.push({ + id: accountPayload.id, + state: allAccounts[accountPayload.id].state, + reason: allAccounts[accountPayload.id].reason, + createdDate: allAccounts[accountPayload.id].createdDate, + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + errorInformation: { + errorCode: , + errorDescription: 'State change not allowed' + } + }) + end note + end + end + end + group Bulk insert settlementParticipantCurrencyStateChange + SETTLE_DAO -> DB: Insert settlementParticipantCurrencyStateChange + activate DB + hnote over DB #lightyellow + settlementParticipantCurrencyStateChange + end hnote + SETTLE_DAO <-- DB: Return **settlementParticipantCurrencyStateChangeIdList** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Merge settlementParticipantCurrencyStateChangeIdList\nto **settlementParticipantCurrencyIdList** in order to\nissue the following update in one knex command + + SETTLE_DAO -> DB: Update pointers to current state change ids + activate DB + hnote over DB #lightyellow + UPDATE **settlementParticipantCurrency** + SET currentStateChangeId = + {settlementParticipantCurrencyStateChangeIdList}, + settlementTransferId = + settlementParticipantCurrencyStateChange.settlementTransferId + -- only for PENDING_SETTLEMENT to PS_TRANSFERS_RECORDED + WHERE settlementParticipantCurrencyId = + {settlementParticipantCurrencyStateChange + .settlementParticipantCurrencyIdList} + end hnote + deactivate DB + end + + opt autoPositionReset == true + alt settlementData.state == 'PENDING_SETTLEMENT' + ||| + ref over SETTLE_DAO, DB: Settlement Transfer Prepare\n\n**Inputs**: settlementId, transactionTimestamp, enums, trx\n + ||| + else settlementData.state == 'PS_TRANSFERS_RECORDED' + ||| + ref over SETTLE_DAO, DB: Settlement Transfer Reserve\n\n**Inputs**: settlementId, transactionTimestamp, enums, trx\n + ||| + else settlementData.state == 'PS_TRANSFERS_RESERVED' + ||| + ref over SETTLE_DAO, DB: Settlement Transfer Commit\n\n**Inputs**: settlementId, transactionTimestamp, enums, trx\n + ||| + end + end + + group Update aggregations, contents & windows + opt settlementParticipantCurrencySettledIdList.length > 0 + SETTLE_DAO -> DB: Change settlementWindowState where applicable + activate DB + deactivate DB + hnote over DB #lightyellow + settlementContentAggregation + transferParticipantStateChange + transferParticipant + settlementWindowContentStateChange + settlementWindowContent + settlementWindowStateChange + settlementWindow + end hnote + end + + SETTLE_DAO -> DB: Retrieve all affected content (incl. when settled) + activate DB + hnote over DB #lightyellow + settlementContentAggregation + settlementWindowContent + settlementWindowContentStateChange + ledgerAccountType + settlementWindow + settlementWindowStateChange + end hnote + SETTLE_DAO <-- DB: Return **affectedWindowsReport** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Use previous result to produce settlementWindowsData (**swd**) array + end + + group Prepare and insert settlementStateChange + note right of SETTLE_DAO #lightgray + let settlementStateChanged = true + end note + alt settlementData.state == 'PENDING_SETTLEMENT'\n&& settlementAccounts.pendingSettlementCount == 0 + note right of SETTLE_DAO #lightgray + settlementData.state = 'PS_TRANSFERS_RECORDED' + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RECORDED' + end note + else settlementData.state == 'PS_TRANSFERS_RECORDED'\n&& settlementAccounts.psTransfersRecordedCount == 0 + note right of SETTLE_DAO #lightgray + settlementData.state = 'PS_TRANSFERS_RESERVED' + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RESERVED' + end note + else settlementData.state == 'PS_TRANSFERS_RESERVED'\n&& settlementAccounts.psTransfersReservedCount == 0 + note right of SETTLE_DAO #lightgray + settlementData.state = 'PS_TRANSFERS_COMMITTED' + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_COMMITTED' + end note + else settlementData.state == 'PS_TRANSFERS_COMMITTED'\n&& settlementAccounts.psTransfersCommittedCount > 0\n&& settlementAccounts.settledCount > 0 + note right of SETTLE_DAO #lightgray + settlementData.state = 'SETTLING' + settlementData.reason = 'Some settlement accounts are SETTLED' + end note + else (settlementData.state == 'PS_TRANSFERS_COMMITTED' || settlementData.state == 'SETTLING')\n&& settlementAccounts.psTransfersCommittedCount == 0 + note right of SETTLE_DAO #lightgray + settlementData.state = 'SETTLED' + settlementData.reason = 'All settlement accounts are SETTLED' + end note + else + note right of SETTLE_DAO #lightgray + settlementStateChanged = false + end note + end + opt settlementStateChanged == true + note right of SETTLE_DAO #lightgray + settlementData.createdDate = currentTimestamp + settlementStateChange.push(settlementData) + end note + + SETTLE_DAO -> DB: Insert settlementStateChange + activate DB + hnote over DB #lightyellow + settlementStateChange + end hnote + SETTLE_DAO <-- DB: Return **settlementStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointer to current state change id + activate DB + hnote over DB #lightyellow + UPDATE **settlement**.currentStateChangeId + end hnote + deactivate DB + end + end + end + SSAPI <-- SETTLE_DAO: Return transaction result + deactivate SETTLE_DAO + + note left of SSAPI #yellow + { + "id": {id}, + "state": settlementData.state, + "createdDate": settlementData.createdDate, + "settlementWindows": [ + { + "id": swd[m].id, + "state": swd[m].state, + "reason": swd[m].reason, + "createdDate": swd[m].createdDate, + "changedDate": swd[m].changedDate, + "content": [ + { + "id": swd[m].content[n].settlementWindowContentId, + "state": swd[m].content[n].settlementWindowStateId, + "ledgerAccountType": swd[m].content[n].ledgerAccountType, + "currencyId": swd[m].content[n].currencyId, + "createdDate": swd[m].content[n].createdDate, + "changedDate": swd[m].content[n].changedDate + } + ] + } + ], + "participants": [ + { + "id": , + "accounts": [ + { + "id": , + "state": ", + "reason": , + "externalReference": , + "createdDate": , + "netSettlementAmount": { + "amount": , + "currency": + } + }, + { + "id": , + "state": , + "reason": , + "createdDate": , + "netSettlementAmount": { + "amount": , + "currency": + }, + "errorInformation": { + "errorCode": , + "errorDescription": + } + } + ] + } + ] + } + end note + + SSAPI --> OPERATOR: Return response + deactivate SSAPI + deactivate OPERATOR +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.svg new file mode 100644 index 000000000..73611b591 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.svg @@ -0,0 +1,1754 @@ + + + + + + + + + + + 6.2.5. Acknowledgement of Settlement Transfer (updateSettlementById) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + + + + + + + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Acknowledgement of Settlement Transfer + + + + + { + + + "participants": [ + + + { + + + "id": 1 + + + "accounts" : [ + + + { "id": 1, "state": "PENDING_SETTLEMENT", "reason": <string> }, + + + { "id": 2, "state": "PS_TRANSFERS_RECORDED", "reason": <string>, "externalReference": <string> }, + + + { "id": 3, "state": "PS_TRANSFERS_RESERVED", "reason": <string> }, + + + { "id": 4, "state": "PS_TRANSFERS_COMMITTED", "reason": <string>, "externalReference": <string> }, + + + { "id": 5, "state": "SETTLED", "reason": <string> } + + + ] + + + }, + + + { + + + "id": 2 + + + "accounts" : [ + + + { "id": 6, "state": "SETTLED", "reason": <string> } + + + ] + + + } + + + ] + + + } + + + + + 1 + + + PUT - /settlement/{id} + + + + + 2 + + + updateSettlementById routine + + + Error code: + + + 2001 + + + + + DB TRANSACTION + + + + + 3 + + + Retrieve settlement information + + + + SELECT s.settlementId, ssc.settlementStateId, + + + ssc.reason, ssc.createdDate, sm.autoPositionReset + + + FROM + + + settlement + + + s + + + JOIN + + + settlementStateChange + + + ssc + + + ON ssc.settlementStateChangeId = s.currentStateChangeId + + + JOIN + + + settlementModel + + + sm + + + ON sm.settlementModelId = s.settlementModelId + + + WHERE s.settlementId = {id} + + + FOR UPDATE + + + + + 4 + + + Return + + + settlementData + + + + + 5 + + + Retrive settlement accounts information + + + + SELECT pc.participantId, spc.participantCurrencyId, + + + spcsc.settlementStateId, spcsc.reason, + + + spcsc.createdDate, spc.netAmount, pc.currencyId, + + + spc.settlementParticipantCurrencyId AS + + + key + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = + + + spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementId = {id} + + + FOR UPDATE + + + + + 6 + + + Return + + + settlementAccountsList + + + + + All objects below are for the purpose of the syncronous request. + + + If at some point, Node process memory limit is reached, we may decide to: + + + A. Limit the amount of transfers per window and windows per settlement or + + + B. Move to asyncronous processing where we don't need these objects + + + + + Available raw datasets from DB: + + + settlementData + + + contains information about settlement and its current state/reason + + + settlementAccountsList + + + holds information about all accounts and their current state/reason + + + Local variables and objects: + + + settlementAccounts + + + : { // (derived from + + + settlementAccountsList + + + ) + + + pendingSettlementCount: <integer>, // count of accounts in PENDING_SETTLEMENT state + + + psTransfersRecordedCount: <integer>, // count of accounts in PS_TRANSFERS_RECORDED state + + + psTransfersReservedCount: <integer>, // count of accounts in PS_TRANSFERS_RESERVED state + + + psTransfersCommittedCount: <integer>, // count of accounts in PS_TRANSFERS_COMMITTED state + + + settledCount: <integer>, // count of accounts in SETTLED state + + + abortedCount: <integer> // count of accounts in ABORTED state + + + unknownCount: <integer>, + + + settledIdList: <array>, + + + changedIdList: <array> + + + } + + + settlementAccountsInit + + + copy of previous object to be preserved for comparission at the end + + + allAccounts + + + : { // same as previous but accessed by account id (derived from + + + settlementAccountsList + + + ) + + + participantCurrencyId_key: { // number used to access the object in map-like style + + + id: participantCurrencyId, + + + state: settlementStateId, + + + reason: reason, + + + createdDate: createdDate, + + + netSettlementAmount: { + + + amount: netAmount, + + + currency: currencyId + + + }, + + + participantId: participantId, // could be used to reconstruct allParticipants + + + key: + + + key + + + // will be used to insert new state for settlementParticipantCurrency + + + } + + + } + + + let + + + transactionTimestamp + + + = now() + + + + + 7 + + + Declare and initialize variables + + + + + let settlementAccounts = { + + + pendingSettlementCount: 0, + + + psTransfersRecordedCount: 0, + + + psTransfersReservedCount: 0, + + + psTransfersCommittedCount: 0, + + + settledCount: 0, + + + abortedCount: 0, + + + unknownCount: 0 + + + } + + + let allAccounts = {} // declare map + + + let pid // participantId + + + let aid // accountId (participantCurrencyId) + + + let state + + + + + loop + + + [settlementAccountsList as account] + + + + + 8 + + + Populate + + + allAccounts + + + + + pid = account.participantId + + + aid = account.participantCurrencyId + + + state = account.settlementStateId + + + allAccounts[aid] = { + + + id: aid, + + + state, + + + reason: account.reason, + + + createDate: account.createdDate, + + + netSettlementAmount: { + + + amount: account.netAmount, + + + currency: account.currencyId + + + }, + + + participantId: pid, + + + key: account.key + + + } + + + + + 9 + + + Populate + + + settlementAccounts + + + + + alt + + + [state == 'PENDING_SETTLEMENT'] + + + + + settlementAccounts.pendingSettlementCount++ + + + + [state == 'PS_TRANSFERS_RECORDED'] + + + + + settlementAccounts.psTransfersRecordedCount++ + + + + [state == 'PS_TRANSFERS_RESERVED'] + + + + + settlementAccounts.psTransfersReservedCount++ + + + + [state == 'PS_TRANSFERS_COMMITTED'] + + + + + settlementAccounts.psTransfersCommittedCount++ + + + + [state == 'SETTLED'] + + + + + settlementAccounts.settledCount++ + + + + [state == 'ABORTED'] + + + + + settlementAccounts.abortedCount++ + + + + [default] + + + + + settlementAccounts.unknownCount++ + + + + + 10 + + + Make a copy of settlementAccounts into + + + settlementAccountsInit + + + + + settlementAccountsInit = Object.assign({}, settlementAccounts) + + + + + Available objects after the setup: + + + settlementAccounts + + + is used for tracing settlement state and state transition allowance + + + allAccounts + + + is helper object, same as previous, providing direct access to account by id + + + Now we are ready to process the + + + payload + + + : + + + participants + + + = [] // part of the response object that lists the affected participants and respective accounts + + + settlementParticipantCurrencyStateChange + + + = [] // array to collect inserts to the table + + + settlementParticipantCurrencySettledList + + + = [] // array to collect settled accounts + + + processedAccounts + + + = [] // array to log processed accounts and restrict subsequent processing + + + + + loop + + + [let participant IN payload.participants] + + + + + 11 + + + Loop payload for each + + + participantPayload + + + + + let participantPayload = payload.participants[participant] + + + participants.push({id: participantPayload.id, accounts: []}) + + + let pi = participants.length - 1 + + + participant = participants[pi] + + + + + loop + + + [let account IN participantPayload.accounts] + + + + + 12 + + + Loop payload for each + + + accountPayload + + + + + let accountPayload = participantPayload.accounts[account] + + + + + alt + + + [allAccounts[accountPayload.id] == undefined] + + + + + 13 + + + If the account doesn't match the settlement + + + + + participant.accounts.push({ + + + id: accountPayload.id, + + + errorInformation: { + + + errorCode: 3000, + + + errorDescription: 'Account not found' + + + } + + + }) + + + + [participantPayload.id != allAccounts[accountPayload.id].participantId] + + + + + 14 + + + If the account doesn't match the participant + + + + + participant.accounts.push({ + + + id: accountPayload.id, + + + errorInformation: { + + + errorCode: 3000, + + + errorDescription: 'Participant and account mismatch' + + + } + + + }) + + + + [processedAccounts.indexOf(accountPayload.id) > -1] + + + + + 15 + + + If the account has been previosly processed (duplicated in the payload) + + + + + participant.accounts.push({ + + + id: accountPayload.id, + + + state: allAccounts[accountPayload.id].state, + + + reason: allAccounts[accountPayload.id].reason, + + + createdDate: allAccounts[accountPayload.id].createdDate, + + + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + + + errorInformation: { + + + errorCode: 3000, + + + errorDescription: 'Account already processed once' + + + } + + + }) + + + + [allAccounts[account.id].state == accountPayload.state // allowed] + + + + + 16 + + + Same-state reason amendment is always allowed + + + + + processedAccounts.push(accountPayload.id) + + + participant.accounts.push({ + + + id: accountPayload.id, + + + state: accountPayload.state, + + + reason: accountPayload.reason, + + + externalReference: accountPayload.externalReference, + + + createdDate: transactionTimestamp, + + + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + + + }) + + + settlementParticipantCurrencyStateChange.push({ + + + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + + + settlementStateId: accountPayload.state, + + + reason: accountPayload.reason, + + + externalReference: accountPayload.externalReference + + + }) + + + allAccounts[accountPayload.id].reason = accountPayload.reason + + + allAccounts[accountPayload.id].createdDate = currentTimestamp + + + + [settlementData.state == 'PENDING_SETTLEMENT' && accountPayload.state == 'PS_TRANSFERS_RECORDED'] + + + + [settlementData.state == 'PS_TRANSFERS_RECORDED' && accountPayload.state == 'PS_TRANSFERS_RESERVED'] + + + + [settlementData.state == 'PS_TRANSFERS_RESERVED' && accountPayload.state == 'PS_TRANSFERS_COMMITTED'] + + + + [settlementData.state == 'PS_TRANSFERS_COMMITTED' || settlementData.state == 'SETTLING' && accountPayload.state == 'SETTLED'] + + + + + Note + + + : Since we previously checked same-state, here we don't need to match + + + allAccounts[account.id].state == settlementData.state. + + + + + 17 + + + Settlement acknowledgement + + + + + processedAccounts.push(accountPayload.id) + + + participant.accounts.push({ + + + id: accountPayload.id, + + + state: accountPayload.state, + + + reason: accountPayload.reason, + + + externalReference: accountPayload.externalReference, + + + createdDate: transactionTimestamp, + + + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + + + }) + + + settlementParticipantCurrencyStateChange.push({ + + + settlementParticipantCurrencyId: allAccounts[accountPayload.id].key, + + + settlementStateId: accountPayload.state, + + + reason: accountPayload.reason, + + + externalReference: accountPayload.externalReference, + + + settlementTransferId: Uuid() -- only for PS_TRANSFERS_RECORDED + + + }) + + + if (accountPayload.state == 'PS_TRANSFERS_RECORDED') { + + + settlementAccounts.pendingSettlementCount-- + + + settlementAccounts.psTransfersRecordedCount++ + + + } else if (accountPayload.state == 'PS_TRANSFERS_RESERVED') { + + + settlementAccounts.psTransfersRecordedCount-- + + + settlementAccounts.psTransfersReservedCount++ + + + } else if (accountPayload.state == 'PS_TRANSFERS_COMMITTED') { + + + settlementAccounts.psTransfersReservedCount-- + + + settlementAccounts.psTransfersCommittedCount++ + + + } else if (accountPayload.state == 'SETTLED') { + + + settlementParticipantCurrencySettledIdList.push(allAccounts[accountPayload.id].key) + + + settlementAccounts.psTransfersCommittedCount-- + + + settlementAccounts.settledCount++ + + + settlementAccounts.settledIdList.push(accountPayload.id) + + + } + + + settlementAccounts.changedIdList.push(accountPayload.id) + + + allAccounts[accountPayload.id].state = accountPayload.state + + + allAccounts[accountPayload.id].reason = accountPayload.reason + + + allAccounts[accountPayload.id].externalReference = accountPayload.externalReference + + + allAccounts[accountPayload.id].createdDate = currentTimestamp + + + + + + 18 + + + All other state transitions are not permitted + + + + + participant.accounts.push({ + + + id: accountPayload.id, + + + state: allAccounts[accountPayload.id].state, + + + reason: allAccounts[accountPayload.id].reason, + + + createdDate: allAccounts[accountPayload.id].createdDate, + + + netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount + + + errorInformation: { + + + errorCode: <integer>, + + + errorDescription: 'State change not allowed' + + + } + + + }) + + + + + Bulk insert settlementParticipantCurrencyStateChange + + + + + 19 + + + Insert settlementParticipantCurrencyStateChange + + + + settlementParticipantCurrencyStateChange + + + + + 20 + + + Return + + + settlementParticipantCurrencyStateChangeIdList + + + + + 21 + + + Merge settlementParticipantCurrencyStateChangeIdList + + + to + + + settlementParticipantCurrencyIdList + + + in order to + + + issue the following update in one knex command + + + + + 22 + + + Update pointers to current state change ids + + + + UPDATE + + + settlementParticipantCurrency + + + SET currentStateChangeId = + + + {settlementParticipantCurrencyStateChangeIdList}, + + + settlementTransferId = + + + settlementParticipantCurrencyStateChange.settlementTransferId + + + -- only for PENDING_SETTLEMENT to PS_TRANSFERS_RECORDED + + + WHERE settlementParticipantCurrencyId = + + + {settlementParticipantCurrencyStateChange + + + .settlementParticipantCurrencyIdList} + + + + + opt + + + [autoPositionReset == true] + + + + + alt + + + [settlementData.state == 'PENDING_SETTLEMENT'] + + + + + ref + + + Settlement Transfer Prepare + + + Inputs + + + : settlementId, transactionTimestamp, enums, trx + + + + [settlementData.state == 'PS_TRANSFERS_RECORDED'] + + + + + ref + + + Settlement Transfer Reserve + + + Inputs + + + : settlementId, transactionTimestamp, enums, trx + + + + [settlementData.state == 'PS_TRANSFERS_RESERVED'] + + + + + ref + + + Settlement Transfer Commit + + + Inputs + + + : settlementId, transactionTimestamp, enums, trx + + + + + Update aggregations, contents & windows + + + + + opt + + + [settlementParticipantCurrencySettledIdList.length > 0] + + + + + 23 + + + Change settlementWindowState where applicable + + + + settlementContentAggregation + + + transferParticipantStateChange + + + transferParticipant + + + settlementWindowContentStateChange + + + settlementWindowContent + + + settlementWindowStateChange + + + settlementWindow + + + + + 24 + + + Retrieve all affected content (incl. when settled) + + + + settlementContentAggregation + + + settlementWindowContent + + + settlementWindowContentStateChange + + + ledgerAccountType + + + settlementWindow + + + settlementWindowStateChange + + + + + 25 + + + Return + + + affectedWindowsReport + + + + + 26 + + + Use previous result to produce settlementWindowsData ( + + + swd + + + ) array + + + + + Prepare and insert settlementStateChange + + + + + let settlementStateChanged = true + + + + + alt + + + [settlementData.state == 'PENDING_SETTLEMENT' + + + && settlementAccounts.pendingSettlementCount == 0] + + + + + settlementData.state = 'PS_TRANSFERS_RECORDED' + + + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RECORDED' + + + + [settlementData.state == 'PS_TRANSFERS_RECORDED' + + + && settlementAccounts.psTransfersRecordedCount == 0] + + + + + settlementData.state = 'PS_TRANSFERS_RESERVED' + + + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RESERVED' + + + + [settlementData.state == 'PS_TRANSFERS_RESERVED' + + + && settlementAccounts.psTransfersReservedCount == 0] + + + + + settlementData.state = 'PS_TRANSFERS_COMMITTED' + + + settlementData.reason = 'All settlement accounts are PS_TRANSFERS_COMMITTED' + + + + [settlementData.state == 'PS_TRANSFERS_COMMITTED' + + + && settlementAccounts.psTransfersCommittedCount > 0 + + + && settlementAccounts.settledCount > 0] + + + + + settlementData.state = 'SETTLING' + + + settlementData.reason = 'Some settlement accounts are SETTLED' + + + + [(settlementData.state == 'PS_TRANSFERS_COMMITTED' || settlementData.state == 'SETTLING') + + + && settlementAccounts.psTransfersCommittedCount == 0] + + + + + settlementData.state = 'SETTLED' + + + settlementData.reason = 'All settlement accounts are SETTLED' + + + + + + settlementStateChanged = false + + + + + opt + + + [settlementStateChanged == true] + + + + + settlementData.createdDate = currentTimestamp + + + settlementStateChange.push(settlementData) + + + + + 27 + + + Insert settlementStateChange + + + + settlementStateChange + + + + + 28 + + + Return + + + settlementStateChangeId + + + + + 29 + + + Update pointer to current state change id + + + + UPDATE + + + settlement + + + .currentStateChangeId + + + + + 30 + + + Return transaction result + + + + + { + + + "id": {id}, + + + "state": settlementData.state, + + + "createdDate": settlementData.createdDate, + + + "settlementWindows": [ + + + { + + + "id": swd[m].id, + + + "state": swd[m].state, + + + "reason": swd[m].reason, + + + "createdDate": swd[m].createdDate, + + + "changedDate": swd[m].changedDate, + + + "content": [ + + + { + + + "id": swd[m].content[n].settlementWindowContentId, + + + "state": swd[m].content[n].settlementWindowStateId, + + + "ledgerAccountType": swd[m].content[n].ledgerAccountType, + + + "currencyId": swd[m].content[n].currencyId, + + + "createdDate": swd[m].content[n].createdDate, + + + "changedDate": swd[m].content[n].changedDate + + + } + + + ] + + + } + + + ], + + + "participants": [ + + + { + + + "id": <integer>, + + + "accounts": [ + + + { + + + "id": <integer>, + + + "state": "<string>, + + + "reason": <string>, + + + "externalReference": <string>, + + + "createdDate": <date>, + + + "netSettlementAmount": { + + + "amount": <decimal>, + + + "currency": <enum> + + + } + + + }, + + + { + + + "id": <integer>, + + + "state": <string>, + + + "reason": <string>, + + + "createdDate": <date>, + + + "netSettlementAmount": { + + + "amount": <decimal>, + + + "currency": <enum> + + + }, + + + "errorInformation": { + + + "errorCode": <integer>, + + + "errorDescription": <string> + + + } + + + } + + + ] + + + } + + + ] + + + } + + + + + 31 + + + Return response + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.plantuml new file mode 100644 index 000000000..357a22d2e --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.plantuml @@ -0,0 +1,282 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.2.6. Settlement Abort (abortSettlementById) +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Settlement Abort + activate OPERATOR + note right of OPERATOR #yellow + { + "state": "ABORTED", + "reason": {abortReasonString} + } + end note + + OPERATOR -> SSAPI: PUT - /settlement/{id} + activate SSAPI + SSAPI -> SETTLE_DAO: abortSettlementById facade method\nError code: 2001 + activate SETTLE_DAO + + SETTLE_DAO -> DB: Retrieve settlement information + activate DB + hnote over DB #lightyellow + SELECT s.settlementId, ssc.settlementStateId, + ssc.reason, ssc.createdDate + FROM **settlement** s + JOIN **settlementStateChange** ssc + ON ssc.settlementStateChangeId = s.currentStateChangeId + WHERE s.settlementId = {id} + end hnote + SETTLE_DAO <-- DB: Return **settlementData** + deactivate DB + + alt settlementData.settlementStateId == 'PS_TRANSFERS_COMMITTED'||\nsettlementData.settlementStateId == 'SETTLING'||\nsettlementData.settlementStateId == 'SETTLED' + break + note right of SETTLE_DAO #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "State change is not allowed" + } + } + end note + end + else settlementData.settlementStateId == 'ABORTED' + break + SETTLE_DAO -> DB: Append additional info at settlement level + activate DB + deactivate DB + hnote over DB #lightyellow + INSERT INTO **settlementStateChange** + (settlementId, settlementStateId, reason) + VALUES ({id}, 'ABORTED', {abortReasonString}) + end hnote + + note right of SETTLE_DAO #yellow + { + "id": {id}, + "state": 'ABORTED', + "reason": {abortReasonString} + } + end note + end + else settlementData.settlementStateId == 'PS_TRANSFERS_RESERVED' + SETTLE_DAO -> DB: Find account in PS_TRANSFERS_COMMITTED + activate DB + hnote over DB #lightyellow + SELECT spc.participantCurrencyId + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + WHERE spc.settlementId = {id} + AND spcsc.settlementStateId = 'PS_TRANSFERS_COMMITTED' + LIMIT 1 + end hnote + SETTLE_DAO <-- DB: Return **transferCommittedAccount** + deactivate DB + break transferCommittedAccount != undefined + note right of SETTLE_DAO #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "At least one settlement transfer is committed" + } + } + end note + end + end + + group DB TRANSACTION + SETTLE_DAO -> DB: Retrive settlement accounts information + activate DB + hnote over DB #lightyellow + SELECT pc.participantId, spc.participantCurrencyId, + spcsc.settlementStateId, spcsc.reason, + spcsc.createdDate, spc.netAmount, pc.currencyId, + spc.settlementParticipantCurrencyId AS key + FROM **settlementParticipantCurrency** spc + JOIN **settlementParticipantCurrencyStateChange** spcsc + ON spcsc.settlementParticipantCurrencyStateChangeId = + spc.currentStateChangeId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = spc.participantCurrencyId + WHERE spc.settlementId = {id} + FOR UPDATE + end hnote + SETTLE_DAO <-- DB: Return **settlementAccountsList** + deactivate DB + + SETTLE_DAO -> DB: Retrive settlement windows information + activate DB + hnote over DB #lightyellow + SELECT ssw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, swsc.createdDate + FROM **settlementSettlementWindow** ssw + JOIN **settlementWindow** sw + ON sw.settlementWindow = ssw.settlementWindowId + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + WHERE ssw.settlementId = {id} + FOR UPDATE + end hnote + SETTLE_DAO <-- DB: Return **windowsList** + deactivate DB + + group Bulk insert settlementParticipantCurrencyStateChange + SETTLE_DAO -> DB: Insert settlementParticipantCurrencyStateChange + activate DB + hnote over DB #lightyellow + settlementParticipantCurrencyStateChange + end hnote + SETTLE_DAO <-- DB: Return **spcscIdList** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Merge spcscIdList into settlementAccountsList + + SETTLE_DAO -> DB: Update all pointers to current state change ids + activate DB + hnote over DB #lightyellow + UPDATE **settlementParticipantCurrency**.currentStateChangeIds + end hnote + deactivate DB + end + + ref over SETTLE_DAO, DB: Settlement Transfer Abort\n\n**Inputs**: settlementId, transactionTimestamp, enums, trx\n + + group Bulk insert and reset contents & aggregations + SETTLE_DAO -> DB: Bulk insert aborted state + activate DB + hnote over DB #lightyellow + settlementWindowContentStateChange + end hnote + SETTLE_DAO <-- DB: Return **swcscIdList** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Merge swcscIdList + + SETTLE_DAO -> DB: Update current state references and reset settlementId + activate DB + hnote over DB #lightyellow + **settlementWindowContent** + .currentStateChangeIds + .settlementId + **settlementContentAggregation** + .settlementId + end hnote + deactivate DB + end + + group Bulk insert settlementWindowStateChange + SETTLE_DAO -> DB: Insert settlementWindowStateChange + activate DB + hnote over DB #lightyellow + settlementWindowStateChange + end hnote + SETTLE_DAO <-- DB: Return **swscIdList** + deactivate DB + + SETTLE_DAO -> SETTLE_DAO: Merge swscIdList into windowList + + SETTLE_DAO -> DB: Update all pointers to current state change ids + activate DB + hnote over DB #lightyellow + UPDATE **settlementWindow**.currentStateChangeIds + end hnote + deactivate DB + end + + group Insert settlementStateChange + SETTLE_DAO -> DB: Insert settlementStateChange + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementStateChange** + (settlementId, settlementStateId, reason) + VALUES ({id}, 'ABORTED', {abortReasonString}) + end hnote + SETTLE_DAO <-- DB: Return **settlementStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointer to current state change id + activate DB + hnote over DB #lightyellow + UPDATE **settlement**.currentStateChangeId + end hnote + deactivate DB + end + end + SSAPI <-- SETTLE_DAO: Return facade method result + deactivate SETTLE_DAO + + alt success + note left of SSAPI #yellow + { + "id": {id}, + "state": 'ABORTED', + "reason": {abortReasonString} + } + end note + + SSAPI --> OPERATOR: Respond HTTP - 200 (OK) + else + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + errorInformation: { + "errorCode": , + "errorDescription": "Client/Server error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP 4xx or 5xx\n(Client/Server error) + deactivate SSAPI + end +end +deactivate OPERATOR +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.svg new file mode 100644 index 000000000..38256e7a0 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.svg @@ -0,0 +1,801 @@ + + + + + + + + + + + 6.2.6. Settlement Abort (abortSettlementById) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Settlement Abort + + + + + { + + + "state": "ABORTED", + + + "reason": {abortReasonString} + + + } + + + + + 1 + + + PUT - /settlement/{id} + + + + + 2 + + + abortSettlementById facade method + + + Error code: + + + 2001 + + + + + 3 + + + Retrieve settlement information + + + + SELECT s.settlementId, ssc.settlementStateId, + + + ssc.reason, ssc.createdDate + + + FROM + + + settlement + + + s + + + JOIN + + + settlementStateChange + + + ssc + + + ON ssc.settlementStateChangeId = s.currentStateChangeId + + + WHERE s.settlementId = {id} + + + + + 4 + + + Return + + + settlementData + + + + + alt + + + [settlementData.settlementStateId == 'PS_TRANSFERS_COMMITTED'|| + + + settlementData.settlementStateId == 'SETTLING'|| + + + settlementData.settlementStateId == 'SETTLED'] + + + + + break + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "State change is not allowed" + + + } + + + } + + + + [settlementData.settlementStateId == 'ABORTED'] + + + + + break + + + + + 5 + + + Append additional info at settlement level + + + + INSERT INTO + + + settlementStateChange + + + (settlementId, settlementStateId, reason) + + + VALUES ({id}, 'ABORTED', {abortReasonString}) + + + + + { + + + "id": {id}, + + + "state": 'ABORTED', + + + "reason": {abortReasonString} + + + } + + + + [settlementData.settlementStateId == 'PS_TRANSFERS_RESERVED'] + + + + + 6 + + + Find account in PS_TRANSFERS_COMMITTED + + + + SELECT spc.participantCurrencyId + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = spc.currentStateChangeId + + + WHERE spc.settlementId = {id} + + + AND spcsc.settlementStateId = 'PS_TRANSFERS_COMMITTED' + + + LIMIT 1 + + + + + 7 + + + Return + + + transferCommittedAccount + + + + + break + + + [transferCommittedAccount != undefined] + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "At least one settlement transfer is committed" + + + } + + + } + + + + + DB TRANSACTION + + + + + 8 + + + Retrive settlement accounts information + + + + SELECT pc.participantId, spc.participantCurrencyId, + + + spcsc.settlementStateId, spcsc.reason, + + + spcsc.createdDate, spc.netAmount, pc.currencyId, + + + spc.settlementParticipantCurrencyId AS + + + key + + + FROM + + + settlementParticipantCurrency + + + spc + + + JOIN + + + settlementParticipantCurrencyStateChange + + + spcsc + + + ON spcsc.settlementParticipantCurrencyStateChangeId = + + + spc.currentStateChangeId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = spc.participantCurrencyId + + + WHERE spc.settlementId = {id} + + + FOR UPDATE + + + + + 9 + + + Return + + + settlementAccountsList + + + + + 10 + + + Retrive settlement windows information + + + + SELECT ssw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, swsc.createdDate + + + FROM + + + settlementSettlementWindow + + + ssw + + + JOIN + + + settlementWindow + + + sw + + + ON sw.settlementWindow = ssw.settlementWindowId + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + WHERE ssw.settlementId = {id} + + + FOR UPDATE + + + + + 11 + + + Return + + + windowsList + + + + + Bulk insert settlementParticipantCurrencyStateChange + + + + + 12 + + + Insert settlementParticipantCurrencyStateChange + + + + settlementParticipantCurrencyStateChange + + + + + 13 + + + Return + + + spcscIdList + + + + + 14 + + + Merge spcscIdList into settlementAccountsList + + + + + 15 + + + Update all pointers to current state change ids + + + + UPDATE + + + settlementParticipantCurrency + + + .currentStateChangeIds + + + + + ref + + + Settlement Transfer Abort + + + Inputs + + + : settlementId, transactionTimestamp, enums, trx + + + + + Bulk insert and reset contents & aggregations + + + + + 16 + + + Bulk insert aborted state + + + + settlementWindowContentStateChange + + + + + 17 + + + Return + + + swcscIdList + + + + + 18 + + + Merge swcscIdList + + + + + 19 + + + Update current state references and reset settlementId + + + + settlementWindowContent + + + .currentStateChangeIds + + + .settlementId + + + settlementContentAggregation + + + .settlementId + + + + + Bulk insert settlementWindowStateChange + + + + + 20 + + + Insert settlementWindowStateChange + + + + settlementWindowStateChange + + + + + 21 + + + Return + + + swscIdList + + + + + 22 + + + Merge swscIdList into windowList + + + + + 23 + + + Update all pointers to current state change ids + + + + UPDATE + + + settlementWindow + + + .currentStateChangeIds + + + + + Insert settlementStateChange + + + + + 24 + + + Insert settlementStateChange + + + + INSERT INTO + + + settlementStateChange + + + (settlementId, settlementStateId, reason) + + + VALUES ({id}, 'ABORTED', {abortReasonString}) + + + + + 25 + + + Return + + + settlementStateChangeId + + + + + 26 + + + Update pointer to current state change id + + + + UPDATE + + + settlement + + + .currentStateChangeId + + + + + 27 + + + Return facade method result + + + + + alt + + + [success] + + + + + { + + + "id": {id}, + + + "state": 'ABORTED', + + + "reason": {abortReasonString} + + + } + + + + + 28 + + + Respond HTTP - 200 (OK) + + + + + + Log ERROR event + + + + + { + + + errorInformation: { + + + "errorCode": <integer>, + + + "errorDescription": "Client/Server error description" + + + } + + + } + + + + + 29 + + + Respond HTTP 4xx or 5xx + + + (Client/Server error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.plantuml new file mode 100644 index 000000000..4bf530146 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.plantuml @@ -0,0 +1,123 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.1.1. Request Settlement Window By Id (getSettlementWindowById) + +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Request Settlement Window + activate OPERATOR + OPERATOR -> SSAPI: GET - /settlementWindows/{id} + activate SSAPI + SSAPI -> SETTLE_DAO: Request settlementWindow by id\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Select from DB + activate DB + hnote over DB #lightyellow + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementWindow** AS sw + JOIN **settlementWindowStateChange** AS swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + WHERE sw.settlementWindowId = {id} + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **data** + deactivate SETTLE_DAO + + alt settlementWindow found + SSAPI -> SETTLE_DAO: Request settlementWindowContent(s)\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Select from DB + activate DB + hnote over DB #lightyellow + settlementWindowContent + settlementWindowContentStateChange + ledgerAccountType + end hnote + deactivate DB + SSAPI <-- SETTLE_DAO: Return **contentList** + deactivate SETTLE_DAO + note left of SSAPI #yellow + { + "id": data.settlementWindowId, + "state": data.settlementWindowStateId, + "reason": data.reason, + "createdDate": data.createdDate, + "changedDate": data.changedDate, + "content": [ + { + "id": contentList.settlementWindowContentId, + "state": contentList.settlementWindowStateId, + "ledgerAccountType": contentList.ledgerAccountType, + "currencyId": contentList.currencyId, + "createdDate": contentList.createdDate, + "changedDate": contentList.changedDate, + "settlementId": contentList.settlementId + } + ] + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 200 (OK) + else + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + "errorInformation": { + "errorCode": , + "errorDescription": + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + deactivate OPERATOR + end +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.svg new file mode 100644 index 000000000..7ce3f9839 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + 6.1.1. Request Settlement Window By Id (getSettlementWindowById) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Request Settlement Window + + + + + 1 + + + GET - /settlementWindows/{id} + + + + + 2 + + + Request settlementWindow by id + + + Error code: + + + 2001 + + + + + 3 + + + Select from DB + + + + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementWindow + + + AS sw + + + JOIN + + + settlementWindowStateChange + + + AS swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + WHERE sw.settlementWindowId = {id} + + + + + 4 + + + Return + + + data + + + + + alt + + + [settlementWindow found] + + + + + 5 + + + Request settlementWindowContent(s) + + + Error code: + + + 2001 + + + + + 6 + + + Select from DB + + + + settlementWindowContent + + + settlementWindowContentStateChange + + + ledgerAccountType + + + + + 7 + + + Return + + + contentList + + + + + { + + + "id": data.settlementWindowId, + + + "state": data.settlementWindowStateId, + + + "reason": data.reason, + + + "createdDate": data.createdDate, + + + "changedDate": data.changedDate, + + + "content": [ + + + { + + + "id": contentList.settlementWindowContentId, + + + "state": contentList.settlementWindowStateId, + + + "ledgerAccountType": contentList.ledgerAccountType, + + + "currencyId": contentList.currencyId, + + + "createdDate": contentList.createdDate, + + + "changedDate": contentList.changedDate, + + + "settlementId": contentList.settlementId + + + } + + + ] + + + } + + + + + 8 + + + Respond HTTP - 200 (OK) + + + + + + Log ERROR event + + + + + { + + + "errorInformation": { + + + "errorCode": <integer>, + + + "errorDescription": <string> + + + } + + + } + + + + + 9 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.plantuml new file mode 100644 index 000000000..79c75a058 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.plantuml @@ -0,0 +1,374 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.1.2. Close Settlement Window (closeSettlementWindow) + +autonumber + +' declare actors +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +collections "topic-\nsettlement-window" as TOPIC_SET_WIN +control "Settlement Window\nHandler" as SET_WIN_HANDLER +collections "topic-event" as TOPIC_EVENT +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant TOPIC_SET_WIN + participant SET_WIN_HANDLER + participant TOPIC_EVENT + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow +group Close Settlement Window + activate OPERATOR + activate SET_WIN_HANDLER + note right of OPERATOR #yellow + { + "state": "CLOSED", + "reason": + } + end note + + OPERATOR -> SSAPI: POST - /settlementWindows/{id} + activate SSAPI + SSAPI -> SSAPI: Validate payload, existing window,\nstate, assigned transfers, etc. + break + note right of SSAPI #yellow + { + "errorInformation": { + "errorCode": , + "errorDescription": "FSPIOP Error Description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 400 (Bad Request) + end + SSAPI -> SETTLE_DAO: Get requested settlementWindow and state\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Get settlementWindow state + activate DB + hnote over DB #lightyellow + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementWindow** AS sw + JOIN **settlementWindowStateChange** AS swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + WHERE sw.settlementWindowId = {id} + end hnote + deactivate DB + SETTLE_DAO --> SSAPI: Return result + deactivate SETTLE_DAO + + alt settlementWindow found && settlementWindowStateId == 'OPEN' + group Process settlement window + SSAPI -> SETTLE_DAO: Terminate current window and open a new one\nError code: 2001 + activate SETTLE_DAO + group DB TRANSACTION: Terminate window usage and initate next + note right of SETTLE_DAO #lightgray + let **transactionTimestamp** = now() + end note + + SETTLE_DAO -> DB: Terminate requested window + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementWindowStateChange** + (settlementWindowId, settlementWindowStateId, reason, createdDate) + VALUES ({id}, 'PROCESSING', {payload.reason}, {transactionTimestamp}) + end hnote + SETTLE_DAO <-- DB: Return **settlementWindowStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointer to current state change id + activate DB + hnote over DB #lightyellow + UPDATE **settlementWindow** + SET currentStateChangeId = {settlementWindowStateChangeId} + WHERE settlementWindowId = {id} + end hnote + deactivate DB + + SETTLE_DAO -> DB: Create new settlementWindow + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementWindow** (reason, createdDate) + VALUES ({payload.reason}, {transactionTimestamp}) + end note + SETTLE_DAO <-- DB: Return **settlementWindowId** + deactivate DB + + SETTLE_DAO -> DB: Insert intial state for the created window + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementWindowStateChange** + (settlementWindowId, settlementWindowStateId, reason, createdDate) + VALUES ({settlementWindowId}, 'OPEN', {payload.reason}, {transactionTimestamp}) + end note + SETTLE_DAO <-- DB: Return **newSettlementWindowStateChangeId** + deactivate DB + + SETTLE_DAO -> DB: Update pointer to current state change id + activate DB + hnote over DB #lightyellow + UPDATE **settlementWindow** + SET currentStateChangeId = {newSettlementWindowStateChangeId} + WHERE settlementWindowId = {settlementWindowId} + end hnote + deactivate DB + end + SSAPI <-- SETTLE_DAO: Return success + deactivate SETTLE_DAO + end + + note right of SSAPI #yellow + Message: + { + id: + from: switch, + to: switch, + type: application/json + content: { + payload: { + settlementWindowId: {id} + } + }, + metadata: { + event: { + id: , + responseTo: null, + type: setwin, + action: close, + createdAt: , + state: { + status: "success", + code: 0 + } + } + } + } + end note + SSAPI -> TOPIC_SET_WIN: Produce Settlement Window\nevent message\nError codes: 2003 + activate TOPIC_SET_WIN + deactivate TOPIC_SET_WIN + + note left of SSAPI #yellow + { + "id": settlementWindowId, + "state": 'OPEN', + "reason": payload.reason, + "createdDate": transactionTimestamp, + "changedDate": transactionTimestamp + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 201 (Created) + deactivate SSAPI + deactivate OPERATOR + + group Close settlement window (Deferred Handler) + TOPIC_SET_WIN <- SET_WIN_HANDLER: Consume Settlement Window\nevent message + activate TOPIC_SET_WIN + deactivate TOPIC_SET_WIN + + group Persist Event Information + ||| + SET_WIN_HANDLER -> TOPIC_EVENT: Publish event information + ref over SET_WIN_HANDLER, TOPIC_EVENT: Event Handler Consume\n + ||| + end + note right of SET_WIN_HANDLER #lightgray + let **id** = message.content.payload.settlementWindowId + let **windowContentReady** = false + let **iter** = 0 + end note + + loop iter < Config.WIN_AGGREGATION_RETRY_COUNT && !windowContentReady + note right of SET_WIN_HANDLER #lightgray + iter++ + end note + + SET_WIN_HANDLER -> SETTLE_DAO: Check if all transferParticipant records\nfor the requested window have been set\n(currentStateChangeId is NOT NULL).\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Use EXISTS query to find NULL currentStateChangeId records + activate DB + hnote over DB #lightyellow + transferFulfilment + transferParticipant + end hnote + deactivate DB + SET_WIN_HANDLER <-- SETTLE_DAO: Return result (success / failure) + deactivate SETTLE_DAO + + opt transferParticipant records have been processed by central-ledger SettlementModelHandler + SET_WIN_HANDLER -> SETTLE_DAO: Generate window content and aggregations\nError code: 2001 + activate SETTLE_DAO + group DB TRANSACTION: Generate window content and aggregations + note right of SETTLE_DAO #lightgray + let **transactionTimestamp** = now() + end note + + SETTLE_DAO -> DB: Change all applicable entries to CLOSED state + activate DB + hnote over DB #lightyellow + transferParticipantStateChange + transferParticipant + + end hnote + deactivate DB + + SETTLE_DAO -> DB: Determine window content and insert + activate DB + SETTLE_DAO -> DB: Get all Settlement Models + SETTLE_DAO -> SETTLE_DAO: create Settlement Models <-> currencyId map + SETTLE_DAO -> SETTLE_DAO: create list of currencies with Settlement Models + SETTLE_DAO -> DB: Get Settlement Window Content list + hnote over DB #lightyellow + SELECT DISTINCT {id} settlementWindowId, pc.ledgerAccountTypeId, + pc.currencyId, m.settlementModelId + FROM **transferFulfilment** tf + JOIN **transferParticipant** tp + ON tp.transferId = tf.transferId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = tp.participantCurrencyId + JOIN **settlementModel** m + ON m.ledgerAccountTypeId = pc.ledgerAccountTypeId + WHERE tf.settlementWindowId = {id} + AND m.settlementGranularityId = 'NET' + end hnote + SETTLE_DAO -> SETTLE_DAO: filter swcList to add currency to each record based on model + SETTLE_DAO -> DB: Insert filtered list + deactivate DB + + SETTLE_DAO -> DB: Aggregate window data and insert + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementContentAggregation** (settlementWindowContentId, participantCurrencyId, + transferParticipantRoleTypeId, ledgerEntryTypeId, currentStateId, createdDate, amount) + SELECT swc.settlementWindowContentId, pc.participantCurrencyId, tp.transferParticipantRoleTypeId, + tp.ledgerEntryTypeId, 'CLOSED', transactionTimestamp, SUM(tp.amount) + FROM **transferFulfilment** tf + JOIN **transferParticipant** tp + ON tf.transferId = tp.transferId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = tp.participantCurrencyId + JOIN **settlementWindowContent** swc + ON swc.settlementWindowId = tf.settlementWindowId + AND swc.ledgerAccountTypeId = pc.ledgerAccountTypeId + AND swc.currencyId = pc.currencyId + JOIN **settlementModel** m + ON m.settlementModelId = swc.settlementModelId + WHERE ttf.settlementWindowId = {id} + GROUP BY swc.settlementWindowContentId, pc.participantCurrencyId, + tp.transferParticipantRoleTypeId, tp.ledgerEntryTypeId + end hnote + deactivate DB + + SETTLE_DAO -> DB: Insert initial window content state change + activate DB + hnote over DB #lightyellow + INSERT INTO **settlementWindowContentStateChange** + (settlementWindowContentId, settlementWindowStateId) + SELECT swc.settlementWindowContentId, 'CLOSED' + FROM **settlementWindowContent** swc + WHERE swc.settlementWindowId = {id} + end hnote + deactivate DB + + SETTLE_DAO -> DB: Update pointers to current state change ids + activate DB + hnote over DB #lightyellow + settlementWindowContent + end hnote + deactivate DB + end + SETTLE_DAO --> SET_WIN_HANDLER: Return result (success / failure) + deactivate SETTLE_DAO + end + + alt success + note right of SET_WIN_HANDLER #lightgray + windowContentReady = true + end note + SET_WIN_HANDLER -> SETTLE_DAO: Close requested window\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Change window state to 'CLOSED' + activate DB + hnote over DB #lightyellow + settlementWindowStateChange + settlementWindow.currentStateChangeId + end hnote + deactivate DB + deactivate SETTLE_DAO + else failure && iter < Config.WIN_AGGREGATION_RETRY_COUNT + note right of SET_WIN_HANDLER #lightgray + **sleep** Config.WIN_AGGREGATION_RETRY_INTERVAL seconds + end note + else failure + SET_WIN_HANDLER -> SETTLE_DAO: Fail requested window\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Change window state to 'FAILED' + activate DB + hnote over DB #lightyellow + settlementWindowStateChange + settlementWindow.currentStateChangeId + end hnote + deactivate DB + deactivate SETTLE_DAO + + SET_WIN_HANDLER ->> SET_WIN_HANDLER: Log ERROR event + end + end + end + else + SSAPI ->> SSAPI: Log ERROR event + activate SSAPI + note left of SSAPI #yellow + { + "errorInformation": { + "errorCode": , + "errorDescription": "Client error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + activate OPERATOR + end + deactivate OPERATOR + deactivate SET_WIN_HANDLER +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.svg new file mode 100644 index 000000000..19abe69d4 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.svg @@ -0,0 +1,1135 @@ + + + + + + + + + + + 6.1.2. Close Settlement Window (closeSettlementWindow) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + + + topic- + + + settlement-window + + + + + topic- + + + settlement-window + + + Settlement Window + + + Handler + + + + + Settlement Window + + + Handler + + + + + + + topic-event + + + + + topic-event + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Close Settlement Window + + + + + { + + + "state": "CLOSED", + + + "reason": <string> + + + } + + + + + 1 + + + POST - /settlementWindows/{id} + + + + + 2 + + + Validate payload, existing window, + + + state, assigned transfers, etc. + + + + + break + + + + + { + + + "errorInformation": { + + + "errorCode": <integer>, + + + "errorDescription": "FSPIOP Error Description" + + + } + + + } + + + + + 3 + + + Respond HTTP - 400 (Bad Request) + + + + + 4 + + + Get requested settlementWindow and state + + + Error code: + + + 2001 + + + + + 5 + + + Get settlementWindow state + + + + SELECT sw.settlementWindowId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementWindow + + + AS sw + + + JOIN + + + settlementWindowStateChange + + + AS swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + WHERE sw.settlementWindowId = {id} + + + + + 6 + + + Return result + + + + + alt + + + [settlementWindow found && settlementWindowStateId == 'OPEN'] + + + + + Process settlement window + + + + + 7 + + + Terminate current window and open a new one + + + Error code: + + + 2001 + + + + + DB TRANSACTION: Terminate window usage and initate next + + + + + let + + + transactionTimestamp + + + = now() + + + + + 8 + + + Terminate requested window + + + + INSERT INTO + + + settlementWindowStateChange + + + (settlementWindowId, settlementWindowStateId, reason, createdDate) + + + VALUES ({id}, 'PROCESSING', {payload.reason}, {transactionTimestamp}) + + + + + 9 + + + Return + + + settlementWindowStateChangeId + + + + + 10 + + + Update pointer to current state change id + + + + UPDATE + + + settlementWindow + + + SET currentStateChangeId = {settlementWindowStateChangeId} + + + WHERE settlementWindowId = {id} + + + + + 11 + + + Create new settlementWindow + + + + INSERT INTO + + + settlementWindow + + + (reason, createdDate) + + + VALUES ({payload.reason}, {transactionTimestamp}) + + + + + 12 + + + Return + + + settlementWindowId + + + + + 13 + + + Insert intial state for the created window + + + + INSERT INTO + + + settlementWindowStateChange + + + (settlementWindowId, settlementWindowStateId, reason, createdDate) + + + VALUES ({settlementWindowId}, 'OPEN', {payload.reason}, {transactionTimestamp}) + + + + + 14 + + + Return + + + newSettlementWindowStateChangeId + + + + + 15 + + + Update pointer to current state change id + + + + UPDATE + + + settlementWindow + + + SET currentStateChangeId = {newSettlementWindowStateChangeId} + + + WHERE settlementWindowId = {settlementWindowId} + + + + + 16 + + + Return success + + + + + Message: + + + { + + + id: <uuid> + + + from: switch, + + + to: switch, + + + type: application/json + + + content: { + + + payload: { + + + settlementWindowId: {id} + + + } + + + }, + + + metadata: { + + + event: { + + + id: <uuid>, + + + responseTo: null, + + + type: setwin, + + + action: close, + + + createdAt: <timestamp>, + + + state: { + + + status: "success", + + + code: 0 + + + } + + + } + + + } + + + } + + + + + 17 + + + Produce Settlement Window + + + event message + + + Error codes: + + + 2003 + + + + + { + + + "id": settlementWindowId, + + + "state": 'OPEN', + + + "reason": payload.reason, + + + "createdDate": transactionTimestamp, + + + "changedDate": transactionTimestamp + + + } + + + + + 18 + + + Respond HTTP - 201 (Created) + + + + + Close settlement window (Deferred Handler) + + + + + 19 + + + Consume Settlement Window + + + event message + + + + + Persist Event Information + + + + + 20 + + + Publish event information + + + + + ref + + + Event Handler Consume + + + + + let + + + id + + + = message.content.payload.settlementWindowId + + + let + + + windowContentReady + + + = false + + + let + + + iter + + + = 0 + + + + + loop + + + [iter < Config.WIN_AGGREGATION_RETRY_COUNT && !windowContentReady] + + + + + iter++ + + + + + 21 + + + Check if all transferParticipant records + + + for the requested window have been set + + + (currentStateChangeId is NOT NULL). + + + Error code: + + + 2001 + + + + + 22 + + + Use EXISTS query to find NULL currentStateChangeId records + + + + transferFulfilment + + + transferParticipant + + + + + 23 + + + Return result (success / failure) + + + + + opt + + + [transferParticipant records have been processed by central-ledger SettlementModelHandler] + + + + + 24 + + + Generate window content and aggregations + + + Error code: + + + 2001 + + + + + DB TRANSACTION: Generate window content and aggregations + + + + + let + + + transactionTimestamp + + + = now() + + + + + 25 + + + Change all applicable entries to CLOSED state + + + + transferParticipantStateChange + + + transferParticipant + + + + + 26 + + + Determine window content and insert + + + + + 27 + + + Get all Settlement Models + + + + + 28 + + + create Settlement Models <-> currencyId map + + + + + 29 + + + create list of currencies with Settlement Models + + + + + 30 + + + Get Settlement Window Content list + + + + SELECT DISTINCT {id} settlementWindowId, pc.ledgerAccountTypeId, + + + pc.currencyId, m.settlementModelId + + + FROM + + + transferFulfilment + + + tf + + + JOIN + + + transferParticipant + + + tp + + + ON tp.transferId = tf.transferId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = tp.participantCurrencyId + + + JOIN + + + settlementModel + + + m + + + ON m.ledgerAccountTypeId = pc.ledgerAccountTypeId + + + WHERE tf.settlementWindowId = {id} + + + AND m.settlementGranularityId = 'NET' + + + + + 31 + + + filter swcList to add currency to each record based on model + + + + + 32 + + + Insert filtered list + + + + + 33 + + + Aggregate window data and insert + + + + INSERT INTO + + + settlementContentAggregation + + + (settlementWindowContentId, participantCurrencyId, + + + transferParticipantRoleTypeId, ledgerEntryTypeId, currentStateId, createdDate, amount) + + + SELECT swc.settlementWindowContentId, pc.participantCurrencyId, tp.transferParticipantRoleTypeId, + + + tp.ledgerEntryTypeId, 'CLOSED', transactionTimestamp, SUM(tp.amount) + + + FROM + + + transferFulfilment + + + tf + + + JOIN + + + transferParticipant + + + tp + + + ON tf.transferId = tp.transferId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = tp.participantCurrencyId + + + JOIN + + + settlementWindowContent + + + swc + + + ON swc.settlementWindowId = tf.settlementWindowId + + + AND swc.ledgerAccountTypeId = pc.ledgerAccountTypeId + + + AND swc.currencyId = pc.currencyId + + + JOIN + + + settlementModel + + + m + + + ON m.settlementModelId = swc.settlementModelId + + + WHERE ttf.settlementWindowId = {id} + + + GROUP BY swc.settlementWindowContentId, pc.participantCurrencyId, + + + tp.transferParticipantRoleTypeId, tp.ledgerEntryTypeId + + + + + 34 + + + Insert initial window content state change + + + + INSERT INTO + + + settlementWindowContentStateChange + + + (settlementWindowContentId, settlementWindowStateId) + + + SELECT swc.settlementWindowContentId, 'CLOSED' + + + FROM + + + settlementWindowContent + + + swc + + + WHERE swc.settlementWindowId = {id} + + + + + 35 + + + Update pointers to current state change ids + + + + settlementWindowContent + + + + + 36 + + + Return result (success / failure) + + + + + alt + + + [success] + + + + + windowContentReady = true + + + + + 37 + + + Close requested window + + + Error code: + + + 2001 + + + + + 38 + + + Change window state to 'CLOSED' + + + + settlementWindowStateChange + + + settlementWindow.currentStateChangeId + + + + [failure && iter < Config.WIN_AGGREGATION_RETRY_COUNT] + + + + + sleep + + + Config.WIN_AGGREGATION_RETRY_INTERVAL seconds + + + + [failure] + + + + + 39 + + + Fail requested window + + + Error code: + + + 2001 + + + + + 40 + + + Change window state to 'FAILED' + + + + settlementWindowStateChange + + + settlementWindow.currentStateChangeId + + + + 41 + + + Log ERROR event + + + + + 42 + + + Log ERROR event + + + + + { + + + "errorInformation": { + + + "errorCode": <integer>, + + + "errorDescription": "Client error description" + + + } + + + } + + + + + 43 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.plantuml b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.plantuml new file mode 100644 index 000000000..c6b7a81ae --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.plantuml @@ -0,0 +1,121 @@ +/'***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * ModusBox + - Georgi Georgiev + -------------- + ******'/ + +@startuml +' declate title +title 6.1.3. Get Settlement Windows By Parameters (getSettlementWindowsByParams) + +autonumber + +' declare actors + +actor "Hub Employee" as OPERATOR +boundary "Settlement Service API" as SSAPI +entity "Settlement DAO" as SETTLE_DAO +database "Central Store" as DB + +box "Central HUB" #lightpink + participant OPERATOR +end box + +box "Settlement Service" #lightgreen + participant SSAPI + participant SETTLE_DAO +end box + +box "Central Services" #lightyellow + participant DB +end box + +' start flow + +group Get Settlement Windows By Parameters + activate OPERATOR + note right of OPERATOR #yellow + **Params:** + ?participantId={participantId} + &state={state} + &fromDateTime={fromDateTime} + &toDateTime={toDateTime} + end note + OPERATOR -> SSAPI: GET - /settlementWindows/{params} + activate SSAPI + SSAPI -> SETTLE_DAO: Request settlementWindow\nError code: 2001 + activate SETTLE_DAO + SETTLE_DAO -> DB: Request settlementWindows + activate DB + hnote over DB #lightyellow + SELECT DISTINCT sw.settlementId, swsc.settlementWindowStateId, + swsc.reason, sw.createdDate, swsc.createdDate changedDate + FROM **settlementWindow** sw + JOIN **settlementWindowStateChange** swsc + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + JOIN **transferFulfilment** tf + ON tf.settlementWindowId = sw.settlementWindowId + JOIN **transferParticipant** tp + ON tp.transferId = tf.transferId + JOIN **participantCurrency** pc + ON pc.participantCurrencyId = tp.participantCurrencyId + [WHERE pc.participantId = {participantId}] + [AND swsc.settlementWindowStateId = {state}] + [AND sw.createdDate >= {fromDateTime}] + [AND sw.createdDate <= {toDateTime}] + + end hnote + SETTLE_DAO <-- DB: Return data + deactivate DB + SSAPI <-- SETTLE_DAO: Return **settlementWindows** + deactivate SETTLE_DAO + alt One or more records found + note left of SSAPI #yellow + [ + { + "id": settlementWindow.settlementId, + "state": settlementWindow.settlementWindowStateId, + "reason": settlementWindow.reason, + "createdDate": settlementWindow.createdDate, + "changedDate": settlementWindow.changedDate + } + ] + end note + OPERATOR <-- SSAPI: Respond HTTP - 200 (OK) + else + note right of SSAPI #lightgray + Log ERROR event + end note + note left of SSAPI #yellow + { + "errorInformation": { + "errorCode": , + "errorDescription": "Client error description" + } + } + end note + OPERATOR <-- SSAPI: Respond HTTP - 4xx (Client error) + deactivate SSAPI + deactivate OPERATOR + end +end +@enduml diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.svg b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.svg new file mode 100644 index 000000000..090e598ac --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + 6.1.3. Get Settlement Windows By Parameters (getSettlementWindowsByParams) + + + + Central HUB + + + + Settlement Service + + + + Central Services + + + + + + + + Hub Employee + + + + + Hub Employee + + + + + Settlement Service API + + + + + Settlement Service API + + + + + Settlement DAO + + + + + Settlement DAO + + + + + Central Store + + + + + Central Store + + + + + + + + Get Settlement Windows By Parameters + + + + + Params: + + + ?participantId={participantId} + + + &state={state} + + + &fromDateTime={fromDateTime} + + + &toDateTime={toDateTime} + + + + + 1 + + + GET - /settlementWindows/{params} + + + + + 2 + + + Request settlementWindow + + + Error code: + + + 2001 + + + + + 3 + + + Request settlementWindows + + + + SELECT DISTINCT sw.settlementId, swsc.settlementWindowStateId, + + + swsc.reason, sw.createdDate, swsc.createdDate changedDate + + + FROM + + + settlementWindow + + + sw + + + JOIN + + + settlementWindowStateChange + + + swsc + + + ON swsc.settlementWindowStateChangeId = sw.currentStateChangeId + + + JOIN + + + transferFulfilment + + + tf + + + ON tf.settlementWindowId = sw.settlementWindowId + + + JOIN + + + transferParticipant + + + tp + + + ON tp.transferId = tf.transferId + + + JOIN + + + participantCurrency + + + pc + + + ON pc.participantCurrencyId = tp.participantCurrencyId + + + [WHERE pc.participantId = {participantId}] + + + [AND swsc.settlementWindowStateId = {state}] + + + [AND sw.createdDate >= {fromDateTime}] + + + [AND sw.createdDate <= {toDateTime}] + + + + + 4 + + + Return data + + + + + 5 + + + Return + + + settlementWindows + + + + + alt + + + [One or more records found] + + + + + [ + + + { + + + "id": settlementWindow.settlementId, + + + "state": settlementWindow.settlementWindowStateId, + + + "reason": settlementWindow.reason, + + + "createdDate": settlementWindow.createdDate, + + + "changedDate": settlementWindow.changedDate + + + } + + + ] + + + + + 6 + + + Respond HTTP - 200 (OK) + + + + + + Log ERROR event + + + + + { + + + "errorInformation": { + + + "errorCode": <integer>, + + + "errorDescription": "Client error description" + + + } + + + } + + + + + 7 + + + Respond HTTP - 4xx (Client error) + + diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema-dbeaver.erd b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema-dbeaver.erd new file mode 100644 index 000000000..57065a8da --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema-dbeaver.erd @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COLOR LEGEND: +Green - subject specific entity +Gray - transfer specific entity +Red - settlement specific entity +Blue - lookup entity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema.png b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema.png new file mode 100644 index 000000000..4930831f1 Binary files /dev/null and b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/assets/entities/central-settlements-db-schema.png differ diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-id.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-id.md new file mode 100644 index 000000000..6e086a50c --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-id.md @@ -0,0 +1,8 @@ +# Request Settlement + +Design for Get Settlement by Id process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.4.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-spa.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-spa.md new file mode 100644 index 000000000..bebda59db --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-by-spa.md @@ -0,0 +1,8 @@ +# Request Settlement By SPA + +Design for Get Settlement by Settlement/Participant/Account process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.3.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-window-by-id.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-window-by-id.md new file mode 100644 index 000000000..75ad34793 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-window-by-id.md @@ -0,0 +1,8 @@ +# Request Settlement Window + +Design for Request Settlement Window by Id process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.1.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-windows-by-params.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-windows-by-params.md new file mode 100644 index 000000000..4c2dcfe26 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlement-windows-by-params.md @@ -0,0 +1,8 @@ +# Settlement Windows By Params + +Design for Get Settlement Windows by Parameters process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.3.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlements-by-params.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlements-by-params.md new file mode 100644 index 000000000..0458a88ab --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/get-settlements-by-params.md @@ -0,0 +1,8 @@ +# Request Settlements By Params + +Design for Query Settlements by Parameters process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.2.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/gross-settlement-handler-consumer.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/gross-settlement-handler-consumer.md new file mode 100644 index 000000000..51e54594b --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/gross-settlement-handler-consumer.md @@ -0,0 +1,7 @@ +# Gross Settlement Handler + +Sequence design diagram for the gross settlement handler. + +## Sequence Diagram + +![example](assets/diagrams/sequence/seq-gross-settlement-handler.svg) diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-close-settlement-window.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-close-settlement-window.md new file mode 100644 index 000000000..20f9d1617 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-close-settlement-window.md @@ -0,0 +1,8 @@ +# Close Settlement Window + +Design for Close Settlement Window process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-setwindow-6.1.2.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-create-settlement.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-create-settlement.md new file mode 100644 index 000000000..07ab60084 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/post-create-settlement.md @@ -0,0 +1,8 @@ +# Create Settlement + +Design for Trigger Settlement Event process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.1.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-abort.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-abort.md new file mode 100644 index 000000000..b43b0646c --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-abort.md @@ -0,0 +1,8 @@ +# Settlement Abort + +Design for Settlement Abort process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.6.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-transfer-ack.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-transfer-ack.md new file mode 100644 index 000000000..3c29eb012 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/put-settlement-transfer-ack.md @@ -0,0 +1,8 @@ +# Settlement Transfer Acknowledgment + +Design for Acknowledgement of Settlement Transfer process. + +## Sequence Diagram + +{% uml src="mojaloop-technical-overview/central-settlements/settlement-process/assets/diagrams/sequence/seq-settlement-6.2.5.plantuml" %} +{% enduml %} diff --git a/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/rules-handler-consumer.md b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/rules-handler-consumer.md new file mode 100644 index 000000000..70270ae50 --- /dev/null +++ b/website/versioned_docs/v1.0.1/technical/central-settlements/settlement-process/rules-handler-consumer.md @@ -0,0 +1,169 @@ +Rules Handler. Interchange fees example. File format. +----------------------------------------------------- + +## Rules Handler +The Rules handler provides the capability to execute custom rule-based actions as a result of transfer processing events. +Here we are giving example with Interchange fee calculation. +## Sequence Diagram +![example](assets/diagrams/sequence/seq-rules-handler.svg) + +### Interchange fee case +In order to support the various options for accumulating interchange or other fees, we need to generate and settle liabilities incurred as a consequence of making transfers between particular types of customer. The general form of an example rule, that we are using to illustrate how it works is as follows: +- If the transaction is a wallet-to-wallet P2P transaction, then the receiver + DFSP pays the sender DFSP 0.6% of the amount of the transaction. +- No interchange fees are levied for on-us transactions. + +The business decisions around this requirement are: +1. The definition of whether or not a payee account is a wallet is + returned by the payee DFSP as part of customer discovery. In the process, the `extension` of the transaction should should be extended with characterization of the account. +2. Interchange fees are captured by the switch when there is a matching trigger condition. +3. Interchange fees have the ledger entry type INTERCHANGE_FEE and are + recorded in accounts whose type is INTERCHANGE_FEE. +4. Interchange fees are settled multilaterally, net and deferred. + _Make sure Settlement type and ledger account type for the INTERCHANGE FEE records are availabe_ + +This functionality is implemented as a general +process for defining and executing rules, and for taking actions based on the +outcome of evaluation of a rule. + +### Evaluating a rule +The process of evaluating a rule is based on the following assumptions: + +1. There is a standard form of rule evaluation with the following + structure: + 1. A transaction object is passed as the parameter to the rule + evaluation function. + 2. The rule evaluation itself uses a combination of simple and/or complex nested if statements + 3. If the rule evaluates to TRUE, then an action should be executeds + +An example of a rule function to evaluate an interchange fee rule could be: +```js +function evaluateInterchangeFee (transaction) { + if( + (transaction.payee.fspId.toLowerCase() != transaction.payer.fspId.toLowerCase()) + && (transaction.extensionList[“payerAccountType”].toLowerCase() == + "Wallet".toLowerCase() + && transaction.extensionList[“payeeAccountType”].toLowerCase() == + "Wallet".toLowerCase()) + && (transaction.transactionType.scenario.toLowerCase() == + "TRANSFER".toLowerCase() + && transaction.transactionType.initiator.toLowerCase() == "PAYER".toLowerCase() + && transaction.transactionType.initiatorType.toLowerCase() == + "CONSUMER".toLowerCase()) + ) + // Do some good stuff + }; +}; +``` + +### Taking action after evaluating a rule +If a rule evaluates to TRUE, then appropriate action is taken. In the case of the immediate example of interchange fees, the action taken should be to add two entries to the participants’ interchange fee accounts, on recording the debit from the payee of the interchange fee amount and the other recording the credit to the payer of the interchange fee amount. + +There is defined class with methods that represent the actions to be taken. The rule evaluation instatiates the class and calls the appropriate functions. + +In the case of the interchange fees, we have defined an action called +addLedgerEntry, with the following parameters: + +1. The transfer ID for which the ledger entry is being created +2. The ledger entry type to be used +3. The currency in which the amount is denominated +4. The amount of the fee +5. The FSP ID of the credit party +6. The FSP ID of the debit party + +This might appear in the rule evaluation function as: + +```js +myAction.addLedgerEntry(transaction.transactionId, + "INTERCHANGE_FEE“, + "INTERCHANGE_FEE“, + transaction.currency, + transaction.amount\*0.006, + transaction.payer.fspId, + transaction.payee.fspId); +``` +### Providing rules +The files should be placed in a scripts directory, configured by the value of the environmental variable `SETTINGS__SCRIPTS_FOLDER` +Each rule file should be valid JS with the specified headers content. The required headers should be in the exact order and format as they are in the example below the table. + +| Header | Description | Required | +| :- | :- | :-: | +| Name | Rule name | no | +| Type | Message event type. Corresponds to Kafka topic | yes | +| Action | Message event action. ex. Commit, Prepare, Log, etc | yes | +| Status | Status of the operation: success or failure | yes | +| Start | Time to start abiding the rule | yes | +| End | Until when the rule is valid | yes | +| Description | Rule description | no | + +Based on the headers the rule is evaluated to be triggered or not. + +In the below example rule script, a message on the `notification` topic with action `commit` and status `success` will trigger the code. + +```js +/* eslint-disable no-undef */ +// ******************************************************** +// Name: Interchange fee calculation +// Type: notification +// Action: commit +// Status: success +// Start: 2020-06-01T00:00:00.000Z +// End: 2100-12-31T23:59:59.999Z +// Description: This script calculates the interchange fees between DFSPs where the account type is "Wallet" +// ******************************************************** + +// ## Globals: +// payload: The contents of the message from the Kafka topic. +// transfer: The transfer object. + +// # Functions: +// ## Data retrieval functions: +// getTransferFromCentralLedger(transferId): Retrieves a mojaloop transfer from the central-ledger API. + +// ## Helper functions: +// getExtensionValue(list, key): Gets a value from an extension list +// log(message): allows the script to log to standard out for debugging purposes + +// Math functions: +// multiply(number1, number2, decimalPlaces): Uses ml-number to handle multiplication of money values + +// Ledger functions: +// addLedgerEntry: Adds a debit and credit ledger entry to the specified account to the specified DFSPs + +log(JSON.stringify(transfer)) +const payerFspId = transfer.payer.partyIdInfo.fspId +const payeeFspId = transfer.payee.partyIdInfo.fspId + +if ((payeeFspId !== payerFspId) && + (transfer.payee.partyIdInfo.extensionList && // WORKAROUND for issue #2149 + transfer.payer.partyIdInfo.extensionList && // WORKAROUND for issue #2149 + transfer.payee.partyIdInfo.extensionList.extension && // WORKAROUND for issue #2149 + transfer.payer.partyIdInfo.extensionList.extension) && // WORKAROUND for issue #2149 + (getExtensionValue(transfer.payee.partyIdInfo.extensionList.extension, 'accountType') === 'Wallet' && + getExtensionValue(transfer.payer.partyIdInfo.extensionList.extension, 'accountType') === 'Wallet') && + (transfer.transactionType.scenario === 'TRANSFER' && + transfer.transactionType.initiator === 'PAYER' && + transfer.transactionType.initiatorType === 'CONSUMER')) { + log(`Adding an interchange fee for Wallet to Wallet from ${payerFspId} to ${payeeFspId}`) + addLedgerEntry(payload.id, 'INTERCHANGE_FEE', // Ledger account type Id + 'INTERCHANGE_FEE', // Ledger entry type Id + multiply(transfer.amount.amount, 0.006, 2), + transfer.amount.currency, + payerFspId, + payeeFspId) +} +``` + +### Rules script API + +#### Globals + +* `payload` - The contents of the message from the Kafka topic that has the rules script triggered +* `transfer` - The transfer object +#### Functions + +* `getTransferFromCentralLedger(transferId: uuid)` - Retrieves a mojaloop transfer from the central-ledger API. +* `getExtensionValue(list: array, key: string)` - Gets a value from an extension list +* `log(message: string)`: allows the script to log to configured logger with level *INFO* for debugging purposes +* `multiply(number1: number, number2: number, decimalPlaces: number)`: Uses ml-number to handle multiplication of money values +* `addLedgerEntry(transferId: uuid, ledgerAccountTypeId: string, ledgerEntryTypeId: string, amount: number, currency: string, payerFspId: string, payeeFspId: string)`: Adds a debit and credit ledger entries with given legdger account type, amount and currency to the accounts of the specified DFSPs