You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by co...@apache.org on 2019/04/18 14:27:38 UTC

[fineract] branch marta-jankovics-mojaloop-fineract_1.0 created (now 066f9f5)

This is an automated email from the ASF dual-hosted git repository.

conradsp pushed a change to branch marta-jankovics-mojaloop-fineract_1.0
in repository https://gitbox.apache.org/repos/asf/fineract.git.


      at 066f9f5  interoperable - config, build

This branch includes the following new commits:

     new 6e52d68  interoperable - service
     new 066f9f5  interoperable - config, build

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[fineract] 02/02: interoperable - config, build

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

conradsp pushed a commit to branch marta-jankovics-mojaloop-fineract_1.0
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 066f9f5c049a008a80efe908d57c38277e30540e
Author: Marti <ma...@dpc.hu>
AuthorDate: Fri Mar 8 12:03:32 2019 +0100

    interoperable - config, build
---
 .../json/bulkTransactions_post_sync_response.json  |   7 +
 .../json/bulkTransactions_put_async_response.json  |  28 +++
 .../json/bulk_payer_post_request.json              |  60 +++++++
 .../json/merchant_payer_post_request.json          |  46 +++++
 .../specification/json/p2p_payee_post_request.json |  42 +++++
 .../specification/json/p2p_payer_post_request.json |  42 +++++
 .../payee_quote_notification_post_request.json     |  53 ++++++
 .../payer_quote_notification_post_request.json     |  57 ++++++
 .../payer_request_notification_post_request.json   |  39 ++++
 .../json/transactions_post_sync_response.json      |   7 +
 .../json/transactions_put_async_response.json      |  16 ++
 .../main/resources/META-INF/spring/appContext.xml  |  14 +-
 .../sql/migrations/core_db/V346__interop_init.sql  |  83 +++++++++
 .../list_db/V1__mifos-platform-shared-tenants.sql  |   2 +
 .../migrations/sample_data/interop_sample_data.sql | 200 +++++++++++++++++++++
 .../sample_data/tn03_interop_sample_data.sql       |  80 +++++++++
 .../sample_data/tn04_interop_sample_data.sql       |  80 +++++++++
 17 files changed, 848 insertions(+), 8 deletions(-)

diff --git a/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json b/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json
new file mode 100644
index 0000000..1bb95ab
--- /dev/null
+++ b/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json
@@ -0,0 +1,7 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions
+
+//Response Body:
+{
+  "bulkTransactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d" // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+}
diff --git a/docs/interoperation/specification/json/bulkTransactions_put_async_response.json b/docs/interoperation/specification/json/bulkTransactions_put_async_response.json
new file mode 100644
index 0000000..c6899d7
--- /dev/null
+++ b/docs/interoperation/specification/json/bulkTransactions_put_async_response.json
@@ -0,0 +1,28 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions
+//GET paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions/11436b17-c690-4a30-8505-42a2c4eafb9d
+
+//Response URI:
+//PUT <FSPIOP-Callback>/interoperation/bulkTransactions/11436b17-c690-4a30-8505-42a2c4eafb9d
+
+//Header:
+//X-Tenant-Identifier: T111
+
+//Response Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601
+  "bulkTransferState": "COMPLETED", // mandatory, Enum of String(1..32): RECEIVED, PENDING, ACCEPTED, PROCESSING, COMPLETED, REJECTED
+  "individualTransferResults": [ // mandatory, (1..1000)
+    {
+      "transferId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+      "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601
+      "transferState": "COMMITTED"// mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED
+    },
+    {
+      "transferId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+      "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601
+      "transferState": "COMMITTED"// mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED
+    }
+  ]
+}
diff --git a/docs/interoperation/specification/json/bulk_payer_post_request.json b/docs/interoperation/specification/json/bulk_payer_post_request.json
new file mode 100644
index 0000000..9cfcc9d
--- /dev/null
+++ b/docs/interoperation/specification/json/bulk_payer_post_request.json
@@ -0,0 +1,60 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions
+// HTTP/1.1
+
+//Request Header:
+//Accept: application/vnd.interoperability.quotes+json;version=1
+//Content-Type: application/vnd.interoperability.quotes+json;version=1.0
+//Content-Length: 975
+//Date: Tue, 15 Nov 2017 10: 13:40 GMT
+//Origin: http://www.example.com
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    }
+  },
+  "amountType": "RECEIVE", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "individualTransfers": [ // mandatory, (1..1000)
+    {
+      "payee": { // mandatory
+        "partyIdInfo": { // mandatory
+          "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+          "partyIdentifier": "IN93116000060000000012345671", // mandatory, String(1..128)
+          "partySubIdOrType": "nothing", // optional, String(1..128)
+        }
+      },
+      "amount": { // mandatory
+        "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+        "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+      }
+    },
+    {
+      "payee": { // mandatory
+        "partyIdInfo": { // mandatory
+          "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+          "partyIdentifier": "IN93116000060000000012345672", // mandatory, String(1..128)
+          "partySubIdOrType": "nothing", // optional, String(1..128)
+        }
+      },
+      "amount": { // mandatory
+        "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+        "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+      }
+    }
+  ],
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
+
diff --git a/docs/interoperation/specification/json/merchant_payer_post_request.json b/docs/interoperation/specification/json/merchant_payer_post_request.json
new file mode 100644
index 0000000..ce5c20b
--- /dev/null
+++ b/docs/interoperation/specification/json/merchant_payer_post_request.json
@@ -0,0 +1,46 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions
+// HTTP/1.1
+
+//Request Header:
+//Accept: application/vnd.interoperability.quotes+json;version=1
+//Content-Type: application/vnd.interoperability.quotes+json;version=1.0
+//Content-Length: 975
+//Date: Tue, 15 Nov 2017 10: 13:40 GMT
+//Origin: http://www.example.com
+//FSPIOP-Callback: http://www.client-server.com/demo
+//FSPIOP-Notification: http://www.client-server.com/demo
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    },
+    "merchantClassificationCode": "" // optional, String(1..4 digits)
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN4550000000058398257466", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+      "fspId": "BankNrOne" // optional, String(1..32)
+    }
+  },
+  "amountType": "RECEIVE", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": { // mandatory
+    "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionType": { // mandatory
+    "scenario": "PAYMENT", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/p2p_payee_post_request.json b/docs/interoperation/specification/json/p2p_payee_post_request.json
new file mode 100644
index 0000000..84a96a7
--- /dev/null
+++ b/docs/interoperation/specification/json/p2p_payee_post_request.json
@@ -0,0 +1,42 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions
+// HTTP/1.1
+
+//Request Header:
+//Accept: application/vnd.interoperability.quotes+json;version=1
+//Content-Type: application/vnd.interoperability.quotes+json;version=1.0
+//Content-Length: 975
+//Date: Tue, 15 Nov 2017 10: 13:40 GMT
+//Origin: http://www.example.com
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    }
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "+12345678901", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+    }
+  },
+  "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": { // mandatory
+    "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/p2p_payer_post_request.json b/docs/interoperation/specification/json/p2p_payer_post_request.json
new file mode 100644
index 0000000..848211d
--- /dev/null
+++ b/docs/interoperation/specification/json/p2p_payer_post_request.json
@@ -0,0 +1,42 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions
+// HTTP/1.1
+
+//Request Header:
+//Accept: application/vnd.interoperability.quotes+json;version=1
+//Content-Type: application/vnd.interoperability.quotes+json;version=1.0
+//Content-Length: 975
+//Date: Tue, 15 Nov 2017 10: 13:40 GMT
+//Origin: http://www.example.com
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "+12345678901", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+    }
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    }
+  },
+  "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": { // mandatory
+    "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/payee_quote_notification_post_request.json b/docs/interoperation/specification/json/payee_quote_notification_post_request.json
new file mode 100644
index 0000000..142166a
--- /dev/null
+++ b/docs/interoperation/specification/json/payee_quote_notification_post_request.json
@@ -0,0 +1,53 @@
+//Request URI:
+//POST <FSPIOP-Notification>/channel/quotes/notification
+//HTTP/1.1
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // optional, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "quoteId": "61058cea-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "12345678901", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+      "fspId": "BankNrOne" // optional, String(1..32)
+    }
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    },
+    "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$
+  },
+  "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": {
+    "amount": "100",
+    "currency": "USD"
+  },
+  "transferAmount": {
+    "amount": "99",
+    "currency": "USD"
+  },
+  "fspFee": { // optional
+    "amount": "1", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "fspCommission": { // optional
+    "amount": "0", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionRole": "PAYEE",
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/payer_quote_notification_post_request.json b/docs/interoperation/specification/json/payer_quote_notification_post_request.json
new file mode 100644
index 0000000..6b02094
--- /dev/null
+++ b/docs/interoperation/specification/json/payer_quote_notification_post_request.json
@@ -0,0 +1,57 @@
+//Request URI:
+//POST <FSPIOP-Notification>/channel/quotes/notification
+// HTTP/1.1
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // optional, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "quoteId": "61058cea-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "+12345678901", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+      "fspId": "BankNrOne" // optional, String(1..32)
+    },
+    "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    }
+  },
+  "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": { // mandatory
+    "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transferAmount": {
+    "amount": "99",
+    "currency": "USD"
+  },
+  "payeeReceiveAmount": {
+    "amount": "100",
+    "currency": "USD"
+  },
+  "fspFee": { // optional
+    "amount": "1", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "fspCommission": { // optional
+    "amount": "0", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionRole": "PAYER",
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/payer_request_notification_post_request.json b/docs/interoperation/specification/json/payer_request_notification_post_request.json
new file mode 100644
index 0000000..3a8d2ad
--- /dev/null
+++ b/docs/interoperation/specification/json/payer_request_notification_post_request.json
@@ -0,0 +1,39 @@
+//Request URI:
+//POST <FSPIOP-Notification>/channel/transactions/{transactionId}/notification
+// HTTP/1.1
+//X-Tenant-Identifier: T111
+
+//Request Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+  "payee": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "+12345678901", // mandatory, String(1..128)
+      "partySubIdOrType": "nothing", // optional, String(1..128)
+      "fspId": "BankNrOne" // optional, String(1..32)
+    },
+    "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$
+  },
+  "payer": { // mandatory
+    "partyIdInfo": { // mandatory
+      "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS
+      "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128)
+      "partySubIdOrType": "something" // optional, String(1..128)
+    }
+  },
+  "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE
+  "amount": { // mandatory
+    "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$
+    "currency": "IDR" // mandatory, ISO 4217 (Rupee)
+  },
+  "transactionType": { // mandatory
+    "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND
+    "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE
+    "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE
+  },
+  "note": "From Mats", // optional, String(1..128)
+  "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601
+}
diff --git a/docs/interoperation/specification/json/transactions_post_sync_response.json b/docs/interoperation/specification/json/transactions_post_sync_response.json
new file mode 100644
index 0000000..c9a89f9
--- /dev/null
+++ b/docs/interoperation/specification/json/transactions_post_sync_response.json
@@ -0,0 +1,7 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions
+
+//Response Body:
+{
+  "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d" // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
+}
diff --git a/docs/interoperation/specification/json/transactions_put_async_response.json b/docs/interoperation/specification/json/transactions_put_async_response.json
new file mode 100644
index 0000000..3ddc178
--- /dev/null
+++ b/docs/interoperation/specification/json/transactions_put_async_response.json
@@ -0,0 +1,16 @@
+//Request URI:
+//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions
+//GET paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions/11436b17-c690-4a30-8505-42a2c4eafb9d
+
+//Response URI:
+//PUT <FSPIOP-Callback>/interoperation/transactions/11436b17-c690-4a30-8505-42a2c4eafb9d
+
+//Header:
+//X-Tenant-Identifier: T111
+
+//Response Body:
+{
+  "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36)
+  "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601
+  "transferState": "COMMITTED" // mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED
+}
diff --git a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
index a2631aa..f3d0701 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
@@ -47,11 +47,12 @@
 										  org.apache.fineract.infrastructure.*,
 										  org.apache.fineract.scheduledjobs.*,
 										  org.apache.fineract.organisation.*,
+										  org.apache.fineract.interoperation.*,
 										  org.apache.fineract.portfolio.loanaccount.*,
 										  org.apache.fineract.portfolio.savingsaccount.*,
 										  org.apache.fineract.portfolio.*,
 										  org.apache.fineract.useradministration.*,
-										  org.apache.fineract.mix.*, 
+										  org.apache.fineract.mix.*,
 										  org.apache.fineract.notification.*,
 										  org.apache.fineract.template.*,
 										  org.apache.fineract.template.service.*,
@@ -72,6 +73,7 @@
 
 	<bean id="auditorAware"
 		class="org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl" />
+
 	<jpa:auditing auditor-aware-ref="auditorAware" />
 	<jpa:repositories base-package="org.apache.fineract.commands.domain" />
 	<jpa:repositories base-package="org.apache.fineract.infrastructure.*.domain" />
@@ -86,12 +88,11 @@
 	<jpa:repositories base-package="org.apache.fineract.adhocquery.domain" />
 	<jpa:repositories base-package="org.apache.fineract.notification.domain"/>
 	<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.email.domain"/>
-	
-	
-	<import resource="infrastructure.xml" />
+	<jpa:repositories base-package="org.apache.fineract.interoperation.domain"/>
 
-	<import resource="securityContext.xml" />
 
+	<import resource="infrastructure.xml" />
+	<import resource="securityContext.xml" />
 	<import resource="cache.xml" />
 
 	<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
@@ -101,8 +102,5 @@
 	</bean>
 
 	<import resource="spmContext.xml"/>
-
 	<import resource="swagger.xml"/>
