interoperable - service
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f1b77db
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.gradle
+.idea
+**/build/
+**/target/
+**/out/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+*.iml
+
+*.log
+
+*.toDelete
\ No newline at end of file
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..90705e0
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,16 @@
+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.
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed 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.
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..822eedb
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Fineract CN Provisioner
+Copyright [2017-2018] The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
\ No newline at end of file
diff --git a/README.md b/README.md
index 07a4dd0..93c01b6 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,26 @@
-fineract-cn-interoperation
+# Apache Fineract CN Provisioner
+
+This service provisions services for tenants of an Apache Fineract CN installation.
+
+## Abstract
+Apache Fineract CN is an application framework for digital financial services, a system to support nationwide and cross-national financial transactions and help to level and speed the creation of an inclusive, interconnected digital economy for every nation in the world.
+
+## Versioning
+The version numbers follow the [Semantic Versioning](http://semver.org/) scheme.
+
+In addition to MAJOR.MINOR.PATCH the following postfixes are used to indicate the development state.
+
+* BUILD-SNAPSHOT - A release currently in development.
+* M - A _milestone_ release include specific sets of functions and are released as soon as the functionality is complete.
+* RC - A _release candidate_ is a version with potential to be a final product, considered _code complete_.
+* RELEASE - indicates that this release is the best available version and is recommended for all usage.
+
+The versioning layout is {MAJOR}.{MINOR}.{PATCH}-{INDICATOR}[.{PATCH}]. Only milestones and release candidates can  have patch versions. Some examples:
+
+1.2.3-BUILD-SNAPSHOT
+1.3.5-M.1
+1.5.7-RC.2
+2.0.0-RELEASE
+
+## License
+See [LICENSE](LICENSE) file.
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..883c7ca
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE'
+    }
+}
+
+plugins {
+    id "com.github.hierynomus.license" version "0.13.1"
+    id("org.nosphere.apache.rat") version "0.3.1"
+}
+
+apply from: '../shared.gradle'
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-feign'],
+            [group: 'org.apache.fineract.cn', name: 'api', version: versions.frameworkapi],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
+            [group: 'org.hibernate', name: 'hibernate-validator-annotation-processor', version: versions.validator],
+            [group: 'org.apache.fineract.cn.deposit-account-management', name: 'api', version: versions.frameworkdeposit]
+    )
+}
+
+publishing {
+    publications {
+        api(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+    }
+}
diff --git a/api/settings.gradle b/api/settings.gradle
new file mode 100644
index 0000000..491f745
--- /dev/null
+++ b/api/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+rootProject.name = 'api'
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/EventConstants.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/EventConstants.java
new file mode 100644
index 0000000..d2d3faf
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/EventConstants.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.cn.interoperation.api.v1;
+
+@SuppressWarnings("unused")
+public interface EventConstants {
+
+  String DESTINATION = "interoperation-v1";
+  String SELECTOR_NAME = "action";
+
+  String OPERATION_HEADER = "operation";
+  String INITIALIZE = "initialize";
+  String SELECTOR_INITIALIZE = OPERATION_HEADER + " = '" + INITIALIZE + "'";
+
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/PermittableGroupIds.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/PermittableGroupIds.java
new file mode 100644
index 0000000..bfee891
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/PermittableGroupIds.java
@@ -0,0 +1,29 @@
+/*
+ * 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.cn.interoperation.api.v1;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+public interface PermittableGroupIds {
+
+  String INTEROPERATION_SINGLE = "interoperation__v1__single";
+  String INTEROPERATION_BULK = "interoperation__v1__bulk";
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/client/InteroperationManager.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/client/InteroperationManager.java
new file mode 100644
index 0000000..0692273
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/client/InteroperationManager.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cn.interoperation.api.v1.client;
+
+import org.apache.fineract.cn.api.util.CustomFeignClientsConfiguration;
+import org.springframework.cloud.netflix.feign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+@SuppressWarnings("unused")
+@FeignClient(value = "interoperation-v1", path = "/interoperation/v1", configuration = CustomFeignClientsConfiguration.class)
+public interface InteroperationManager {
+
+    @RequestMapping(
+            value = "/health",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    void fetchActions();
+
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropActionState.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropActionState.java
new file mode 100644
index 0000000..4bb4001
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/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.cn.interoperation.api.v1.domain;
+
+public enum InteropActionState {
+    ACCEPTED,
+    REJECTED
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropActionType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropActionType.java
new file mode 100644
index 0000000..7e72a73
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropActionType.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.cn.interoperation.api.v1.domain;
+
+public enum InteropActionType {
+
+    REQUEST,
+    QUOTE,
+    PREPARE,
+    COMMIT,
+    ABORT,
+    ;
+
+    public static InteropActionType[] VALUES = values();
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropAmountType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropAmountType.java
new file mode 100644
index 0000000..02dafe4
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropAmountType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.cn.interoperation.api.v1.domain;
+
+public enum InteropAmountType {
+
+    SEND,
+    RECEIVE
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropIdentifierType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropIdentifierType.java
new file mode 100644
index 0000000..08780f4
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/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.cn.interoperation.api.v1.domain;
+
+public enum InteropIdentifierType {
+
+    MSISDN,
+    EMAIL,
+    PERSONAL_ID,
+    BUSINESS,
+    DEVICE,
+    ACCOUNT_ID,
+    IBAN,
+    ALIAS
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropInitiatorType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropInitiatorType.java
new file mode 100644
index 0000000..376a571
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropInitiatorType.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.cn.interoperation.api.v1.domain;
+
+public enum InteropInitiatorType {
+
+    CONSUMER,
+    AGENT,
+    BUSINESS,
+    DEVICE
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropState.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropState.java
new file mode 100644
index 0000000..3cc7505
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropState.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.cn.interoperation.api.v1.domain;
+
+public enum InteropState {
+
+    REQUESTED,
+    QUOTED,
+    PREPARED,
+    COMMITTED,
+    ABORTED,
+    ;
+
+    public static InteropState[] VALUES = values();
+
+
+    public static InteropState forAction(InteropActionType action) {
+        switch (action) {
+            case REQUEST:
+                return REQUESTED;
+            case QUOTE:
+                return QUOTED;
+            case PREPARE:
+                return PREPARED;
+            case COMMIT:
+                return COMMITTED;
+            case ABORT:
+                return ABORTED;
+            default:
+                return null; // TODO fail
+        }
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropStateMachine.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropStateMachine.java
new file mode 100644
index 0000000..e5bc409
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropStateMachine.java
@@ -0,0 +1,57 @@
+/*
+ * 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.cn.interoperation.api.v1.domain;
+
+import org.apache.fineract.cn.lang.ServiceException;
+
+import javax.validation.constraints.NotNull;
+
+public class InteropStateMachine {
+
+    private InteropStateMachine() {
+
+    }
+
+    /**
+     * Executes the given transition from one status to another when the given transition (actualEvent + action) is valid.
+     *
+     * @param currentState actual {@link InteropState}
+     * @param action        the {@link InteropActionType}
+     * @return the new {@link InteropState} for the tag if the given transition is valid
+     *
+     */
+    public static InteropState handleTransition(InteropState currentState, @NotNull InteropActionType action) {
+        InteropState transitionState = getTransitionState(currentState, action);
+        if (transitionState == null) {
+            throw new ServiceException("State transition is not valid: ({0}) and action: ({1})", currentState, action);
+        }
+        return transitionState;
+    }
+
+    public static boolean isValidAction(InteropState currentState, @NotNull InteropActionType action) {
+        if (currentState == null || action == InteropActionType.ABORT)
+            return true;
+        InteropState transitionState = getTransitionState(currentState, action);
+        return transitionState != null && transitionState.ordinal() > currentState.ordinal();
+    }
+
+    private static InteropState getTransitionState(InteropState currentState, @NotNull InteropActionType action) {
+        return InteropState.forAction(action);
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransactionRole.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransactionRole.java
new file mode 100644
index 0000000..2a0b800
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransactionRole.java
@@ -0,0 +1,34 @@
+/*
+ * 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.cn.interoperation.api.v1.domain;
+
+public enum InteropTransactionRole {
+
+    PAYER,
+    PAYEE,
+    ;
+
+    public boolean isWithdraw() {
+        return this == PAYER;
+    }
+
+    public TransactionType getTransactionType() {
+        return this == PAYER ? TransactionType.CURRENCY_WITHDRAWAL : TransactionType.CURRENCY_DEPOSIT;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransactionScenario.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransactionScenario.java
new file mode 100644
index 0000000..de5b776
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/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.cn.interoperation.api.v1.domain;
+
+public enum InteropTransactionScenario {
+
+    DEPOSIT,
+    WITHDRAWAL,
+    TRANSFER,
+    PAYMENT,
+    REFUND
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransferActionType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/InteropTransferActionType.java
new file mode 100644
index 0000000..3f48ecb
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/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.cn.interoperation.api.v1.domain;
+
+public enum InteropTransferActionType {
+
+    PREPARE,
+    CREATE,
+    ;
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/TransactionType.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/TransactionType.java
new file mode 100644
index 0000000..3ecf30e
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/TransactionType.java
@@ -0,0 +1,106 @@
+/*
+ * 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.cn.interoperation.api.v1.domain;
+
+/**
+ * @code org.apache.fineract.cn.accounting.service.internal.repository.AccountEntryEntity.type
+ */
+public enum TransactionType {
+
+    ACCOUNT_OPENING("ACCO"),
+    ACCOUNT_CLOSING("ACCC"),
+    ACCOUNT_TRANSFER("ACCT"),
+
+    ACH_CREDIT("ACDT"),
+    ACH_DEBIT("ADBT"),
+    ACH_ADJUSTMENTS("ADJT"),
+    ACH_PREAUTHORISED("APAC"),
+    ACH_RETURN("ARET"),
+    ACH_REVERSAL("AREV"),
+    ACH_SETTLEMENT("ASET"),
+    ACH_TRANSACTION("ATXN"),
+
+    ARP_DEBIT("ARPD"),
+    CURRENCY_DEPOSIT("FCDP"),
+    CURRENCY_WITHDRAWAL("FCWD"),
+
+    BRANCH_TRANSFER("BACT"),
+    BRANCH_DEPOSIT("BCDP"),
+    BRANCH_WITHDRAWAL("BCWD"),
+
+    CASH_DEPOSIT("CDPT"),
+    CASH_WITHDRAWAL("CWDL"),
+    CASH_LETTER("CASH"),
+
+    CHEQUE("CCHQ"),
+    BRANCH_CHEQUE("BCHQ"),
+    CERTIFIED_CHEQUE("CCCH"),
+    CROSSED_CHEQUE("CRCQ"),
+    CHEQUE_REVERSAL("CQRV"),
+    CHEQUE_OPEN("OPCQ"),
+    CHEQUE_ORDER("ORCQ"),
+
+    CHARGES_PAYMENT("CHRG"),
+    FEES_PAYMENT("FEES"),
+    TAXES_PAYMENT("TAXE"),
+    PRINCIPAL_PAYMENT("PPAY"),
+    INTEREST_PAYMENT("INTR"),
+
+    DIRECTDEBIT_PAYMENT("PMDD"),
+    DIRECTDEBIT_PREAUTHORISED("PADD"),
+    BRANCH_DIRECTDEBIT("BBDD"),
+
+    SMARTCARD_PAYMENT("SMRT"),
+    POINTOFSALE_CREDITCARD("POSC"),
+    POINTOFSALE_DEBITCARD("POSD"),
+    POINTOFSALE_PAYMENT("POSP"),
+
+    DOMESTIC_CREDIT("DMCT"),
+    INTRACOMPANY_TRANSFER("ICCT"),
+
+    MIXED_DEPOSIT("MIXD"),
+    MISCELLANEOUS_DEPOSIT("MSCD"),
+    OTHER("OTHR"),
+
+    CONTROLLED_DISBURSEMENT("CDIS"),
+    CONTROLLED_DISBURSEMENT2("DSBR"),
+    CREDIT_ADJUSTMENT("CAJT"),
+    DEBIT_ADJUSTMENT("DAJT"),
+    EXCHANGERATE_ADJUSTMENT("ERTA"),
+    YID_ADJUSTMENT("YTDA"),
+    REIMBURSEMENT("RIMB"),
+    DRAWDOWN("DDWN"),
+
+    ERROR_NOTAVAILABLE("NTAV"),
+    ERROR_POSTING("PSTE"),
+    ERROR_CANCELLATION("RCDD"),
+    ERROR_CANCELLATION2("RPCR"),
+    ERROR_ZEROBALANCE("ZABA"),
+    ;
+
+    private final String code;
+
+    TransactionType(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/ExtensionData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/ExtensionData.java
new file mode 100644
index 0000000..7cd7e3f
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/ExtensionData.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.cn.interoperation.api.v1.domain.data;
+
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotNull;
+
+public class ExtensionData {
+
+    @NotNull
+    @Length(min = 1, max = 128)
+    private String key;
+
+    @NotNull
+    @Length(min = 1, max = 128)
+    private String value;
+
+    protected ExtensionData() {
+    }
+
+    public ExtensionData(@NotNull String key, @NotNull String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    protected void setKey(String key) {
+        this.key = key;
+    }
+
+    protected void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/GeoCodeData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/GeoCodeData.java
new file mode 100644
index 0000000..ca862db
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/GeoCodeData.java
@@ -0,0 +1,57 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+public class GeoCodeData {
+
+    @NotNull
+    @Pattern(regexp = "^(\\+|-)?(?:90(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\\.[0-9]{1,6})?))$")
+    private String latitude;
+
+    @NotNull
+    @Pattern(regexp = "^(\\+|-)?(?:180(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\\.[0-9]{1,6})?))$")
+    private String longitude;
+
+    protected GeoCodeData() {
+    }
+
+    public GeoCodeData(String latitude, String longitude) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    public String getLatitude() {
+        return latitude;
+    }
+
+    public String getLongitude() {
+        return longitude;
+    }
+
+    protected void setLatitude(String latitude) {
+        this.latitude = latitude;
+    }
+
+    protected void setLongitude(String longitude) {
+        this.longitude = longitude;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierCommand.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierCommand.java
new file mode 100644
index 0000000..ed66827
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropIdentifierType;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.constraints.NotNull;
+
+public class InteropIdentifierCommand extends InteropIdentifierData {
+
+    @NotNull
+    private InteropIdentifierType idType;
+    @NotEmpty
+    @Length(max = 128)
+    private String idValue;
+    @Length(max = 128)
+    private String subIdOrType;
+
+
+    protected InteropIdentifierCommand() {
+    }
+
+    public InteropIdentifierCommand(@NotNull InteropIdentifierData requestData, @NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) {
+        super(requestData.getAccountId());
+        this.idType = idType;
+        this.idValue = idValue;
+        this.subIdOrType = subIdOrType;
+    }
+
+    @NotNull
+    public InteropIdentifierType getIdType() {
+        return idType;
+    }
+
+    @NotNull
+    public String getIdValue() {
+        return idValue;
+    }
+
+    public String getSubIdOrType() {
+        return subIdOrType;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierData.java
new file mode 100644
index 0000000..1872135
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierData.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.cn.interoperation.api.v1.domain.data;
+
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.constraints.NotNull;
+
+public class InteropIdentifierData {
+
+    @NotEmpty
+    @Length(max = 32)
+    private String accountId;
+
+
+    protected InteropIdentifierData() {
+    }
+
+    public InteropIdentifierData(@NotNull String accountId) {
+        this.accountId = accountId;
+    }
+
+    @NotNull
+    public String getAccountId() {
+        return accountId;
+    }
+
+    protected void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierDeleteCommand.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierDeleteCommand.java
new file mode 100644
index 0000000..a98294f
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropIdentifierDeleteCommand.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropIdentifierType;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.constraints.NotNull;
+
+public class InteropIdentifierDeleteCommand {
+
+    @NotNull
+    private InteropIdentifierType idType;
+    @NotEmpty
+    @Length(max = 128)
+    private String idValue;
+    @Length(max = 128)
+    private String subIdOrType;
+
+
+    protected InteropIdentifierDeleteCommand() {
+    }
+
+    public InteropIdentifierDeleteCommand(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) {
+        this.idType = idType;
+        this.idValue = idValue;
+        this.subIdOrType = subIdOrType;
+    }
+
+    @NotNull
+    public InteropIdentifierType getIdType() {
+        return idType;
+    }
+
+    @NotNull
+    public String getIdValue() {
+        return idValue;
+    }
+
+    public String getSubIdOrType() {
+        return subIdOrType;
+    }}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropQuoteRequestData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropQuoteRequestData.java
new file mode 100644
index 0000000..4eaa853
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropAmountType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionRole;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.beans.Transient;
+import java.time.LocalDateTime;
+import java.util.List;
+
+
+public class InteropQuoteRequestData extends InteropRequestData {
+
+    @NotNull
+    @Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
+    private String quoteCode;
+
+    @NotNull
+    private InteropAmountType amountType;
+
+    @Valid
+    private MoneyData fees; // only for disclosed Payer fees on the Payee side
+
+    private InteropQuoteRequestData() {
+        super();
+    }
+
+    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 Currency currency) {
+        super.normalizeAmounts(currency);
+        if (fees != null)
+            fees.normalizeAmount(currency);
+    }
+
+    protected void setQuoteCode(String quoteCode) {
+        this.quoteCode = quoteCode;
+    }
+
+    protected void setAmountType(InteropAmountType amountType) {
+        this.amountType = amountType;
+    }
+
+    protected void setFees(MoneyData fees) {
+        this.fees = fees;
+    }
+
+    @NotNull
+    @Transient
+    @Override
+    public InteropActionType getActionType() {
+        return InteropActionType.QUOTE;
+    }
+
+    @Transient
+    @NotNull
+    @Override
+    public String getIdentifier() {
+        return quoteCode;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropQuoteResponseData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropQuoteResponseData.java
new file mode 100644
index 0000000..f572b8d
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropQuoteResponseData.java
@@ -0,0 +1,69 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class InteropQuoteResponseData extends InteropResponseData {
+
+    @NotNull
+    private final String quoteCode;
+
+    private MoneyData fspFee;
+
+    private MoneyData fspCommission;
+
+    private InteropQuoteResponseData(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, List<ExtensionData> extensionList, @NotNull String quoteCode,
+                                     MoneyData fspFee, MoneyData fspCommission) {
+        super(transactionCode, state, expiration, extensionList);
+        this.quoteCode = quoteCode;
+        this.fspFee = fspFee;
+        this.fspCommission = fspCommission;
+    }
+
+    public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, List<ExtensionData> extensionList,
+                                                 @NotNull String quoteCode, MoneyData fspFee, MoneyData fspCommission) {
+        return new InteropQuoteResponseData(transactionCode, state, expiration, extensionList, quoteCode, fspFee, fspCommission);
+    }
+
+    public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, @NotNull String quoteCode,
+                                                 MoneyData fspFee, MoneyData fspCommission) {
+        return build(transactionCode, state, expiration, null, quoteCode, fspFee, fspCommission);
+    }
+
+    public String getQuoteCode() {
+        return quoteCode;
+    }
+
+    public MoneyData getFspFee() {
+        return fspFee;
+    }
+
+    public MoneyData getFspCommission() {
+        return fspCommission;
+    }
+
+    public void setFspCommission(MoneyData fspCommission) {
+        this.fspCommission = fspCommission;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropRequestData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropRequestData.java
new file mode 100644
index 0000000..c8a5444
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropRequestData.java
@@ -0,0 +1,195 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionRole;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.beans.Transient;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public abstract class InteropRequestData {
+
+    public static final String IDENTIFIER_SEPARATOR = "_";
+
+    @NotNull
+    @Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
+    private String transactionCode;
+
+    @Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
+    private String requestCode;
+
+    @NotEmpty
+    @Length(max = 32)
+    private String accountId;
+
+    @NotNull
+    @Valid
+    private MoneyData amount;
+
+    @NotNull
+    private InteropTransactionRole transactionRole;
+
+    @Valid
+    private InteropTransactionTypeData transactionType;
+
+    @Length(max = 128)
+    private String note;
+
+    @Valid
+    private GeoCodeData geoCode;
+
+    private LocalDateTime expiration;
+
+    @Size(max = 16)
+    @Valid
+    private List<ExtensionData> extensionList;
+
+    protected InteropRequestData() {
+    }
+
+    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 Currency currency) {
+        amount.normalizeAmount(currency);
+    }
+
+    protected void setTransactionCode(String transactionCode) {
+        this.transactionCode = transactionCode;
+    }
+
+    protected void setRequestCode(String requestCode) {
+        this.requestCode = requestCode;
+    }
+
+    protected void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+
+    protected void setAmount(MoneyData amount) {
+        this.amount = amount;
+    }
+
+    protected void setTransactionRole(InteropTransactionRole transactionRole) {
+        this.transactionRole = transactionRole;
+    }
+
+    protected void setTransactionType(InteropTransactionTypeData transactionType) {
+        this.transactionType = transactionType;
+    }
+
+    @Transient
+    public abstract InteropActionType getActionType();
+
+    @Transient
+    @NotNull
+    public String getIdentifier() {
+        return transactionCode;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropResponseData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropResponseData.java
new file mode 100644
index 0000000..d8f7cfd
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropResponseData.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+public abstract class InteropResponseData {
+
+    @NotNull
+    private final String transactionCode;
+
+    @NotNull
+    private final InteropActionState state;
+
+    private final String expiration;
+
+    private final List<ExtensionData> extensionList;
+
+
+    protected InteropResponseData(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, List<ExtensionData> extensionList) {
+        this.transactionCode = transactionCode;
+        this.state = state;
+        this.expiration = format(expiration);
+        this.extensionList = extensionList;
+    }
+
+    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, DateTimeFormatter.ISO_DATE_TIME);
+    }
+
+    protected static String format(LocalDateTime date) {
+        return date == null ? null : date.format(DateTimeFormatter.ISO_DATE_TIME);
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestData.java
new file mode 100644
index 0000000..b150abb
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestData.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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionRole;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class InteropTransactionRequestData extends InteropRequestData {
+
+    protected InteropTransactionRequestData() {
+        super();
+    }
+
+    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());
+    }
+
+    @NotNull
+    @Transient
+    @Override
+    public InteropActionType getActionType() {
+        return InteropActionType.REQUEST;
+    }
+
+    @Transient
+    @NotNull
+    @Override
+    public String getIdentifier() {
+        return getRequestCode();
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestResponseData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestResponseData.java
new file mode 100644
index 0000000..9ea80ca
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionRequestResponseData.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class InteropTransactionRequestResponseData extends InteropResponseData {
+
+    @NotNull
+    @Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
+    private final String requestCode;
+
+    private InteropTransactionRequestResponseData(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, List<ExtensionData> extensionList,
+                                                  @NotNull String requestCode) {
+        super(transactionCode, state, expiration, extensionList);
+        this.requestCode = requestCode;
+    }
+
+    public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              LocalDateTime expiration, List<ExtensionData> extensionList,
+                                                              @NotNull String requestCode) {
+        return new InteropTransactionRequestResponseData(transactionCode, state, expiration, extensionList, requestCode);
+    }
+
+    public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                              LocalDateTime expiration, @NotNull String requestCode) {
+        return build(transactionCode, state, expiration, null, requestCode);
+    }
+
+    public String getRequestCode() {
+        return requestCode;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionTypeData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionTypeData.java
new file mode 100644
index 0000000..19047b5
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransactionTypeData.java
@@ -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.
+ */
+package org.apache.fineract.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropInitiatorType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionRole;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionScenario;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+public class InteropTransactionTypeData {
+
+    @NotNull
+    private InteropTransactionScenario scenario;
+
+    @Pattern(regexp = "^[A-Z_]{1,32}$")
+    private String subScenario;
+
+    @NotNull
+    private InteropTransactionRole initiator;
+
+    @NotNull
+    private InteropInitiatorType initiatorType;
+
+    protected InteropTransactionTypeData() {
+    }
+
+    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;
+    }
+
+    protected void setScenario(InteropTransactionScenario scenario) {
+        this.scenario = scenario;
+    }
+
+    protected void setSubScenario(String subScenario) {
+        this.subScenario = subScenario;
+    }
+
+    protected void setInitiator(InteropTransactionRole initiator) {
+        this.initiator = initiator;
+    }
+
+    protected void setInitiatorType(InteropInitiatorType initiatorType) {
+        this.initiatorType = initiatorType;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferCommand.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferCommand.java
new file mode 100644
index 0000000..15a75cb
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferCommand.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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransferActionType;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+
+public class InteropTransferCommand extends InteropTransferRequestData {
+
+    @NotNull
+    private final InteropTransferActionType action;
+
+    public InteropTransferCommand(@NotNull InteropTransferRequestData requestData, @NotNull InteropTransferActionType action) {
+        super(requestData.getTransactionCode(), requestData.getAccountId(), requestData.getAmount(), requestData.getTransactionRole(),
+                requestData.getNote(), requestData.getExpiration(), requestData.getExtensionList(), requestData.getTransferCode(),
+                requestData.getFspFee(), requestData.getFspCommission());
+        this.action = action;
+    }
+
+    public InteropTransferActionType getAction() {
+        return action;
+    }
+
+    @NotNull
+    @Transient
+    @Override
+    public InteropActionType getActionType() {
+        return action == InteropTransferActionType.PREPARE ? InteropActionType.PREPARE : InteropActionType.COMMIT;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferRequestData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferRequestData.java
new file mode 100644
index 0000000..08b1539
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferRequestData.java
@@ -0,0 +1,97 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransactionRole;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.beans.Transient;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class InteropTransferRequestData extends InteropRequestData {
+
+    @NotNull
+    @Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
+    private String transferCode;
+
+    @Valid
+    private MoneyData fspFee;
+
+    @Valid
+    private MoneyData fspCommission;
+
+    protected InteropTransferRequestData() {
+        super();
+    }
+
+    public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount,
+                                      @NotNull InteropTransactionRole transactionRole, String note, LocalDateTime expiration,
+                                      List<ExtensionData> extensionList, @NotNull String transferCode, MoneyData fspFee, MoneyData fspCommission) {
+        super(transactionCode, null, accountId, amount, transactionRole, null, note, null, expiration, extensionList);
+        this.transferCode = transferCode;
+        this.fspFee = fspFee;
+        this.fspCommission = fspCommission;
+    }
+
+    public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount,
+                                      @NotNull InteropTransactionRole transactionRole, @NotNull String transferCode) {
+        this(transactionCode, accountId, amount, transactionRole, 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.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 Currency currency) {
+        super.normalizeAmounts(currency);
+        if (fspFee != null)
+            fspFee.normalizeAmount(currency);
+    }
+
+    @Transient
+    @Override
+    public InteropActionType getActionType() {
+        return null;
+    }
+
+    @Transient
+    @NotNull
+    @Override
+    public String getIdentifier() {
+        return transferCode;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferResponseData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferResponseData.java
new file mode 100644
index 0000000..2e24a3e
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/InteropTransferResponseData.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+
+import javax.validation.constraints.NotNull;
+import java.beans.Transient;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class InteropTransferResponseData extends InteropResponseData {
+
+    @NotNull
+    private final String transferCode;
+
+    private String completedTimestamp;
+
+    private InteropTransferResponseData(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration,
+                                        List<ExtensionData> extensionList, @NotNull String transferCode, LocalDateTime completedTimestamp) {
+        super(transactionCode, state, expiration, extensionList);
+        this.transferCode = transferCode;
+        this.completedTimestamp = format(completedTimestamp);
+    }
+
+    public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                    LocalDateTime expiration, List<ExtensionData> extensionList,
+                                                    @NotNull String transferCode, LocalDateTime completedTimestamp) {
+        return new InteropTransferResponseData(transactionCode, state, expiration, extensionList, transferCode, completedTimestamp);
+    }
+
+    public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state,
+                                                    LocalDateTime expiration, @NotNull String transferCode, LocalDateTime completedTimestamp) {
+        return build(transactionCode, state, expiration, transferCode, completedTimestamp);
+    }
+
+    public String getTransferCode() {
+        return transferCode;
+    }
+
+    public String getCompletedTimestamp() {
+        return completedTimestamp;
+    }
+
+    @Transient
+    public LocalDateTime getCompletedTimestampDate() {
+        return parse(completedTimestamp);
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/MoneyData.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/MoneyData.java
new file mode 100644
index 0000000..972d838
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/data/MoneyData.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.data;
+
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+import org.apache.fineract.cn.interoperation.api.v1.util.MathUtil;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+public class MoneyData {
+
+    @NotNull
+    @Min(0)
+    @Digits(integer = 15, fraction = 4) // interoperation schema allows integer = 18, AccountEntity amount allows fraction = 5
+    private BigDecimal amount;
+
+    @NotNull
+    @Length(min = 3, max = 3)
+    private String currency;
+
+    protected MoneyData() {
+    }
+
+    MoneyData(BigDecimal amount, String currency) {
+        this.amount = amount;
+        this.currency = currency;
+    }
+
+    public static MoneyData build(BigDecimal amount, String currency) {
+        return amount == null ? null : new MoneyData(amount, currency);
+    }
+
+    public static MoneyData build(BigDecimal amount, Currency currency) {
+        return amount == null ? null : build(amount, currency.getCode());
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void normalizeAmount(@NotNull Currency currency) {
+        if (!currency.getCode().equals(this.currency))
+            throw new UnsupportedOperationException("Internal error: Invalid currency " + currency.getCode());
+        MathUtil.normalize(amount, currency);
+    }
+
+    protected void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    protected void setCurrency(String currency) {
+        this.currency = currency;
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/validation/InteroperationDataValidator.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/validation/InteroperationDataValidator.java
new file mode 100644
index 0000000..2931ecf
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/domain/validation/InteroperationDataValidator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cn.interoperation.api.v1.domain.validation;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropIdentifierCommand;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropIdentifierDeleteCommand;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropQuoteRequestData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransactionRequestData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransferCommand;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class InteroperationDataValidator {
+
+    public InteroperationDataValidator() {
+    }
+
+    public InteropIdentifierCommand registerAccountIdentifier(InteropIdentifierCommand requestData) {
+        return requestData;
+    }
+
+    public InteropIdentifierDeleteCommand deleteAccountIdentifier(InteropIdentifierDeleteCommand requestData) {
+        return requestData;
+    }
+
+    public InteropTransactionRequestData validateCreateRequest(InteropTransactionRequestData requestData) {
+        return requestData;
+    }
+
+    public InteropQuoteRequestData validateCreateQuote(InteropQuoteRequestData requestData) {
+        return requestData;
+    }
+
+    public InteropTransferCommand validatePrepareTransfer(InteropTransferCommand requestData) {
+        return validateCommitTransfer(requestData);
+    }
+
+    public InteropTransferCommand validateCommitTransfer(InteropTransferCommand requestData) {
+        return requestData;
+    }
+
+    private void throwExceptionIfValidationWarningsExist(List<String> errors) {
+        if (errors != null && !errors.isEmpty()) {
+            throw new UnsupportedOperationException(String.join(", ", errors)); // TODO
+        }
+    }
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/InteroperationUtil.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/InteroperationUtil.java
new file mode 100644
index 0000000..b24d5a7
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/InteroperationUtil.java
@@ -0,0 +1,23 @@
+/*
+ * 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.cn.interoperation.api.v1.util;
+
+public class InteroperationUtil {
+
+}
diff --git a/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/MathUtil.java b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/MathUtil.java
new file mode 100644
index 0000000..abfecfc
--- /dev/null
+++ b/api/src/main/java/org/apache/fineract/cn/interoperation/api/v1/util/MathUtil.java
@@ -0,0 +1,325 @@
+/*
+ * 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.cn.interoperation.api.v1.util;
+
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+public class MathUtil {
+
+    public static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(2, RoundingMode.HALF_EVEN);
+    public static final MathContext CALCULATION_MATH_CONTEXT = new MathContext(5, RoundingMode.HALF_EVEN);
+
+    public static Double nullToZero(Double value) {
+        return nullToDefault(value, 0D);
+    }
+
+    public static Double nullToDefault(Double value, Double def) {
+        return value == null ? def : value;
+    }
+
+    public static Double zeroToNull(Double value) {
+        return isEmpty(value) ? null : value;
+    }
+
+    /** @return parameter value or ZERO if it is negative */
+    public static Double negativeToZero(Double value) {
+        return isGreaterThanZero(value) ? value : 0D;
+    }
+
+    public static boolean isEmpty(Double value) {
+        return value == null || value.equals(0D);
+    }
+
+    public static boolean isGreaterThanZero(Double value) {
+        return value != null && value > 0D;
+    }
+
+    public static boolean isLessThanZero(Double value) {
+        return value != null && value < 0D;
+    }
+
+    public static boolean isZero(Double value) {
+        return value != null && value.equals(0D);
+    }
+
+    public static boolean isEqualTo(Double first, Double second) {
+        return nullToZero(first).equals(nullToZero(second));
+    }
+
+    public static boolean isGreaterThan(Double first, Double second) {
+        return nullToZero(first) > nullToZero(second);
+    }
+
+    public static boolean isLessThan(Double first, Double second) {
+        return nullToZero(first) < nullToZero(second);
+    }
+
+    public static boolean isGreaterThanOrEqualTo(Double first, Double second) {
+        return nullToZero(first) >= nullToZero(second);
+    }
+
+    public static boolean isLessThanOrEqualZero(Double value) {
+        return nullToZero(value) <= 0D;
+    }
+
+    /** @return parameter value or negated value to positive */
+    public static Double abs(Double value) {
+        return value == null ? 0D : 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 Double min(Double first, Double 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 Double min(Double first, Double second, Double third, boolean notNull) {
+        return min(min(first, second, notNull), third, notNull);
+    }
+
+    /** @return sum the two values considering null values */
+    public static Double add(Double first, Double second) {
+        return first == null
+                ? second
+                : second == null ? first : first + second;
+    }
+
+    /** @return sum the values considering null values */
+    public static Double add(Double first, Double second, Double third) {
+        return add(add(first, second), third);
+    }
+
+    /** @return sum the values considering null values */
+    public static Double add(Double first, Double second, Double third, Double fourth) {
+        return add(add(add(first, second), third), fourth);
+    }
+
+    /** @return sum the values considering null values */
+    public static Double add(Double first, Double second, Double third, Double fourth, Double fifth) {
+        return add(add(add(add(first, second), third), fourth), fifth);
+    }
+
+    /** @return first minus second considering null values, maybe negative */
+    public static Double subtract(Double first, Double second) {
+        return first == null
+                ? null
+                : second == null ? first : first - second;
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Double subtractToZero(Double first, Double second, Double third) {
+        return subtractToZero(subtract(first, second), third);
+    }
+
+    /** @return first minus the others considering null values, maybe negative */
+    public static Double subtractToZero(Double first, Double second, Double third, Double fourth) {
+        return subtractToZero(subtract(subtract(first, second), third), fourth);
+    }
+
+    /** @return NONE negative first minus second considering null values */
+    public static Double subtractToZero(Double first, Double second) {
+        return negativeToZero(subtract(first, second));
+    }
+
+    /** @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency */
+    public static Double normalize(Double amount, @NotNull Currency currency) {
+        return amount == null ? null : normalize(BigDecimal.valueOf(amount), currency).doubleValue();
+    }
+
+    /** @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency */
+    public static Double normalize(Double amount, @NotNull MathContext mc) {
+        return amount == null ? null : normalize(BigDecimal.valueOf(amount), mc).doubleValue();
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static Double negate(Double amount) {
+        return isEmpty(amount) ? amount : amount * -1;
+    }
+
+
+    // ----------------- 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, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @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, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @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, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @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, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @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, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @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 = CALCULATION_MATH_CONTEXT;
+        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 normalize(BigDecimal amount, @NotNull Currency currency) {
+        return amount == null ? null : amount.setScale(currency.getScale(), CALCULATION_MATH_CONTEXT.getRoundingMode());
+    }
+
+    /** @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency */
+    public static BigDecimal normalize(BigDecimal amount, @NotNull MathContext mc) {
+        return amount == null ? null : amount.setScale(mc.getPrecision(), mc.getRoundingMode());
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static BigDecimal negate(BigDecimal amount) {
+        return negate(amount, CALCULATION_MATH_CONTEXT);
+    }
+
+    /** @return BigDecimal null safe negate */
+    public static BigDecimal negate(BigDecimal amount, MathContext mc) {
+        return isEmpty(amount) ? amount : amount.negate(mc);
+    }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..949f5bb
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+group 'org.apache.fineract.cn'
+
+task publishApiToMavenLocal {
+    dependsOn gradle.includedBuild('api').task(':publishToMavenLocal')
+}
+
+task publishServiceToMavenLocal {
+    mustRunAfter publishApiToMavenLocal
+    dependsOn gradle.includedBuild('service').task(':publishToMavenLocal')
+}
+
+task publishToMavenLocal {
+    group 'all'
+    dependsOn publishApiToMavenLocal
+    dependsOn publishServiceToMavenLocal
+}
+
+task licenseFormat {
+    group 'all'
+    dependsOn gradle.includedBuild('api').task(':licenseFormat')
+    dependsOn gradle.includedBuild('service').task(':licenseFormat')
+}
+
+task rat {
+    group 'all'
+    dependsOn gradle.includedBuild('api').task(':rat')
+    dependsOn gradle.includedBuild('service').task(':rat')
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..811409c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e2f405e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Apr 14 20:59:05 CEST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..f6cb21f
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,173 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+JAVA_OPTS="-Xms512m -Xmx2048m"
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/service/build.gradle b/service/build.gradle
new file mode 100644
index 0000000..5035f25
--- /dev/null
+++ b/service/build.gradle
@@ -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.
+ */
+
+buildscript {
+    ext {
+        springBootVersion = '1.4.1.RELEASE'
+    }
+
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
+    }
+}
+
+plugins {
+    id "com.github.hierynomus.license" version "0.13.1"
+    id("org.nosphere.apache.rat") version "0.3.1"
+}
+
+apply from: '../shared.gradle'
+
+apply plugin: 'spring-boot'
+
+springBoot {
+    executable = true
+    classifier = 'boot'
+}
+
+dependencies {
+    compile(
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'],
+            [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
+            [group: 'com.google.code.gson', name: 'gson'],
+            [group: 'org.apache.fineract.cn', name: 'lang', version: versions.frameworklang],
+            [group: 'org.apache.fineract.cn', name: 'async', version: versions.frameworkasync],
+            [group: 'org.apache.fineract.cn', name: 'cassandra', version: versions.frameworkcassandra],
+            [group: 'org.apache.fineract.cn', name: 'mariadb', version: versions.frameworkmariadb],
+            [group: 'org.apache.fineract.cn', name: 'command', version: versions.frameworkcommand],
+            [group: 'org.apache.fineract.cn.interoperation', name: 'api', version: versions.frameworkinter],
+            [group: 'org.apache.fineract.cn.anubis', name: 'library', version: versions.frameworkanubis],
+            [group: 'org.apache.fineract.cn.accounting', name: 'api', version: versions.frameworkledger],
+//            [group: 'org.apache.fineract.cn.teller', name: 'service', version: versions.frameworkteller],
+    )
+}
+
+publishToMavenLocal.dependsOn bootRepackage
+
+publishing {
+    publications {
+        service(MavenPublication) {
+            from components.java
+            groupId project.group
+            artifactId project.name
+            version project.version
+        }
+        bootService(MavenPublication) {
+            // "boot" jar
+            artifact("$buildDir/libs/$project.name-$version-boot.jar")
+            groupId project.group
+            artifactId("$project.name-boot")
+            version project.version
+        }
+    }
+}
diff --git a/service/settings.gradle b/service/settings.gradle
new file mode 100644
index 0000000..1a4c2fd
--- /dev/null
+++ b/service/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+rootProject.name = 'service'
\ No newline at end of file
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropApplication.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropApplication.java
new file mode 100644
index 0000000..aaa559a
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropApplication.java
@@ -0,0 +1,32 @@
+/*
+ * 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.cn.interoperation.service;
+
+import org.springframework.boot.SpringApplication;
+
+public class InteropApplication {
+
+  public InteropApplication() {
+    super();
+  }
+
+  public static void main(String[] args) {
+    SpringApplication.run(InteropServiceConfiguration.class, args);
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropServiceConfiguration.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropServiceConfiguration.java
new file mode 100644
index 0000000..899d938
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/InteropServiceConfiguration.java
@@ -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.
+ */
+package org.apache.fineract.cn.interoperation.service;
+
+import com.google.gson.Gson;
+import org.apache.fineract.cn.accounting.api.v1.client.LedgerManager;
+import org.apache.fineract.cn.anubis.config.EnableAnubis;
+import org.apache.fineract.cn.async.config.EnableAsync;
+import org.apache.fineract.cn.cassandra.config.EnableCassandra;
+import org.apache.fineract.cn.command.config.EnableCommandProcessing;
+import org.apache.fineract.cn.deposit.api.v1.client.DepositAccountManager;
+import org.apache.fineract.cn.lang.config.EnableServiceException;
+import org.apache.fineract.cn.lang.config.EnableTenantContext;
+import org.apache.fineract.cn.mariadb.config.EnableMariaDB;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.feign.EnableFeignClients;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@SuppressWarnings("WeakerAccess")
+@Configuration
+@EnableAutoConfiguration
+@EnableDiscoveryClient
+@EnableAsync
+@EnableTenantContext
+@EnableCassandra
+@EnableMariaDB
+@EnableCommandProcessing
+@EnableAnubis
+@EnableServiceException
+@EnableFeignClients(clients = {
+        LedgerManager.class,
+        DepositAccountManager.class
+})
+@ComponentScan({
+        "org.apache.fineract.cn.interoperation.service.rest",
+        "org.apache.fineract.cn.interoperation.service.internal.service",
+        "org.apache.fineract.cn.interoperation.service.internal.repository",
+        "org.apache.fineract.cn.interoperation.service.internal.command.handler",
+        "org.apache.fineract.cn.interoperation.api.v1.domain.validation",
+        "org.apache.fineract.cn.interoperation.internal.command.handler"
+})
+@EnableJpaRepositories(basePackages = "org.apache.fineract.cn.interoperation.service.internal.repository")
+@EntityScan(basePackages = "org.apache.fineract.cn.interoperation.service.internal.repository")
+public class InteropServiceConfiguration extends WebMvcConfigurerAdapter {
+
+    public InteropServiceConfiguration() {
+        super();
+    }
+
+    @Bean(name = ServiceConstants.LOGGER_NAME)
+    public Logger logger() {
+        return LoggerFactory.getLogger(ServiceConstants.LOGGER_NAME);
+    }
+
+    @Bean(name = ServiceConstants.GSON_NAME)
+    public Gson gson() {
+        return new Gson();
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/ServiceConstants.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/ServiceConstants.java
new file mode 100644
index 0000000..9df33c5
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/ServiceConstants.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.cn.interoperation.service;
+
+public interface ServiceConstants {
+
+  String LOGGER_NAME = "interoperation-logger";
+  String GSON_NAME = "interoperation-gson";
+
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/InitializeServiceCommand.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/InitializeServiceCommand.java
new file mode 100644
index 0000000..f8acab6
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/InitializeServiceCommand.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.cn.interoperation.service.internal.command;
+
+public class InitializeServiceCommand {
+
+  public InitializeServiceCommand() {
+    super();
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/InteropHandler.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/InteropHandler.java
new file mode 100644
index 0000000..f35d281
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/InteropHandler.java
@@ -0,0 +1,112 @@
+/*
+ * 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.cn.interoperation.service.internal.command.handler;
+
+import org.apache.fineract.cn.command.annotation.Aggregate;
+import org.apache.fineract.cn.command.annotation.CommandHandler;
+import org.apache.fineract.cn.command.annotation.CommandLogLevel;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropIdentifierCommand;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropIdentifierData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropIdentifierDeleteCommand;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropQuoteRequestData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropQuoteResponseData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransactionRequestData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransactionRequestResponseData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransferCommand;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.InteropTransferResponseData;
+import org.apache.fineract.cn.interoperation.api.v1.domain.validation.InteroperationDataValidator;
+import org.apache.fineract.cn.interoperation.service.ServiceConstants;
+import org.apache.fineract.cn.interoperation.service.internal.service.InteropService;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.validation.constraints.NotNull;
+
+@SuppressWarnings("unused")
+@Aggregate
+public class InteropHandler {
+
+    private final Logger logger;
+
+    private final InteroperationDataValidator dataValidator;
+
+    private final InteropService interopService;
+
+    @Autowired
+    public InteropHandler(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                          InteroperationDataValidator interoperationDataValidator,
+                          InteropService interopService) {
+        this.logger = logger;
+        this.dataValidator = interoperationDataValidator;
+        this.interopService = interopService;
+    }
+
+    @NotNull
+    @Transactional
+    @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+    public InteropIdentifierData registerAccountIdentifier(@NotNull InteropIdentifierCommand command) {
+        command = dataValidator.registerAccountIdentifier(command);
+        return interopService.registerAccountIdentifier(command);
+    }
+
+    @NotNull
+    @Transactional
+    @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+    public InteropIdentifierData deleteAccountIdentifier(@NotNull InteropIdentifierDeleteCommand command) {
+        command = dataValidator.deleteAccountIdentifier(command);
+        return interopService.deleteAccountIdentifier(command);
+    }
+
+    @NotNull
+    @Transactional
+    @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+    public InteropTransactionRequestResponseData createTransactionRequest(@NotNull InteropTransactionRequestData command) {
+        // only when Payee request transaction from Payer, so here role must be always Payer
+        command = dataValidator.validateCreateRequest(command);
+        return interopService.createTransactionRequest(command);
+    }
+
+    @NotNull
+    @Transactional
+    @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+    public InteropQuoteResponseData createQuote(@NotNull InteropQuoteRequestData command) {
+        command = dataValidator.validateCreateQuote(command);
+        return interopService.createQuote(command);
+    }
+
+    @NotNull
+    @Transactional
+    @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+    public InteropTransferResponseData performTransfer(@NotNull InteropTransferCommand command) {
+        switch (command.getAction()) {
+            case PREPARE: {
+                command = dataValidator.validatePrepareTransfer(command);
+                return interopService.prepareTransfer(command);
+            }
+            case CREATE: {
+                command = dataValidator.validateCommitTransfer(command);
+                return interopService.commitTransfer(command);
+            }
+            default:
+                return null;
+        }
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/MigrationAggregate.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/MigrationAggregate.java
new file mode 100644
index 0000000..f60bdbc
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/command/handler/MigrationAggregate.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.cn.interoperation.service.internal.command.handler;
+
+import org.apache.fineract.cn.command.annotation.Aggregate;
+import org.apache.fineract.cn.command.annotation.CommandHandler;
+import org.apache.fineract.cn.command.annotation.EventEmitter;
+import org.apache.fineract.cn.interoperation.api.v1.EventConstants;
+import org.apache.fineract.cn.interoperation.service.ServiceConstants;
+import org.apache.fineract.cn.interoperation.service.internal.command.InitializeServiceCommand;
+import org.apache.fineract.cn.lang.ApplicationName;
+import org.apache.fineract.cn.mariadb.domain.FlywayFactoryBean;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.sql.DataSource;
+
+@Aggregate
+public class MigrationAggregate {
+
+  private final Logger logger;
+  private final DataSource dataSource;
+  private final FlywayFactoryBean flywayFactoryBean;
+  private final ApplicationName applicationName;
+
+  @Autowired
+  public MigrationAggregate(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                            final DataSource dataSource,
+                            final FlywayFactoryBean flywayFactoryBean,
+                            final ApplicationName applicationName) {
+    super();
+    this.logger = logger;
+    this.dataSource = dataSource;
+    this.flywayFactoryBean = flywayFactoryBean;
+    this.applicationName = applicationName;
+  }
+
+  @CommandHandler
+  @EventEmitter(selectorName = EventConstants.OPERATION_HEADER, selectorValue = EventConstants.INITIALIZE)
+  @Transactional
+  public String process(final InitializeServiceCommand initializeCommand) {
+    this.logger.info("Starting migration for interoperation version: {}.", applicationName.getVersionString());
+    this.flywayFactoryBean.create(this.dataSource).migrate();
+    return EventConstants.INITIALIZE;
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionEntity.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionEntity.java
new file mode 100644
index 0000000..e4157ab
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionEntity.java
@@ -0,0 +1,250 @@
+/*
+ * 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.cn.interoperation.service.internal.repository;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "hathor_actions", uniqueConstraints = {
+        @UniqueConstraint(name = "uk_hathor_actions_id", columnNames = {"identifier"}),
+        @UniqueConstraint(name = "uk_hathor_actions_type", columnNames = {"transaction_id", "action_type"}),
+        @UniqueConstraint(name = "uk_hathor_actions_seq", columnNames = {"transaction_id", "seq_no"})
+})
+public class InteropActionEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "identifier", nullable = false, length = 64)
+    private String identifier;
+
+    @ManyToOne(optional = false, fetch = FetchType.LAZY)
+    @JoinColumn(name = "transaction_id", referencedColumnName="id", nullable = false)
+    private InteropTransactionEntity transaction;
+
+    @Column(name = "action_type", nullable = false, length = 32)
+    @Enumerated(EnumType.STRING)
+    private InteropActionType actionType;
+
+    @Column(name = "seq_no", nullable = false)
+    private int seqNo;
+
+    @Column(name = "state", nullable = false, length = 32)
+    @Enumerated(EnumType.STRING)
+    private InteropActionState state;
+
+    @Column(name = "amount", nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "fee")
+    private BigDecimal fee;
+
+    @Column(name = "commission")
+    private BigDecimal commission;
+//
+//    @Column(name = "charges", nullable = false, length = 1024)
+//    private String charges;
+//
+//    @Column(name = "ledgers", nullable = false, length = 1024)
+//    private String ledgers;
+
+    @Column(name = "error_code", length = 4)
+    private String errorCode;
+
+    @Column(name = "error_msg", length = 128)
+    private String errorMsg;
+
+    @Column(name = "expiration_date")
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime expirationDate;
+
+    @Column(name = "created_by", nullable = false, length = 32)
+    private String createdBy;
+
+    @Column(name = "created_on", nullable = false)
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime createdOn;
+
+
+    protected InteropActionEntity() {
+    }
+
+    public InteropActionEntity(@NotNull String identifier, @NotNull InteropTransactionEntity transaction, @NotNull InteropActionType actionType,
+                               int seqNo, @NotNull String createdBy, @NotNull LocalDateTime createdOn) {
+        this.identifier = identifier;
+        this.transaction = transaction;
+        this.actionType = actionType;
+        this.seqNo = seqNo;
+        this.createdBy = createdBy;
+        this.createdOn = createdOn;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    private void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    private void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public InteropTransactionEntity getTransaction() {
+        return transaction;
+    }
+
+    private void setTransaction(InteropTransactionEntity transaction) {
+        this.transaction = transaction;
+    }
+
+    public InteropActionType getActionType() {
+        return actionType;
+    }
+
+    private void setActionType(InteropActionType actionType) {
+        this.actionType = actionType;
+    }
+
+    public int getSeqNo() {
+        return seqNo;
+    }
+
+    private void setSeqNo(int seqNo) {
+        this.seqNo = seqNo;
+    }
+
+    public InteropActionState getState() {
+        return state;
+    }
+
+    public void setState(InteropActionState state) {
+        this.state = state;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public BigDecimal getFee() {
+        return fee;
+    }
+
+    public void setFee(BigDecimal fee) {
+        this.fee = fee;
+    }
+
+    public BigDecimal getCommission() {
+        return commission;
+    }
+
+    public void setCommission(BigDecimal commission) {
+        this.commission = commission;
+    }
+//
+//    public String getCharges() {
+//        return charges;
+//    }
+//
+//    public void setCharges(String charges) {
+//        this.charges = charges;
+//    }
+//
+//    public String getLedgers() {
+//        return ledgers;
+//    }
+//
+//    public void setLedgers(String ledgers) {
+//        this.ledgers = ledgers;
+//    }
+
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(String errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public LocalDateTime getExpirationDate() {
+        return expirationDate;
+    }
+
+    public void setExpirationDate(LocalDateTime expirationDate) {
+        this.expirationDate = expirationDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    private void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public LocalDateTime getCreatedOn() {
+        return createdOn;
+    }
+
+    private void setCreatedOn(LocalDateTime createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        InteropActionEntity that = (InteropActionEntity) o;
+
+        return identifier.equals(that.identifier);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionRepository.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionRepository.java
new file mode 100644
index 0000000..de5d46d
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropActionRepository.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cn.interoperation.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import javax.validation.constraints.NotNull;
+
+@Repository
+public interface InteropActionRepository extends JpaRepository<InteropActionEntity, Long> {
+
+    InteropActionEntity findByIdentifier(@NotNull String identifier);
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropIdentifierEntity.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropIdentifierEntity.java
new file mode 100644
index 0000000..7af5745
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropIdentifierEntity.java
@@ -0,0 +1,177 @@
+/*
+ * 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.cn.interoperation.service.internal.repository;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropIdentifierType;
+import org.apache.fineract.cn.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "hathor_identifiers", uniqueConstraints = {
+        @UniqueConstraint(name = "uk_hathor_identifiers_value", columnNames = {"type", "a_value", "sub_value_or_type"})
+})
+public class InteropIdentifierEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "customer_account_identifier", nullable = false, length = 32)
+    private String customerAccountIdentifier;
+
+    @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;
+
+    @Column(name = "created_on", nullable = false)
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime createdOn;
+
+    @Column(name = "last_modified_by", length = 32)
+    private String lastModifiedBy;
+
+    @Column(name = "last_modified_on")
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime lastModifiedOn;
+
+
+    protected InteropIdentifierEntity() {
+    }
+
+    public InteropIdentifierEntity(@NotNull String customerAccountIdentifier, @NotNull InteropIdentifierType type, @NotNull String value,
+                                   String subValueOrType, @NotNull String createdBy, @NotNull LocalDateTime createdOn) {
+        this.customerAccountIdentifier = customerAccountIdentifier;
+        this.type = type;
+        this.value = value;
+        this.subValueOrType = subValueOrType;
+        this.createdBy = createdBy;
+        this.createdOn = createdOn;
+    }
+
+    public InteropIdentifierEntity(@NotNull String customerAccountIdentifier, @NotNull InteropIdentifierType type, @NotNull String createdBy,
+                                   @NotNull LocalDateTime createdOn) {
+        this(customerAccountIdentifier, type, null, null, createdBy, createdOn);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    private void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCustomerAccountIdentifier() {
+        return customerAccountIdentifier;
+    }
+
+    private void setCustomerAccountIdentifier(String customerAccountIdentifier) {
+        this.customerAccountIdentifier = customerAccountIdentifier;
+    }
+
+    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 LocalDateTime getCreatedOn() {
+        return createdOn;
+    }
+
+    private void setCreatedOn(LocalDateTime createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    public String getLastModifiedBy() {
+        return lastModifiedBy;
+    }
+
+    public void setLastModifiedBy(String lastModifiedBy) {
+        this.lastModifiedBy = lastModifiedBy;
+    }
+
+    public LocalDateTime getLastModifiedOn() {
+        return lastModifiedOn;
+    }
+
+    public void setLastModifiedOn(LocalDateTime lastModifiedOn) {
+        this.lastModifiedOn = lastModifiedOn;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        InteropIdentifierEntity that = (InteropIdentifierEntity) o;
+
+        if (!customerAccountIdentifier.equals(that.customerAccountIdentifier)) 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/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropIdentifierRepository.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropIdentifierRepository.java
new file mode 100644
index 0000000..8686e3b
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/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.cn.interoperation.service.internal.repository;
+
+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<InteropIdentifierEntity, Long>, JpaSpecificationExecutor<InteropIdentifierEntity> {
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionEntity.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionEntity.java
new file mode 100644
index 0000000..217f589
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionEntity.java
@@ -0,0 +1,258 @@
+/*
+ * 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.cn.interoperation.service.internal.repository;
+
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropState;
+import org.apache.fineract.cn.interoperation.api.v1.domain.TransactionType;
+import org.apache.fineract.cn.mariadb.util.LocalDateTimeConverter;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Table(name = "hathor_transactions", uniqueConstraints = {@UniqueConstraint(name = "uk_hathor_transactions_id", columnNames = {"identifier"})})
+public class InteropTransactionEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private Long id;
+
+    @Column(name = "identifier", nullable = false, length = 36)
+    private String identifier;
+
+    @Column(name = "a_name", length = 256)
+    private String name;
+
+    @Column(name = "description", length = 1024)
+    private String description;
+
+    @Column(name = "transaction_type", nullable = false, length = 32)
+    @Enumerated(EnumType.STRING)
+    private TransactionType transactionType;
+
+    @Column(name = "amount", nullable = false)
+    private BigDecimal amount;
+
+    @Column(name = "state", nullable = false, length = 32)
+    @Enumerated(EnumType.STRING)
+    private InteropState state;
+
+    @Column(name = "customer_account_identifier", nullable = false, length = 32)
+    private String customerAccountIdentifier;
+
+    @Column(name = "payable_account_identifier", length = 32)
+    private String prepareAccountIdentifier;
+
+    @Column(name = "nostro_account_identifier", nullable = false, length = 32)
+    private String nostroAccountIdentifier;
+
+    @Column(name = "transaction_date")
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime transactionDate;
+
+    @Column(name = "expiration_date")
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime expirationDate;
+
+    @Column(name = "created_by", nullable = false, length = 32)
+    private String createdBy;
+
+    @Column(name = "created_on", nullable = false)
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime createdOn;
+
+    @Column(name = "last_modified_by", length = 32)
+    private String lastModifiedBy;
+
+    @Column(name = "last_modified_on")
+    @Convert(converter = LocalDateTimeConverter.class)
+    private LocalDateTime lastModifiedOn;
+
+    @OneToMany(cascade = CascadeType.ALL, mappedBy = "transaction", orphanRemoval = true, fetch=FetchType.LAZY)
+    @OrderBy("seqNo")
+    private List<InteropActionEntity> actions = new ArrayList<>();
+
+
+    protected InteropTransactionEntity() {
+    }
+
+    public InteropTransactionEntity(@NotNull String identifier, @NotNull String customerAccountIdentifier, @NotNull String createdBy,
+                                    @NotNull LocalDateTime createdOn) {
+        this.identifier = identifier;
+        this.customerAccountIdentifier = customerAccountIdentifier;
+        this.createdBy = createdBy;
+        this.createdOn = createdOn;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    private void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    private void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
+    public void setTransactionType(TransactionType transactionType) {
+        this.transactionType = transactionType;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public InteropState getState() {
+        return state;
+    }
+
+    public void setState(InteropState state) {
+        this.state = state;
+    }
+
+    public String getCustomerAccountIdentifier() {
+        return customerAccountIdentifier;
+    }
+
+    private void setCustomerAccountIdentifier(String customerAccountIdentifier) {
+        this.customerAccountIdentifier = customerAccountIdentifier;
+    }
+
+    public String getPrepareAccountIdentifier() {
+        return prepareAccountIdentifier;
+    }
+
+    public void setPrepareAccountIdentifier(String prepareAccountIdentifier) {
+        this.prepareAccountIdentifier = prepareAccountIdentifier;
+    }
+
+    public String getNostroAccountIdentifier() {
+        return nostroAccountIdentifier;
+    }
+
+    public void setNostroAccountIdentifier(String nostroAccountIdentifier) {
+        this.nostroAccountIdentifier = nostroAccountIdentifier;
+    }
+
+    public LocalDateTime getTransactionDate() {
+        return transactionDate;
+    }
+
+    public void setTransactionDate(LocalDateTime transactionDate) {
+        this.transactionDate = transactionDate;
+    }
+
+    public LocalDateTime getExpirationDate() {
+        return expirationDate;
+    }
+
+    public void setExpirationDate(LocalDateTime expirationDate) {
+        this.expirationDate = expirationDate;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    private void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public LocalDateTime getCreatedOn() {
+        return createdOn;
+    }
+
+    private void setCreatedOn(LocalDateTime createdOn) {
+        this.createdOn = createdOn;
+    }
+
+    public String getLastModifiedBy() {
+        return lastModifiedBy;
+    }
+
+    public void setLastModifiedBy(String lastModifiedBy) {
+        this.lastModifiedBy = lastModifiedBy;
+    }
+
+    public LocalDateTime getLastModifiedOn() {
+        return lastModifiedOn;
+    }
+
+    public void setLastModifiedOn(LocalDateTime lastModifiedOn) {
+        this.lastModifiedOn = lastModifiedOn;
+    }
+
+    public List<InteropActionEntity> getActions() {
+        return actions;
+    }
+
+    public void setActions(List<InteropActionEntity> actions) {
+        this.actions = actions;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        InteropTransactionEntity that = (InteropTransactionEntity) o;
+
+        return identifier.equals(that.identifier);
+    }
+
+    @Override
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionRepository.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionRepository.java
new file mode 100644
index 0000000..d02023d
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/repository/InteropTransactionRepository.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cn.interoperation.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import javax.validation.constraints.NotNull;
+
+@Repository
+public interface InteropTransactionRepository extends JpaRepository<InteropTransactionEntity, Long> {
+
+    InteropTransactionEntity findOneByIdentifier(@NotNull String identifier);
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/InteropService.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/InteropService.java
new file mode 100644
index 0000000..ae9f119
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/InteropService.java
@@ -0,0 +1,847 @@
+/*
+ * 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.cn.interoperation.service.internal.service;
+
+import org.apache.fineract.cn.accounting.api.v1.domain.Account;
+import org.apache.fineract.cn.accounting.api.v1.domain.AccountType;
+import org.apache.fineract.cn.accounting.api.v1.domain.Creditor;
+import org.apache.fineract.cn.accounting.api.v1.domain.Debtor;
+import org.apache.fineract.cn.accounting.api.v1.domain.JournalEntry;
+import org.apache.fineract.cn.api.util.UserContextHolder;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Charge;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Currency;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.ProductDefinition;
+import org.apache.fineract.cn.deposit.api.v1.instance.domain.ProductInstance;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionState;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropIdentifierType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropState;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropStateMachine;
+import org.apache.fineract.cn.interoperation.api.v1.domain.TransactionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.*;
+import org.apache.fineract.cn.interoperation.api.v1.util.MathUtil;
+import org.apache.fineract.cn.interoperation.service.ServiceConstants;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropActionEntity;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropActionRepository;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropIdentifierEntity;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropIdentifierRepository;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropTransactionEntity;
+import org.apache.fineract.cn.interoperation.service.internal.repository.InteropTransactionRepository;
+import org.apache.fineract.cn.interoperation.service.internal.service.helper.InteropAccountingService;
+import org.apache.fineract.cn.interoperation.service.internal.service.helper.InteropDepositService;
+import org.apache.fineract.cn.lang.DateConverter;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+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.math.MathContext;
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+//import static org.apache.fineract.cn.interoperation.api.v1.util.InteroperationUtil.DEFAULT_ROUTING_CODE;
+
+@Service
+public class InteropService {
+
+    public static final String ACCOUNT_NAME_NOSTRO = "Interoperation NOSTRO";
+
+    private final Logger logger;
+
+    private final InteropIdentifierRepository identifierRepository;
+    private final InteropTransactionRepository transactionRepository;
+    private final InteropActionRepository actionRepository;
+
+    private final InteropDepositService depositService;
+    private final InteropAccountingService accountingService;
+
+
+    @Autowired
+    public InteropService(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
+                          InteropIdentifierRepository interopIdentifierRepository,
+                          InteropTransactionRepository interopTransactionRepository,
+                          InteropActionRepository interopActionRepository,
+                          InteropDepositService interopDepositService,
+                          InteropAccountingService interopAccountingService) {
+        this.logger = logger;
+        this.identifierRepository = interopIdentifierRepository;
+        this.transactionRepository = interopTransactionRepository;
+        this.actionRepository = interopActionRepository;
+        this.depositService = interopDepositService;
+        this.accountingService = interopAccountingService;
+    }
+
+    @NotNull
+    public InteropIdentifierData getAccountByIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) {
+        InteropIdentifierEntity identifier = findIdentifier(idType, idValue, subIdOrType);
+        if (identifier == null)
+            throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType)));
+
+        return new InteropIdentifierData(identifier.getCustomerAccountIdentifier());
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropIdentifierData registerAccountIdentifier(@NotNull InteropIdentifierCommand request) {
+        //TODO: error handling
+        String accountId = request.getAccountId();
+        validateAndGetAccount(accountId);
+
+        String createdBy = getLoginUser();
+        LocalDateTime createdOn = getNow();
+
+        InteropIdentifierEntity identifier = new InteropIdentifierEntity(accountId, request.getIdType(), request.getIdValue(),
+                request.getSubIdOrType(), createdBy, createdOn);
+
+        identifierRepository.save(identifier);
+
+        return new InteropIdentifierData(accountId);
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropIdentifierData deleteAccountIdentifier(@NotNull InteropIdentifierDeleteCommand request) {
+        InteropIdentifierType idType = request.getIdType();
+        String idValue = request.getIdValue();
+        String subIdOrType = request.getSubIdOrType();
+
+        InteropIdentifierEntity identifier = findIdentifier(idType, idValue, subIdOrType);
+        if (identifier == null)
+            throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType)));
+
+        String customerAccountIdentifier = identifier.getCustomerAccountIdentifier();
+
+        identifierRepository.delete(identifier);
+
+        return new InteropIdentifierData(customerAccountIdentifier);
+    }
+
+    public InteropTransactionRequestResponseData getTransactionRequest(@NotNull String transactionCode, @NotNull String requestCode) {
+        InteropActionEntity action = validateAndGetAction(transactionCode, calcActionIdentifier(requestCode, InteropActionType.REQUEST),
+                InteropActionType.REQUEST);
+        return InteropTransactionRequestResponseData.build(transactionCode, action.getState(), action.getExpirationDate(), requestCode);
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransactionRequestResponseData createTransactionRequest(@NotNull InteropTransactionRequestData request) {
+        // only when Payee request transaction from Payer, so here role must be always Payer
+        //TODO: error handling
+        AccountWrapper accountWrapper = validateAndGetAccount(request);
+        //TODO: transaction expiration separated from action expiration
+        InteropTransactionEntity transaction = validateAndGetTransaction(request, accountWrapper);
+        InteropActionEntity action = addAction(transaction, request);
+
+        transactionRepository.save(transaction);
+
+        return InteropTransactionRequestResponseData.build(request.getTransactionCode(), action.getState(), action.getExpirationDate(),
+                request.getExtensionList(), request.getRequestCode());
+    }
+
+    public InteropQuoteResponseData getQuote(@NotNull String transactionCode, @NotNull String quoteCode) {
+        InteropActionEntity action = validateAndGetAction(transactionCode, calcActionIdentifier(quoteCode, InteropActionType.QUOTE),
+                InteropActionType.QUOTE);
+
+        Currency currency = getCurrency(action);
+
+        return InteropQuoteResponseData.build(transactionCode, action.getState(), action.getExpirationDate(), quoteCode,
+                MoneyData.build(action.getFee(), currency), MoneyData.build(action.getCommission(), currency));
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropQuoteResponseData createQuote(@NotNull InteropQuoteRequestData request) {
+        //TODO: error handling
+        AccountWrapper accountWrapper = validateAndGetAccount(request);
+        //TODO: transaction expiration separated from action expiration
+        InteropTransactionEntity transaction = validateAndGetTransaction(request, accountWrapper);
+
+        TransactionType transactionType = request.getTransactionRole().getTransactionType();
+        String accountId = accountWrapper.account.getIdentifier();
+        List<Charge> charges = depositService.getCharges(accountId, transactionType);
+
+        BigDecimal amount = request.getAmount().getAmount();
+        BigDecimal fee = MathUtil.normalize(calcTotalCharges(charges, amount), MathUtil.DEFAULT_MATH_CONTEXT);
+
+        Double withdrawableBalance = getWithdrawableBalance(accountWrapper.account, accountWrapper.productDefinition);
+        boolean withdraw = request.getTransactionRole().isWithdraw();
+
+        BigDecimal total = MathUtil.nullToZero(withdraw ? MathUtil.add(amount, fee) : fee);
+        if (withdraw && withdrawableBalance < total.doubleValue())
+            throw new UnsupportedOperationException("Account balance is not enough to pay the fee " + accountId);
+
+        // TODO add action and set the status to failed in separated transaction
+        InteropActionEntity action = addAction(transaction, request);
+        action.setFee(fee);
+        // TODO: extend Charge with a property that could be stored in charges
+
+        transactionRepository.save(transaction);
+
+        InteropQuoteResponseData build = InteropQuoteResponseData.build(request.getTransactionCode(), action.getState(),
+                action.getExpirationDate(), request.getExtensionList(), request.getQuoteCode(),
+                MoneyData.build(fee, accountWrapper.productDefinition.getCurrency()), null);
+        return build;
+    }
+
+    public InteropTransferResponseData getTransfer(@NotNull String transactionCode, @NotNull String transferCode) {
+        InteropActionEntity action = validateAndGetAction(transactionCode, calcActionIdentifier(transferCode, InteropActionType.PREPARE),
+                InteropActionType.PREPARE, false);
+        if (action == null)
+            action = validateAndGetAction(transactionCode, calcActionIdentifier(transferCode, InteropActionType.COMMIT),
+                    InteropActionType.COMMIT);
+
+        return InteropTransferResponseData.build(transactionCode, action.getState(), action.getExpirationDate(), transferCode, action.getCreatedOn());
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransferResponseData prepareTransfer(@NotNull InteropTransferCommand request) {
+        //TODO: error handling
+        //TODO: ABORT
+        AccountWrapper accountWrapper = validateAndGetAccount(request);
+
+        LocalDateTime transactionDate = getNow();
+        //TODO: transaction expiration separated from action expiration
+        InteropTransactionEntity transaction = validateAndGetTransaction(request, accountWrapper, transactionDate, true);
+
+        validateTransfer(request, accountWrapper);
+
+        TransactionType transactionType = request.getTransactionRole().getTransactionType();
+        List<Charge> charges = depositService.getCharges(accountWrapper.account.getIdentifier(), transactionType);
+
+        // TODO add action and set the status to failed in separated transaction
+        InteropActionEntity action = addAction(transaction, request, transactionDate);
+        MoneyData fee = request.getFspFee();
+        action.setFee(fee == null ? null : fee.getAmount());
+        // TODO: extend Charge with a property that could be stored in charges
+
+        prepareTransfer(request, accountWrapper, action, charges, transactionDate);
+
+        transactionRepository.save(transaction);
+
+        return InteropTransferResponseData.build(request.getTransferCode(), action.getState(), action.getExpirationDate(),
+                request.getExtensionList(), request.getTransferCode(), transactionDate);
+    }
+
+    @NotNull
+    @Transactional(propagation = Propagation.MANDATORY)
+    public InteropTransferResponseData commitTransfer(@NotNull InteropTransferCommand request) {
+        //TODO: error handling
+        //TODO: ABORT
+        AccountWrapper accountWrapper = validateAndGetAccount(request);
+
+        LocalDateTime transactionDate = getNow();
+        //TODO: transaction expiration separated from action expiration
+        InteropTransactionEntity transaction = validateAndGetTransaction(request, accountWrapper, transactionDate, true);
+        transaction.setTransactionDate(transactionDate);
+
+        validateTransfer(request, accountWrapper);
+
+        TransactionType transactionType = request.getTransactionRole().getTransactionType();
+        List<Charge> charges = depositService.getCharges(accountWrapper.account.getIdentifier(), transactionType);
+
+        // TODO add action and set the status to failed in separated transaction
+        InteropActionEntity action = addAction(transaction, request, transactionDate);
+        MoneyData fee = request.getFspFee();
+        action.setFee(fee == null ? null : fee.getAmount());
+        // TODO: extend Charge with a property that could be stored in charges
+
+        bookTransfer(request, accountWrapper, action, charges, transactionDate);
+
+        transactionRepository.save(transaction);
+
+        return InteropTransferResponseData.build(request.getTransferCode(), action.getState(), action.getExpirationDate(),
+                request.getExtensionList(), request.getTransferCode(), transactionDate);
+    }
+
+    Double getWithdrawableBalance(Account account, ProductDefinition productDefinition) {
+        // on-hold amount, if any, is subtracted to payable account
+        return MathUtil.subtractToZero(account.getBalance(), productDefinition.getMinimumBalance());
+    }
+
+    private void prepareTransfer(@NotNull InteropTransferCommand request, @NotNull AccountWrapper accountWrapper, @NotNull InteropActionEntity action,
+                                 List<Charge> charges, LocalDateTime transactionDate) {
+        BigDecimal amount = request.getAmount().getAmount();
+        // TODO: validate amount with quote amount
+        boolean isDebit = request.getTransactionRole().isWithdraw();
+        if (!isDebit)
+            return;
+
+        String prepareAccountId = action.getTransaction().getPrepareAccountIdentifier();
+        Account payableAccount = prepareAccountId == null ? null : validateAndGetAccount(request, prepareAccountId);
+        if (payableAccount == null) {
+            logger.warn("Can not prepare transfer: Payable account was not found for " + accountWrapper.account.getIdentifier());
+            return;
+        }
+
+        final JournalEntry journalEntry = createJournalEntry(action.getIdentifier(), TransactionType.CURRENCY_WITHDRAWAL.getCode(),
+                DateConverter.toIsoString(transactionDate), request.getNote(), getLoginUser());
+
+        HashSet<Debtor> debtors = new HashSet<>(1);
+        HashSet<Creditor> creditors = new HashSet<>(1);
+
+        addCreditor(accountWrapper.account.getIdentifier(), amount.doubleValue(), creditors);
+        addDebtor(payableAccount.getIdentifier(), amount.doubleValue(), debtors);
+
+        prepareCharges(request, accountWrapper, action, charges, payableAccount, debtors, creditors);
+
+        if (debtors.isEmpty()) // must be same size as creditors
+            return;
+
+        journalEntry.setDebtors(debtors);
+        journalEntry.setCreditors(creditors);
+        accountingService.createJournalEntry(journalEntry);
+    }
+
+    private void prepareCharges(@NotNull InteropTransferCommand request, @NotNull AccountWrapper accountWrapper, @NotNull InteropActionEntity action,
+                                @NotNull List<Charge> charges, Account payableAccount, HashSet<Debtor> debtors, HashSet<Creditor> creditors) {
+        MoneyData fspFee = request.getFspFee(); // TODO compare with calculated and with quote
+
+        BigDecimal amount = request.getAmount().getAmount();
+        Currency currency = accountWrapper.productDefinition.getCurrency();
+
+        BigDecimal total = MathUtil.normalize(calcTotalCharges(charges, amount), currency);
+
+        if (MathUtil.isEmpty(total)) {
+            return;
+        }
+
+        if (creditors == null) {
+            creditors = new HashSet<>(1);
+        }
+        if (debtors == null) {
+            debtors = new HashSet<>(charges.size());
+        }
+        addCreditor(accountWrapper.account.getIdentifier(), total.doubleValue(), creditors);
+        addDebtor(payableAccount.getIdentifier(), total.doubleValue(), debtors);
+    }
+
+    private void bookTransfer(@NotNull InteropTransferCommand request, @NotNull AccountWrapper accountWrapper, @NotNull InteropActionEntity action,
+                              List<Charge> charges, LocalDateTime transactionDate) {
+        boolean isDebit = request.getTransactionRole().isWithdraw();
+        String accountId = accountWrapper.account.getIdentifier();
+        String message = request.getNote();
+        double doubleAmount = request.getAmount().getAmount().doubleValue();
+
+        String loginUser = getLoginUser();
+        String transactionTypeCode = (isDebit ? TransactionType.CURRENCY_WITHDRAWAL : TransactionType.CURRENCY_DEPOSIT).getCode();
+        String transactionDateString = DateConverter.toIsoString(transactionDate);
+
+        InteropTransactionEntity transaction = action.getTransaction();
+        Account nostroAccount = validateAndGetAccount(request, transaction.getNostroAccountIdentifier());
+        Account payableAccount = null;
+
+        double preparedAmount = 0d;
+        double accountNostroAmount = doubleAmount;
+
+        if (isDebit) {
+            InteropActionEntity prepareAction = findAction(transaction, InteropActionType.PREPARE);
+            if (prepareAction != null) {
+                JournalEntry prepareJournal = accountingService.findJournalEntry(prepareAction.getIdentifier());
+                if (prepareJournal == null)
+                    throw new UnsupportedOperationException("Can not find prepare result for " + action.getActionType() +
+                            "/" + request.getIdentifier());
+
+                payableAccount = validateAndGetAccount(request, transaction.getPrepareAccountIdentifier());
+                preparedAmount = prepareJournal.getDebtors().stream().mapToDouble(d -> Double.valueOf(d.getAmount())).sum();
+                if (preparedAmount < doubleAmount)
+                    throw new UnsupportedOperationException("Prepared amount " + preparedAmount + " is less than transfer amount " +
+                            doubleAmount + " for " + request.getIdentifier());
+
+                // now fails if prepared is not enough
+                accountNostroAmount = preparedAmount >= doubleAmount ? 0d : doubleAmount - preparedAmount;
+
+                double fromPrepareToNostroAmount = doubleAmount - accountNostroAmount;
+                preparedAmount -= fromPrepareToNostroAmount;
+
+                if (fromPrepareToNostroAmount > 0) {
+                    final JournalEntry fromPrepareToNostroEntry = createJournalEntry(action.getIdentifier(),
+                            transactionTypeCode, transactionDateString, message + " #commit", loginUser);
+
+                    HashSet<Debtor> debtors = new HashSet<>(1);
+                    HashSet<Creditor> creditors = new HashSet<>(1);
+
+                    addCreditor(payableAccount.getIdentifier(), fromPrepareToNostroAmount, creditors);
+                    addDebtor(nostroAccount.getIdentifier(), fromPrepareToNostroAmount, debtors);
+
+                    fromPrepareToNostroEntry.setDebtors(debtors);
+                    fromPrepareToNostroEntry.setCreditors(creditors);
+                    accountingService.createJournalEntry(fromPrepareToNostroEntry);
+                }
+            }
+        }
+        if (accountNostroAmount > 0) {
+            // can not happen that prepared amount is less than requested transfer amount (identifier and message can be default)
+            final JournalEntry journalEntry = createJournalEntry(action.getIdentifier(), transactionTypeCode,
+                    transactionDateString, message/* + (payableAccount == null ? "" : " #difference")*/, loginUser);
+
+            HashSet<Debtor> debtors = new HashSet<>(1);
+            HashSet<Creditor> creditors = new HashSet<>(1);
+
+            addCreditor(isDebit ? accountId : nostroAccount.getIdentifier(), accountNostroAmount, creditors);
+            addDebtor(isDebit ? nostroAccount.getIdentifier() : accountId, accountNostroAmount, debtors);
+
+            journalEntry.setDebtors(debtors);
+            journalEntry.setCreditors(creditors);
+            accountingService.createJournalEntry(journalEntry);
+        }
+
+        preparedAmount = bookCharges(request, accountWrapper, action, charges, payableAccount, preparedAmount, transactionDate);
+
+        if (preparedAmount > 0) {
+//            throw new UnsupportedOperationException("Prepared amount differs from transfer amount " + doubleAmount + " for " + request.getIdentifier());
+            // transfer back remaining prepared amount TODO: JM maybe fail this case?
+
+            final JournalEntry fromPrepareToAccountEntry = createJournalEntry(action.getIdentifier() + InteropRequestData.IDENTIFIER_SEPARATOR + "diff",
+                    transactionTypeCode, transactionDateString, message + " #release difference", loginUser);
+
+            HashSet<Debtor> debtors = new HashSet<>(1);
+            HashSet<Creditor> creditors = new HashSet<>(1);
+
+            addCreditor(payableAccount.getIdentifier(), preparedAmount, creditors);
+            addDebtor(accountId, preparedAmount, debtors);
+
+            fromPrepareToAccountEntry.setDebtors(debtors);
+            fromPrepareToAccountEntry.setCreditors(creditors);
+            accountingService.createJournalEntry(fromPrepareToAccountEntry);
+        }
+    }
+
+    private double bookCharges(@NotNull InteropTransferCommand request, @NotNull AccountWrapper accountWrapper, @NotNull InteropActionEntity action,
+                               @NotNull List<Charge> charges, Account payableAccount, double preparedAmount, LocalDateTime transactionDate) {
+        boolean isDebit = request.getTransactionRole().isWithdraw();
+        String accountId = accountWrapper.account.getIdentifier();
+        String message = request.getNote();
+        BigDecimal amount = request.getAmount().getAmount();
+        Currency currency = accountWrapper.productDefinition.getCurrency();
+
+        BigDecimal calcFee = MathUtil.normalize(calcTotalCharges(charges, amount), currency);
+        BigDecimal requestFee = request.getFspFee().getAmount();
+        if (!MathUtil.isEqualTo(calcFee, requestFee))
+            throw new UnsupportedOperationException("Quote fee " + requestFee + " differs from transfer fee " + calcFee);
+
+        if (MathUtil.isEmpty(calcFee)) {
+            return preparedAmount;
+        }
+
+        String loginUser = getLoginUser();
+        String transactionTypeCode = (isDebit ? TransactionType.CURRENCY_WITHDRAWAL : TransactionType.CURRENCY_DEPOSIT).getCode();
+        String transactionDateString = DateConverter.toIsoString(transactionDate);
+
+        ArrayList<Charge> unpaidCharges = new ArrayList<>(charges);
+        if (preparedAmount > 0) {
+            InteropActionEntity prepareAction = findAction(action.getTransaction(), InteropActionType.PREPARE);
+            if (prepareAction != null) {
+                final JournalEntry fromPrepareToRevenueEntry = createJournalEntry(action.getIdentifier() + InteropRequestData.IDENTIFIER_SEPARATOR + "fee",
+                        transactionTypeCode, transactionDateString, message + " #commit fee", loginUser);
+
+                double payedAmount = 0d;
+
+                HashSet<Debtor> debtors = new HashSet<>(1);
+                HashSet<Creditor> creditors = new HashSet<>(1);
+
+                for (Charge charge : charges) {
+                    BigDecimal value = calcChargeAmount(amount, charge, currency, true);
+                    if (value == null)
+                        continue;
+
+                    double doubleValue = value.doubleValue();
+                    if (doubleValue > preparedAmount) {
+                        break;
+                    }
+                    unpaidCharges.remove(charge);
+                    preparedAmount -= doubleValue;
+                    payedAmount += doubleValue;
+
+                    addDebtor(charge.getIncomeAccountIdentifier(), doubleValue, debtors);
+                }
+                if (!unpaidCharges.isEmpty())
+                    throw new UnsupportedOperationException("Prepared amount " + preparedAmount + " is less than transfer fee amount for " +
+                            request.getIdentifier());
+
+                if (payedAmount > 0) {
+                    addCreditor(payableAccount.getIdentifier(), payedAmount, creditors);
+
+                    fromPrepareToRevenueEntry.setDebtors(debtors);
+                    fromPrepareToRevenueEntry.setCreditors(creditors);
+                    accountingService.createJournalEntry(fromPrepareToRevenueEntry);
+                }
+            }
+        }
+        if (!unpaidCharges.isEmpty()) {
+            // can not happen that prepared amount is more or less than requested transfer amount (identifier and message can be default)
+            final JournalEntry journalEntry = createJournalEntry(action.getIdentifier() + InteropRequestData.IDENTIFIER_SEPARATOR + "fee",
+                    transactionTypeCode, transactionDateString, message + " #fee", loginUser);
+
+            HashSet<Debtor> debtors = new HashSet<>(1);
+            HashSet<Creditor> creditors = new HashSet<>(1);
+
+            double payedAmount = 0d;
+            for (Charge charge : charges) {
+                BigDecimal value = calcChargeAmount(amount, charge, currency, true);
+                if (value == null)
+                    continue;
+
+                double doubleValue = value.doubleValue();
+                payedAmount += doubleValue;
+
+                addDebtor(charge.getIncomeAccountIdentifier(), doubleValue, debtors);
+            }
+
+            if (payedAmount > 0) {
+                addCreditor(accountId, payedAmount, creditors);
+
+                journalEntry.setDebtors(debtors);
+                journalEntry.setCreditors(creditors);
+                accountingService.createJournalEntry(journalEntry);
+            }
+        }
+
+        return preparedAmount;
+    }
+
+    // Util
+
+    private JournalEntry createJournalEntry(String actionIdentifier, String transactionType, String transactionDate, String message, String loginUser) {
+        final JournalEntry fromPrepareToNostroEntry = new JournalEntry();
+        fromPrepareToNostroEntry.setTransactionIdentifier(actionIdentifier);
+        fromPrepareToNostroEntry.setTransactionType(transactionType);
+        fromPrepareToNostroEntry.setTransactionDate(transactionDate);
+        fromPrepareToNostroEntry.setMessage(message);
+        fromPrepareToNostroEntry.setClerk(loginUser);
+        return fromPrepareToNostroEntry;
+    }
+
+    private void addCreditor(String accountNumber, double amount, HashSet<Creditor> creditors) {
+        Creditor creditor = new Creditor();
+        creditor.setAccountNumber(accountNumber);
+        creditor.setAmount(Double.toString(amount));
+        creditors.add(creditor);
+    }
+
+    private void addDebtor(String accountNumber, double amount, HashSet<Debtor> debtors) {
+        Debtor debtor = new Debtor();
+        debtor.setAccountNumber(accountNumber);
+        debtor.setAmount(Double.toString(amount));
+        debtors.add(debtor);
+    }
+
+    private BigDecimal calcChargeAmount(BigDecimal amount, Charge charge) {
+        return calcChargeAmount(amount, charge, null, false);
+    }
+
+    private BigDecimal calcChargeAmount(@NotNull BigDecimal amount, @NotNull Charge charge, Currency currency, boolean norm) {
+        Double value = charge.getAmount();
+        if (value == null)
+            return null;
+
+        BigDecimal portion = BigDecimal.valueOf(100.00d);
+        MathContext mc = MathUtil.CALCULATION_MATH_CONTEXT;
+        BigDecimal feeAmount = BigDecimal.valueOf(MathUtil.nullToZero(charge.getAmount()));
+        BigDecimal result = charge.getProportional()
+                ? amount.multiply(feeAmount.divide(portion, mc), mc)
+                : feeAmount;
+        return norm ? MathUtil.normalize(result, currency) : result;
+    }
+
+    @NotNull
+    private BigDecimal calcTotalCharges(@NotNull List<Charge> charges, BigDecimal amount) {
+        return charges.stream().map(charge -> calcChargeAmount(amount, charge)).reduce(MathUtil::add).orElse(BigDecimal.ZERO);
+    }
+
+    private Account validateAndGetAccount(@NotNull String accountId) {
+        //TODO: error handling
+        Account account = accountingService.findAccount(accountId);
+        validateAccount(account);
+
+        return account;
+    }
+
+    private AccountWrapper validateAndGetAccount(@NotNull InteropRequestData request) {
+        //TODO: error handling
+        String accountId = request.getAccountId();
+        Account account = accountingService.findAccount(accountId);
+        validateAccount(request, account);
+
+        ProductInstance product = depositService.findProductInstance(accountId);
+        ProductDefinition productDefinition = depositService.findProductDefinition(product.getProductIdentifier());
+
+        Currency currency = productDefinition.getCurrency();
+        if (!currency.getCode().equals(request.getAmount().getCurrency()))
+            throw new UnsupportedOperationException();
+
+        request.normalizeAmounts(currency);
+
+        Double withdrawableBalance = getWithdrawableBalance(account, productDefinition);
+        if (request.getTransactionRole().isWithdraw() && withdrawableBalance < request.getAmount().getAmount().doubleValue())
+            throw new UnsupportedOperationException();
+
+        return new AccountWrapper(account, product, productDefinition, withdrawableBalance);
+    }
+
+    private Account validateAndGetPayableAccount(@NotNull InteropRequestData request, @NotNull AccountWrapper wrapper) {
+        String referenceId = wrapper.account.getReferenceAccount();
+        if (referenceId == null)
+            return null;
+
+        return validateAndGetAccount(request, referenceId);
+    }
+
+    @NotNull
+    private Account validateAndGetNostroAccount(@NotNull InteropRequestData request) {
+        //TODO: error handling
+        List<Account> nostros = fetchAccounts(false, ACCOUNT_NAME_NOSTRO, AccountType.ASSET.name(), false, null, null, null, null);
+        int size = nostros.size();
+        if (size != 1)
+            throw new UnsupportedOperationException("NOSTRO Account " + (size == 0 ? "not found" : "is ambigous"));
+
+        Account nostro = nostros.get(0);
+        validateAccount(request, nostro);
+        return nostro;
+    }
+
+    @NotNull
+    private Account validateAndGetAccount(@NotNull InteropRequestData request, @NotNull String accountId) {
+        //TODO: error handling
+        Account account = accountingService.findAccount(accountId);
+
+        validateAccount(request, account);
+        return account;
+    }
+
+    private void validateAccount(Account account) {
+        if (account == null)
+            throw new UnsupportedOperationException("Account not found");
+        if (!account.getState().equals(Account.State.OPEN.name()))
+            throw new UnsupportedOperationException("Account is in state " + account.getState());
+    }
+
+    private void validateAccount(@NotNull InteropRequestData request, Account account) {
+        validateAccount(account);
+
+        String accountId = account.getIdentifier();
+
+        if (account.getHolders() != null) { // customer account
+            ProductInstance product = depositService.findProductInstance(accountId);
+            ProductDefinition productDefinition = depositService.findProductDefinition(product.getProductIdentifier());
+            if (!Boolean.TRUE.equals(productDefinition.getActive()))
+                throw new UnsupportedOperationException("NOSTRO Product Definition is inactive");
+
+            Currency currency = productDefinition.getCurrency();
+            if (!currency.getCode().equals(request.getAmount().getCurrency()))
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    private BigDecimal validateTransfer(@NotNull InteropTransferRequestData request, @NotNull AccountWrapper accountWrapper) {
+        BigDecimal amount = request.getAmount().getAmount();
+
+        boolean isDebit = request.getTransactionRole().isWithdraw();
+        Currency currency = accountWrapper.productDefinition.getCurrency();
+
+        BigDecimal total = isDebit ? amount : MathUtil.negate(amount);
+        MoneyData fspFee = request.getFspFee();
+        if (fspFee != null) {
+            if (!currency.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 (!currency.getCode().equals(fspCommission.getCurrency()))
+                throw new UnsupportedOperationException();
+            //TODO: compare with calculated quote commission
+            total = MathUtil.subtractToZero(total, fspCommission.getAmount());
+        }
+        if (isDebit && accountWrapper.withdrawableBalance < request.getAmount().getAmount().doubleValue())
+            throw new UnsupportedOperationException();
+        return total;
+    }
+
+    public List<Account> fetchAccounts(boolean includeClosed, String term, String type, boolean includeCustomerAccounts,
+                                       Integer pageIndex, Integer size, String sortColumn, String sortDirection) {
+        return accountingService.fetchAccounts(includeClosed, term, type, includeCustomerAccounts, pageIndex, size, sortColumn, sortDirection);
+    }
+
+    public InteropIdentifierEntity 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<InteropIdentifierEntity> idTypeEqual(@NotNull InteropIdentifierType idType) {
+        return (Root<InteropIdentifierEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("type"), idType));
+    }
+
+    public static Specification<InteropIdentifierEntity> idValueEqual(@NotNull String idValue) {
+        return (Root<InteropIdentifierEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("value"), idValue));
+    }
+
+    public static Specification<InteropIdentifierEntity> subIdOrTypeEqual(String subIdOrType) {
+        return (Root<InteropIdentifierEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
+            Path<Object> path = root.get("subValueOrType");
+            return cb.and(subIdOrType == null ? cb.isNull(path) : cb.equal(path, subIdOrType));
+        };
+    }
+
+    @NotNull
+    private String calcActionIdentifier(@NotNull String actionCode, @NotNull InteropActionType actionType) {
+        return actionType == InteropActionType.PREPARE || actionType == InteropActionType.COMMIT ? actionType + InteropRequestData.IDENTIFIER_SEPARATOR + actionCode : actionCode;
+    }
+
+    private InteropActionEntity validateAndGetAction(@NotNull String transactionCode, @NotNull String actionIdentifier, @NotNull InteropActionType actionType) {
+        return validateAndGetAction(transactionCode, actionIdentifier, actionType, true);
+    }
+
+    private InteropActionEntity validateAndGetAction(@NotNull String transactionCode, @NotNull String actionIdentifier, @NotNull InteropActionType actionType, boolean one) {
+        //TODO: error handling
+        InteropActionEntity action = actionRepository.findByIdentifier(actionIdentifier);
+        if (action == null) {
+            if (one)
+                throw new UnsupportedOperationException("Interperation action " + actionType + '/' + actionIdentifier + " was not found for this transaction " + transactionCode);
+            return null;
+        }
+        if (!action.getTransaction().getIdentifier().equals(transactionCode))
+            throw new UnsupportedOperationException("Interperation action " + actionType + '/' + actionIdentifier + " does not exist in this transaction " + transactionCode);
+        if (action.getActionType() != actionType)
+            throw new UnsupportedOperationException("Interperation action " + actionIdentifier + " is not this type " + actionType);
+
+        return action;
+    }
+
+    @NotNull
+    private InteropTransactionEntity validateAndGetTransaction(@NotNull InteropRequestData request, @NotNull AccountWrapper accountWrapper) {
+        return validateAndGetTransaction(request, accountWrapper, true);
+    }
+
+    @NotNull
+    private InteropTransactionEntity validateAndGetTransaction(@NotNull InteropRequestData request, @NotNull AccountWrapper accountWrapper, boolean create) {
+        return validateAndGetTransaction(request, accountWrapper, getNow(), create);
+    }
+
+    @NotNull
+    private InteropTransactionEntity validateAndGetTransaction(@NotNull InteropRequestData request, @NotNull AccountWrapper accountWrapper,
+                                                               @NotNull LocalDateTime createdOn, boolean create) {
+        //TODO: error handling
+        String transactionCode = request.getTransactionCode();
+        InteropTransactionEntity transaction = transactionRepository.findOneByIdentifier(request.getTransactionCode());
+        InteropState state = InteropStateMachine.handleTransition(transaction == null ? null : transaction.getState(), request.getActionType());
+        LocalDateTime now = getNow();
+        if (transaction == null) {
+            if (!create)
+                throw new UnsupportedOperationException("Interperation transaction " + request.getTransactionCode() + " does not exist ");
+            transaction = new InteropTransactionEntity(transactionCode, accountWrapper.account.getIdentifier(), getLoginUser(), now);
+            transaction.setState(state);
+            transaction.setAmount(request.getAmount().getAmount());
+            transaction.setName(request.getNote());
+            transaction.setTransactionType(request.getTransactionRole().getTransactionType());
+            Account payableAccount = validateAndGetPayableAccount(request, accountWrapper);
+            transaction.setPrepareAccountIdentifier(payableAccount == null ? null : payableAccount.getIdentifier());
+            transaction.setNostroAccountIdentifier(validateAndGetNostroAccount(request).getIdentifier());
+            transaction.setExpirationDate(request.getExpiration());
+        }
+        LocalDateTime expirationDate = transaction.getExpirationDate();
+        if (expirationDate != null && expirationDate.isBefore(now))
+            throw new UnsupportedOperationException("Interperation transaction expired on " + expirationDate);
+        return transaction;
+    }
+
+    private Currency getCurrency(InteropActionEntity action) {
+        ProductInstance product = depositService.findProductInstance(action.getTransaction().getCustomerAccountIdentifier());
+        ProductDefinition productDefinition = depositService.findProductDefinition(product.getProductIdentifier());
+        return productDefinition.getCurrency();
+    }
+
+    private InteropActionEntity addAction(@NotNull InteropTransactionEntity transaction, @NotNull InteropRequestData request) {
+        return addAction(transaction, request, getNow());
+    }
+
+    private InteropActionEntity addAction(@NotNull InteropTransactionEntity transaction, @NotNull InteropRequestData request,
+                                          @NotNull LocalDateTime createdOn) {
+        InteropActionEntity lastAction = getLastAction(transaction);
+
+        InteropActionType actionType = request.getActionType();
+        String actionIdentifier = calcActionIdentifier(request.getIdentifier(), request.getActionType());
+        InteropActionEntity action = new InteropActionEntity(actionIdentifier, transaction, request.getActionType(),
+                (lastAction == null ? 0 : lastAction.getSeqNo() + 1), getLoginUser(),
+                (lastAction == null ? transaction.getCreatedOn() : createdOn));
+        action.setState(InteropActionState.ACCEPTED);
+        action.setAmount(request.getAmount().getAmount());
+        action.setExpirationDate(request.getExpiration());
+        transaction.getActions().add(action);
+
+        InteropState currentState = transaction.getState();
+        if (transaction.getId() != null || InteropStateMachine.isValidAction(currentState, actionType)) // newly created was already set
+            transaction.setState(InteropStateMachine.handleTransition(currentState, actionType));
+        return action;
+    }
+
+    private InteropActionEntity getLastAction(InteropTransactionEntity transaction) {
+        List<InteropActionEntity> actions = transaction.getActions();
+        int size = actions.size();
+        return size == 0 ? null : actions.get(size - 1);
+    }
+
+    private InteropActionEntity findAction(InteropTransactionEntity transaction, InteropActionType actionType) {
+        List<InteropActionEntity> actions = transaction.getActions();
+        for (InteropActionEntity action : actions) {
+            if (action.getActionType() == actionType)
+                return action;
+        }
+        return null;
+    }
+
+    private LocalDateTime getNow() {
+        return LocalDateTime.now(Clock.systemUTC());
+    }
+
+    private String getLoginUser() {
+        return UserContextHolder.checkedGetUser();
+    }
+
+    public static class AccountWrapper {
+        @NotNull
+        private final Account account;
+        @NotNull
+        private final ProductInstance product;
+        @NotNull
+        private final ProductDefinition productDefinition;
+        @NotNull
+        private final Double withdrawableBalance;
+
+        public AccountWrapper(Account account, ProductInstance product, ProductDefinition productDefinition, Double withdrawableBalance) {
+            this.account = account;
+            this.product = product;
+            this.productDefinition = productDefinition;
+            this.withdrawableBalance = withdrawableBalance;
+        }
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropAccountingService.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropAccountingService.java
new file mode 100644
index 0000000..9d98fbc
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropAccountingService.java
@@ -0,0 +1,120 @@
+/*
+ * 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.cn.interoperation.service.internal.service.helper;
+
+import com.google.common.collect.Lists;
+import org.apache.fineract.cn.accounting.api.v1.client.AccountNotFoundException;
+import org.apache.fineract.cn.accounting.api.v1.client.LedgerManager;
+import org.apache.fineract.cn.accounting.api.v1.client.LedgerNotFoundException;
+import org.apache.fineract.cn.accounting.api.v1.domain.Account;
+import org.apache.fineract.cn.accounting.api.v1.domain.AccountEntry;
+import org.apache.fineract.cn.accounting.api.v1.domain.AccountPage;
+import org.apache.fineract.cn.accounting.api.v1.domain.JournalEntry;
+import org.apache.fineract.cn.accounting.api.v1.domain.Ledger;
+import org.apache.fineract.cn.interoperation.service.ServiceConstants;
+import org.apache.fineract.cn.lang.ServiceException;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.List;
+
+@Service
+public class InteropAccountingService {
+
+    private Logger logger;
+    private LedgerManager ledgerManager;
+
+    @Autowired
+    public InteropAccountingService(@Qualifier(ServiceConstants.LOGGER_NAME) Logger logger,
+                                    LedgerManager ledgerManager) {
+        super();
+        this.logger = logger;
+        this.ledgerManager = ledgerManager;
+    }
+
+    public void createAccount(String equityLedger,
+                              String productName,
+                              String customer,
+                              String accountNumber,
+                              String alternativeAccountNumber,
+                              Double balance) {
+        try {
+            Ledger ledger = ledgerManager.findLedger(equityLedger);
+            Account account = new Account();
+            account.setIdentifier(accountNumber);
+            account.setType(ledger.getType());
+            account.setLedger(equityLedger);
+            account.setName(productName);
+            account.setHolders(new HashSet<>(Lists.newArrayList(customer)));
+            account.setBalance(balance != null ? balance : 0.00D);
+            account.setAlternativeAccountNumber(alternativeAccountNumber);
+
+            ledgerManager.createAccount(account);
+        } catch (LedgerNotFoundException lnfex) {
+            throw ServiceException.notFound("Ledger {0} not found.", equityLedger);
+        }
+    }
+
+    public List<Account> fetchAccounts(boolean includeClosed, String term, String type, boolean includeCustomerAccounts,
+                                     Integer pageIndex, Integer size, String sortColumn, String sortDirection) {
+        return ledgerManager.fetchAccounts(includeClosed, term, type, includeCustomerAccounts, pageIndex, size, sortColumn, sortDirection).getAccounts();
+    }
+
+    public Account findAccount(final String accountNumber) {
+        try {
+            return this.ledgerManager.findAccount(accountNumber);
+        } catch (final AccountNotFoundException anfex) {
+            final AccountPage accountPage = this.ledgerManager.fetchAccounts(true, accountNumber, null, true,
+                    0, 10, null, null);
+
+            return accountPage.getAccounts()
+                    .stream()
+                    .filter(account -> account.getAlternativeAccountNumber().equals(accountNumber))
+                    .findFirst()
+                    .orElseThrow(() -> ServiceException.notFound("Account {0} not found.", accountNumber));
+        }
+    }
+
+    public void modifyAccount(Account account) {
+        ledgerManager.modifyAccount(account.getIdentifier(), account);
+    }
+
+    public List<AccountEntry> fetchAccountEntries(String identifier, String dateRange, String direction) {
+        return ledgerManager
+                .fetchAccountEntries(identifier, dateRange, null, 0, 1, "transactionDate", direction)
+                .getAccountEntries();
+    }
+
+    public List<JournalEntry> fetchJournalEntries(final String dateRange, final String accountNumber, final BigDecimal amount) {
+        return ledgerManager.fetchJournalEntries(dateRange, accountNumber, amount);
+    }
+
+    public JournalEntry findJournalEntry(@NotNull String transactionIdentifier) {
+        return ledgerManager.findJournalEntry(transactionIdentifier);
+    }
+
+    public void createJournalEntry(@NotNull JournalEntry journalEntry) {
+        ledgerManager.createJournalEntry(journalEntry);
+    }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropDepositService.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropDepositService.java
new file mode 100644
index 0000000..80bfd52
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/internal/service/helper/InteropDepositService.java
@@ -0,0 +1,128 @@
+/*
+ * 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.cn.interoperation.service.internal.service.helper;
+
+import org.apache.fineract.cn.deposit.api.v1.client.DepositAccountManager;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Action;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.Charge;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.DividendDistribution;
+import org.apache.fineract.cn.deposit.api.v1.definition.domain.ProductDefinition;
+import org.apache.fineract.cn.deposit.api.v1.instance.domain.AvailableTransactionType;
+import org.apache.fineract.cn.deposit.api.v1.instance.domain.ProductInstance;
+import org.apache.fineract.cn.interoperation.api.v1.domain.TransactionType;
+import org.apache.fineract.cn.interoperation.service.ServiceConstants;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Service
+public class InteropDepositService {
+
+    private Logger logger;
+    private DepositAccountManager depositAccountManager;
+
+    @Autowired
+    public InteropDepositService(@Qualifier(ServiceConstants.LOGGER_NAME) Logger logger,
+                                 DepositAccountManager depositAccountManager) {
+        super();
+        this.logger = logger;
+        this.depositAccountManager = depositAccountManager;
+    }
+
+    public List<Charge> getWithdrawCharges(String accountIdentifier) {
+        return getCharges(accountIdentifier, TransactionType.CURRENCY_WITHDRAWAL);
+    }
+
+    public List<Charge> getDepositCharges(String accountIdentifier) {
+        return getCharges(accountIdentifier, TransactionType.CURRENCY_DEPOSIT);
+    }
+
+    public List<Charge> getCharges(String accountIdentifier, TransactionType transactionType) {
+        List<Action> actions = depositAccountManager.fetchActions();
+
+        List<String> actionIds = actions
+                .stream()
+                .filter(action -> action.getTransactionType().equals(transactionType.getCode()))
+                .map(Action::getIdentifier)
+                .collect(Collectors.toList());
+
+        ProductInstance productInstance = depositAccountManager.findProductInstance(accountIdentifier);
+        ProductDefinition productDefinition = depositAccountManager.findProductDefinition(productInstance.getProductIdentifier());
+
+        return productDefinition.getCharges()
+                .stream()
+                .filter(charge -> actionIds.contains(charge.getActionIdentifier()))
+                .collect(Collectors.toList());
+    }
+
+    public void createAction(@NotNull Action action) {
+        depositAccountManager.create(action);
+    }
+
+    public List<Action> fetchActions() {
+        return depositAccountManager.fetchActions();
+    }
+
+    public void createProductDefinition(@NotNull ProductDefinition productDefinition) {
+        depositAccountManager.create(productDefinition);
+    }
+
+    public List<ProductDefinition> fetchProductDefinitions() {
+        return depositAccountManager.fetchProductDefinitions();
+    }
+
+    public ProductDefinition findProductDefinition(@NotNull String identifier) {
+        return depositAccountManager.findProductDefinition(identifier);
+    }
+
+    public List<ProductInstance> findProductInstances(@NotNull String identifier) {
+        return depositAccountManager.findProductInstances(identifier);
+    }
+
+    public void createProductInstance(@NotNull ProductInstance productInstance) {
+        depositAccountManager.create(productInstance);
+    }
+
+    public List<ProductInstance> fetchProductInstances(@NotNull String customer) {
+        return depositAccountManager.fetchProductInstances(customer);
+    }
+
+    public Set<AvailableTransactionType> fetchPossibleTransactionTypes(String customer) {
+        return depositAccountManager.fetchPossibleTransactionTypes(customer);
+    }
+
+    public ProductInstance findProductInstance(@NotNull String accountIdentifier) {
+        return depositAccountManager.findProductInstance(accountIdentifier);
+    }
+
+    public void dividendDistribution(@NotNull String identifier, @NotNull DividendDistribution distribution) {
+        depositAccountManager.dividendDistribution(identifier, distribution);
+    }
+
+    public List<DividendDistribution> fetchDividendDistributions(@NotNull String identifier) {
+        return depositAccountManager.fetchDividendDistributions(identifier);
+    }
+}
+
diff --git a/service/src/main/java/org/apache/fineract/cn/interoperation/service/rest/InteropRestController.java b/service/src/main/java/org/apache/fineract/cn/interoperation/service/rest/InteropRestController.java
new file mode 100644
index 0000000..b00fbd5
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/interoperation/service/rest/InteropRestController.java
@@ -0,0 +1,277 @@
+/**
+ * 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.cn.interoperation.service.rest;
+
+import org.apache.fineract.cn.anubis.annotation.AcceptedTokenType;
+import org.apache.fineract.cn.anubis.annotation.Permittable;
+import org.apache.fineract.cn.command.domain.CommandCallback;
+import org.apache.fineract.cn.command.gateway.CommandGateway;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropIdentifierType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.InteropTransferActionType;
+import org.apache.fineract.cn.interoperation.api.v1.domain.data.*;
+import org.apache.fineract.cn.interoperation.service.internal.command.InitializeServiceCommand;
+import org.apache.fineract.cn.interoperation.service.internal.service.InteropService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import static org.apache.fineract.cn.interoperation.api.v1.PermittableGroupIds.INTEROPERATION_SINGLE;
+
+@SuppressWarnings("unused")
+@RestController
+@RequestMapping("/") //interoperation/v1
+public class InteropRestController {
+
+    private CommandGateway commandGateway;
+
+    private InteropService interopService;
+
+    @Autowired
+    public InteropRestController(CommandGateway commandGateway,
+                                 InteropService interopService) {
+        this.commandGateway = commandGateway;
+        this.interopService = interopService;
+    }
+
+    @Permittable(value = AcceptedTokenType.SYSTEM)
+    @RequestMapping(
+            value = "/initialize",
+            method = RequestMethod.POST,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    public
+    @ResponseBody
+    ResponseEntity<Void> initialize() {
+        this.commandGateway.process(new InitializeServiceCommand());
+        return ResponseEntity.accepted().build();
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/health",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    public ResponseEntity<Void> health(@Context UriInfo uriInfo) {
+        return ResponseEntity.ok().build();
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    public ResponseEntity<InteropIdentifierData> getAccountByIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                        @PathVariable("idValue") String idValue) {
+        InteropIdentifierData account = interopService.getAccountByIdentifier(idType, idValue, null);
+
+        return ResponseEntity.ok(account);
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}/{subIdOrType}",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    public ResponseEntity<InteropIdentifierData> getAccountByIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                        @PathVariable("idValue") String idValue,
+                                                                        @PathVariable(value = "subIdOrType") String subIdOrType) {
+        InteropIdentifierData account = interopService.getAccountByIdentifier(idType, idValue, subIdOrType);
+
+        return ResponseEntity.ok(account);
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}",
+            method = RequestMethod.POST,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropIdentifierData> registerAccountIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                           @PathVariable("idValue") String idValue,
+                                                                           @RequestBody @Valid InteropIdentifierData requestData)
+            throws Throwable {
+        CommandCallback<InteropIdentifierData> result = commandGateway.process(new InteropIdentifierCommand(requestData,
+                        idType, idValue, null), InteropIdentifierData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}/{subIdOrType}",
+            method = RequestMethod.POST,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropIdentifierData> registerAccountIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                           @PathVariable("idValue") String idValue,
+                                                                           @PathVariable(value = "subIdOrType", required = false) String subIdOrType,
+                                                                           @RequestBody @Valid InteropIdentifierData requestData)
+            throws Throwable {
+        CommandCallback<InteropIdentifierData> result = commandGateway.process(new InteropIdentifierCommand(requestData,
+                idType, idValue, subIdOrType), InteropIdentifierData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}",
+            method = RequestMethod.DELETE,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropIdentifierData> deleteAccountIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                           @PathVariable("idValue") String idValue)
+            throws Throwable {
+        CommandCallback<InteropIdentifierData> result = commandGateway.process(new InteropIdentifierDeleteCommand(idType, idValue, null), InteropIdentifierData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/parties/{idType}/{idValue}/{subIdOrType}",
+            method = RequestMethod.DELETE,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropIdentifierData> deleteAccountIdentifier(@PathVariable("idType") InteropIdentifierType idType,
+                                                                         @PathVariable("idValue") String idValue,
+                                                                         @PathVariable(value = "subIdOrType", required = false) String subIdOrType)
+            throws Throwable {
+        CommandCallback<InteropIdentifierData> result = commandGateway.process(new InteropIdentifierDeleteCommand(idType, idValue, subIdOrType), InteropIdentifierData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "transactions/{transactionCode}/requests/{requestCode}",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropTransactionRequestResponseData> getTransactionRequest(@PathVariable("transactionCode") String transactionCode,
+                                                                                       @PathVariable("requestCode") String requestCode) {
+        InteropTransactionRequestResponseData result = interopService.getTransactionRequest(transactionCode, requestCode);
+
+        return ResponseEntity.ok(result);
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/requests",
+            method = RequestMethod.POST,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropTransactionRequestResponseData> createTransactionRequest(@RequestBody @Valid InteropTransactionRequestData requestData)
+            throws Throwable {
+        CommandCallback<InteropTransactionRequestResponseData> result = commandGateway.process(requestData, InteropTransactionRequestResponseData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "transactions/{transactionCode}/quotes/{quoteCode}",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropQuoteResponseData> getQuote(@PathVariable("transactionCode") String transactionCode,
+                                                             @PathVariable("quoteCode") String quoteCode) {
+        InteropQuoteResponseData result = interopService.getQuote(transactionCode, quoteCode);
+
+        return ResponseEntity.ok(result);
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/quotes",
+            method = RequestMethod.POST,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropQuoteResponseData> createQuote(@RequestBody @Valid InteropQuoteRequestData requestData) throws Throwable {
+        CommandCallback<InteropQuoteResponseData> result = commandGateway.process(requestData, InteropQuoteResponseData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "transactions/{transactionCode}/transfers/{transferCode}",
+            method = RequestMethod.GET,
+            consumes = MediaType.ALL_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropTransferResponseData> getTransfer(@PathVariable("transactionCode") String transactionCode,
+                                                                   @PathVariable("transferCode") String transferCode) {
+        InteropTransferResponseData result = interopService.getTransfer(transactionCode, transferCode);
+
+        return ResponseEntity.ok(result);
+    }
+
+    @Permittable(value = AcceptedTokenType.TENANT, groupId = INTEROPERATION_SINGLE)
+    @RequestMapping(
+            value = "/transfers",
+            method = RequestMethod.POST,
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE
+    )
+    @ResponseBody
+    public ResponseEntity<InteropTransferResponseData> performTransfer(@RequestParam("action") String action,
+                                                                       @RequestBody @Valid InteropTransferRequestData requestData)
+            throws Throwable {
+        CommandCallback<InteropTransferResponseData> result = commandGateway.process(new InteropTransferCommand(requestData, InteropTransferActionType.valueOf(action)),
+                InteropTransferResponseData.class);
+
+        return ResponseEntity.ok(result.get());
+    }
+}
\ No newline at end of file
diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml
new file mode 100644
index 0000000..68e0269
--- /dev/null
+++ b/service/src/main/resources/application.yml
@@ -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.
+#
+
+server:
+  port: 2034
+  contextPath: /interoperation/v1/*
+
+async:
+  corePoolSize: 32
+  maxPoolSize: 16384
+  queueCapacity: 0
+  threadName: async-processor-
+
+flyway:
+  enabled: false
diff --git a/service/src/main/resources/bootstrap.yml b/service/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..ce260b0
--- /dev/null
+++ b/service/src/main/resources/bootstrap.yml
@@ -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.
+#
+
+spring:
+    application:
+        name: interoperation-v1
+    cloud:
+        config:
+            uri: http://localhost:8761/config
+            username: config
+            password: config1234
diff --git a/service/src/main/resources/db/migrations/mariadb/V1__interop_init.sql b/service/src/main/resources/db/migrations/mariadb/V1__interop_init.sql
new file mode 100644
index 0000000..6a942df
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V1__interop_init.sql
@@ -0,0 +1,91 @@
+--
+-- 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.
+--
+
+CREATE TABLE hathor_identifiers (
+  id                          BIGINT       NOT NULL AUTO_INCREMENT,
+  customer_account_identifier VARCHAR(32)  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(3) NOT NULL,
+  last_modified_by            VARCHAR(32)  NULL,
+  last_modified_on            TIMESTAMP(3) NULL,
+  CONSTRAINT pk_hathor_identifiers PRIMARY KEY (id),
+  CONSTRAINT uk_hathor_identifiers_value UNIQUE (type, a_value, sub_value_or_type)
+);
+
+
+CREATE TABLE hathor_transactions (
+  id                          BIGINT         NOT NULL AUTO_INCREMENT,
+  identifier                  VARCHAR(36)    NOT NULL,
+  a_name                      VARCHAR(256)   NULL,
+  description                 VARCHAR(1024)  NULL,
+  transaction_type            VARCHAR(32)    NOT NULL,
+  amount                      NUMERIC(22, 4) NOT NULL,
+  state                       VARCHAR(32)    NOT NULL,
+  customer_account_identifier VARCHAR(32)    NOT NULL,
+  payable_account_identifier  VARCHAR(32)    NULL,
+  nostro_account_identifier   VARCHAR(32)    NOT NULL,
+  transaction_date            TIMESTAMP(3)   NULL,
+  expiration_date             TIMESTAMP(3)   NULL,
+  created_by                  VARCHAR(32)    NOT NULL,
+  created_on                  TIMESTAMP(3)   NOT NULL,
+  last_modified_by            VARCHAR(32)    NULL,
+  last_modified_on            TIMESTAMP(3)   NULL,
+  CONSTRAINT pk_hathor_transactions PRIMARY KEY (id),
+  CONSTRAINT uk_hathor_transactions_id UNIQUE (identifier)
+);
+
+
+CREATE TABLE hathor_actions (
+  id              BIGINT         NOT NULL AUTO_INCREMENT,
+  identifier      VARCHAR(64)    NOT NULL,
+  transaction_id  BIGINT         NOT NULL,
+  action_type     VARCHAR(32)    NOT NULL,
+  seq_no          INT            NOT NULL,
+  state           VARCHAR(32)    NOT NULL,
+  amount          NUMERIC(22, 4) NOT NULL,
+  fee             NUMERIC(22, 4) NULL,
+  commission      NUMERIC(22, 4) NULL,
+  error_code      CHAR(4)        NULL,
+  error_msg       VARCHAR(128)   NULL,
+  expiration_date TIMESTAMP(3)   NULL,
+  created_by      VARCHAR(32)    NOT NULL,
+  created_on      TIMESTAMP(3)   NOT NULL,
+  CONSTRAINT pk_hathor_identifiers PRIMARY KEY (id),
+  CONSTRAINT uk_hathor_actions_id UNIQUE (identifier),
+  CONSTRAINT uk_hathor_actions_type UNIQUE (transaction_id, action_type),
+  CONSTRAINT uk_hathor_actions_seq UNIQUE (transaction_id, seq_no),
+  CONSTRAINT fk_hathor_actions_trans FOREIGN KEY (transaction_id) REFERENCES hathor_transactions (id)
+);
+
+-- data initialization
+
+INSERT INTO thoth_tx_types (identifier, a_name, description)
+VALUES ('FCWD', 'Interoperation withdrawal', 'Demo FCWD Interoperation withdrawal');
+INSERT INTO thoth_tx_types (identifier, a_name, description)
+VALUES ('FCDP', 'Interoperation deposit', 'Demo FCDP Interoperation deposit');
+
+
+INSERT INTO shed_actions (identifier, a_name, description, transaction_type)
+VALUES ('InteropWithdraw', 'Interoperation withdrawal', 'Demo FCWD Interoperation withdrawal', 'FCWD');
+INSERT INTO shed_actions (identifier, a_name, description, transaction_type)
+VALUES ('InteropDeposit', 'Interoperation deposit', 'Demo FCDP Interoperation deposit', 'FCDP');
+
diff --git a/service/src/main/resources/db/migrations/sample_data/interop_sample_data.sql b/service/src/main/resources/db/migrations/sample_data/interop_sample_data.sql
new file mode 100644
index 0000000..503c179
--- /dev/null
+++ b/service/src/main/resources/db/migrations/sample_data/interop_sample_data.sql
@@ -0,0 +1,61 @@
+--
+-- 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.
+--
+
+-- sample data initialization
+
+INSERT INTO horus_offices (identifier, a_name, description, created_by, created_on)
+VALUES ('Headquarter', 'Headquarter', 'Demo Headquarter', 'operator', CURDATE());
+
+SET @office_id = -1;
+SELECT id INTO @office_id FROM horus_offices WHERE identifier = 'Headquarter';
+
+INSERT INTO horus_addresses (office_id, street, city, country_code, country)
+VALUES (@office_id, 'Headquarter street', 'Headquarter city', 'IC', 'Inclusia');
+
+INSERT INTO maat_addresses (street, city, country_code, country)
+VALUES ('Customer street', 'Customer city', 'IC', 'Inclusia');
+
+
+SET @ledger_1300_id = -1;
+SELECT id INTO @ledger_1300_id FROM thoth_ledgers WHERE identifier = '1300';
+SET @ledger_3700_id = -1;
+SELECT id INTO @ledger_3700_id FROM thoth_ledgers WHERE identifier = '3700';
+SET @ledger_7300_id = -1;
+SELECT id INTO @ledger_7300_id FROM thoth_ledgers WHERE identifier = '7300';
+SET @ledger_7900_id = -1;
+SELECT id INTO @ledger_7900_id FROM thoth_ledgers WHERE identifier = '7900';
+SET @ledger_8100_id = -1;
+SELECT id INTO @ledger_8100_id FROM thoth_ledgers WHERE identifier = '8100';
+SET @ledger_9100_id = -1;
+SELECT id INTO @ledger_9100_id FROM thoth_ledgers WHERE identifier = '9100';
+
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('97b1470ffd664cb799c3a9', 'Interoperation Payable Liability', 'LIABILITY', 1000000, @ledger_8100_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('353388f8c343445eac1bd6', 'Interoperation NOSTRO', 'ASSET', 1000000, @ledger_7900_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('0faaf81e05674f19859f18', 'Interoperation Product Cash', 'ASSET', 0, @ledger_7300_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('72df4f6613a911e9ab14d6', 'Interoperation Product Expenses', 'EXPENSE', 0, @ledger_3700_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('65f7de0e13a811e9ab14d6', 'Interoperation Product Accrue Liability', 'LIABILITY', 0, @ledger_8100_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('020323b613aa11e9ab14d6', 'Interoperation Product Equity', 'EQUITY', 0, @ledger_9100_id, 'OPEN', 'operator', CURDATE());
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, ledger_id, a_state, created_by, created_on)
+VALUES ('87d607ba13aa11e9ab14d6', 'Interoperation Product Fees Revenue', 'REVENUE', 1000000, @ledger_1300_id, 'OPEN', 'operator', CURDATE());
\ No newline at end of file
diff --git a/service/src/main/resources/db/migrations/sample_data/tn01_interop_sample_data.sql b/service/src/main/resources/db/migrations/sample_data/tn01_interop_sample_data.sql
new file mode 100644
index 0000000..aa277fb
--- /dev/null
+++ b/service/src/main/resources/db/migrations/sample_data/tn01_interop_sample_data.sql
@@ -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.
+--
+
+USE tn01;
+
+INSERT INTO maat_customers (identifier, a_type, given_name, surname, date_of_birth, is_member, current_state, address_id, created_by, created_on)
+  VALUES ('InteropCustomer', 'PERSON', 'Interoperation', 'Customer', '2000-01-01', 1, 'ACTIVE',
+        (SELECT id FROM maat_addresses WHERE street = 'Customer street'),
+        'operator', CURDATE());
+
+
+SET @ledger_7900_id = -1;
+SELECT id INTO @ledger_7900_id FROM thoth_ledgers WHERE identifier = '7900';
+
+SET @account_payable_id = -1;
+SELECT id INTO @account_payable_id FROM thoth_accounts WHERE identifier = '97b1470ffd664cb799c3a9';
+
+SET @account_customer_id = '31d77b06141c11e9ab14d6';
+
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, reference_account_id, ledger_id, a_state, created_by, created_on)
+VALUES (@account_customer_id, 'Interoperation Customer Account', 'ASSET', 1000000, @account_payable_id, @ledger_7900_id, 'OPEN', 'operator', CURDATE());
+
+
+INSERT INTO hathor_identifiers (customer_account_identifier, type, a_value, sub_value_or_type, created_by, created_on)
+VALUES (@account_customer_id, 'IBAN', 'IC11in01tn0131d77b06141c11e9ab14d6', null, 'operator', CURDATE());
+INSERT INTO hathor_identifiers (customer_account_identifier, type, a_value, sub_value_or_type, created_by, created_on)
+VALUES (@account_customer_id, 'MSISDN', '27710101999', null, 'operator', CURDATE());
+
+
+SET @action_withdraw_id = -1;
+SELECT id INTO @action_withdraw_id FROM shed_actions WHERE identifier = 'InteropWithdraw';
+
+INSERT INTO shed_product_definitions (identifier, a_name, description, a_type, minimum_balance, equity_ledger_identifier,
+                                      cash_account_identifier, expense_account_identifier, accrue_account_identifier, is_flexible,
+                                      is_active, created_by, created_on)
+  VALUES ('InteropCustomerProduct', 'Interoperation Customer Product', 'Demo Interoperation Product', 'SAVINGS', 100, '9100', '0faaf81e05674f19859f18',
+          '72df4f6613a911e9ab14d6', '65f7de0e13a811e9ab14d6', 0, 1,  'operator', CURDATE());
+
+SET @prod_def_id = -1;
+SELECT id INTO @prod_def_id FROM shed_product_definitions WHERE identifier = 'InteropCustomerProduct';
+
+INSERT INTO shed_currencies (product_definition_id, a_code, a_name, sign, scale)
+  VALUES (@prod_def_id, 'TZS', 'Inclusia Rupee', 'TSh', 2);
+
+INSERT INTO shed_terms (product_definition_id, period, time_unit, interest_payable)
+  VALUES (@prod_def_id, 1, 'YEAR', 'ANNUALLY');
+
+INSERT INTO shed_charges (product_definition_id, action_id, a_name, description, income_account_identifier, proportional, amount)
+VALUES (@prod_def_id, @action_withdraw_id, 'Interoperation Withdraw Fee', 'Interoperation Revenue Fees', '87d607ba13aa11e9ab14d6', 1, 1.00);
+
+INSERT INTO shed_product_instances (product_definition_id, customer_identifier, account_identifier, a_state, opened_on, created_by, created_on)
+VALUES (@prod_def_id, 'InteropCustomer', @account_customer_id, 'ACTIVE', CURDATE(), 'operator', CURDATE());
\ No newline at end of file
diff --git a/service/src/main/resources/db/migrations/sample_data/tn02_interop_sample_data.sql b/service/src/main/resources/db/migrations/sample_data/tn02_interop_sample_data.sql
new file mode 100644
index 0000000..efa3669
--- /dev/null
+++ b/service/src/main/resources/db/migrations/sample_data/tn02_interop_sample_data.sql
@@ -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.
+--
+
+USE tn02;
+
+INSERT INTO maat_customers (identifier, a_type, given_name, surname, date_of_birth, is_member, current_state, address_id, created_by, created_on)
+  VALUES ('InteropMerchant', 'PERSON', 'Interoperation', 'Merchant', '2000-01-01', 1, 'ACTIVE',
+        (SELECT id FROM maat_addresses WHERE street = 'Customer street'),
+        'operator', CURDATE());
+
+
+SET @ledger_7900_id = -1;
+SELECT id INTO @ledger_7900_id FROM thoth_ledgers WHERE identifier = '7900';
+
+SET @account_payable_id = -1;
+SELECT id INTO @account_payable_id FROM thoth_accounts WHERE identifier = '97b1470ffd664cb799c3a9';
+
+SET @account_customer_id = '83c4ed1c074b484e85cc79';
+
+INSERT INTO thoth_accounts (identifier, a_name, a_type, balance, reference_account_id, ledger_id, a_state, created_by, created_on)
+VALUES (@account_customer_id, 'Interoperation Merchant Account', 'ASSET', 1000000, @account_payable_id, @ledger_7900_id, 'OPEN', 'operator', CURDATE());
+
+
+INSERT INTO hathor_identifiers (customer_account_identifier, type, a_value, sub_value_or_type, created_by, created_on)
+VALUES (@account_customer_id, 'IBAN', 'IC11in01tn0283c4ed1c074b484e85cc79', null, 'operator', CURDATE());
+INSERT INTO hathor_identifiers (customer_account_identifier, type, a_value, sub_value_or_type, created_by, created_on)
+VALUES (@account_customer_id, 'MSISDN', '27710102999', null, 'operator', CURDATE());
+
+
+SET @action_withdraw_id = -1;
+SELECT id INTO @action_withdraw_id FROM shed_actions WHERE identifier = 'InteropWithdraw';
+
+INSERT INTO shed_product_definitions (identifier, a_name, description, a_type, minimum_balance, equity_ledger_identifier,
+                                      cash_account_identifier, expense_account_identifier, accrue_account_identifier, is_flexible,
+                                      is_active, created_by, created_on)
+  VALUES ('InteropMerchantProduct', 'Interoperation Merchant Product', 'Demo Interoperation Product', 'SAVINGS', 100, '9100', '0faaf81e05674f19859f18',
+          '72df4f6613a911e9ab14d6', '65f7de0e13a811e9ab14d6', 0, 1,  'operator', CURDATE());
+
+SET @prod_def_id = -1;
+SELECT id INTO @prod_def_id FROM shed_product_definitions WHERE identifier = 'InteropMerchantProduct';
+
+INSERT INTO shed_currencies (product_definition_id, a_code, a_name, sign, scale)
+  VALUES (@prod_def_id, 'TZS', 'Inclusia Rupee', 'TSh', 2);
+
+INSERT INTO shed_terms (product_definition_id, period, time_unit, interest_payable)
+  VALUES (@prod_def_id, 1, 'YEAR', 'ANNUALLY');
+
+INSERT INTO shed_charges (product_definition_id, action_id, a_name, description, income_account_identifier, proportional, amount)
+VALUES (@prod_def_id, @action_withdraw_id, 'Interoperation Withdraw Fee', 'Interoperation Revenue Fees', '87d607ba13aa11e9ab14d6', 1, 1.00);
+
+INSERT INTO shed_product_instances (product_definition_id, customer_identifier, account_identifier, a_state, opened_on, created_by, created_on)
+VALUES (@prod_def_id, 'InteropMerchant', @account_customer_id, 'ACTIVE', CURDATE(), 'operator', CURDATE());
\ No newline at end of file
diff --git a/service/src/main/resources/logback.xml b/service/src/main/resources/logback.xml
new file mode 100644
index 0000000..a5b8aa3
--- /dev/null
+++ b/service/src/main/resources/logback.xml
@@ -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.
+
+-->
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>logs/interoperation.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>logs/archive/interoperation.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>7</maxHistory>
+            <totalSizeCap>2GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="com" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="org" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="io" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <logger name="net" level="INFO">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <root level="DEBUG">
+        <appender-ref ref="FILE"/>
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f36b432
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+rootProject.name = 'interoperation'
+
+includeBuild 'api'
+includeBuild 'service'
+
diff --git a/shared.gradle b/shared.gradle
new file mode 100644
index 0000000..9d10733
--- /dev/null
+++ b/shared.gradle
@@ -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.
+ */
+
+group 'org.apache.fineract.cn.interoperation'
+version '0.1.0-BUILD-SNAPSHOT'
+
+ext.versions = [
+        frameworkanubis    : '0.1.0-BUILD-SNAPSHOT',
+        frameworkapi       : '0.1.0-BUILD-SNAPSHOT',
+        frameworklang      : '0.1.0-BUILD-SNAPSHOT',
+        frameworkmariadb   : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcassandra : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcommand   : '0.1.0-BUILD-SNAPSHOT',
+        frameworktest      : '0.1.0-BUILD-SNAPSHOT',
+        frameworkasync     : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcustomer  : '0.1.0-BUILD-SNAPSHOT',
+        frameworkcompany   : '0.1.0-BUILD-SNAPSHOT',
+        frameworkledger    : '0.1.0-BUILD-SNAPSHOT',
+        frameworkdeposit   : '0.1.0-BUILD-SNAPSHOT',
+        frameworkteller    : '0.1.0-BUILD-SNAPSHOT',
+        frameworkinter     : '0.1.0-BUILD-SNAPSHOT',
+        apachecsvreader    : '1.4',
+        validator   : '5.3.0.Final'
+]
+
+apply plugin: 'java'
+apply plugin: 'idea'
+apply plugin: 'maven-publish'
+apply plugin: 'io.spring.dependency-management'
+
+tasks.withType(JavaCompile) {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+repositories {
+    jcenter()
+    mavenLocal()
+}
+
+dependencyManagement {
+    imports {
+        mavenBom 'io.spring.platform:platform-bom:Athens-RELEASE'
+        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR1'
+    }
+}
+
+// override certain dependency provided by Spring platform using newer releases
+ext['cassandra.version'] = '3.6'
+ext['cassandra-driver.version'] = '3.1.2'
+ext['activemq.version'] = '5.13.2'
+ext['spring-data-releasetrain.version'] = 'Gosling-SR2A'
+
+dependencies {
+    compile(
+            [group: 'com.google.code.findbugs', name: 'jsr305']
+    )
+
+    testCompile(
+            [group: 'org.springframework.boot', name: 'spring-boot-starter-test']
+    )
+}
+
+jar {
+    from sourceSets.main.allSource
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+license {
+    header rootProject.file('../HEADER')
+    strictCheck false
+    mapping {
+        java = 'SLASHSTAR_STYLE'
+        xml = 'XML_STYLE'
+        yml = 'SCRIPT_STYLE'
+        yaml = 'SCRIPT_STYLE'
+    }
+}
+
+rat {
+    // List of exclude directives, defaults to ['**/.gradle/**']
+    excludes = [
+            "**/.idea/**",
+            "**/.gradle/**",
+            "**/gradle/**",
+            "**/build/**",
+            "gradlew",
+            "gradlew.bat",
+            "README.md"
+    ]
+}