-
-
 </beans>
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V346__interop_init.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V346__interop_init.sql
new file mode 100644
index 0000000..c2b4e5f
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V346__interop_init.sql
@@ -0,0 +1,83 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file 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,
+-- software distributed under the License is 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.
+--
+
+DROP TABLE IF EXISTS `interop_identifier`;
+CREATE TABLE `interop_identifier` (
+	`id`                BIGINT(20)   NOT NULL AUTO_INCREMENT,
+	`account_id`        BIGINT(20)   NOT NULL,
+	`type`              VARCHAR(32)  NOT NULL,
+	`a_value`           VARCHAR(128) NOT NULL,
+	`sub_value_or_type` VARCHAR(128) NULL,
+	`created_by`        VARCHAR(32)  NOT NULL,
+	`created_on`        TIMESTAMP    NOT NULL,
+	`modified_by`       VARCHAR(32)  NULL,
+	`modified_on`       TIMESTAMP    NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `uk_interop_identifier_account` (`account_id`, `type`),
+	UNIQUE INDEX `uk_interop_identifier_value` (`type`, `a_value`, `sub_value_or_type`),
+	INDEX `fk_interop_identifier_account` (`account_id`),
+	CONSTRAINT `fk_interop_identifier_account` FOREIGN KEY (`account_id`) REFERENCES `m_savings_account` (`id`)
+)
+	COLLATE = 'utf8_general_ci'
+	ENGINE = InnoDB;
+
+-- user+roles
+
+SET @interop_username = 'interopUser';
+INSERT INTO `m_appuser`
+VALUES (NULL, 0, 1, NULL, @interop_username, 'Interop', 'User', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a',
+							'email@email.com', b'0', b'1', b'1', b'1', b'1', CURDATE(), 0, b'0');
+
+INSERT INTO `m_appuser_role` VALUES ((SELECT id FROM m_appuser WHERE username = @interop_username), 1);
+
+-- Interoperation permissions
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'READ_INTERID', 'INTERID', 'READ', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'READ_INTERREQUEST', 'INTERREQUEST', 'READ', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'READ_INTERQUOTE', 'INTERQUOTE', 'READ', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'READ_INTERTRANSFER', 'INTERTRANSFER', 'READ', 0);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'PREPARE_INTERTRANSFER', 'INTERTRANSFER', 'PREPARE', 0);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'CREATE_INTERID', 'INTERID', 'CREATE', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'CREATE_INTERREQUEST', 'INTERREQUEST', 'CREATE', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'CREATE_INTERQUOTE', 'INTERQUOTE', 'CREATE', 0);
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'CREATE_INTERTRANSFER', 'INTERTRANSFER', 'CREATE', 0);
+
+INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+VALUES ('interop', 'DELETE_INTERID', 'INTERID', 'DELETE', 0);
+
+
+INSERT IGNORE INTO m_code (code_name, is_system_defined) VALUES ('PaymentType', 1);
+
+SET @code_id = -1;
+SELECT id INTO @code_id FROM m_code WHERE code_name = 'PaymentType';
+
+INSERT IGNORE INTO m_code_value (code_id, code_value, order_position) VALUES (@code_id, 'Money Transfer', 1);
+
+INSERT IGNORE INTO m_payment_type (value, description, order_position) VALUES ('Money Transfer', 'Money Transfer', 1);
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql
index eb7c9c2..e9e36f6 100644
--- a/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql
+++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql
@@ -67,6 +67,8 @@ CREATE TABLE `tenants` (
 LOCK TABLES `tenants` WRITE;
 /*!40000 ALTER TABLE `tenants` DISABLE KEYS */;
 INSERT INTO `tenants` VALUES (1,'default','Default Demo Tenant','mifostenant-default','Asia/Kolkata',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1);
+INSERT INTO `tenants` VALUES (2,'tn03','Rhino','tn03','Africa/Bujumbura',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1);
+INSERT INTO `tenants` VALUES (3,'tn04','Elephant','tn04','Africa/Bujumbura',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1);
 /*!40000 ALTER TABLE `tenants` ENABLE KEYS */;
 UNLOCK TABLES;
 
diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql
new file mode 100644
index 0000000..430b515
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql
@@ -0,0 +1,200 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file 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,
+-- software distributed under the License is 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.
+--
+
+-- To use: Change saving_account_no before every run!
+-- !both tn03, tn04 tenants
+
+-- saving product, account
+SET @last_saving_prod_id = -1;
+SELECT COALESCE(max(id), 1) into @last_saving_prod_id from m_savings_product;
+
+SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id);
+
+INSERT INTO `m_savings_product`
+(`name`, `short_name`, `description`, `deposit_type_enum`, `currency_code`, `currency_digits`,
+ `currency_multiplesof`, `nominal_annual_interest_rate`, `interest_compounding_period_enum`,
+ `interest_posting_period_enum`, `interest_calculation_type_enum`, `interest_calculation_days_in_year_type_enum`,
+ `min_required_opening_balance`, `accounting_type`, `withdrawal_fee_amount`, `withdrawal_fee_type_enum`,
+ `withdrawal_fee_for_transfer`, `allow_overdraft`, `min_required_balance`, `enforce_min_required_balance`,
+ `min_balance_for_interest_calculation`, `withhold_tax`, `tax_group_id`, `is_dormancy_tracking_active`)
+VALUES (@saving_prod_name, concat('SP', @last_saving_prod_id), 'Saving Product', 100, 'TZS', 2, NULL, 0.000000, 1,
+                           4, 1, 360, NULL, 2, NULL, NULL, 0, 0, 0.000000, 1, NULL, 0, NULL, 0);
+
+SET @saving_prod_id = -1;
+SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name;
+
+-- interop_identifier
+
+-- charge, mapping
+-- gl_account, mappings
+-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5
+
+SET @payment_type_id = -1;
+SELECT id INTO @payment_type_id FROM m_payment_type WHERE value = 'Money Transfer';
+
+SET @saving_gl_name = 'Interoperation Saving';
+INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`)
+VALUES (@saving_gl_name, NULL, NULL, 'Interop_Saving', 0, 1, 1, 1, 'Interoperation Saving Asset'); -- account_usage: DETAIL, classification_enum: ASSET
+
+INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`)
+VALUES ((SELECT id FROM acc_gl_account WHERE name = @saving_gl_name), @saving_prod_id, 2, @payment_type_id, NULL, 1); -- product_type: SAVING, financial_account_type: ASSET
+
+SET @nostro_gl_name = 'Interoperation NOSTRO';
+INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`)
+VALUES (@nostro_gl_name, NULL, NULL, 'Interop_Nostro', 0, 0, 1, 2, 'Interoperation NOSTRO Liability'); -- account_usage: DETAIL, classification_enum: LIABILITY
+
+INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`)
+VALUES ((SELECT id FROM acc_gl_account WHERE name = @nostro_gl_name), @saving_prod_id, 2, NULL, NULL, 2); -- product_type: SAVING, financial_account_type: LIABILITY
+
+SET @fee_gl_name = 'Interoperation Fee';
+INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`)
+VALUES (@fee_gl_name, NULL, NULL, 'Interop_Fee', 0, 0, 1, 4, 'Interoperation Fee Income'); -- account_usage: DETAIL, classification_enum: INCOME
+
+SET @fee_gl_id = -1;
+SELECT id INTO @fee_gl_id FROM acc_gl_account WHERE name = @fee_gl_name;
+
+INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`)
+VALUES (@fee_gl_id, @saving_prod_id, 2, NULL, NULL, 4); -- product_type: SAVING, financial_account_type: INCOME
+
+SET @charge_name = 'Interoperation Withdraw Fee';
+INSERT INTO `m_charge`
+(`name`,`currency_code`,`charge_applies_to_enum`,`charge_time_enum`,`charge_calculation_enum`,`charge_payment_mode_enum`,
+ `amount`,`fee_on_day`,`fee_interval`,`fee_on_month`,`is_penalty`,`is_active`,`is_deleted`,`min_cap`,`max_cap`,`fee_frequency`,
+ `income_or_liability_account_id`,`tax_group_id`)
+VALUES (@charge_name, 'TZS', 2, 5, 1, NULL, 1.000000, NULL, NULL, NULL, 0, 0, 0, NULL, NULL, NULL, @fee_gl_id, NULL);
+
+-- loan product
+/*
+SET @last_ext_id = -1;
+SELECT COALESCE(max(external_id), 1) INTO @last_ext_id FROM m_product_loan;
+
+INSERT INTO `m_product_loan`
+VALUES
+(CONCAT('IP', @last_product_id), 'EUR', 2, 1, 50000.000000, NULL, NULL, NULL, concat('Interoperation Customer Product', @last_product_id),
+  'Demo Interoperation Product', NULL, b'0', b'0', 1.000000, 1.000000, NULL, NULL, 3, 1.000000, 0, 1, 1, 1, 2, 1200, NULL,
+  NULL, NULL, NULL, NULL, 1, 1, 3, @last_ext_id + 1, 0, 0,ADDDATE(curdate(),-100),ADDDATE(curdate(),100), 0, 0, NULL, NULL,
+  NULL, 1, 30, 0, 0, 0.00, 0, 1, 0, 0, 0);
+
+SET @product_id = -1;
+SELECT id INTO @product_id FROM m_product_loan WHERE name = concat('Interoperation Customer Product', @last_product_id);
+
+-- charge, mapping
+INSERT INTO `m_charge` VALUES (
+  NULL, concat('Loan Withdraw Fee_', @product_id), 'TZS', 1, 2,
+        1, 0, 1.000000, NULL, NULL,
+        NULL, 0, 1, 0, NULL,
+  NULL, NULL, NULL, NULL);
+
+INSERT INTO `m_product_loan_charge` VALUES
+  (@product_id, (SELECT id
+                 FROM m_charge
+                 WHERE name = concat('Loan Withdraw Fee_', @product_id)));
+
+-- gl_account, mappings
+-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5
+SET @liab_acc_name = concat('Loan Payable Liability_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @liab_acc_name, NULL, NULL, concat('0360009420', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Payable Liability');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @liab_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 2);
+
+SET @nostro_acc_name = concat('Loan NOSTRO_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @nostro_acc_name, NULL, NULL, concat('0360009421', @product_id),
+        0, 1, 1, 1, NULL, 'Loan NOSTRO');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @nostro_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 1);
+
+SET @cash_acc_name = concat('Loan Product Cash_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @cash_acc_name, NULL, NULL, concat('0360009422', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Product Cash');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @cash_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 1);
+
+SET @expen_acc_name = concat('Loan Product Expenses_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @expen_acc_name, NULL, NULL, concat('0360009423', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Product Expenses');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @expen_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 5);
+
+SET @accrue_acc_name = concat('Loan Product Accrue Liability_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @accrue_acc_name, NULL, NULL, concat('0360009424', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Product Accrue Liability');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @accrue_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 2);
+
+SET @equ_acc_name = concat('Loan Product Equity_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @equ_acc_name, NULL, NULL, concat('0360009425', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Product Equity');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @equ_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 3);
+
+SET @feer_acc_name = concat('Loan Product Fees Revenue_', @product_id);
+INSERT INTO `acc_gl_account` VALUES (
+  NULL, @feer_acc_name, NULL, NULL, concat('0360009426', @product_id),
+        0, 1, 1, 1, NULL, 'Loan Product Fees Revenue');
+
+INSERT INTO `acc_product_mapping` VALUES (
+  NULL,
+  (SELECT id
+   FROM acc_gl_account
+   WHERE name = @feer_acc_name),
+  @product_id,
+  NULL, NULL, NULL, 4);*/
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql
new file mode 100644
index 0000000..14f3fde
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql
@@ -0,0 +1,80 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file 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,
+-- software distributed under the License is 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.
+--
+
+-- To use: Change saving_account_no before every run!
+
+USE `tn03`;
+
+-- data initialization
+-- user+roles
+
+-- client
+SET @saving_account_no = '9062b90de19b43989005';
+
+INSERT INTO `m_client` (`account_no`, `external_id`, `status_enum`, `sub_status`, `activation_date`, `office_joining_date`,
+                        `office_id`, `transfer_to_office_id`, `staff_id`, `firstname`, `middlename`, `lastname`, `fullname`,
+                        `display_name`, `mobile_no`, `gender_cv_id`, `date_of_birth`, `image_id`, `closure_reason_cv_id`,
+                        `closedon_date`, `updated_by`, `updated_on`, `submittedon_date`, `submittedon_userid`, `activatedon_userid`,
+                        `closedon_userid`, `default_savings_product`, `default_savings_account`, `client_type_cv_id`, `client_classification_cv_id`,
+                        `reject_reason_cv_id`, `rejectedon_date`, `rejectedon_userid`, `withdraw_reason_cv_id`, `withdrawn_on_date`,
+                        `withdraw_on_userid`, `reactivated_on_date`, `reactivated_on_userid`, `legal_form_enum`, `reopened_on_date`,
+                        `reopened_by_userid`)
+VALUES (@saving_account_no, NULL, 300, NULL, ADDDATE(curdate(), -100), NULL, 1, NULL, NULL, NULL, NULL, NULL,
+        'InteropCustomer', 'InteropCustomer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ADDDATE(curdate(), -100),
+         NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL);
+
+-- saving product, account
+SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id);
+SET @saving_prod_id = -1;
+SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name;
+
+SET @client_id = -1;
+SELECT id INTO @client_id FROM m_client WHERE fullname = 'InteropCustomer';
+
+INSERT INTO `m_savings_account`
+(`account_no`, `external_id`, `client_id`, `group_id`, `product_id`, `field_officer_id`, `status_enum`,
+ `sub_status_enum`, `account_type_enum`, `deposit_type_enum`, `submittedon_date`, `submittedon_userid`,
+ `approvedon_date`, `approvedon_userid`, `activatedon_date`, `activatedon_userid`,
+ `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`,
+ `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`,
+ `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `withdrawal_fee_for_transfer`,
+ `allow_overdraft`, `account_balance_derived`, `min_required_balance`, `enforce_min_required_balance`,
+ `version`, `withhold_tax`)
+VALUES (@saving_account_no, '9062b90de19b43989005d9', @client_id, NULL, @saving_prod_id, NULL, 300, 0, 1, 100, ADDDATE(curdate(), -100),
+  NULL, ADDDATE(curdate(), -100), NULL, ADDDATE(curdate(), -100), NULL, 'TZS', 2, NULL, 1.000000, 1, 4, 1, -- 29. - 4
+  360, NULL, 1, 1, 100000000.000000, 0.000000, 1, 1, 0);
+
+-- interop_identifier
+SET @saving_acc_id = -1;
+SELECT id INTO @saving_acc_id FROM m_savings_account WHERE account_no = @saving_account_no;
+
+INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on)
+VALUES (NULL, @saving_acc_id, 'IBAN', 'IC11in02tn039062b90de19b43989005d9', NULL, 'operator', CURDATE(), 'operator',
+        CURDATE());
+INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on)
+VALUES (NULL, @saving_acc_id, 'MSISDN', '27710203999', NULL, 'operator', CURDATE(), 'operator', CURDATE());
+
+-- charge, mapping
+-- gl_account, mappings
+-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5
+SET @charge_name = 'Interoperation Withdraw Fee';
+
+INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_calculation_enum`,
+                                        `amount`, `amount_outstanding_derived`,`is_paid_derived`, `waived`, `is_active`)
+VALUES (@saving_acc_id, (SELECT id FROM m_charge WHERE name = @charge_name), 0, 5, 1, 1.000000, 0.000000, 0, 0, 1);
diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql
new file mode 100644
index 0000000..4135d70
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql
@@ -0,0 +1,80 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file 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,
+-- software distributed under the License is 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.
+--
+
+-- To use: Change saving_account_no before every run!
+
+USE `tn04`;
+
+-- data initialization
+-- user+roles
+
+-- client
+SET @saving_account_no = '9062b90de19b43989005';
+
+INSERT INTO `m_client` (`account_no`, `external_id`, `status_enum`, `sub_status`, `activation_date`, `office_joining_date`,
+                        `office_id`, `transfer_to_office_id`, `staff_id`, `firstname`, `middlename`, `lastname`, `fullname`,
+                        `display_name`, `mobile_no`, `gender_cv_id`, `date_of_birth`, `image_id`, `closure_reason_cv_id`,
+                        `closedon_date`, `updated_by`, `updated_on`, `submittedon_date`, `submittedon_userid`, `activatedon_userid`,
+                        `closedon_userid`, `default_savings_product`, `default_savings_account`, `client_type_cv_id`, `client_classification_cv_id`,
+                        `reject_reason_cv_id`, `rejectedon_date`, `rejectedon_userid`, `withdraw_reason_cv_id`, `withdrawn_on_date`,
+                        `withdraw_on_userid`, `reactivated_on_date`, `reactivated_on_userid`, `legal_form_enum`, `reopened_on_date`,
+                        `reopened_by_userid`)
+VALUES (@saving_account_no, NULL, 300, NULL, ADDDATE(curdate(), -100), NULL, 1, NULL, NULL, NULL, NULL, NULL,
+ 'InteropMerchant', 'InteropMerchant', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ADDDATE(curdate(), -100),
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL);
+
+-- saving product, account
+SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id);
+SET @saving_prod_id = -1;
+SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name;
+
+SET @client_id = -1;
+SELECT id INTO @client_id FROM m_client WHERE fullname = 'InteropMerchant';
+
+INSERT INTO `m_savings_account`
+(`account_no`, `external_id`, `client_id`, `group_id`, `product_id`, `field_officer_id`, `status_enum`,
+ `sub_status_enum`, `account_type_enum`, `deposit_type_enum`, `submittedon_date`, `submittedon_userid`,
+ `approvedon_date`, `approvedon_userid`, `activatedon_date`, `activatedon_userid`,
+ `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`,
+ `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`,
+ `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `withdrawal_fee_for_transfer`,
+ `allow_overdraft`, `account_balance_derived`, `min_required_balance`, `enforce_min_required_balance`,
+ `version`, `withhold_tax`)
+VALUES (@saving_account_no, '9062b90de19b43989005d9', @client_id, NULL, @saving_prod_id, NULL, 300, 0, 1, 100, ADDDATE(curdate(), -100),
+  NULL, ADDDATE(curdate(), -100), NULL, ADDDATE(curdate(), -100), NULL, 'TZS', 2, NULL, 1.000000, 1, 4, 1, -- 29. - 4
+  360, NULL, 1, 1, 100000000.000000, 0.000000, 1, 1, 0);
+
+-- interop_identifier
+SET @saving_acc_id = -1;
+SELECT id INTO @saving_acc_id FROM m_savings_account WHERE account_no = @saving_account_no;
+
+INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on)
+VALUES (NULL, @saving_acc_id, 'IBAN', 'IC11in02tn049062b90de19b43989005d9', NULL, 'operator', CURDATE(), 'operator',
+        CURDATE());
+INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on)
+VALUES (NULL, @saving_acc_id, 'MSISDN', '27710204999', NULL, 'operator', CURDATE(), 'operator', CURDATE());
+
+-- charge, mapping
+-- gl_account, mappings
+-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5
+SET @charge_name = 'Interoperation Withdraw Fee';
+
+INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_calculation_enum`,
+                                        `amount`, `amount_outstanding_derived`,`is_paid_derived`, `waived`, `is_active`)
+VALUES (@saving_acc_id, (SELECT id FROM m_charge WHERE name = @charge_name), 0, 5, 1, 1.000000, 0.000000, 0, 0, 1);


[fineract] 01/02: interoperable - service

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

conradsp pushed a commit to branch marta-jankovics-mojaloop-fineract_1.0
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 6e52d686e63e6f15f598acb9f311d51e72a9e184
Author: Marti <ma...@dpc.hu>
AuthorDate: Fri Mar 8 12:01:34 2019 +0100

    interoperable - service
---
 .../interoperation/api/InteropApiResource.java     | 317 +++++++++++++++
 .../interoperation/api/InteropWrapperBuilder.java  |  82 ++++
 .../interoperation/data/ExtensionData.java         |  71 ++++
 .../fineract/interoperation/data/GeoCodeData.java  |  68 ++++
 .../data/InteropIdentifierRequestData.java         |  86 +++++
 .../data/InteropIdentifierResponseData.java        |  58 +++
 .../data/InteropQuoteRequestData.java              | 117 ++++++
 .../data/InteropQuoteResponseData.java             |  88 +++++
 .../interoperation/data/InteropRequestData.java    | 193 +++++++++
 .../interoperation/data/InteropResponseData.java   | 105 +++++
 .../data/InteropTransactionRequestData.java        |  71 ++++
 .../InteropTransactionRequestResponseData.java     |  64 +++
 .../data/InteropTransactionTypeData.java           |  96 +++++
 .../data/InteropTransferRequestData.java           | 111 ++++++
 .../data/InteropTransferResponseData.java          |  79 ++++
 .../fineract/interoperation/data/MoneyData.java    |  86 +++++
 .../interoperation/domain/InteropActionState.java  |  24 ++
 .../interoperation/domain/InteropAmountType.java   |  24 ++
 .../interoperation/domain/InteropIdentifier.java   | 167 ++++++++
 .../domain/InteropIdentifierRepository.java        |  27 ++
 .../domain/InteropIdentifierType.java              |  31 ++
 .../domain/InteropInitiatorType.java               |  26 ++
 .../domain/InteropTransactionRole.java             |  35 ++
 .../domain/InteropTransactionScenario.java         |  28 ++
 .../domain/InteropTransferActionType.java          |  26 ++
 .../handler/CommitInteropTransferHandler.java      |  49 +++
 .../handler/CreateInteropIdentifierHandler.java    |  56 +++
 .../handler/CreateInteropQuoteHandler.java         |  48 +++
 .../handler/CreateInteropRequestHandler.java       |  49 +++
 .../handler/DeleteInteropRequestHandler.java       |  55 +++
 .../handler/PrepareInteropTransferHandler.java     |  49 +++
 .../serialization/InteropDataValidator.java        | 110 ++++++
 .../interoperation/service/InteropService.java     |  60 +++
 .../interoperation/service/InteropServiceImpl.java | 429 +++++++++++++++++++++
 .../fineract/interoperation/util/InteropUtil.java  |  76 ++++
 .../fineract/interoperation/util/MathUtil.java     | 403 +++++++++++++++++++
 36 files changed, 3464 insertions(+)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java
new file mode 100644
index 0000000..1aba6a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java
@@ -0,0 +1,317 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.interoperation.data.InteropIdentifierRequestData;
+import org.apache.fineract.interoperation.data.InteropIdentifierResponseData;
+import org.apache.fineract.interoperation.data.InteropQuoteRequestData;
+import org.apache.fineract.interoperation.data.InteropQuoteResponseData;
+import org.apache.fineract.interoperation.data.InteropTransactionRequestData;
+import org.apache.fineract.interoperation.data.InteropTransactionRequestResponseData;
+import org.apache.fineract.interoperation.data.InteropTransferRequestData;
+import org.apache.fineract.interoperation.data.InteropTransferResponseData;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.domain.InteropTransferActionType;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST;
+import static org.apache.fineract.interoperation.util.InteropUtil.ROOT_PATH;
+
+@Path("/interoperation") //api/v1/
+@Component
+@Scope
+@Api(value = ROOT_PATH, description = "")
+public class InteropApiResource {
+
+    private PlatformSecurityContext context;
+    private ApiRequestParameterHelper apiRequestParameterHelper;
+
+    private DefaultToApiJsonSerializer<CommandProcessingResult> jsonSerializer;
+
+    private InteropService interopService;
+    private PortfolioCommandSourceWritePlatformService commandsSourceService;
+
+    @Autowired
+    public InteropApiResource(PlatformSecurityContext context,
+                              ApiRequestParameterHelper apiRequestParameterHelper,
+                              DefaultToApiJsonSerializer<CommandProcessingResult> defaultToApiJsonSerializer,
+                              InteropService interopService,
+                              PortfolioCommandSourceWritePlatformService portfolioCommandSourceWritePlatformService) {
+        this.context = context;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.jsonSerializer = defaultToApiJsonSerializer;
+        this.interopService = interopService;
+        this.commandsSourceService = portfolioCommandSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("health")
+    @ApiOperation(value = "Query Interoperation Health Request", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK")})
+    public String health(@Context UriInfo uriInfo) {
+        return "OK";
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}")
+    @ApiOperation(value = "Query Interoperation Account by secondary identifier", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String getAccountByIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                         @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                         @Context UriInfo uriInfo) {
+        InteropIdentifierResponseData result = interopService.getAccountByIdentifier(idType, idValue, null);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}/{subIdOrType}")
+    @ApiOperation(value = "Query Interoperation Account by secondary identifier", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String getAccountByIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                         @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                         @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType,
+                                         @Context UriInfo uriInfo) {
+        InteropIdentifierResponseData result = interopService.getAccountByIdentifier(idType, idValue, subIdOrType);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}")
+    @ApiOperation(value = "Interoperation Identifier registration", httpMethod = "POST", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropIdentifierRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String registerAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                            @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                            @ApiParam(hidden = true) String identifierJson, @Context UriInfo uriInfo)
+            throws Throwable {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().registerAccountIdentifier(idType, idValue, null).withJson(identifierJson).build();
+
+        InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}/{subIdOrType}")
+    @ApiOperation(value = "Interoperation Identifier registration", httpMethod = "POST", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropIdentifierRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String registerAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                            @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                            @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType,
+                                            @ApiParam(hidden = true) String identifierJson, @Context UriInfo uriInfo)
+            throws Throwable {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().registerAccountIdentifier(idType, idValue, subIdOrType).withJson(identifierJson).build();
+
+        InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @DELETE
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}")
+    @ApiOperation(value = "Allow Interoperation Identifier registration", httpMethod = "DELETE", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropIdentifierRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String deleteAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                          @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                          @Context UriInfo uriInfo)
+            throws Throwable {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().deleteAccountIdentifier(idType, idValue, null).build();
+
+        InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @DELETE
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("parties/{idType}/{idValue}/{subIdOrType}")
+    @ApiOperation(value = "Allow Interoperation Identifier registration", httpMethod = "DELETE", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropIdentifierRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)})
+    public String deleteAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType,
+                                          @PathParam("idValue") @ApiParam(value = "idValue") String idValue,
+                                          @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType,
+                                          @Context UriInfo uriInfo)
+            throws Throwable {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().deleteAccountIdentifier(idType, idValue, subIdOrType).build();
+
+        InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("transactions/{transactionCode}/requests/{requestCode}")
+    @ApiOperation(value = "Query Interoperation Transaction Request", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransactionRequestResponseData.class)})
+    public String getTransactionRequest(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode,
+                                        @PathParam("requestCode") @ApiParam(value = "requestCode") String requestCode, @Context UriInfo uriInfo) {
+        context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_REQUEST);
+
+        InteropTransactionRequestResponseData result = interopService.getTransactionRequest(transactionCode, requestCode);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("requests")
+    @ApiOperation(value = "Allow Interoperation Transaction Request", httpMethod = "POST", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropTransactionRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransactionRequestResponseData.class)})
+    public String createTransactionRequest(@ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().createTransactionRequest().withJson(quotesJson).build();
+
+        InteropTransactionRequestResponseData result = (InteropTransactionRequestResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("transactions/{transactionCode}/quotes/{quoteCode}")
+    @ApiOperation(value = "Query Interoperation Quote", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropQuoteResponseData.class)})
+    public String getQuote(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode,
+                           @PathParam("quoteCode") @ApiParam(value = "quoteCode") String quoteCode,
+                           @Context UriInfo uriInfo) {
+        context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_QUOTE);
+
+        InteropQuoteResponseData result = interopService.getQuote(transactionCode, quoteCode);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.jsonSerializer.serialize(settings, result);
+    }
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("quotes")
+    @ApiOperation(value = "Calculate Interoperation Quote", httpMethod = "POST", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropQuoteRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropQuoteResponseData.class)})
+    public String createQuote(@ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().createQuotes().withJson(quotesJson).build();
+
+        InteropQuoteResponseData result = (InteropQuoteResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+
+    @GET
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("transactions/{transactionCode}/transfers/{transferCode}")
+    @ApiOperation(value = "Query Interoperation Transfer", httpMethod = "GET", notes = "")
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransferResponseData.class)})
+    public String getTransfer(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode,
+                              @PathParam("transferCode") @ApiParam(value = "transferCode") String transferCode,
+                              @Context UriInfo uriInfo) {
+        context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_QUOTE);
+
+        InteropTransferResponseData result = interopService.getTransfer(transactionCode, transferCode);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return this.jsonSerializer.serialize(settings, result);
+    }
+
+    @POST
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    @Path("transfers")
+    @ApiOperation(value = "Prepare Interoperation Transfer", httpMethod = "POST", notes = "")
+    @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body",
+            dataTypeClass = InteropTransferRequestData.class)})
+    @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransferResponseData.class)})
+    public String performTransfer(@QueryParam("action") @ApiParam(value = "action") String action,
+                                  @ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) {
+        CommandWrapper commandRequest = new InteropWrapperBuilder().performTransfer(InteropTransferActionType.valueOf(action)).withJson(quotesJson).build();
+
+        InteropTransferResponseData result = (InteropTransferResponseData) commandsSourceService.logCommandSource(commandRequest);
+        ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        return jsonSerializer.serialize(settings, result);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java
new file mode 100644
index 0000000..140591a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.api;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.domain.InteropTransferActionType;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_IDENTIFIER;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER;
+import static org.apache.fineract.interoperation.util.InteropUtil.ROOT_PATH;
+
+public class InteropWrapperBuilder {
+
+    private String actionName;
+    private String entityName;
+    private String href;
+    private String json = "{}";
+
+    public CommandWrapper build() {
+        return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null,
+                null, null, null);
+    }
+
+    public InteropWrapperBuilder withJson(final String json) {
+        this.json = json;
+        return this;
+    }
+
+    public InteropWrapperBuilder registerAccountIdentifier(InteropIdentifierType idType, String idValue, String subIdOrType) {
+        this.actionName = "CREATE";
+        this.entityName = ENTITY_NAME_IDENTIFIER;
+        this.href = "/" + ROOT_PATH + "/parties/" + idType + "/" + idValue + "/" + (subIdOrType == null ? "" : subIdOrType);
+        return this;
+    }
+
+    public InteropWrapperBuilder deleteAccountIdentifier(InteropIdentifierType idType, String idValue, String subIdOrType) {
+        this.actionName = "DELETE";
+        this.entityName = ENTITY_NAME_IDENTIFIER;
+        this.href = "/" + ROOT_PATH + "/parties/" + idType + "/" + idValue + "/" + (subIdOrType == null ? "" : subIdOrType);
+        return this;
+    }
+
+    public InteropWrapperBuilder createTransactionRequest() {
+        this.actionName = "CREATE";
+        this.entityName = ENTITY_NAME_REQUEST;
+        this.href = "/" + ROOT_PATH + "/requests";
+        return this;
+    }
+
+    public InteropWrapperBuilder createQuotes() {
+        this.actionName = "CREATE";
+        this.entityName = ENTITY_NAME_QUOTE;
+        this.href = "/" + ROOT_PATH + "/quotes";
+        return this;
+    }
+
+    public InteropWrapperBuilder performTransfer(InteropTransferActionType action) {
+        this.actionName = action.name();
+        this.entityName = ENTITY_NAME_TRANSFER;
+        this.href = "/" + ROOT_PATH + "/transfers";
+        return this;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java
new file mode 100644
index 0000000..e2c4992
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_KEY;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_VALUE;
+
+public class ExtensionData {
+
+    public static final String[] PARAMS = {PARAM_KEY, PARAM_VALUE};
+
+    @NotNull
+    private final String key;
+
+    private String value;
+
+    public ExtensionData(String key, String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public static ExtensionData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        String key = jsonHelper.extractStringNamed(PARAM_KEY, element);
+        DataValidatorBuilder  dataValidatorCopy = dataValidator.reset().parameter(PARAM_KEY).value(key).notBlank();
+
+        String value = jsonHelper.extractStringNamed(PARAM_VALUE, element);
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new ExtensionData(key, value);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java
new file mode 100644
index 0000000..5b8eb8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LATITUDE;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LONGITUDE;
+
+public class GeoCodeData {
+
+    public static final String[] PARAMS = {PARAM_LATITUDE, PARAM_LONGITUDE};
+
+    @NotNull
+    private final String latitude;
+    @NotNull
+    private final String longitude;
+
+    public GeoCodeData(String latitude, String longitude) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    public String getLatitude() {
+        return latitude;
+    }
+
+    public String getLongitude() {
+        return longitude;
+    }
+
+    public static GeoCodeData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        String latitude = jsonHelper.extractStringNamed(PARAM_LATITUDE, element);
+        DataValidatorBuilder  dataValidatorCopy = dataValidator.reset().parameter(PARAM_LATITUDE).value(latitude).notBlank();
+
+        String longitude = jsonHelper.extractStringNamed(PARAM_LONGITUDE, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_LONGITUDE).value(longitude).notBlank();
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new GeoCodeData(latitude, longitude);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java
new file mode 100644
index 0000000..0347b53
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.bval.constraints.NotEmpty;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.*;
+
+public class InteropIdentifierRequestData {
+
+    static final String[] PARAMS = {PARAM_ACCOUNT_ID};
+
+    @NotEmpty
+    private final InteropIdentifierType idType;
+    @NotEmpty
+    private final String idValue;
+
+    private final String subIdOrType;
+
+    @NotEmpty
+    private final String accountId;
+
+    public InteropIdentifierRequestData(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType, String accountId) {
+        this.idType = idType;
+        this.idValue = idValue;
+        this.subIdOrType = subIdOrType;
+        this.accountId = accountId;
+    }
+
+    public InteropIdentifierType getIdType() {
+        return idType;
+    }
+
+    public String getIdValue() {
+        return idValue;
+    }
+
+    public String getSubIdOrType() {
+        return subIdOrType;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public static InteropIdentifierRequestData validateAndParse(final DataValidatorBuilder dataValidator, @NotNull InteropIdentifierType idType,
+                                                                @NotNull String idValue, String subIdOrType, JsonObject element,
+                                                                FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        String accountId = jsonHelper.extractStringNamed(PARAM_ACCOUNT_ID, element);
+        DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_ACCOUNT_ID).value(accountId).notBlank();
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new InteropIdentifierRequestData(idType, idValue, subIdOrType, accountId);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java
new file mode 100644
index 0000000..1f7b74a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import org.apache.bval.constraints.NotEmpty;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+public class InteropIdentifierResponseData extends CommandProcessingResult {
+
+    @NotEmpty
+    private String accountId;
+
+
+    protected InteropIdentifierResponseData(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly, @NotNull String accountId) {
+        super(resourceId, officeId, commandId, changesOnly);
+        this.accountId = accountId;
+    }
+
+    protected static InteropIdentifierResponseData build(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly, @NotNull String accountId) {
+        return new InteropIdentifierResponseData(resourceId, officeId, commandId, changesOnly, accountId);
+    }
+
+    protected static InteropIdentifierResponseData build(Long commandId, @NotNull String accountId) {
+        return build(null, null, commandId, null, accountId);
+    }
+
+    public static InteropIdentifierResponseData build(@NotNull String accountId) {
+        return build(null, accountId);
+    }
+
+    @NotNull
+    public String getAccountId() {
+        return accountId;
+    }
+
+    protected void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java
new file mode 100644
index 0000000..a5a88cb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.interoperation.domain.InteropAmountType;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.*;
+
+public class InteropQuoteRequestData extends InteropRequestData {
+
+    static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_REQUEST_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_TYPE,
+            PARAM_TRANSACTION_ROLE, PARAM_NOTE, PARAM_GEO_CODE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_QUOTE_CODE,
+            PARAM_AMOUNT_TYPE, PARAM_FEES, PARAM_LOCALE, PARAM_DATE_FORMAT};
+    @NotNull
+    private final String quoteCode;
+    @NotNull
+    private final InteropAmountType amountType;
+
+    private final MoneyData fees; // only for disclosed Payer fees on the Payee side
+
+    public InteropQuoteRequestData(@NotNull String transactionCode, String requestCode, @NotNull String accountId, @NotNull MoneyData amount,
+                                   @NotNull InteropTransactionRole transactionRole, @NotNull InteropTransactionTypeData transactionType,
+                                   String note, GeoCodeData geoCode, LocalDateTime expiration, List<ExtensionData> extensionList,
+                                   @NotNull String quoteCode, @NotNull InteropAmountType amountType, MoneyData fees) {
+        super(transactionCode, requestCode, accountId, amount, transactionRole, transactionType, note, geoCode, expiration, extensionList);
+        this.quoteCode = quoteCode;
+        this.amountType = amountType;
+        this.fees = fees;
+    }
+
+    public InteropQuoteRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull InteropAmountType amountType,
+                                   @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole, @NotNull InteropTransactionTypeData transactionType,
+                                   @NotNull String quoteCode) {
+        this(transactionCode, null, accountId, amount, transactionRole, transactionType, null, null, null, null, quoteCode,
+                amountType, null);
+    }
+
+    private InteropQuoteRequestData(@NotNull InteropRequestData other, @NotNull String quoteCode, @NotNull InteropAmountType amountType,
+                                    MoneyData fees) {
+        this(other.getTransactionCode(), other.getRequestCode(), other.getAccountId(), other.getAmount(), other.getTransactionRole(),
+                other.getTransactionType(), other.getNote(), other.getGeoCode(), other.getExpiration(), other.getExtensionList(),
+                quoteCode, amountType, fees);
+    }
+
+    public String getQuoteCode() {
+        return quoteCode;
+    }
+
+    public InteropAmountType getAmountType() {
+        return amountType;
+    }
+
+    public MoneyData getFees() {
+        return fees;
+    }
+
+    public void normalizeAmounts(@NotNull MonetaryCurrency currency) {
+        super.normalizeAmounts(currency);
+        if (fees != null)
+            fees.normalizeAmount(currency);
+    }
+
+    public static InteropQuoteRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper);
+
+        String quoteCode = jsonHelper.extractStringNamed(PARAM_QUOTE_CODE, element);
+        DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_QUOTE_CODE).value(quoteCode).notBlank();
+
+        String amountTypeString = jsonHelper.extractStringNamed(PARAM_AMOUNT_TYPE, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_AMOUNT_TYPE).value(amountTypeString).notBlank();
+        InteropAmountType amountType = InteropAmountType.valueOf(amountTypeString);
+
+        JsonObject feesElement = jsonHelper.extractJsonObjectNamed(PARAM_FEES, element);
+        dataValidator.merge(dataValidatorCopy);
+        MoneyData fees = MoneyData.validateAndParse(dataValidator, feesElement, jsonHelper);
+
+        String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element);
+        dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_ROLE).value(transactionRoleString).notNull();
+
+        JsonObject transactionTypeElement = jsonHelper.extractJsonObjectNamed(PARAM_TRANSACTION_TYPE, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_TYPE).value(transactionTypeElement).notNull();
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new InteropQuoteRequestData(interopRequestData, quoteCode, amountType, fees);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java
new file mode 100644
index 0000000..bd2d7b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import org.apache.fineract.interoperation.domain.InteropActionState;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+
+public class InteropQuoteResponseData extends InteropResponseData {
+
+    @NotNull
+    private final String quoteCode;
+
+    private MoneyData fspFee;
+
+    private MoneyData fspCommission;
+
+    
+    private InteropQuoteResponseData(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly,
+                                     @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration,
+                                     List<ExtensionData> extensionList, @NotNull String quoteCode, MoneyData fspFee, MoneyData fspCommission) {
+        super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList);
+        this.quoteCode = quoteCode;
+        this.fspFee = fspFee;
+        this.fspCommission = fspCommission;
+    }
+
+    public static InteropQuoteResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                 LocalDateTime expiration, List<ExtensionData> extensionList, @NotNull String quoteCode,
+                                                 MoneyData fspFee, MoneyData fspCommission) {
+        return new InteropQuoteResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList,
+                quoteCode, fspFee, fspCommission);
+    }
+
+    public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration,
+                                                 List<ExtensionData> extensionList, @NotNull String quoteCode, MoneyData fspFee,
+                                                 MoneyData fspCommission) {
+        return build(null, transactionCode, state, expiration, extensionList, quoteCode, fspFee, fspCommission);
+    }
+
+    public static InteropQuoteResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                 @NotNull String quoteCode) {
+        return build(commandId, transactionCode, state, null, null, quoteCode, null, null);
+    }
+
+    public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, @NotNull String quoteCode) {
+        return build(null, transactionCode, state, quoteCode);
+    }
+
+    public String getQuoteCode() {
+        return quoteCode;
+    }
+
+    public MoneyData getFspFee() {
+        return fspFee;
+    }
+
+    public void setFspFee(MoneyData fspFee) {
+        this.fspFee = fspFee;
+    }
+
+    public MoneyData getFspCommission() {
+        return fspCommission;
+    }
+
+    public void setFspCommission(MoneyData fspCommission) {
+        this.fspCommission = fspCommission;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java
new file mode 100644
index 0000000..e05adbc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.*;
+
+public class InteropRequestData {
+
+    @NotNull
+    private final String transactionCode;
+
+    private final String requestCode;
+    @NotNull
+    private final String accountId;
+    @NotNull
+    private final MoneyData amount;
+    @NotNull
+    private final InteropTransactionRole transactionRole;
+
+    private final InteropTransactionTypeData transactionType;
+
+    private String note;
+
+    private GeoCodeData geoCode;
+
+    private LocalDateTime expiration;
+
+    private List<ExtensionData> extensionList;
+
+    protected InteropRequestData(@NotNull String transactionCode, String requestCode, @NotNull String accountId, @NotNull MoneyData amount,
+                                 @NotNull InteropTransactionRole transactionRole, InteropTransactionTypeData transactionType, String note,
+                                 GeoCodeData geoCode, LocalDateTime expiration, List<ExtensionData> extensionList) {
+        this.transactionCode = transactionCode;
+        this.requestCode = requestCode;
+        this.accountId = accountId;
+        this.amount = amount;
+        this.transactionType = transactionType;
+        this.transactionRole = transactionRole;
+        this.note = note;
+        this.geoCode = geoCode;
+        this.expiration = expiration;
+        this.extensionList = extensionList;
+    }
+
+    protected InteropRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole) {
+        this(transactionCode, null, accountId, amount, transactionRole, null, null, null, null, null);
+    }
+
+    @NotNull
+    public String getTransactionCode() {
+        return transactionCode;
+    }
+
+    public String getRequestCode() {
+        return requestCode;
+    }
+
+    @NotNull
+    public String getAccountId() {
+        return accountId;
+    }
+
+    @NotNull
+    public MoneyData getAmount() {
+        return amount;
+    }
+
+    public InteropTransactionTypeData getTransactionType() {
+        return transactionType;
+    }
+
+    @NotNull
+    public InteropTransactionRole getTransactionRole() {
+        return transactionRole;
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+
+    public GeoCodeData getGeoCode() {
+        return geoCode;
+    }
+
+    public void setGeoCode(GeoCodeData geoCode) {
+        this.geoCode = geoCode;
+    }
+
+    public LocalDateTime getExpiration() {
+        return expiration;
+    }
+
+    public LocalDate getExpirationLocalDate() {
+        return expiration == null ? null : expiration.toLocalDate();
+    }
+
+    public void setExpiration(LocalDateTime expiration) {
+        this.expiration = expiration;
+    }
+
+    public List<ExtensionData> getExtensionList() {
+        return extensionList;
+    }
+
+    public void setExtensionList(List<ExtensionData> extensionList) {
+        this.extensionList = extensionList;
+    }
+
+    public void normalizeAmounts(@NotNull MonetaryCurrency currency) {
+        amount.normalizeAmount(currency);
+    }
+
+    public static InteropRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        String transactionCode = jsonHelper.extractStringNamed(PARAM_TRANSACTION_CODE, element);
+        DataValidatorBuilder  dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_CODE).value(transactionCode).notBlank();
+
+        String requestCode = jsonHelper.extractStringNamed(PARAM_REQUEST_CODE, element);
+
+        String accountId = jsonHelper.extractStringNamed(PARAM_ACCOUNT_ID, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_ACCOUNT_ID).value(accountId).notBlank();
+
+        JsonObject moneyElement = jsonHelper.extractJsonObjectNamed(PARAM_AMOUNT, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_AMOUNT).value(moneyElement).notNull();
+        dataValidator.merge(dataValidatorCopy);
+        MoneyData amount = MoneyData.validateAndParse(dataValidator, moneyElement, jsonHelper);
+
+        JsonObject transactionTypeElement = jsonHelper.extractJsonObjectNamed(PARAM_TRANSACTION_TYPE, element);
+        InteropTransactionTypeData transactionType = InteropTransactionTypeData.validateAndParse(dataValidator, transactionTypeElement, jsonHelper);
+
+        String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element);
+        InteropTransactionRole transactionRole = transactionRoleString == null ? InteropTransactionRole.PAYER : InteropTransactionRole.valueOf(transactionRoleString);
+
+        String note = jsonHelper.extractStringNamed(PARAM_NOTE, element);
+
+        JsonObject geoCodeElement = jsonHelper.extractJsonObjectNamed(PARAM_GEO_CODE, element);
+        GeoCodeData geoCode = GeoCodeData.validateAndParse(dataValidator, geoCodeElement, jsonHelper);
+
+        String locale = jsonHelper.extractStringNamed(PARAM_LOCALE, element);
+        LocalDateTime expiration = locale == null
+                ? jsonHelper.extractLocalTimeNamed(PARAM_EXPIRATION, element, ISO8601_DATE_TIME_FORMAT, DEFAULT_LOCALE)
+                : jsonHelper.extractLocalTimeNamed(PARAM_EXPIRATION, element); // PARAM_DATE_FORMAT also must be set
+
+        JsonArray extensionArray = jsonHelper.extractJsonArrayNamed(PARAM_EXTENSION_LIST, element);
+        ArrayList<ExtensionData> extensionList = null;
+        if (extensionArray != null) {
+            extensionList = new ArrayList<>(extensionArray.size());
+            for (JsonElement jsonElement : extensionArray) {
+                if (jsonElement.isJsonObject())
+                    extensionList.add(ExtensionData.validateAndParse(dataValidator, jsonElement.getAsJsonObject(), jsonHelper));
+            }
+        }
+
+        return dataValidator.hasError() ? null : new InteropRequestData(transactionCode, requestCode, accountId, amount,
+                transactionRole, transactionType, note, geoCode, expiration, extensionList);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java
new file mode 100644
index 0000000..aecf2a0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.domain.InteropActionState;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Map;
+
+public class InteropResponseData extends CommandProcessingResult {
+
+    public static final String ISO_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
+//    public static final SimpleDateFormat ISO_DATE_TIME_FORMATTER = new SimpleDateFormat(ISO_DATE_TIME_PATTERN); // TODO: not synchronized
+    public static DateTimeFormatter ISO_DATE_TIME_FORMATTER = DateTimeFormat.forPattern(ISO_DATE_TIME_PATTERN);
+
+    @NotNull
+    private final String transactionCode;
+
+    @NotNull
+    private final InteropActionState state;
+
+    private final String expiration;
+
+    private final List<ExtensionData> extensionList;
+
+
+    protected InteropResponseData(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly, @NotNull String transactionCode,
+                                  @NotNull InteropActionState state, LocalDateTime expiration, List<ExtensionData> extensionList) {
+        super(resourceId, officeId, commandId, changesOnly);
+        this.transactionCode = transactionCode;
+        this.state = state;
+        this.expiration = format(expiration);
+        this.extensionList = extensionList;
+    }
+
+    protected static InteropResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                               LocalDateTime expiration, List<ExtensionData> extensionList) {
+        return new InteropResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList);
+    }
+
+    public static InteropResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                            LocalDateTime expiration, List<ExtensionData> extensionList) {
+        return build(null, transactionCode, state, expiration, extensionList);
+    }
+
+    public static InteropResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state) {
+        return build(commandId, transactionCode, state, null, null);
+    }
+
+    public static InteropResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state) {
+        return build(null, transactionCode, state);
+    }
+
+    public String getTransactionCode() {
+        return transactionCode;
+    }
+
+    public InteropActionState getState() {
+        return state;
+    }
+
+    public String getExpiration() {
+        return expiration;
+    }
+
+    @Transient
+    public LocalDateTime getExpirationDate() {
+        return parse(expiration);
+    }
+
+    public List<ExtensionData> getExtensionList() {
+        return extensionList;
+    }
+
+    protected static LocalDateTime parse(String date) {
+        return date == null ? null : LocalDateTime.parse(date, ISO_DATE_TIME_FORMATTER);
+    }
+
+    protected static String format(LocalDateTime date) {
+        return date == null ? null : date.toString(ISO_DATE_TIME_FORMATTER);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java
new file mode 100644
index 0000000..1912ce1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.*;
+
+public class InteropTransactionRequestData extends InteropRequestData {
+
+    static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_REQUEST_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_ROLE,
+            PARAM_TRANSACTION_TYPE, PARAM_NOTE, PARAM_GEO_CODE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_LOCALE, PARAM_DATE_FORMAT};
+
+
+    public InteropTransactionRequestData(@NotNull String transactionCode, @NotNull String requestCode, @NotNull String accountId,
+                                         @NotNull MoneyData amount, @NotNull InteropTransactionTypeData transactionType, String note,
+                                         GeoCodeData geoCode, LocalDateTime expiration, List<ExtensionData> extensionList) {
+        super(transactionCode, requestCode, accountId, amount, InteropTransactionRole.PAYER, transactionType, note, geoCode, expiration, extensionList);
+    }
+
+    public InteropTransactionRequestData(@NotNull String transactionCode, @NotNull String requestCode, @NotNull String accountId,
+                                         @NotNull MoneyData amount, @NotNull InteropTransactionTypeData transactionType) {
+        this(transactionCode, requestCode, accountId, amount, transactionType, null, null, null, null);
+    }
+
+    private InteropTransactionRequestData(InteropRequestData other) {
+        this(other.getTransactionCode(), other.getRequestCode(), other.getAccountId(), other.getAmount(), other.getTransactionType(),
+                other.getNote(), other.getGeoCode(), other.getExpiration(), other.getExtensionList());
+    }
+
+    public static InteropTransactionRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper);
+
+        DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_REQUEST_CODE).value(interopRequestData.getRequestCode()).notNull();
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_TYPE).value(interopRequestData.getTransactionType()).notNull();
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_ROLE).value(interopRequestData.getTransactionRole()).ignoreIfNull()
+                .isOneOfTheseValues(InteropTransactionRole.PAYER);
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new InteropTransactionRequestData(interopRequestData);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java
new file mode 100644
index 0000000..1743aac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import org.apache.fineract.interoperation.domain.InteropActionState;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+
+public class InteropTransactionRequestResponseData extends InteropResponseData {
+
+    @NotNull
+    private final String requestCode;
+
+
+    private InteropTransactionRequestResponseData(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly,
+                                                  @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration,
+                                                  List<ExtensionData> extensionList, @NotNull String requestCode) {
+        super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList);
+        this.requestCode = requestCode;
+    }
+
+    public static InteropTransactionRequestResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              LocalDateTime expiration, List<ExtensionData> extensionList, @NotNull String requestCode) {
+        return new InteropTransactionRequestResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList, requestCode);
+    }
+
+    public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              LocalDateTime expiration, List<ExtensionData> extensionList, @NotNull String requestCode) {
+        return build(null, transactionCode, state, expiration, extensionList, requestCode);
+    }
+
+    public static InteropTransactionRequestResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              @NotNull String requestCode) {
+        return build(commandId, transactionCode, state, null, null, requestCode);
+    }
+
+    public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              @NotNull String requestCode) {
+        return build(null, transactionCode, state, requestCode);
+    }
+
+    public String getRequestCode() {
+        return requestCode;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java
new file mode 100644
index 0000000..2b9f999
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.interoperation.domain.InteropInitiatorType;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.apache.fineract.interoperation.domain.InteropTransactionScenario;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_INITIATOR;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_INITIATOR_TYPE;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_SCENARIO;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_SUB_SCENARIO;
+
+public class InteropTransactionTypeData {
+
+    public static final String[] PARAMS = {PARAM_SCENARIO, PARAM_SUB_SCENARIO, PARAM_INITIATOR, PARAM_INITIATOR_TYPE};
+
+    @NotNull
+    private final InteropTransactionScenario scenario;
+
+    private final String subScenario;
+    @NotNull
+    private final InteropTransactionRole initiator;
+    @NotNull
+    private final InteropInitiatorType initiatorType;
+
+    public InteropTransactionTypeData(InteropTransactionScenario scenario, String subScenario, InteropTransactionRole initiator, InteropInitiatorType initiatorType) {
+        this.scenario = scenario;
+        this.subScenario = subScenario;
+        this.initiator = initiator;
+        this.initiatorType = initiatorType;
+    }
+
+    public InteropTransactionScenario getScenario() {
+        return scenario;
+    }
+
+    public String getSubScenario() {
+        return subScenario;
+    }
+
+    public InteropTransactionRole getInitiator() {
+        return initiator;
+    }
+
+    public InteropInitiatorType getInitiatorType() {
+        return initiatorType;
+    }
+
+
+    public static InteropTransactionTypeData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        String scenarioString = jsonHelper.extractStringNamed(PARAM_SCENARIO, element);
+        DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_SCENARIO).value(scenarioString).notBlank();
+        InteropTransactionScenario scenario = InteropTransactionScenario.valueOf(scenarioString);
+
+        String subScenario = jsonHelper.extractStringNamed(PARAM_SUB_SCENARIO, element);
+
+        String initiatorString = jsonHelper.extractStringNamed(PARAM_INITIATOR, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_INITIATOR).value(initiatorString).notBlank();
+        InteropTransactionRole initiator = InteropTransactionRole.valueOf(initiatorString);
+
+        String initiatorTypeString = jsonHelper.extractStringNamed(PARAM_INITIATOR_TYPE, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_INITIATOR_TYPE).value(initiatorTypeString).notBlank();
+        InteropInitiatorType initiatorType = InteropInitiatorType.valueOf(initiatorTypeString);
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new InteropTransactionTypeData(scenario, subScenario, initiator, initiatorType);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java
new file mode 100644
index 0000000..849b69d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.interoperation.domain.InteropTransactionRole;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.*;
+
+public class InteropTransferRequestData extends InteropRequestData {
+
+    static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_ROLE, PARAM_TRANSACTION_TYPE,
+            PARAM_NOTE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_TRANSFER_CODE, PARAM_FSP_FEE, PARAM_FSP_COMMISSION,
+            PARAM_LOCALE, PARAM_DATE_FORMAT};
+
+
+    @NotNull
+    private final String transferCode;
+
+    // validation: what was specified in quotes step
+    private MoneyData fspFee;
+
+    private MoneyData fspCommission;
+
+    public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount,
+                                      @NotNull InteropTransactionRole transactionRole, InteropTransactionTypeData transactionType, String note, LocalDateTime expiration,
+                                      List<ExtensionData> extensionList, @NotNull String transferCode, MoneyData fspFee, MoneyData fspCommission) {
+        super(transactionCode, null, accountId, amount, transactionRole, transactionType, note, null, expiration, extensionList);
+        this.transferCode = transferCode;
+        this.fspFee = fspFee;
+        this.fspCommission = fspCommission;
+    }
+
+    public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String transferCode, @NotNull String accountId,
+                                      @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole) {
+        this(transactionCode, accountId, amount, transactionRole, null, null, null, null, transferCode, null, null);
+    }
+
+    private InteropTransferRequestData(InteropRequestData other, @NotNull String transferCode, MoneyData fspFee, MoneyData fspCommission) {
+        this(other.getTransactionCode(), other.getAccountId(), other.getAmount(), other.getTransactionRole(), other.getTransactionType(),
+                other.getNote(), other.getExpiration(), other.getExtensionList(), transferCode, fspFee, fspCommission);
+    }
+
+    public String getTransferCode() {
+        return transferCode;
+    }
+
+    public MoneyData getFspFee() {
+        return fspFee;
+    }
+
+    public MoneyData getFspCommission() {
+        return fspCommission;
+    }
+
+    public void normalizeAmounts(@NotNull MonetaryCurrency currency) {
+        super.normalizeAmounts(currency);
+        if (fspFee != null)
+            fspFee.normalizeAmount(currency);
+    }
+
+    public static InteropTransferRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper);
+
+        String transferCode = jsonHelper.extractStringNamed(PARAM_TRANSFER_CODE, element);
+        DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSFER_CODE).value(transferCode).notBlank();
+
+        JsonObject fspFeeElement = jsonHelper.extractJsonObjectNamed(PARAM_FSP_FEE, element);
+        dataValidator.merge(dataValidatorCopy);
+        MoneyData fspFee = MoneyData.validateAndParse(dataValidator, fspFeeElement, jsonHelper);
+
+        JsonObject fspCommissionElement = jsonHelper.extractJsonObjectNamed(PARAM_FSP_COMMISSION, element);
+        dataValidator.merge(dataValidatorCopy);
+        MoneyData fspCommission = MoneyData.validateAndParse(dataValidator, fspCommissionElement, jsonHelper);
+
+        String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element);
+        dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_ROLE).value(transactionRoleString).notNull();
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new InteropTransferRequestData(interopRequestData, transferCode, fspFee, fspCommission);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java
new file mode 100644
index 0000000..bc89172
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import org.apache.fineract.interoperation.domain.InteropActionState;
+import org.joda.time.LocalDateTime;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+
+public class InteropTransferResponseData extends InteropResponseData {
+
+    @NotNull
+    private final String transferCode;
+
+    private String completedTimestamp;
+
+    private InteropTransferResponseData(Long resourceId, Long officeId, Long commandId, Map<String, Object> changesOnly,
+                                        @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration,
+                                        List<ExtensionData> extensionList, @NotNull String transferCode, LocalDateTime completedTimestamp) {
+        super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList);
+        this.transferCode = transferCode;
+        this.completedTimestamp = format(completedTimestamp);
+    }
+
+    public static InteropTransferResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                    LocalDateTime expiration, List<ExtensionData> extensionList, @NotNull String transferCode,
+                                                    LocalDateTime completedTimestamp) {
+        return new InteropTransferResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList,
+                transferCode, completedTimestamp);
+    }
+
+    public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                    List<ExtensionData> extensionList, @NotNull String transferCode,
+                                                    LocalDateTime completedTimestamp) {
+        return build(null, transactionCode, state, null, extensionList, transferCode, completedTimestamp);
+    }
+
+    public static InteropTransferResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state,
+                                                    @NotNull String transferCode) {
+        return build(commandId, transactionCode, state, null, null, transferCode, null);
+    }
+
+    public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, @NotNull String transferCode) {
+        return build(null, transactionCode, state, transferCode);
+    }
+
+    public String getTransferCode() {
+        return transferCode;
+    }
+
+    public String getCompletedTimestamp() {
+        return completedTimestamp;
+    }
+
+    @Transient
+    public LocalDateTime getCompletedTimestampDate() throws ParseException {
+        return parse(completedTimestamp);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java
new file mode 100644
index 0000000..4b48298
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.data;
+
+import com.google.gson.JsonObject;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.interoperation.util.MathUtil;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Arrays;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_LOCALE;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_AMOUNT;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_CURRENCY;
+import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LOCALE;
+
+public class MoneyData {
+
+    public static final String[] PARAMS = {PARAM_AMOUNT, PARAM_CURRENCY, PARAM_LOCALE};
+
+    @NotNull
+    private final BigDecimal amount;
+    @NotNull
+    private final String currency;
+
+    MoneyData(BigDecimal amount, String currency) {
+        this.amount = amount;
+        this.currency = currency;
+    }
+
+    public static MoneyData build(BigDecimal amount, String currency) {
+        return new MoneyData(amount, currency);
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void normalizeAmount(@NotNull MonetaryCurrency currency) {
+        if (!currency.getCode().equals(this.currency))
+            throw new UnsupportedOperationException("Internal error: Invalid currency " + currency.getCode());
+        MathUtil.normalizeAmount(amount, currency);
+    }
+
+    public static MoneyData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) {
+        if (element == null)
+            return null;
+
+        jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS));
+
+        String locale = jsonHelper.extractStringNamed(PARAM_LOCALE, element);
+        BigDecimal amount = locale == null
+                ? jsonHelper.extractBigDecimalNamed(PARAM_AMOUNT, element, DEFAULT_LOCALE)
+                : jsonHelper.extractBigDecimalWithLocaleNamed(PARAM_AMOUNT, element);
+        DataValidatorBuilder  dataValidatorCopy = dataValidator.reset().parameter(PARAM_AMOUNT).value(amount).notBlank().zeroOrPositiveAmount();
+
+        String currency = jsonHelper.extractStringNamed(PARAM_CURRENCY, element);
+        dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_CURRENCY).value(currency).notBlank().notExceedingLengthOf(3);
+
+        dataValidator.merge(dataValidatorCopy);
+        return dataValidator.hasError() ? null : new MoneyData(amount, currency);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java
new file mode 100644
index 0000000..73b761d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropActionState {
+    ACCEPTED,
+    REJECTED
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java
new file mode 100644
index 0000000..1c4cab8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropAmountType {
+    SEND,
+    RECEIVE
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java
new file mode 100644
index 0000000..b37c179
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+@Entity
+@Table(name = "interop_identifier", uniqueConstraints = {
+        @UniqueConstraint(name = "uk_hathor_identifier_account", columnNames = {"account_id", "type"}),
+        @UniqueConstraint(name = "uk_hathor_identifier_value", columnNames = {"type", "a_value", "sub_value_or_type"})
+})
+public class InteropIdentifier extends AbstractPersistableCustom<Long> {
+
+    @ManyToOne(optional = false)
+    @JoinColumn(name = "account_id", nullable = false)
+    private SavingsAccount account;
+
+    @Column(name = "type", nullable = false, length = 32)
+    @Enumerated(EnumType.STRING)
+    private InteropIdentifierType type;
+
+    @Column(name = "a_value", nullable = false, length = 128)
+    private String value;
+
+    @Column(name = "sub_value_or_type", length = 128)
+    private String subValueOrType;
+
+    @Column(name = "created_by", nullable = false, length = 32)
+    private String createdBy;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "created_on", nullable = false)
+    private Date createdOn;
+
+    @Column(name = "modified_by", length = 32)
+    private String modifiedBy;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "modified_on")
+    private Date modifiedOn;
+
+
+    protected InteropIdentifier() {
+    }
+
+    public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String value,
+                                   String subValueOrType, @NotNull String createdBy, @NotNull Date createdOn) {
+        this.account = account;
+        this.type = type;
+        this.value = value;
+        this.subValueOrType = subValueOrType;
+        this.createdBy = createdBy;
+        this.createdOn = createdOn;
+    }
+
+    public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String createdBy,
+                                   @NotNull Date createdOn) {
+        this(account, type, null, null, createdBy, createdOn);
+    }
+
+    public SavingsAccount getAccount() {
+        return account;
+    }
+
+    private void setAccount(SavingsAccount account) {
+        this.account = account;
+    }
+
+    public InteropIdentifierType getType() {
+        return type;
+    }
+
+    private void setType(InteropIdentifierType type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getSubValueOrType() {
+        return subValueOrType;
+    }
+
+    public void setSubValueOrType(String subValueOrType) {
+        this.subValueOrType = subValueOrType;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    private void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public Date getCreatedOn() {
+        return createdOn;
+    }
+
+    private void setCreatedOn(Date createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    public String geModifiedBy() {
+        return modifiedBy;
+    }
+
+    public void setModifiedBy(String modifiedBy) {
+        this.modifiedBy = modifiedBy;
+    }
+
+    public Date getModifiedOn() {
+        return modifiedOn;
+    }
+
+    public void setModifiedOn(Date modifiedOn) {
+        this.modifiedOn = modifiedOn;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        InteropIdentifier that = (InteropIdentifier) o;
+
+        if (!account.equals(that.account)) return false;
+        if (type != that.type) return false;
+        if (!value.equals(that.value)) return false;
+        return subValueOrType != null ? subValueOrType.equals(that.subValueOrType) : that.subValueOrType == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type.hashCode();
+        result = 31 * result + value.hashCode();
+        result = 31 * result + (subValueOrType != null ? subValueOrType.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java
new file mode 100644
index 0000000..c9f418c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface InteropIdentifierRepository extends JpaRepository<InteropIdentifier, Long>, JpaSpecificationExecutor<InteropIdentifier> {
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java
new file mode 100644
index 0000000..b0e2e98
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropIdentifierType {
+
+    MSISDN,
+    EMAIL,
+    PERSONAL_ID,
+    BUSINESS,
+    DEVICE,
+    ACCOUNT_ID,
+    IBAN,
+    ALIAS
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java
new file mode 100644
index 0000000..fb6ff8c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropInitiatorType {
+    CONSUMER,
+    AGENT,
+    BUSINESS,
+    DEVICE
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java
new file mode 100644
index 0000000..232bf78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+
+public enum InteropTransactionRole {
+    PAYER,
+    PAYEE,
+    ;
+
+    public boolean isWithdraw() {
+        return this == PAYER;
+    }
+
+    public SavingsAccountTransactionType getTransactionType() {
+        return this == PAYER ? SavingsAccountTransactionType.WITHDRAWAL : SavingsAccountTransactionType.DEPOSIT;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java
new file mode 100644
index 0000000..e3db1ad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropTransactionScenario {
+
+    DEPOSIT,
+    WITHDRAWAL,
+    TRANSFER,
+    PAYMENT,
+    REFUND
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java
new file mode 100644
index 0000000..df4b05d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.domain;
+
+public enum InteropTransferActionType {
+
+    PREPARE,
+    CREATE,
+    ;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java
new file mode 100644
index 0000000..3cf3033
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ACTION_TRANSFER_COMMIT;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER;
+
+@Service
+@CommandType(entity = ENTITY_NAME_TRANSFER, action = ACTION_TRANSFER_COMMIT)
+public class CommitInteropTransferHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public CommitInteropTransferHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.interopService.commitTransfer(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java
new file mode 100644
index 0000000..1b88821
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import com.google.common.base.Strings;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_IDENTIFIER;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST;
+
+@Service
+@CommandType(entity = ENTITY_NAME_IDENTIFIER, action = "CREATE")
+public class CreateInteropIdentifierHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public CreateInteropIdentifierHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        String[] split = command.getUrl().split("/");
+        int length = split.length;
+        String subIdOrType = Strings.emptyToNull(split[length]);
+        String idValue = split[length - 1];
+        InteropIdentifierType idType = InteropIdentifierType.valueOf(split[length - 2].toUpperCase());
+        return this.interopService.registerAccountIdentifier(idType, idValue, subIdOrType, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java
new file mode 100644
index 0000000..e389d4b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE;
+
+@Service
+@CommandType(entity = ENTITY_NAME_QUOTE, action = "CREATE")
+public class CreateInteropQuoteHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public CreateInteropQuoteHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.interopService.createQuote(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java
new file mode 100644
index 0000000..98e2e23
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST;
+
+@Service
+@CommandType(entity = ENTITY_NAME_REQUEST, action = "CREATE")
+public class CreateInteropRequestHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public CreateInteropRequestHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.interopService.createTransactionRequest(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java
new file mode 100644
index 0000000..dda41c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import com.google.common.base.Strings;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST;
+
+@Service
+@CommandType(entity = ENTITY_NAME_REQUEST, action = "DELETE")
+public class DeleteInteropRequestHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public DeleteInteropRequestHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        String[] split = command.getUrl().split("/");
+        int length = split.length;
+        String subIdOrType = Strings.emptyToNull(split[length]);
+        String idValue = split[length - 1];
+        InteropIdentifierType idType = InteropIdentifierType.valueOf(split[length - 2].toUpperCase());
+        return this.interopService.deleteAccountIdentifier(idType, idValue, subIdOrType);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java
new file mode 100644
index 0000000..3994641
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.interoperation.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.ACTION_TRANSFER_PREPARE;
+import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER;
+
+@Service
+@CommandType(entity = ENTITY_NAME_TRANSFER, action = ACTION_TRANSFER_PREPARE)
+public class PrepareInteropTransferHandler implements NewCommandSourceHandler {
+
+    private final InteropService interopService;
+
+    @Autowired
+    public PrepareInteropTransferHandler(InteropService interopService) {
+        this.interopService = interopService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+        return this.interopService.prepareTransfer(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java
new file mode 100644
index 0000000..8d90d5a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.interoperation.data.InteropIdentifierRequestData;
+import org.apache.fineract.interoperation.data.InteropQuoteRequestData;
+import org.apache.fineract.interoperation.data.InteropTransactionRequestData;
+import org.apache.fineract.interoperation.data.InteropTransferRequestData;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+
+@Component
+public class InteropDataValidator {
+
+    private final FromJsonHelper jsonHelper;
+
+    @Autowired
+    public InteropDataValidator(final FromJsonHelper fromJsonHelper) {
+        this.jsonHelper = fromJsonHelper;
+    }
+
+    public InteropIdentifierRequestData validateAndParseCreateIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue,
+                                                                          String subIdOrType, JsonCommand command) {
+        final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.identifier");
+        JsonObject element = extractJsonObject(command);
+
+        InteropIdentifierRequestData result = InteropIdentifierRequestData.validateAndParse(dataValidator, idType, idValue, subIdOrType, element, jsonHelper);
+        throwExceptionIfValidationWarningsExist(dataValidator);
+
+        return result;
+    }
+
+    public InteropTransactionRequestData validateAndParseCreateRequest(JsonCommand command) {
+        final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.request");
+        JsonObject element = extractJsonObject(command);
+
+        InteropTransactionRequestData result = InteropTransactionRequestData.validateAndParse(dataValidator, element, jsonHelper);
+        throwExceptionIfValidationWarningsExist(dataValidator);
+
+        return result;
+    }
+
+    public InteropQuoteRequestData validateAndParseCreateQuote(JsonCommand command) {
+        final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.quote");
+        JsonObject element = extractJsonObject(command);
+
+        InteropQuoteRequestData result = InteropQuoteRequestData.validateAndParse(dataValidator, element, jsonHelper);
+        throwExceptionIfValidationWarningsExist(dataValidator);
+
+        return result;
+    }
+
+    public InteropTransferRequestData validateAndParsePrepareTransfer(JsonCommand command) {
+        return validateAndParseCreateTransfer(command);
+    }
+
+    public InteropTransferRequestData validateAndParseCreateTransfer(JsonCommand command) {
+        final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.transfer");
+        JsonObject element = extractJsonObject(command);
+
+        InteropTransferRequestData result = InteropTransferRequestData.validateAndParse(dataValidator, element, jsonHelper);
+        throwExceptionIfValidationWarningsExist(dataValidator);
+
+        return result;
+    }
+
+    private JsonObject extractJsonObject(JsonCommand command) {
+        String json = command.json();
+        if (StringUtils.isBlank(json))
+            throw new InvalidJsonException();
+
+        final JsonElement element = jsonHelper.parse(json);
+        return element.getAsJsonObject();
+    }
+
+    private void throwExceptionIfValidationWarningsExist(DataValidatorBuilder dataValidator) {
+        if (dataValidator.hasError()) {
+            throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
+                    "Validation errors exist.", dataValidator.getDataValidationErrors());
+        }
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java
new file mode 100644
index 0000000..e0b0e7f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.interoperation.data.InteropIdentifierResponseData;
+import org.apache.fineract.interoperation.data.InteropQuoteResponseData;
+import org.apache.fineract.interoperation.data.InteropTransactionRequestResponseData;
+import org.apache.fineract.interoperation.data.InteropTransferResponseData;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+
+import javax.validation.constraints.NotNull;
+
+public interface InteropService {
+
+    @NotNull
+    InteropIdentifierResponseData getAccountByIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType);
+
+    @NotNull
+    InteropIdentifierResponseData registerAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue,
+                                                            String subIdOrType, @NotNull JsonCommand command);
+
+    @NotNull
+    InteropIdentifierResponseData deleteAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue,
+                                                          String subIdOrType);
+
+    InteropTransactionRequestResponseData getTransactionRequest(@NotNull String transactionCode, @NotNull String requestCode);
+
+    @NotNull
+    InteropTransactionRequestResponseData createTransactionRequest(@NotNull JsonCommand command);
+
+    InteropQuoteResponseData getQuote(@NotNull String transactionCode, @NotNull String quoteCode);
+
+    @NotNull
+    InteropQuoteResponseData createQuote(@NotNull JsonCommand command);
+
+    InteropTransferResponseData getTransfer(@NotNull String transactionCode, @NotNull String transferCode);
+
+    @NotNull
+    InteropTransferResponseData prepareTransfer(@NotNull JsonCommand command);
+
+    @NotNull
+    InteropTransferResponseData commitTransfer(@NotNull JsonCommand command);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
new file mode 100644
index 0000000..51c0b25
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java
@@ -0,0 +1,429 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.service;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.interoperation.data.*;
+import org.apache.fineract.interoperation.domain.InteropIdentifier;
+import org.apache.fineract.interoperation.domain.InteropIdentifierRepository;
+import org.apache.fineract.interoperation.domain.InteropIdentifierType;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
+import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.interoperation.domain.InteropActionState;
+import org.apache.fineract.interoperation.serialization.InteropDataValidator;
+import org.apache.fineract.interoperation.util.MathUtil;
+import org.apache.fineract.portfolio.note.domain.Note;
+import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
+import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository;
+import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
+import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionSummaryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
+import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.jpa.domain.Specifications;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Path;
+import javax.persistence.criteria.Root;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_LOCALE;
+import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_ROUTING_CODE;
+import static org.apache.fineract.interoperation.util.InteropUtil.ISO8601_DATE_TIME_FORMAT;
+
+@Service
+public class InteropServiceImpl implements InteropService {
+
+    private final static Logger LOG = LoggerFactory.getLogger(InteropServiceImpl.class);
+
+    private final PlatformSecurityContext securityContext;
+    private final InteropDataValidator dataValidator;
+
+    private final SavingsAccountRepository savingsAccountRepository;
+    private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
+    private final ApplicationCurrencyRepository currencyRepository;
+    private final NoteRepository noteRepository;
+    private final PaymentTypeRepository paymentTypeRepository;
+    private final InteropIdentifierRepository identifierRepository;
+
+    private final SavingsHelper savingsHelper;
+    private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
+
+    private final SavingsAccountDomainService savingsAccountService;
+    private final PaymentDetailWritePlatformService paymentDetailService;
+
+    @Autowired
+    public InteropServiceImpl(PlatformSecurityContext securityContext,
+                              InteropDataValidator interopDataValidator,
+                              SavingsAccountRepository savingsAccountRepository,
+                              SavingsAccountTransactionRepository savingsAccountTransactionRepository,
+                              ApplicationCurrencyRepository applicationCurrencyRepository,
+                              NoteRepository noteRepository,
+                              PaymentTypeRepository paymentTypeRepository,
+                              InteropIdentifierRepository identifierRepository,
+                              SavingsHelper savingsHelper,
+                              SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
+                              SavingsAccountDomainService savingsAccountService,
+                              PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
+        this.securityContext = securityContext;
+        this.dataValidator = interopDataValidator;
+        this.savingsAccountRepository = savingsAccountRepository;
+        this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
+        this.currencyRepository = applicationCurrencyRepository;
+        this.noteRepository = noteRepository;
+        this.paymentTypeRepository = paymentTypeRepository;
+        this.identifierRepository = identifierRepository;
+        this.savingsHelper = savingsHelper;
+        this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
+        this.savingsAccountService = savingsAccountService;
+        this.paymentDetailService = paymentDetailWritePlatformService;
+    }
+
+    @NotNull
+    @Transactional
+    public InteropIdentifierResponseData getAccountByIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) {
+        InteropIdentifier identifier = findIdentifier(idType, idValue, subIdOrType);
+        if (identifier == null)
+            throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType)));
+
+        return InteropIdentifierResponseData.build(identifier.getAccount().getExternalId());
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropIdentifierResponseData registerAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue,
+                                                                   String subIdOrType, @NotNull JsonCommand command) {
+        InteropIdentifierRequestData request = dataValidator.validateAndParseCreateIdentifier(idType, idValue, subIdOrType, command);
+        //TODO: error handling
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId());
+
+        AppUser createdBy = getLoginUser();
+
+        InteropIdentifier identifier = new InteropIdentifier(savingsAccount, request.getIdType(), request.getIdValue(),
+                request.getSubIdOrType(), createdBy.getUsername(), DateUtils.getDateOfTenant());
+
+        identifierRepository.save(identifier);
+
+        return InteropIdentifierResponseData.build(savingsAccount.getExternalId());
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropIdentifierResponseData deleteAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue,
+                                                          String subIdOrType) {
+        InteropIdentifier identifier = findIdentifier(idType, idValue, subIdOrType);
+        if (identifier == null)
+            throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType)));
+
+        String accountId = identifier.getAccount().getExternalId();
+
+        identifierRepository.delete(identifier);
+
+        return InteropIdentifierResponseData.build(accountId);
+    }
+
+    @Override
+    public InteropTransactionRequestResponseData getTransactionRequest(@NotNull String transactionCode, @NotNull String requestCode) {
+        // always REJECTED until request info is stored
+        return InteropTransactionRequestResponseData.build(transactionCode, InteropActionState.REJECTED, requestCode);
+    }
+
+    @Override
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransactionRequestResponseData createTransactionRequest(@NotNull JsonCommand command) {
+        // only when Payee request transaction from Payer, so here role must be always Payer
+        InteropTransactionRequestData request = dataValidator.validateAndParseCreateRequest(command);
+
+        //TODO: error handling
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+
+        return InteropTransactionRequestResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED,
+                request.getExpiration(), request.getExtensionList(), request.getRequestCode());
+    }
+
+    @Override
+    public InteropQuoteResponseData getQuote(@NotNull String transactionCode, @NotNull String quoteCode) {
+        return null;
+    }
+
+    @Override
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropQuoteResponseData createQuote(@NotNull JsonCommand command) {
+        InteropQuoteRequestData request = dataValidator.validateAndParseCreateQuote(command);
+
+        //TODO: error handling
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+        SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType();
+
+        BigDecimal fee = transactionType.isDebit() ? savingsAccount.calculateWithdrawalFee(request.getAmount().getAmount()) : BigDecimal.ZERO;
+
+        return InteropQuoteResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED,
+                request.getExpiration(), request.getExtensionList(), request.getQuoteCode(), MoneyData.build(fee, savingsAccount.getCurrency().getCode()),
+                null);
+    }
+
+    @Override
+    public InteropTransferResponseData getTransfer(@NotNull String transactionCode, @NotNull String transferCode) {
+        return null;
+    }
+
+    @Override
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransferResponseData prepareTransfer(@NotNull JsonCommand command) {
+        InteropTransferRequestData request = dataValidator.validateAndParsePrepareTransfer(command);
+
+        //TODO: error handling
+        //TODO: REVERSE
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+
+        BigDecimal total = validateTransfer(request, savingsAccount);
+
+        String transferCode = request.getTransferCode();
+        LocalDateTime transactionDate = DateUtils.getLocalDateTimeOfTenant();
+        if (MathUtil.isGreaterThanZero(total)) {
+            if (MathUtil.isLessThan(savingsAccount.getWithdrawableBalance(), total)) {
+                throw new UnsupportedOperationException();
+            }
+            if (findTransaction(savingsAccount, transferCode, SavingsAccountTransactionType.AMOUNT_HOLD) != null)
+                throw new UnsupportedOperationException("Transfer amount was already put on hold " + transferCode);
+
+            PaymentDetail paymentDetail = PaymentDetail.instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null);
+            AppUser appUser = getLoginUser();
+            SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(savingsAccount, savingsAccount.office(),
+                    paymentDetail, transactionDate.toLocalDate(), Money.of(savingsAccount.getCurrency(), total), new Date(),
+                    appUser);
+
+            savingsAccount.holdAmount(total);
+            savingsAccount.addTransaction(transaction);
+
+            savingsAccountRepository.save(savingsAccount);
+        }
+
+        return InteropTransferResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED, request.getExpiration(),
+                request.getExtensionList(), transferCode, transactionDate);
+    }
+
+    @Override
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransferResponseData commitTransfer(@NotNull JsonCommand command) {
+        InteropTransferRequestData request = dataValidator.validateAndParseCreateTransfer(command);
+        SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType();
+        boolean debit = transactionType.isDebit();
+
+        //TODO: error handling
+        //TODO: REVERSE
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request);
+
+        validateTransfer(request, savingsAccount);
+
+        String transferCode = request.getTransferCode();
+        if (findTransaction(savingsAccount, transferCode, debit ? SavingsAccountTransactionType.WITHDRAWAL : SavingsAccountTransactionType.DEPOSIT) != null)
+            throw new UnsupportedOperationException("Transfer was already committed " + transferCode);
+
+        PaymentDetail paymentDetail = PaymentDetail.instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null);
+
+        LocalDateTime transactionDateTime = DateUtils.getLocalDateTimeOfTenant();
+        LocalDate transactionDate = transactionDateTime.toLocalDate();
+        Date createdDate = new Date();
+
+        SavingsAccountTransaction holdTransaction = findTransaction(savingsAccount, transferCode, SavingsAccountTransactionType.AMOUNT_HOLD);
+        if (holdTransaction != null && holdTransaction.getReleaseIdOfHoldAmountTransaction() == null) {
+            AppUser appUser = getLoginUser();
+
+            SavingsAccountTransaction releaseTransaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, appUser);
+            releaseTransaction = savingsAccountTransactionRepository.saveAndFlush(releaseTransaction);
+            holdTransaction.updateReleaseId(releaseTransaction.getId());
+
+            savingsAccount.releaseAmount(holdTransaction.getAmount());
+            savingsAccount.addTransaction(releaseTransaction);
+
+            savingsAccountRepository.save(savingsAccount);
+        }
+
+        BigDecimal amount = request.getAmount().getAmount();
+        DateTimeFormatter fmt = getDateTimeFormatter(command);
+
+        SavingsAccountTransaction transaction;
+        if (debit) {
+            SavingsTransactionBooleanValues transactionValues = new SavingsTransactionBooleanValues(false, true, true, false, false);
+            transaction = savingsAccountService.handleWithdrawal(savingsAccount, fmt, transactionDate, amount,
+                    paymentDetail, transactionValues);
+        }
+        else {
+            transaction = savingsAccountService.handleDeposit(savingsAccount, fmt, transactionDate, amount,
+                    paymentDetail, false, true);
+        }
+
+        String note = request.getNote();
+        if (!StringUtils.isBlank(note)) {
+            noteRepository.save(Note.savingsTransactionNote(savingsAccount, transaction, note));
+        }
+
+        return InteropTransferResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED,
+                request.getExpiration(), request.getExtensionList(), request.getTransferCode(), transactionDateTime);
+    }
+
+    // Util
+
+    private SavingsAccount validateAndGetSavingAccount(String accountId) {
+        SavingsAccount savingsAccount = savingsAccountRepository.findByExternalId(accountId);
+        if (savingsAccount == null)
+            throw new SavingsAccountNotFoundException(accountId);
+        return savingsAccount;
+    }
+
+    private SavingsAccount validateAndGetSavingAccount(@NotNull InteropRequestData request) {
+        //TODO: error handling
+        SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId());
+        savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper);
+
+        ApplicationCurrency currency = currencyRepository.findOneByCode(request.getAmount().getCurrency());
+        if (!savingsAccount.getCurrency().getCode().equals(currency.getCode()))
+            throw new UnsupportedOperationException();
+
+        SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType();
+        if (!savingsAccount.isTransactionAllowed(transactionType, request.getExpirationLocalDate()))
+            throw new UnsupportedOperationException();
+
+        request.normalizeAmounts(savingsAccount.getCurrency());
+        if (transactionType.isDebit() && MathUtil.isLessThan(savingsAccount.getWithdrawableBalance(), request.getAmount().getAmount()))
+            throw new UnsupportedOperationException();
+
+        return savingsAccount;
+    }
+
+    private BigDecimal validateTransfer(@NotNull InteropTransferRequestData request, @NotNull SavingsAccount savingsAccount) {
+        BigDecimal amount = request.getAmount().getAmount();
+        SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType();
+
+        BigDecimal total = transactionType.isDebit() ? amount : MathUtil.negate(amount);
+        MoneyData fspFee = request.getFspFee();
+        if (fspFee != null) {
+            if (!savingsAccount.getCurrency().getCode().equals(fspFee.getCurrency()))
+                throw new UnsupportedOperationException();
+            //TODO: compare with calculated quote fee
+            total = MathUtil.add(total, fspFee.getAmount());
+        }
+        MoneyData fspCommission = request.getFspCommission();
+        if (fspCommission != null) {
+            if (!savingsAccount.getCurrency().getCode().equals(fspCommission.getCurrency()))
+                throw new UnsupportedOperationException();
+            //TODO: compare with calculated quote commission
+            total = MathUtil.subtractToZero(total, fspCommission.getAmount());
+        }
+        return total;
+    }
+
+    private DateTimeFormatter getDateTimeFormatter(@NotNull JsonCommand command) {
+        Locale locale = command.extractLocale();
+        if (locale == null)
+            locale = DEFAULT_LOCALE;
+        String dateFormat = command.dateFormat();
+        if (StringUtils.isEmpty(dateFormat))
+            dateFormat = "yyyy-MM-dd HH:mm:ss.SSS";
+
+        return DateTimeFormat.forPattern(dateFormat).withLocale(locale);
+    }
+
+    PaymentType findPaymentType() {
+        List<PaymentType> paymentTypes = paymentTypeRepository.findAll();
+        for (PaymentType paymentType : paymentTypes) {
+            if (!paymentType.isCashPayment())
+                return paymentType;
+            //TODO: for now first not cash is retured:
+            // 1. must be added as initial setup,
+            // 2. if more than one non-cashe type added then update this code
+        }
+        return null;
+    }
+
+    SavingsAccountTransaction findTransaction(@NotNull SavingsAccount savingsAccount, @NotNull String transactionCode, SavingsAccountTransactionType transactionType) {
+        String routingCode = getRoutingCode();
+        for (SavingsAccountTransaction transaction : savingsAccount.getTransactions()) {
+            if (transactionType != null && !transactionType.getValue().equals(transaction.getTypeOf()))
+                continue;
+
+            PaymentDetail detail = transaction.getPaymentDetail();
+            if (detail != null && routingCode.equals(detail.getRoutingCode()) && transactionCode.equals(detail.getReceiptNumber()))
+                return transaction;
+        }
+        return null;
+    }
+
+    public InteropIdentifier findIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) {
+        return identifierRepository.findOne(Specifications.where(idTypeEqual(idType)).and(idValueEqual(idValue)).and(subIdOrTypeEqual(subIdOrType)));
+    }
+
+    public static Specification<InteropIdentifier> idTypeEqual(@NotNull InteropIdentifierType idType) {
+        return (Root<InteropIdentifier> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("type"), idType));
+    }
+
+    public static Specification<InteropIdentifier> idValueEqual(@NotNull String idValue) {
+        return (Root<InteropIdentifier> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("value"), idValue));
+    }
+
+    public static Specification<InteropIdentifier> subIdOrTypeEqual(String subIdOrType) {
+        return (Root<InteropIdentifier> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
+            Path<Object> path = root.get("subValueOrType");
+            return cb.and(subIdOrType == null ? cb.isNull(path) : cb.equal(path, subIdOrType));
+        };
+    }
+
+    private AppUser getLoginUser() {
+        return securityContext.getAuthenticatedUserIfPresent();
+    }
+
+    @NotNull
+    String getRoutingCode() {
+        return DEFAULT_ROUTING_CODE;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
new file mode 100644
index 0000000..b33adeb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.util;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import serp.util.Strings;
+
+import java.util.Locale;
+
+public class InteropUtil {
+
+    public static final String ISO8601_DATE_TIME_FORMAT = "yyyy-MM-ddTHH:mm:ss.SSS[-HH:MM]";
+    public static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd";
+
+    public static final Locale DEFAULT_LOCALE = Locale.US;
+
+    public static final String ROOT_PATH = "interoperation";
+    public static final String DEFAULT_ROUTING_CODE = "INTEROPERATION";
+
+    public static final String ENTITY_NAME_IDENTIFIER = "INTERID";
+    public static final String ENTITY_NAME_REQUEST = "INTERREQUEST";
+    public static final String ENTITY_NAME_QUOTE = "INTERQUOTE";
+    public static final String ENTITY_NAME_TRANSFER = "INTERTRANSFER";
+
+    public static final String ACTION_TRANSFER_PREPARE = "PREPARE";
+    public static final String ACTION_TRANSFER_COMMIT = "CREATE";
+
+    public static final String PARAM_LOCALE = "locale";
+    public static final String PARAM_DATE_FORMAT = "dateFormat";
+
+    public static final String PARAM_TRANSACTION_CODE = "transactionCode";
+    public static final String PARAM_REQUEST_CODE = "requestCode";
+    public static final String PARAM_QUOTE_CODE = "quoteCode";
+    public static final String PARAM_TRANSFER_CODE = "transferCode";
+    public static final String PARAM_ACCOUNT_ID = "accountId";
+    public static final String PARAM_AMOUNT_TYPE = "amountType";
+    public static final String PARAM_AMOUNT = "amount";
+    public static final String PARAM_FEES = "fees";
+    public static final String PARAM_FSP_FEE = "fspFee";
+    public static final String PARAM_FSP_COMMISSION = "fspCommission";
+    public static final String PARAM_TRANSACTION_TYPE = "transactionType";
+    public static final String PARAM_TRANSACTION_ROLE = "transactionRole";
+    public static final String PARAM_NOTE = "note";
+    public static final String PARAM_GEO_CODE = "geoCode";
+
+    public static final String PARAM_CURRENCY = "currency";
+
+    public static final String PARAM_SCENARIO = "scenario";
+    public static final String PARAM_SUB_SCENARIO = "subScenario";
+    public static final String PARAM_INITIATOR = "initiator";
+    public static final String PARAM_INITIATOR_TYPE = "initiatorType";
+    public static final String PARAM_EXPIRATION = "expiration";
+
+    public static final String PARAM_LATITUDE = "latitude";
+    public static final String PARAM_LONGITUDE = "latitude";
+
+    public static final String PARAM_EXTENSION_LIST = "extensionList";
+    public static final String PARAM_KEY = "key";
+    public static final String PARAM_VALUE = "value";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java
new file mode 100644
index 0000000..a5c7a78
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file 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,
+ * software distributed under the License is 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.
+ */
+package org.apache.fineract.interoperation.util;
+
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+public class MathUtil {
+
+    public static Long nullToZero(Long value) {
+        return nullToDefault(value, 0L);
+    }
+
+    public static Long nullToDefault(Long value, Long def) {
+        return value == null ? def : value;
+    }
+
+    public static Long zeroToNull(Long value) {
+        return isEmpty(value) ? null : value;
+    }
+
+    /** @return parameter value or ZERO if it is negative */
+    public static Long negativeToZero(Long value) {
+        return isGreaterThanZero(value) ? value : 0L;
+    }
+
+    public static boolean isEmpty(Long value) {
+        return value == null || value.equals(0L);
+    }
+
+    public static boolean isGreaterThanZero(Long value) {
+        return value != null && value > 0L;
+    }
+
+    public static boolean isLessThanZero(Long value) {
+        return value != null && value < 0L;
+    }
+
+    public static boolean isZero(Long value) {
+        return value != null && value.equals(0L);
+    }
+
+    public static boolean isEqualTo(Long first, Long second) {
+        return nullToZero(first).equals(nullToZero(second));
+    }
+
+    public static boolean isGreaterThan(Long first, Long second) {
+        return nullToZero(first) > nullToZero(second);
+    }
+
+    public static boolean isLessThan(Long first, Long second) {
+        return nullToZero(first) < nullToZero(second);
+    }
+
+    public static boolean isGreaterThanOrEqualTo(Long first, Long second) {
+        return nullToZero(first) >= nullToZero(second);
+    }
+
+    public static boolean isLessThanOrEqualZero(Long value) {
+        return nullToZero(value) <= 0L;
+    }
+
+    /** @return parameter value or negated value to positive */
+    public static Long abs(Long value) {
+        return value == null ? 0L : Math.abs(value);
+    }
+
+    /** @return calculates minimum of the two values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static Long min(Long first, Long second, boolean notNull) {
+        return first == null
+                ? (notNull ? second : null)
+                : second == null ? (notNull ? first : null) : Math.min(first, second);
+    }
+
+    /** @return calculates minimum of the values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static Long min(Long first, Long second, Long third, boolean notNull) {
+        return min(min(first, second, notNull), third, notNull);
+    }
+
+    /** @return sum the two values considering null values */
+    public static Long add(Long first, Long second) {
+        return first == null
+                ? second
+                : second == null ? first : Math.addExact(first, second);
+    }
+
+    /** @return sum the values considering null values */
+    public static Long add(Long first, Long second, Long third) {
+        return add(add(first, second), third);
+    }
+
+    /** @return sum the values considering null values */
+    public static Long add(Long first, Long second, Long third, Long fourth) {
+        return add(add(add(first, second), third), fourth);
+    }
+
+    /** @return sum the values considering null values */
+    public static Long add(Long first, Long second, Long third, Long fourth, Long fifth) {
+        return add(add(add(add(first, second), third), fourth), fifth);
+    }
+
+    /** @return first minus second considering null values, maybe negative */
+    public static Long subtract(Long first, Long second) {
+        return first == null
+                ? null
+                : second == null ? first : Math.subtractExact(first, second);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Long subtractToZero(Long first, Long second, Long third) {
+        return subtractToZero(subtract(first, second), third);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Long subtractToZero(Long first, Long second, Long third, Long fourth) {
+        return subtractToZero(subtract(subtract(first, second), third), fourth);
+    }
+
+    /** @return NONE negative first minus second considering null values */
+    public static Long subtractToZero(Long first, Long second) {
+        return negativeToZero(subtract(first, second));
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static Long negate(Long amount) {
+        return isEmpty(amount) ? amount : Math.negateExact(amount);
+    }
+
+
+    // ----------------- BigDecimal -----------------
+
+    public static BigDecimal nullToZero(BigDecimal value) {
+        return nullToDefault(value, BigDecimal.ZERO);
+    }
+
+    public static BigDecimal nullToDefault(BigDecimal value, BigDecimal def) {
+        return value == null ? def : value;
+    }
+
+    public static BigDecimal zeroToNull(BigDecimal value) {
+        return isEmpty(value) ? null : value;
+    }
+
+    /** @return parameter value or ZERO if it is negative */
+    public static BigDecimal negativeToZero(BigDecimal value) {
+        return isGreaterThanZero(value) ? value : BigDecimal.ZERO;
+    }
+
+    public static boolean isEmpty(BigDecimal value) {
+        return value == null || BigDecimal.ZERO.compareTo(value) == 0;
+    }
+
+    public static boolean isGreaterThanZero(BigDecimal value) {
+        return value != null && value.compareTo(BigDecimal.ZERO) > 0;
+    }
+
+    public static boolean isLessThanZero(BigDecimal value) {
+        return value != null && value.compareTo(BigDecimal.ZERO) < 0;
+    }
+
+    public static boolean isZero(BigDecimal value) {
+        return value != null && value.compareTo(BigDecimal.ZERO) == 0;
+    }
+
+    public static boolean isEqualTo(BigDecimal first, BigDecimal second) {
+        return nullToZero(first).compareTo(nullToZero(second)) == 0;
+    }
+
+    public static boolean isGreaterThan(BigDecimal first, BigDecimal second) {
+        return nullToZero(first).compareTo(nullToZero(second)) > 0;
+    }
+
+    public static boolean isLessThan(BigDecimal first, BigDecimal second) {
+        return nullToZero(first).compareTo(nullToZero(second)) < 0;
+    }
+
+    public static boolean isGreaterThanOrEqualTo(BigDecimal first, BigDecimal second) {
+        return nullToZero(first).compareTo(nullToZero(second)) >= 0;
+    }
+
+    public static boolean isLessThanOrEqualZero(BigDecimal value) {
+        return nullToZero(value).compareTo(BigDecimal.ZERO) <= 0;
+    }
+
+    /** @return parameter value or negated value to positive */
+    public static BigDecimal abs(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value.abs();
+    }
+
+    /** @return calculates minimum of the two values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) {
+        return notNull
+                ? first == null
+                ? second
+                : second == null ? first : min(first, second, false)
+                : isLessThan(first, second) ? first : second;
+    }
+
+    /** @return calculates minimum of the values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static BigDecimal min(BigDecimal first, BigDecimal second, BigDecimal third, boolean notNull) {
+        return min(min(first, second, notNull), third, notNull);
+    }
+
+    /** @return sum the two values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second) {
+        return add(first, second, MoneyHelper.getMathContext());
+    }
+
+    /** @return sum the two values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, MathContext mc) {
+        return first == null
+                ? second
+                : second == null ? first : first.add(second, mc);
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third) {
+        return add(first, second, third, MoneyHelper.getMathContext());
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, MathContext mc) {
+        return add(add(first, second, mc), third, mc);
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
+        return add(first, second, third, fourth, MoneyHelper.getMathContext());
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, MathContext mc) {
+        return add(add(add(first, second, mc), third, mc), fourth, mc);
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth) {
+        return add(first, second, third, fourth, fifth, MoneyHelper.getMathContext());
+    }
+
+    /** @return sum the values considering null values */
+    public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth, MathContext mc) {
+        return add(add(add(add(first, second, mc), third, mc), fourth, mc), fifth, mc);
+    }
+
+    /** @return first minus second considering null values, maybe negative */
+    public static BigDecimal subtract(BigDecimal first, BigDecimal second) {
+        return first == null
+                ? null
+                : second == null ? first : first.subtract(second, MoneyHelper.getMathContext());
+    }
+
+    /** @return NONE negative first minus second considering null values */
+    public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second) {
+        return negativeToZero(subtract(first, second));
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third) {
+        MathContext mc = MoneyHelper.getMathContext();
+        return subtractToZero(subtract(first, second), third);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
+        return subtractToZero(subtract(subtract(first, second), third), fourth);
+    }
+
+    /** @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency */
+    public static BigDecimal normalizeAmount(BigDecimal amount, @NotNull MonetaryCurrency currency) {
+        return amount == null ? null : amount.setScale(currency.getDigitsAfterDecimal(), MoneyHelper.getRoundingMode());
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static BigDecimal negate(BigDecimal amount) {
+        return negate(amount, MoneyHelper.getMathContext());
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static BigDecimal negate(BigDecimal amount, MathContext mc) {
+        return isEmpty(amount) ? amount : amount.negate(mc);
+    }
+
+
+    // ----------------- Money -----------------
+
+    public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency) {
+        return nullToDefault(value, Money.zero(currency));
+    }
+
+    public static Money nullToDefault(Money value, Money def) {
+        return value == null ? def : value;
+    }
+
+    public static Money zeroToNull(Money value) {
+        return isEmpty(value) ? null : value;
+    }
+
+    /** @return parameter value or ZERO if it is negative */
+    public static Money negativeToZero(Money value) {
+        return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency());
+    }
+
+    public static boolean isEmpty(Money value) {
+        return value == null || value.isZero();
+    }
+
+    public static boolean isGreaterThanZero(Money value) {
+        return value != null && value.isGreaterThanZero();
+    }
+
+    public static boolean isLessThanZero(Money value) {
+        return value != null && value.isLessThanZero();
+    }
+
+    public static boolean isEqualTo(Money first, Money second) {
+        return first == null ? second == null : (second != null && first.isEqualTo(second));
+    }
+
+    public static boolean isGreaterThan(Money first, Money second) {
+        return second == null || (first != null && first.isGreaterThan(second));
+    }
+
+    public static boolean isLessThan(Money first, Money second) {
+        return first == null || (second != null && first.isLessThan(second));
+    }
+
+    public static Money plus(Money first, Money second) {
+        return first == null
+                ? second
+                : second == null ? first : first.plus(second);
+    }
+
+    public static Money plus(Money first, Money second, Money third) {
+        return plus(plus(first, second), third);
+    }
+
+    public static Money plus(Money first, Money second, Money third, Money fourth) {
+        return plus(plus(plus(first, second), third), fourth);
+    }
+
+    public static Money minus(Money first, Money second) {
+        return first == null
+                ? null
+                : second == null ? first : first.minus(second);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Money minusToZero(Money first, Money second, Money third) {
+        return minusToZero(minus(first, second), third);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Money minusToZero(Money first, Money second, Money third, Money fourth) {
+        return minusToZero(minus(minus(first, second), third), fourth);
+    }
+
+    /** @return NONE negative first minus second considering null values */
+    public static Money minusToZero(Money first, Money second) {
+        return negativeToZero(minus(first, second));
+    }
+
+    /** @return calculates minimum of the two values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static Money min(Money first, Money second, boolean notNull) {
+        return notNull
+                ? first == null
+                ? second
+                : second == null ? first : min(first, second, false)
+                : isLessThan(first, second) ? first : second;
+    }
+
+    /** @return calculates minimum of the values considering null values
+     * @param notNull if true then null parameter is omitted, otherwise returns null */
+    public static Money min(Money first, Money second, Money third, boolean notNull) {
+        return min(min(first, second, notNull), third, notNull);
+    }
+}