diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..d4c4a74
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openg2p-g2p-bridge-example-bank-api/.copier-answers.yml b/openg2p-g2p-bridge-example-bank-api/.copier-answers.yml
new file mode 100644
index 0000000..60cfca7
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.copier-answers.yml
@@ -0,0 +1,17 @@
+# Do NOT update manually; changes here will be overwritten by Copier
+_commit: af08ec1
+_src_path: https://github.com/openg2p/openg2p-fastapi-template
+github_ci_docker_build: true
+github_ci_openapi_publish: true
+github_ci_precommit: true
+github_ci_pypi_publish: true
+github_ci_tests: true
+github_ci_tests_codecov: true
+module_name: openg2p_g2p_bridge_api
+org_name: OpenG2P
+org_slug: OpenG2P
+package_name: openg2p-g2p-bridge-api
+repo_name: ' openg2p-g2p-bridge-api
+
+ '
+repo_slug: openg2p-g2p-bridge-api
diff --git a/openg2p-g2p-bridge-example-bank-api/.dockerignore b/openg2p-g2p-bridge-example-bank-api/.dockerignore
new file mode 100644
index 0000000..d47f7ee
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.dockerignore
@@ -0,0 +1,89 @@
+# Git
+.git
+.gitignore
+.gitattributes
+
+
+# CI
+.codeclimate.yml
+.travis.yml
+.taskcluster.yml
+
+# Docker
+docker-compose.yml
+Dockerfile
+.docker
+.dockerignore
+
+# Byte-compiled / optimized / DLL files
+**/__pycache__/
+**/*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Virtual environment
+../.env
+.venv/
+venv/
+
+# PyCharm
+.idea
+
+# Python mode for VIM
+.ropeproject
+**/.ropeproject
+
+# Vim swap files
+**/*.swp
+
+# VS Code
+.vscode/
diff --git a/openg2p-g2p-bridge-example-bank-api/.editorconfig b/openg2p-g2p-bridge-example-bank-api/.editorconfig
new file mode 100644
index 0000000..7d8f3a5
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.editorconfig
@@ -0,0 +1,20 @@
+# Configuration for known file extensions
+[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml,toml,jinja}]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{yml,yaml,rst,md,jinja}]
+indent_size = 2
+
+# Do not configure editor for libs and autogenerated content
+[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
+charset = unset
+end_of_line = unset
+indent_size = unset
+indent_style = unset
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/openg2p-g2p-bridge-example-bank-api/.gitignore b/openg2p-g2p-bridge-example-bank-api/.gitignore
new file mode 100644
index 0000000..c633cec
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.gitignore
@@ -0,0 +1,81 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+/venv
+/.pytest_cache
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+*.eggs
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Pycharm
+.idea
+
+# Eclipse
+.settings
+
+# Visual Studio cache/options directory
+.vs/
+.vscode
+
+# OSX Files
+.DS_Store
+
+# Django stuff:
+*.log
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Sphinx documentation
+docs/_build/
+
+# Backup files
+*~
+*.swp
+
+# OCA rules
+!static/lib/
+
+# Ruff stuff
+.ruff_cache
+
+# Ignore secret files and env
+.secrets.*
+../.env
diff --git a/openg2p-g2p-bridge-example-bank-api/.pre-commit-config.yaml b/openg2p-g2p-bridge-example-bank-api/.pre-commit-config.yaml
new file mode 100644
index 0000000..336b7e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.pre-commit-config.yaml
@@ -0,0 +1,49 @@
+exclude: |
+ (?x)
+ # We don't want to mess with tool-generated files
+ .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
+ # Maybe reactivate this when all README files include prettier ignore tags?
+ ^README\.md$|
+ # Repos using Sphinx to generate docs don't need prettying
+ ^docs/_templates/.*\.html$|
+ # You don't usually want a bot to modify your legal texts
+ (LICENSE.*|COPYING.*)
+default_language_version:
+ python: python3
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: debug-statements
+ - id: fix-encoding-pragma
+ args: ["--remove"]
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: check-executables-have-shebangs
+ - id: check-merge-conflict
+ - id: check-symlinks
+ - id: check-toml
+ - id: check-yaml
+ args:
+ - --unsafe
+ - id: mixed-line-ending
+ args: ["--fix=lf"]
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.11.0
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py3-plus
+ - --keep-runtime-typing
+ - repo: https://github.com/psf/black
+ rev: 23.9.1
+ hooks:
+ - id: black
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.0.289
+ hooks:
+ - id: ruff
+ args:
+ - --fix
diff --git a/openg2p-g2p-bridge-example-bank-api/.ruff.toml b/openg2p-g2p-bridge-example-bank-api/.ruff.toml
new file mode 100644
index 0000000..aa1fc5b
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/.ruff.toml
@@ -0,0 +1,16 @@
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+]
+ignore = [
+ "E501", # line too long, handled by black
+ "B008", # do not perform function calls in argument defaults
+ "C901", # too complex
+]
+
+[per-file-ignores]
+"__init__.py" = ["F401"]
diff --git a/openg2p-g2p-bridge-example-bank-api/CODE-OF-CONDUCT.md b/openg2p-g2p-bridge-example-bank-api/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000..e1949e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/CODE-OF-CONDUCT.md
@@ -0,0 +1,114 @@
+# Code of Conduct
+
+## Contributor Covenant Code of Conduct
+
+### Preamble
+
+OpenG2P was created to foster an open, innovative and inclusive community around open source & open standard.
+To clarify expected behaviour in our communities we have adopted the Contributor Covenant. This code of
+conduct has been adopted by many other open source communities and we feel it expresses our values well.
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free
+experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex
+characteristics, gender identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy
+community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment for our community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address, without their explicit
+ permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+### Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will
+take appropriate and fair corrective action in response to any behavior that they deem inappropriate,
+threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki
+edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate
+reasons for moderation decisions when appropriate.
+
+### Scope
+
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially
+representing the community in public spaces. Examples of representing our community include using an official
+e-mail address, posting via an official social media account, or acting as an appointed representative at an
+online or offline event.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders
+responsible for enforcement at \[INSERT CONTACT METHOD]. All complaints will be reviewed and investigated
+promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any incident.
+
+### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action
+they deem in violation of this Code of Conduct:
+
+#### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in
+the community.
+
+**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the
+violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+
+#### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved,
+including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time.
+This includes avoiding interactions in community spaces as well as external channels like social media.
+Violating these terms may lead to a temporary or permanent ban.
+
+#### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a
+specified period of time. No public or private interaction with the people involved, including unsolicited
+interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may
+lead to a permanent ban.
+
+#### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained
+inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of
+individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the community.
+
+### Attribution
+
+This Code of Conduct is adapted from the
+[Contributor Covenant version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+
+For answers to common questions about this code of conduct, see the
+[FAQ](https://www.contributor-covenant.org/faq). Translations are available
+[here](https://www.contributor-covenant.org/translations).
diff --git a/openg2p-g2p-bridge-example-bank-api/CONTRIBUTING.md b/openg2p-g2p-bridge-example-bank-api/CONTRIBUTING.md
new file mode 100644
index 0000000..c187f93
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/CONTRIBUTING.md
@@ -0,0 +1,2 @@
+Refer to contribution guidelines
+[here](https://github.com/OpenG2P/openg2p-documentation/blob/1.0.0/community/contributing-to-openg2p.md).
diff --git a/openg2p-g2p-bridge-example-bank-api/LICENSE b/openg2p-g2p-bridge-example-bank-api/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/openg2p-g2p-bridge-example-bank-api/README.md b/openg2p-g2p-bridge-example-bank-api/README.md
new file mode 100644
index 0000000..15fc796
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/README.md
@@ -0,0 +1,14 @@
+# openg2p-g2p-bridge-api
+
+[![Pre-commit Status](https://github.com/OpenG2P/openg2p-g2p-bridge-api/actions/workflows/pre-commit.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-g2p-bridge-api/actions/workflows/pre-commit.yml?query=branch%3Adevelop)
+[![Build Status](https://github.com/OpenG2P/openg2p-g2p-bridge-api/actions/workflows/test.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-g2p-bridge-api/actions/workflows/test.yml?query=branch%3Adevelop)
+[![codecov](https://codecov.io/gh/OpenG2P/openg2p-g2p-bridge-api/branch/develop/graph/badge.svg)](https://codecov.io/gh/OpenG2P/openg2p-g2p-bridge-api)
+[![openapi](https://img.shields.io/badge/open--API-swagger-brightgreen)](https://validator.swagger.io/?url=https://raw.githubusercontent.com/OpenG2P/openg2p-g2p-bridge-api/develop/api-docs/generated/openapi.json)
+![PyPI](https://img.shields.io/pypi/v/openg2p-g2p-bridge-api?label=pypi%20package)
+![PyPI - Downloads](https://img.shields.io/pypi/dm/openg2p-g2p-bridge-api)
+
+
+
+## Licenses
+
+This repository is licensed under [MPL-2.0](LICENSE).
diff --git a/openg2p-g2p-bridge-example-bank-api/__init__.py b/openg2p-g2p-bridge-example-bank-api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openg2p-g2p-bridge-example-bank-api/celerybeat-schedule.db b/openg2p-g2p-bridge-example-bank-api/celerybeat-schedule.db
new file mode 100644
index 0000000..246dbee
Binary files /dev/null and b/openg2p-g2p-bridge-example-bank-api/celerybeat-schedule.db differ
diff --git a/openg2p-g2p-bridge-example-bank-api/main.py b/openg2p-g2p-bridge-example-bank-api/main.py
new file mode 100755
index 0000000..f97a162
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/main.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+
+# ruff: noqa: I001
+
+from openg2p_g2p_bridge_example_bank_api.app import Initializer
+from openg2p_fastapi_common.ping import PingInitializer
+
+initializer = Initializer()
+PingInitializer()
+
+app = initializer.return_app()
+
+if __name__ == "__main__":
+ initializer.main()
diff --git a/openg2p-g2p-bridge-example-bank-api/pyproject.toml b/openg2p-g2p-bridge-example-bank-api/pyproject.toml
new file mode 100644
index 0000000..afe215a
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/pyproject.toml
@@ -0,0 +1,31 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "openg2p-g2p-bridge-example-bank-api"
+authors = [
+ { name="OpenG2P", email="info@openg2p.org" },
+]
+description = "OpenG2P G2P Bridge API"
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+ "Operating System :: OS Independent",
+]
+dependencies = [
+ "openg2p-fastapi-common",
+ "openg2p-fastapi-auth",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://openg2p.org"
+Documentation = "https://docs.openg2p.org/"
+Repository = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-bank-api"
+Source = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-bank-api"
+
+[tool.hatch.version]
+path = "src/openg2p_g2p_bridge_example_bank_api/__init__.py"
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/__init__.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/__init__.py
new file mode 100644
index 0000000..5becc17
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/__init__.py
@@ -0,0 +1 @@
+__version__ = "1.0.0"
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py
new file mode 100644
index 0000000..6b9be17
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/app.py
@@ -0,0 +1,45 @@
+# ruff: noqa: E402
+import asyncio
+import logging
+
+from openg2p_g2p_bridge_example_bank_api.config import Settings
+
+_config = Settings.get_config()
+
+from openg2p_fastapi_common.app import Initializer as BaseInitializer
+from sqlalchemy import create_engine
+
+from openg2p_g2p_bridge_example_bank_api.controllers import (
+ BlockFundsController,
+ FundAvailabilityController,
+ PaymentController,
+)
+from openg2p_g2p_bridge_example_bank_models.models import Account, FundBlock, InitiatePaymentRequest
+
+_logger = logging.getLogger(_config.logging_default_logger_name)
+
+
+class Initializer(BaseInitializer):
+ def initialize(self, **kwargs):
+ super().initialize()
+
+ BlockFundsController().post_init()
+ FundAvailabilityController().post_init()
+ PaymentController().post_init()
+
+ def migrate_database(self, args):
+ super().migrate_database(args)
+
+ async def migrate():
+ _logger.info("Migrating database")
+ await Account.create_migrate()
+ await FundBlock.create_migrate()
+ await InitiatePaymentRequest.create_migrate()
+
+ asyncio.run(migrate())
+
+
+def get_engine():
+ if _config.db_datasource:
+ db_engine = create_engine(_config.db_datasource)
+ return db_engine
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/config.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/config.py
new file mode 100644
index 0000000..10989be
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/config.py
@@ -0,0 +1,20 @@
+from openg2p_fastapi_common.config import Settings as BaseSettings
+from pydantic_settings import SettingsConfigDict
+
+from . import __version__
+
+
+class Settings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_prefix="example_bank_", env_file=".env", extra="allow"
+ )
+
+ openapi_title: str = "Example Bank APIs for Cash Transfer"
+ openapi_description: str = """
+ ***********************************
+ Further details goes here
+ ***********************************
+ """
+ openapi_version: str = __version__
+
+ db_dbname: str = "example_bank_db"
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py
new file mode 100644
index 0000000..9c545da
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/__init__.py
@@ -0,0 +1,3 @@
+from openg2p_g2p_bridge_example_bank_api.controllers.block_funds import BlockFundsController
+from openg2p_g2p_bridge_example_bank_api.controllers.check_available_funds import FundAvailabilityController
+from openg2p_g2p_bridge_example_bank_api.controllers.initiate_payment import PaymentController
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py
new file mode 100644
index 0000000..6030cad
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/block_funds.py
@@ -0,0 +1,74 @@
+import uuid
+
+from openg2p_fastapi_common.context import dbengine
+from openg2p_fastapi_common.controller import BaseController
+from sqlalchemy import update
+from sqlalchemy.ext.asyncio import async_sessionmaker
+from sqlalchemy.future import select
+
+from openg2p_g2p_bridge_example_bank_models.models import Account, FundBlock
+from openg2p_g2p_bridge_example_bank_models.schemas import BlockFundsRequest, BlockFundsResponse
+
+
+class BlockFundsController(BaseController):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ self.router.tags += ["Funds Management"]
+
+ self.router.add_api_route(
+ "/block_funds",
+ self.block_funds,
+ response_model=BlockFundsResponse,
+ methods=["POST"],
+ )
+
+ async def block_funds(self, request: BlockFundsRequest) -> BlockFundsResponse:
+ session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False)
+ async with session_maker() as session:
+ stmt = select(Account).where(
+ (Account.account_number == request.account_no)
+ & (Account.account_currency == request.currency)
+ )
+ result = await session.execute(stmt)
+ account = result.scalars().first()
+
+ if not account:
+ return BlockFundsResponse(
+ status="failed",
+ block_reference_no="",
+ error_message="Account not found",
+ )
+ if account.available_balance < request.amount:
+ return BlockFundsResponse(
+ status="failed",
+ block_reference_no="",
+ error_message="Insufficient funds",
+ )
+
+ await session.execute(
+ update(Account)
+ .where(Account.account_number == request.account_no)
+ .values(
+ available_balance=account.book_balance
+ - (account.blocked_amount + request.amount),
+ blocked_amount=account.blocked_amount + request.amount,
+ )
+ )
+
+ block_reference_no = str(uuid.uuid4())
+ fund_block = FundBlock(
+ block_reference_no=block_reference_no,
+ account_no=request.account_no,
+ amount=request.amount,
+ currency=request.currency,
+ active=True,
+ )
+ session.add(fund_block)
+
+ await session.commit()
+ return BlockFundsResponse(
+ status="success",
+ block_reference_no=block_reference_no,
+ error_message="",
+ )
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py
new file mode 100644
index 0000000..37a08c4
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/check_available_funds.py
@@ -0,0 +1,55 @@
+from openg2p_fastapi_common.context import dbengine
+from openg2p_fastapi_common.controller import BaseController
+from sqlalchemy.ext.asyncio import async_sessionmaker
+from sqlalchemy.future import select
+
+from openg2p_g2p_bridge_example_bank_models.models import Account
+from openg2p_g2p_bridge_example_bank_models.schemas import CheckFundRequest, CheckFundResponse
+
+
+class FundAvailabilityController(BaseController):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ self.router.tags += ["Fund Availability"]
+
+ self.router.add_api_route(
+ "/check_funds",
+ self.check_available_funds,
+ response_model=CheckFundResponse,
+ methods=["POST"],
+ )
+
+ async def check_available_funds(
+ self, request: CheckFundRequest
+ ) -> CheckFundResponse:
+ session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False)
+ async with session_maker() as session:
+ stmt = select(Account).where(
+ Account.account_number == request.account_number
+ )
+ result = await session.execute(stmt)
+ account = result.scalars().first()
+
+ if not account:
+ return CheckFundResponse(
+ status="failed",
+ account_number=request.account_number,
+ has_sufficient_funds=False,
+ error_message="Account not found",
+ )
+
+ if account.available_balance >= request.total_funds_needed:
+ return CheckFundResponse(
+ status="success",
+ account_number=account.account_number,
+ has_sufficient_funds=True,
+ error_message="",
+ )
+ else:
+ return CheckFundResponse(
+ status="failed",
+ account_number=account.account_number,
+ has_sufficient_funds=False,
+ error_message="Insufficient funds",
+ )
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py
new file mode 100644
index 0000000..797e633
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/controllers/initiate_payment.py
@@ -0,0 +1,78 @@
+from typing import List
+
+from openg2p_fastapi_common.context import dbengine
+from openg2p_fastapi_common.controller import BaseController
+from sqlalchemy.ext.asyncio import async_sessionmaker
+from sqlalchemy.future import select
+
+from openg2p_g2p_bridge_example_bank_models.models import FundBlock, InitiatePaymentRequest
+from openg2p_g2p_bridge_example_bank_models.schemas import InitiatePaymentPayload, InitiatorPaymentResponse
+
+
+class PaymentController(BaseController):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ self.router.tags += ["Payments Management"]
+
+ self.router.add_api_route(
+ "/initiate_payment",
+ self.initiate_payment,
+ response_model=InitiatorPaymentResponse,
+ methods=["POST"],
+ )
+
+ async def initiate_payment(
+ self, initiate_payment_payloads: List[InitiatePaymentPayload]
+ ) -> InitiatorPaymentResponse:
+ session_maker = async_sessionmaker(dbengine.get(), expire_on_commit=False)
+ async with session_maker() as session:
+ for initiate_payment_payload in initiate_payment_payloads:
+ fund_block_stmt = select(FundBlock).where(
+ FundBlock.block_reference_no
+ == initiate_payment_payload.funds_blocked_reference_number
+ )
+ fund_block_result = await session.execute(fund_block_stmt)
+ fund_block = fund_block_result.scalars().first()
+
+ if (
+ not fund_block
+ or initiate_payment_payload.payment_amount > fund_block.amount
+ or fund_block.currency
+ != initiate_payment_payload.remitting_account_currency
+ ):
+ return InitiatorPaymentResponse(
+ status="failed",
+ error_message="Invalid funds block reference or mismatch in details",
+ )
+
+ payment = InitiatePaymentRequest(
+ payment_reference_number=initiate_payment_payload.payment_reference_number,
+ remitting_account=initiate_payment_payload.remitting_account,
+ remitting_account_currency=initiate_payment_payload.remitting_account_currency,
+ payment_amount=initiate_payment_payload.payment_amount,
+ funds_blocked_reference_number=initiate_payment_payload.funds_blocked_reference_number,
+ beneficiary_name=initiate_payment_payload.beneficiary_name,
+ beneficiary_account=initiate_payment_payload.beneficiary_account,
+ beneficiary_account_currency=initiate_payment_payload.beneficiary_account_currency,
+ beneficiary_account_type=initiate_payment_payload.beneficiary_account_type,
+ beneficiary_bank_code=initiate_payment_payload.beneficiary_bank_code,
+ beneficiary_branch_code=initiate_payment_payload.beneficiary_branch_code,
+ beneficiary_mobile_wallet_provider=initiate_payment_payload.beneficiary_mobile_wallet_provider,
+ beneficiary_phone_no=initiate_payment_payload.beneficiary_phone_no,
+ beneficiary_email=initiate_payment_payload.beneficiary_email,
+ beneficiary_email_wallet_provider=initiate_payment_payload.beneficiary_email_wallet_provider,
+ payment_date=initiate_payment_payload.payment_date,
+ narrative_1=initiate_payment_payload.narrative_1,
+ narrative_2=initiate_payment_payload.narrative_2,
+ narrative_3=initiate_payment_payload.narrative_3,
+ narrative_4=initiate_payment_payload.narrative_4,
+ narrative_5=initiate_payment_payload.narrative_5,
+ narrative_6=initiate_payment_payload.narrative_6,
+ active=True,
+ )
+ session.add(payment)
+
+ await session.commit()
+
+ return InitiatorPaymentResponse(status="success", error_message="")
diff --git a/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/utils/__init__.py b/openg2p-g2p-bridge-example-bank-api/src/openg2p_g2p_bridge_example_bank_api/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openg2p-g2p-bridge-example-bank-api/test-requirements.txt b/openg2p-g2p-bridge-example-bank-api/test-requirements.txt
new file mode 100644
index 0000000..4f53afa
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/test-requirements.txt
@@ -0,0 +1,3 @@
+pytest-cov
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-common
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-auth
diff --git a/openg2p-g2p-bridge-example-bank-api/tests/__init__.py b/openg2p-g2p-bridge-example-bank-api/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement.py b/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement.py
new file mode 100644
index 0000000..8171c3e
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement.py
@@ -0,0 +1,175 @@
+import datetime
+from unittest.mock import AsyncMock, patch
+
+import pytest
+from openg2p_g2p_bridge_api.controllers import DisbursementController
+from openg2p_g2p_bridge_models.errors.codes import G2PBridgeErrorCodes
+from openg2p_g2p_bridge_models.errors.exceptions import DisbursementException
+from openg2p_g2p_bridge_models.models import CancellationStatus
+from openg2p_g2p_bridge_models.schemas import (
+ DisbursementPayload,
+ DisbursementRequest,
+ DisbursementResponse,
+ ResponseStatus,
+)
+
+
+def mock_create_disbursements(is_valid, disbursement_payloads):
+ if not is_valid:
+ raise DisbursementException(
+ code=G2PBridgeErrorCodes.INVALID_DISBURSEMENT_PAYLOAD,
+ disbursement_payloads=disbursement_payloads,
+ )
+ return disbursement_payloads
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementService.get_component")
+async def test_create_disbursements_success(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ disbursement_payloads = [
+ DisbursementPayload(
+ disbursement_envelope_id="env123",
+ beneficiary_id="123AB",
+ disbursement_amount=1000,
+ )
+ ]
+ mock_service_instance.create_disbursements = AsyncMock(
+ return_value=mock_create_disbursements(True, disbursement_payloads)
+ )
+ mock_service_instance.construct_disbursement_success_response = AsyncMock(
+ return_value=DisbursementResponse(
+ response_status=ResponseStatus.SUCCESS,
+ response_payload=disbursement_payloads,
+ )
+ )
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ controller = DisbursementController()
+ request_payload = DisbursementRequest(request_payload=disbursement_payloads)
+
+ response = await controller.create_disbursements(request_payload)
+
+ assert response.response_status == ResponseStatus.SUCCESS
+ assert response.response_payload == disbursement_payloads
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementService.get_component")
+async def test_create_disbursements_failure(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ disbursement_payloads = [
+ DisbursementPayload(
+ disbursement_envelope_id="env123",
+ beneficiary_id="123AB",
+ disbursement_amount=1000,
+ )
+ ]
+ mock_service_instance.create_disbursements = AsyncMock(
+ side_effect=lambda req: mock_create_disbursements(False, req.request_payload)
+ )
+ mock_service_instance.construct_disbursement_error_response = AsyncMock(
+ return_value=DisbursementResponse(
+ response_status=ResponseStatus.FAILURE,
+ response_error_code=G2PBridgeErrorCodes.INVALID_DISBURSEMENT_PAYLOAD,
+ response_payload=disbursement_payloads,
+ )
+ )
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ controller = DisbursementController()
+ request_payload = DisbursementRequest(request_payload=disbursement_payloads)
+
+ response = await controller.create_disbursements(request_payload)
+
+ assert response.response_status == ResponseStatus.FAILURE
+ assert (
+ response.response_error_code == G2PBridgeErrorCodes.INVALID_DISBURSEMENT_PAYLOAD
+ )
+
+
+def mock_cancel_disbursements(is_valid, disbursement_payloads):
+ if not is_valid:
+ raise DisbursementException(
+ code=G2PBridgeErrorCodes.DISBURSEMENT_ALREADY_CANCELED,
+ disbursement_payloads=disbursement_payloads,
+ )
+ for payload in disbursement_payloads:
+ payload.cancellation_status = CancellationStatus.Cancelled
+ payload.cancellation_time_stamp = datetime.datetime.utcnow()
+ return disbursement_payloads
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementService.get_component")
+async def test_cancel_disbursements_success(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ disbursement_payloads = [
+ DisbursementPayload(
+ disbursement_id="123",
+ beneficiary_id="123AB",
+ disbursement_amount=1000,
+ cancellation_status=None,
+ )
+ ]
+ mock_service_instance.cancel_disbursements = AsyncMock(
+ return_value=mock_cancel_disbursements(True, disbursement_payloads)
+ )
+ mock_service_instance.construct_disbursement_success_response = AsyncMock(
+ return_value=DisbursementResponse(
+ response_status=ResponseStatus.SUCCESS,
+ response_payload=disbursement_payloads,
+ )
+ )
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ controller = DisbursementController()
+ request_payload = DisbursementRequest(request_payload=disbursement_payloads)
+
+ response = await controller.cancel_disbursements(request_payload)
+
+ assert response.response_status == ResponseStatus.SUCCESS
+ assert all(
+ payload.cancellation_status == CancellationStatus.Cancelled
+ for payload in response.response_payload
+ )
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementService.get_component")
+async def test_cancel_disbursements_failure(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ disbursement_payloads = [
+ DisbursementPayload(
+ disbursement_id="123",
+ beneficiary_id="123AB",
+ disbursement_amount=1000,
+ cancellation_status=None,
+ )
+ ]
+ mock_service_instance.cancel_disbursements = AsyncMock(
+ side_effect=lambda req: mock_cancel_disbursements(False, req.request_payload)
+ )
+ mock_service_instance.construct_disbursement_error_response = AsyncMock(
+ return_value=DisbursementResponse(
+ response_status=ResponseStatus.FAILURE,
+ response_error_code=G2PBridgeErrorCodes.DISBURSEMENT_ALREADY_CANCELED,
+ response_payload=disbursement_payloads,
+ )
+ )
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ controller = DisbursementController()
+ request_payload = DisbursementRequest(request_payload=disbursement_payloads)
+
+ response = await controller.cancel_disbursements(request_payload)
+
+ assert response.response_status == ResponseStatus.FAILURE
+ assert (
+ response.response_error_code
+ == G2PBridgeErrorCodes.DISBURSEMENT_ALREADY_CANCELED
+ )
diff --git a/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement_envelope.py b/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement_envelope.py
new file mode 100644
index 0000000..4ccfd8a
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-api/tests/test_disbursement_envelope.py
@@ -0,0 +1,197 @@
+from datetime import datetime
+from unittest.mock import AsyncMock, patch
+
+import pytest
+from openg2p_g2p_bridge_api.controllers import DisbursementEnvelopeController
+from openg2p_g2p_bridge_models.errors.codes import G2PBridgeErrorCodes
+from openg2p_g2p_bridge_models.errors.exceptions import DisbursementEnvelopeException
+from openg2p_g2p_bridge_models.schemas import (
+ DisbursementEnvelopePayload,
+ DisbursementEnvelopeRequest,
+ DisbursementEnvelopeResponse,
+ ResponseStatus,
+)
+
+
+def mock_create_disbursement_envelope(is_valid, error_code=None):
+ if not is_valid:
+ raise DisbursementEnvelopeException(
+ code=error_code, message=f"{error_code} error."
+ )
+ return DisbursementEnvelopePayload(
+ disbursement_envelope_id="env123",
+ benefit_program_mnemonic="TEST123",
+ disbursement_frequency="Monthly",
+ cycle_code_mnemonic="CYCLE42",
+ number_of_beneficiaries=100,
+ number_of_disbursements=100,
+ total_disbursement_amount=5000.00,
+ disbursement_schedule_date=datetime.date(datetime.utcnow()),
+ )
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementEnvelopeService.get_component")
+async def test_create_disbursement_envelope_success(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ mock_service_instance.create_disbursement_envelope = AsyncMock(
+ return_value=mock_create_disbursement_envelope(True)
+ )
+ mock_service_instance.construct_disbursement_envelope_success_response = AsyncMock()
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ expected_payload = mock_create_disbursement_envelope(True)
+ expected_response = DisbursementEnvelopeResponse(
+ response_status=ResponseStatus.SUCCESS, response_payload=expected_payload
+ )
+ mock_service_instance.construct_disbursement_envelope_success_response.return_value = (
+ expected_response
+ )
+ controller = DisbursementEnvelopeController()
+ request_payload = DisbursementEnvelopeRequest(
+ request_payload=DisbursementEnvelopePayload(
+ benefit_program_mnemonic="TEST123",
+ disbursement_frequency="Monthly",
+ cycle_code_mnemonic="CYCLE42",
+ number_of_beneficiaries=100,
+ number_of_disbursements=100,
+ total_disbursement_amount=5000.00,
+ disbursement_schedule_date=datetime.date(datetime.utcnow()),
+ )
+ )
+
+ actual_response = await controller.create_disbursement_envelope(request_payload)
+
+ assert actual_response == expected_response
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementEnvelopeService.get_component")
+@pytest.mark.parametrize("error_code", list(G2PBridgeErrorCodes))
+async def test_create_disbursement_envelope_errors(
+ mock_service_get_component, error_code
+):
+ mock_service_instance = AsyncMock()
+ mock_service_instance.create_disbursement_envelope.side_effect = (
+ lambda request: mock_create_disbursement_envelope(False, error_code)
+ )
+ mock_service_instance.construct_disbursement_envelope_error_response = AsyncMock()
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ error_response = DisbursementEnvelopeResponse(
+ response_status=ResponseStatus.FAILURE,
+ response_error_code=error_code,
+ )
+
+ mock_service_instance.construct_disbursement_envelope_error_response.return_value = (
+ error_response
+ )
+
+ controller = DisbursementEnvelopeController()
+
+ request_payload = DisbursementEnvelopeRequest(
+ request_payload=DisbursementEnvelopePayload(
+ benefit_program_mnemonic="", # Trigger the error
+ disbursement_frequency="Monthly",
+ cycle_code_mnemonic="CYCLE42",
+ number_of_beneficiaries=100,
+ number_of_disbursements=100,
+ total_disbursement_amount=5000.00,
+ disbursement_schedule_date=datetime.date(datetime.utcnow()),
+ )
+ )
+
+ actual_response = await controller.create_disbursement_envelope(request_payload)
+
+ assert (
+ actual_response == error_response
+ ), f"The response did not match the expected error response for {error_code}."
+
+
+def mock_cancel_disbursement_envelope(is_valid, error_code=None):
+ if not is_valid:
+ raise DisbursementEnvelopeException(
+ code=error_code, message=f"{error_code} error."
+ )
+
+ return DisbursementEnvelopePayload(
+ disbursement_envelope_id="env123",
+ benefit_program_mnemonic="TEST123",
+ disbursement_frequency="Monthly",
+ cycle_code_mnemonic="CYCLE42",
+ number_of_beneficiaries=100,
+ number_of_disbursements=100,
+ total_disbursement_amount=5000.00,
+ disbursement_schedule_date=datetime.date(datetime.utcnow()),
+ )
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementEnvelopeService.get_component")
+async def test_cancel_disbursement_envelope_success(mock_service_get_component):
+ mock_service_instance = AsyncMock()
+ mock_service_instance.cancel_disbursement_envelope = AsyncMock(
+ return_value=mock_cancel_disbursement_envelope(True)
+ )
+ mock_service_instance.construct_disbursement_envelope_success_response = AsyncMock()
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ successful_payload = mock_cancel_disbursement_envelope(True)
+ expected_response = DisbursementEnvelopeResponse(
+ response_status=ResponseStatus.SUCCESS, response_payload=successful_payload
+ )
+ mock_service_instance.construct_disbursement_envelope_success_response.return_value = (
+ expected_response
+ )
+
+ controller = DisbursementEnvelopeController()
+ request_payload = DisbursementEnvelopeRequest(
+ request_payload=DisbursementEnvelopePayload(disbursement_envelope_id="env123")
+ )
+
+ actual_response = await controller.cancel_disbursement_envelope(request_payload)
+ assert actual_response == expected_response
+
+
+@pytest.mark.asyncio
+@patch("openg2p_g2p_bridge_api.services.DisbursementEnvelopeService.get_component")
+@pytest.mark.parametrize(
+ "error_code",
+ [
+ G2PBridgeErrorCodes.DISBURSEMENT_ENVELOPE_NOT_FOUND,
+ G2PBridgeErrorCodes.DISBURSEMENT_ENVELOPE_ALREADY_CANCELED,
+ ],
+)
+async def test_cancel_disbursement_envelope_failure(
+ mock_service_get_component, error_code
+):
+ mock_service_instance = AsyncMock()
+ mock_service_instance.cancel_disbursement_envelope.side_effect = (
+ lambda request: mock_cancel_disbursement_envelope(False, error_code)
+ )
+ mock_service_instance.construct_disbursement_envelope_error_response = AsyncMock()
+
+ mock_service_get_component.return_value = mock_service_instance
+
+ error_response = DisbursementEnvelopeResponse(
+ response_status=ResponseStatus.FAILURE,
+ response_error_code=error_code.value,
+ )
+ mock_service_instance.construct_disbursement_envelope_error_response.return_value = (
+ error_response
+ )
+
+ controller = DisbursementEnvelopeController()
+ request_payload = DisbursementEnvelopeRequest(
+ request_payload=DisbursementEnvelopePayload(
+ disbursement_envelope_id="env123" # Assuming this ID triggers the error
+ )
+ )
+
+ actual_response = await controller.cancel_disbursement_envelope(request_payload)
+ assert (
+ actual_response == error_response
+ ), f"The response for {error_code} did not match the expected error response."
diff --git a/openg2p-g2p-bridge-example-bank-celery/.copier-answers.yml b/openg2p-g2p-bridge-example-bank-celery/.copier-answers.yml
new file mode 100644
index 0000000..60cfca7
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.copier-answers.yml
@@ -0,0 +1,17 @@
+# Do NOT update manually; changes here will be overwritten by Copier
+_commit: af08ec1
+_src_path: https://github.com/openg2p/openg2p-fastapi-template
+github_ci_docker_build: true
+github_ci_openapi_publish: true
+github_ci_precommit: true
+github_ci_pypi_publish: true
+github_ci_tests: true
+github_ci_tests_codecov: true
+module_name: openg2p_g2p_bridge_api
+org_name: OpenG2P
+org_slug: OpenG2P
+package_name: openg2p-g2p-bridge-api
+repo_name: ' openg2p-g2p-bridge-api
+
+ '
+repo_slug: openg2p-g2p-bridge-api
diff --git a/openg2p-g2p-bridge-example-bank-celery/.dockerignore b/openg2p-g2p-bridge-example-bank-celery/.dockerignore
new file mode 100644
index 0000000..d47f7ee
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.dockerignore
@@ -0,0 +1,89 @@
+# Git
+.git
+.gitignore
+.gitattributes
+
+
+# CI
+.codeclimate.yml
+.travis.yml
+.taskcluster.yml
+
+# Docker
+docker-compose.yml
+Dockerfile
+.docker
+.dockerignore
+
+# Byte-compiled / optimized / DLL files
+**/__pycache__/
+**/*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Virtual environment
+../.env
+.venv/
+venv/
+
+# PyCharm
+.idea
+
+# Python mode for VIM
+.ropeproject
+**/.ropeproject
+
+# Vim swap files
+**/*.swp
+
+# VS Code
+.vscode/
diff --git a/openg2p-g2p-bridge-example-bank-celery/.editorconfig b/openg2p-g2p-bridge-example-bank-celery/.editorconfig
new file mode 100644
index 0000000..7d8f3a5
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.editorconfig
@@ -0,0 +1,20 @@
+# Configuration for known file extensions
+[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml,toml,jinja}]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{yml,yaml,rst,md,jinja}]
+indent_size = 2
+
+# Do not configure editor for libs and autogenerated content
+[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
+charset = unset
+end_of_line = unset
+indent_size = unset
+indent_style = unset
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/openg2p-g2p-bridge-example-bank-celery/.gitignore b/openg2p-g2p-bridge-example-bank-celery/.gitignore
new file mode 100644
index 0000000..c633cec
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.gitignore
@@ -0,0 +1,81 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+/venv
+/.pytest_cache
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+*.eggs
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Pycharm
+.idea
+
+# Eclipse
+.settings
+
+# Visual Studio cache/options directory
+.vs/
+.vscode
+
+# OSX Files
+.DS_Store
+
+# Django stuff:
+*.log
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Sphinx documentation
+docs/_build/
+
+# Backup files
+*~
+*.swp
+
+# OCA rules
+!static/lib/
+
+# Ruff stuff
+.ruff_cache
+
+# Ignore secret files and env
+.secrets.*
+../.env
diff --git a/openg2p-g2p-bridge-example-bank-celery/.pre-commit-config.yaml b/openg2p-g2p-bridge-example-bank-celery/.pre-commit-config.yaml
new file mode 100644
index 0000000..336b7e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.pre-commit-config.yaml
@@ -0,0 +1,49 @@
+exclude: |
+ (?x)
+ # We don't want to mess with tool-generated files
+ .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
+ # Maybe reactivate this when all README files include prettier ignore tags?
+ ^README\.md$|
+ # Repos using Sphinx to generate docs don't need prettying
+ ^docs/_templates/.*\.html$|
+ # You don't usually want a bot to modify your legal texts
+ (LICENSE.*|COPYING.*)
+default_language_version:
+ python: python3
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: debug-statements
+ - id: fix-encoding-pragma
+ args: ["--remove"]
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: check-executables-have-shebangs
+ - id: check-merge-conflict
+ - id: check-symlinks
+ - id: check-toml
+ - id: check-yaml
+ args:
+ - --unsafe
+ - id: mixed-line-ending
+ args: ["--fix=lf"]
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.11.0
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py3-plus
+ - --keep-runtime-typing
+ - repo: https://github.com/psf/black
+ rev: 23.9.1
+ hooks:
+ - id: black
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.0.289
+ hooks:
+ - id: ruff
+ args:
+ - --fix
diff --git a/openg2p-g2p-bridge-example-bank-celery/.ruff.toml b/openg2p-g2p-bridge-example-bank-celery/.ruff.toml
new file mode 100644
index 0000000..aa1fc5b
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/.ruff.toml
@@ -0,0 +1,16 @@
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+]
+ignore = [
+ "E501", # line too long, handled by black
+ "B008", # do not perform function calls in argument defaults
+ "C901", # too complex
+]
+
+[per-file-ignores]
+"__init__.py" = ["F401"]
diff --git a/openg2p-g2p-bridge-example-bank-celery/CODE-OF-CONDUCT.md b/openg2p-g2p-bridge-example-bank-celery/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000..e1949e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/CODE-OF-CONDUCT.md
@@ -0,0 +1,114 @@
+# Code of Conduct
+
+## Contributor Covenant Code of Conduct
+
+### Preamble
+
+OpenG2P was created to foster an open, innovative and inclusive community around open source & open standard.
+To clarify expected behaviour in our communities we have adopted the Contributor Covenant. This code of
+conduct has been adopted by many other open source communities and we feel it expresses our values well.
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free
+experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex
+characteristics, gender identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy
+community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment for our community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address, without their explicit
+ permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+### Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will
+take appropriate and fair corrective action in response to any behavior that they deem inappropriate,
+threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki
+edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate
+reasons for moderation decisions when appropriate.
+
+### Scope
+
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially
+representing the community in public spaces. Examples of representing our community include using an official
+e-mail address, posting via an official social media account, or acting as an appointed representative at an
+online or offline event.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders
+responsible for enforcement at \[INSERT CONTACT METHOD]. All complaints will be reviewed and investigated
+promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any incident.
+
+### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action
+they deem in violation of this Code of Conduct:
+
+#### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in
+the community.
+
+**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the
+violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+
+#### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved,
+including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time.
+This includes avoiding interactions in community spaces as well as external channels like social media.
+Violating these terms may lead to a temporary or permanent ban.
+
+#### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a
+specified period of time. No public or private interaction with the people involved, including unsolicited
+interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may
+lead to a permanent ban.
+
+#### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained
+inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of
+individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the community.
+
+### Attribution
+
+This Code of Conduct is adapted from the
+[Contributor Covenant version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+
+For answers to common questions about this code of conduct, see the
+[FAQ](https://www.contributor-covenant.org/faq). Translations are available
+[here](https://www.contributor-covenant.org/translations).
diff --git a/openg2p-g2p-bridge-example-bank-celery/CONTRIBUTING.md b/openg2p-g2p-bridge-example-bank-celery/CONTRIBUTING.md
new file mode 100644
index 0000000..c187f93
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/CONTRIBUTING.md
@@ -0,0 +1,2 @@
+Refer to contribution guidelines
+[here](https://github.com/OpenG2P/openg2p-documentation/blob/1.0.0/community/contributing-to-openg2p.md).
diff --git a/openg2p-g2p-bridge-example-bank-celery/LICENSE b/openg2p-g2p-bridge-example-bank-celery/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/openg2p-g2p-bridge-example-bank-celery/README.md b/openg2p-g2p-bridge-example-bank-celery/README.md
new file mode 100644
index 0000000..35b2d2e
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/README.md
@@ -0,0 +1,5 @@
+# openg2p-g2p-bridge-example-bank-celery
+
+## Licenses
+
+This repository is licensed under [MPL-2.0](LICENSE).
diff --git a/openg2p-g2p-bridge-example-bank-celery/__init__.py b/openg2p-g2p-bridge-example-bank-celery/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db b/openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db
new file mode 100644
index 0000000..0bd077f
Binary files /dev/null and b/openg2p-g2p-bridge-example-bank-celery/celerybeat-schedule.db differ
diff --git a/openg2p-g2p-bridge-example-bank-celery/main.py b/openg2p-g2p-bridge-example-bank-celery/main.py
new file mode 100644
index 0000000..6b22376
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/main.py
@@ -0,0 +1,3 @@
+from openg2p_g2p_bridge_example_bank_celery.app import celery_app
+
+celery_app = celery_app
diff --git a/openg2p-g2p-bridge-example-bank-celery/pyproject.toml b/openg2p-g2p-bridge-example-bank-celery/pyproject.toml
new file mode 100644
index 0000000..9a1ec08
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/pyproject.toml
@@ -0,0 +1,32 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "openg2p-g2p-bridge-example-bank-celery"
+authors = [
+ { name="OpenG2P", email="info@openg2p.org" },
+]
+description = "OpenG2P G2P Bridge Example Bank Celery"
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+ "Operating System :: OS Independent",
+]
+dependencies = [
+ "openg2p-fastapi-common",
+ "openg2p-fastapi-auth",
+ "celery",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://openg2p.org"
+Documentation = "https://docs.openg2p.org/"
+Repository = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-bank"
+Source = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-ban"
+
+[tool.hatch.version]
+path = "src/openg2p_g2p_bridge_example_bank_celery/__init__.py"
diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/__init__.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/__init__.py
new file mode 100644
index 0000000..5becc17
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/__init__.py
@@ -0,0 +1 @@
+__version__ = "1.0.0"
diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py
new file mode 100644
index 0000000..5cc9dc1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/app.py
@@ -0,0 +1,49 @@
+# ruff: noqa: E402
+import logging
+
+from .config import Settings
+_config = Settings.get_config()
+
+
+from celery import Celery
+from openg2p_fastapi_common.app import Initializer as BaseInitializer
+from openg2p_g2p_bridge_example_bank_api.controllers import (
+ BlockFundsController,
+ FundAvailabilityController,
+ PaymentController,
+)
+from sqlalchemy import create_engine
+
+_logger = logging.getLogger(_config.logging_default_logger_name)
+
+
+class Initializer(BaseInitializer):
+ def initialize(self, **kwargs):
+ super().initialize()
+
+ BlockFundsController().post_init()
+ FundAvailabilityController().post_init()
+ PaymentController().post_init()
+
+
+def get_engine():
+ if _config.db_datasource:
+ db_engine = create_engine(_config.db_datasource)
+ return db_engine
+
+
+celery_app = Celery(
+ "example_bank_celery_tasks",
+ broker="redis://localhost:6379/0",
+ backend="redis://localhost:6379/0",
+ include=["openg2p_g2p_bridge_example_bank_celery.tasks.process_payment"],
+)
+
+celery_app.conf.beat_schedule = {
+ "process_payments": {
+ "task": "process_payments",
+ "schedule": _config.process_payment_frequency,
+ }
+}
+
+celery_app.conf.timezone = "UTC"
diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py
new file mode 100644
index 0000000..d5015c5
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/config.py
@@ -0,0 +1,24 @@
+from openg2p_fastapi_common.config import Settings as BaseSettings
+from pydantic_settings import SettingsConfigDict
+
+from . import __version__
+
+
+class Settings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_prefix="example_bank_", env_file=".env", extra="allow"
+ )
+
+ openapi_title: str = "Example Bank APIs for Cash Transfer"
+ openapi_description: str = """
+ ***********************************
+ Further details goes here
+ ***********************************
+ """
+ openapi_version: str = __version__
+
+ db_dbname: str = "example_bank_db"
+ db_driver: str = "postgresql"
+
+ process_payment_frequency: int = 10
+ payment_initiate_attempts: int = 3
diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py
new file mode 100644
index 0000000..545b021
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/__init__.py
@@ -0,0 +1 @@
+from .process_payment import process_payments
diff --git a/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py
new file mode 100644
index 0000000..232c79e
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/src/openg2p_g2p_bridge_example_bank_celery/tasks/process_payment.py
@@ -0,0 +1,173 @@
+import random
+import uuid
+from datetime import datetime
+from typing import List
+
+from openg2p_g2p_bridge_example_bank_models.models import (
+ Account,
+ AccountingLog,
+ DebitCreditTypes,
+ FundBlock,
+ InitiatePaymentRequest,
+ PaymentStatus,
+)
+from sqlalchemy import select
+from sqlalchemy.orm import sessionmaker
+
+from ..app import celery_app, get_engine
+from ..config import Settings
+
+_config = Settings.get_config()
+_engine = get_engine()
+
+
+@celery_app.task(name="process_payments")
+def process_payments():
+ session_maker = sessionmaker(bind=_engine, expire_on_commit=False)
+ with session_maker() as session:
+ initiate_payment_requests = (
+ session.execute(
+ select(InitiatePaymentRequest).where(
+ (InitiatePaymentRequest.payment_status.in_(["PENDING", "FAILED"]))
+ & (
+ InitiatePaymentRequest.payment_initiate_attempts
+ < _config.payment_initiate_attempts
+ )
+ )
+ )
+ .scalars()
+ .all()
+ )
+
+ failure_logs = []
+ corresponding_fund_blocks = {}
+ for initiate_payment_request in initiate_payment_requests:
+ account = (
+ session.execute(
+ select(Account).where(
+ Account.account_number
+ == initiate_payment_request.remitting_account
+ )
+ )
+ .scalars()
+ .first()
+ )
+
+ fund_block = (
+ session.execute(
+ select(FundBlock).where(
+ FundBlock.block_reference_no
+ == initiate_payment_request.funds_blocked_reference_number
+ )
+ )
+ .scalars()
+ .first()
+ )
+
+ accounting_log: AccountingLog = construct_accounting_log(
+ initiate_payment_request
+ )
+
+ update_account(account, initiate_payment_request.payment_amount)
+ update_fund_block(fund_block, initiate_payment_request.payment_amount)
+ initiate_payment_request.payment_status = PaymentStatus.SUCCESS
+ initiate_payment_request.payment_initiate_attempts += 1
+
+ failure_random_number = random.randint(1, 100)
+ if failure_random_number <= 30:
+ failure_logs.append(accounting_log)
+ corresponding_fund_blocks[accounting_log.reference_no] = fund_block
+
+ session.add(accounting_log)
+ session.add(fund_block)
+ session.add(account)
+
+ # End of loop
+
+ generate_failures(failure_logs, corresponding_fund_blocks, session)
+
+ session.commit()
+
+
+def construct_accounting_log(initiate_payment_request: InitiatePaymentRequest):
+ return AccountingLog(
+ reference_no=str(uuid.uuid4()),
+ customer_reference_no=initiate_payment_request.payment_reference_number,
+ debit_credit=DebitCreditTypes.DEBIT,
+ account_number=initiate_payment_request.remitting_account,
+ transaction_amount=initiate_payment_request.payment_amount,
+ transaction_date=datetime.utcnow(),
+ transaction_currency=initiate_payment_request.remitting_account_currency,
+ transaction_code="DBT",
+ narrative_1=initiate_payment_request.narrative_1,
+ narrative_2=initiate_payment_request.narrative_2,
+ narrative_3=initiate_payment_request.narrative_3,
+ narrative_4=initiate_payment_request.narrative_4,
+ narrative_5=initiate_payment_request.narrative_5,
+ narrative_6=initiate_payment_request.narrative_6,
+ active=True,
+ )
+
+
+def generate_failures(
+ failure_logs: List[AccountingLog], corresponding_fund_blocks: dict, session
+):
+ failure_reasons = [
+ "ACCOUNT_CLOSED",
+ "ACCOUNT_NOT_FOUND",
+ "ACCOUNT_DORMANT",
+ "ACCOUNT_DECEASED",
+ ]
+ for failure_log in failure_logs:
+ account_log: AccountingLog = AccountingLog(
+ reference_no=str(uuid.uuid4()),
+ customer_reference_no=failure_log.customer_reference_no,
+ debit_credit=failure_log.debit_credit,
+ account_number=failure_log.account_number,
+ transaction_amount=-failure_log.transaction_amount,
+ transaction_date=failure_log.transaction_date,
+ transaction_currency=failure_log.transaction_currency,
+ transaction_code=failure_log.transaction_code,
+ narrative_1=failure_log.narrative_1,
+ narrative_2=failure_log.narrative_2,
+ narrative_3=failure_log.narrative_3,
+ narrative_4=failure_log.narrative_4,
+ narrative_5=failure_log.narrative_5,
+ narrative_6=random.choice(failure_reasons),
+ active=True,
+ )
+ session.add(account_log)
+
+ account = (
+ session.execute(
+ select(Account).where(
+ Account.account_number == account_log.account_number
+ )
+ )
+ .scalars()
+ .first()
+ )
+
+ fund_block = (
+ session.execute(
+ select(FundBlock).where(
+ FundBlock.block_reference_no
+ == corresponding_fund_blocks[account_log.reference_no].block_reference_no
+ )
+ )
+ .scalars()
+ .first()
+ )
+
+ update_account(account, account_log.transaction_amount)
+ update_fund_block(fund_block, account_log.transaction_amount)
+
+
+def update_account(account, payment_amount):
+ account.book_balance -= payment_amount
+ account.blocked_amount -= payment_amount
+ account.available_balance = account.book_balance - account.blocked_amount
+
+
+def update_fund_block(fund_block, payment_amount):
+ fund_block.amount_released += payment_amount
diff --git a/openg2p-g2p-bridge-example-bank-celery/test-requirements.txt b/openg2p-g2p-bridge-example-bank-celery/test-requirements.txt
new file mode 100644
index 0000000..4f53afa
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-celery/test-requirements.txt
@@ -0,0 +1,3 @@
+pytest-cov
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-common
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-auth
diff --git a/openg2p-g2p-bridge-example-bank-models/.gitignore b/openg2p-g2p-bridge-example-bank-models/.gitignore
new file mode 100644
index 0000000..c633cec
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/.gitignore
@@ -0,0 +1,81 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+/venv
+/.pytest_cache
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+*.eggs
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+
+# Pycharm
+.idea
+
+# Eclipse
+.settings
+
+# Visual Studio cache/options directory
+.vs/
+.vscode
+
+# OSX Files
+.DS_Store
+
+# Django stuff:
+*.log
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# Rope
+.ropeproject
+
+# Sphinx documentation
+docs/_build/
+
+# Backup files
+*~
+*.swp
+
+# OCA rules
+!static/lib/
+
+# Ruff stuff
+.ruff_cache
+
+# Ignore secret files and env
+.secrets.*
+../.env
diff --git a/openg2p-g2p-bridge-example-bank-models/.pre-commit-config.yaml b/openg2p-g2p-bridge-example-bank-models/.pre-commit-config.yaml
new file mode 100644
index 0000000..336b7e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/.pre-commit-config.yaml
@@ -0,0 +1,49 @@
+exclude: |
+ (?x)
+ # We don't want to mess with tool-generated files
+ .svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
+ # Maybe reactivate this when all README files include prettier ignore tags?
+ ^README\.md$|
+ # Repos using Sphinx to generate docs don't need prettying
+ ^docs/_templates/.*\.html$|
+ # You don't usually want a bot to modify your legal texts
+ (LICENSE.*|COPYING.*)
+default_language_version:
+ python: python3
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: debug-statements
+ - id: fix-encoding-pragma
+ args: ["--remove"]
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: check-executables-have-shebangs
+ - id: check-merge-conflict
+ - id: check-symlinks
+ - id: check-toml
+ - id: check-yaml
+ args:
+ - --unsafe
+ - id: mixed-line-ending
+ args: ["--fix=lf"]
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.11.0
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py3-plus
+ - --keep-runtime-typing
+ - repo: https://github.com/psf/black
+ rev: 23.9.1
+ hooks:
+ - id: black
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.0.289
+ hooks:
+ - id: ruff
+ args:
+ - --fix
diff --git a/openg2p-g2p-bridge-example-bank-models/.ruff.toml b/openg2p-g2p-bridge-example-bank-models/.ruff.toml
new file mode 100644
index 0000000..aa1fc5b
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/.ruff.toml
@@ -0,0 +1,16 @@
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+]
+ignore = [
+ "E501", # line too long, handled by black
+ "B008", # do not perform function calls in argument defaults
+ "C901", # too complex
+]
+
+[per-file-ignores]
+"__init__.py" = ["F401"]
diff --git a/openg2p-g2p-bridge-example-bank-models/CODE-OF-CONDUCT.md b/openg2p-g2p-bridge-example-bank-models/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000..e1949e1
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/CODE-OF-CONDUCT.md
@@ -0,0 +1,114 @@
+# Code of Conduct
+
+## Contributor Covenant Code of Conduct
+
+### Preamble
+
+OpenG2P was created to foster an open, innovative and inclusive community around open source & open standard.
+To clarify expected behaviour in our communities we have adopted the Contributor Covenant. This code of
+conduct has been adopted by many other open source communities and we feel it expresses our values well.
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free
+experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex
+characteristics, gender identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy
+community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment for our community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address, without their explicit
+ permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
+
+### Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will
+take appropriate and fair corrective action in response to any behavior that they deem inappropriate,
+threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki
+edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate
+reasons for moderation decisions when appropriate.
+
+### Scope
+
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially
+representing the community in public spaces. Examples of representing our community include using an official
+e-mail address, posting via an official social media account, or acting as an appointed representative at an
+online or offline event.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders
+responsible for enforcement at \[INSERT CONTACT METHOD]. All complaints will be reviewed and investigated
+promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any incident.
+
+### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action
+they deem in violation of this Code of Conduct:
+
+#### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in
+the community.
+
+**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the
+violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+
+#### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved,
+including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time.
+This includes avoiding interactions in community spaces as well as external channels like social media.
+Violating these terms may lead to a temporary or permanent ban.
+
+#### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a
+specified period of time. No public or private interaction with the people involved, including unsolicited
+interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may
+lead to a permanent ban.
+
+#### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained
+inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of
+individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the community.
+
+### Attribution
+
+This Code of Conduct is adapted from the
+[Contributor Covenant version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+
+For answers to common questions about this code of conduct, see the
+[FAQ](https://www.contributor-covenant.org/faq). Translations are available
+[here](https://www.contributor-covenant.org/translations).
diff --git a/openg2p-g2p-bridge-example-bank-models/CONTRIBUTING.md b/openg2p-g2p-bridge-example-bank-models/CONTRIBUTING.md
new file mode 100644
index 0000000..c187f93
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/CONTRIBUTING.md
@@ -0,0 +1,2 @@
+Refer to contribution guidelines
+[here](https://github.com/OpenG2P/openg2p-documentation/blob/1.0.0/community/contributing-to-openg2p.md).
diff --git a/openg2p-g2p-bridge-example-bank-models/LICENSE b/openg2p-g2p-bridge-example-bank-models/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/openg2p-g2p-bridge-example-bank-models/README.md b/openg2p-g2p-bridge-example-bank-models/README.md
new file mode 100644
index 0000000..bd08149
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/README.md
@@ -0,0 +1,6 @@
+# openg2p-g2p-bridge-example-bank-models
+
+
+## Licenses
+
+This repository is licensed under [MPL-2.0](LICENSE).
diff --git a/openg2p-g2p-bridge-example-bank-models/pyproject.toml b/openg2p-g2p-bridge-example-bank-models/pyproject.toml
new file mode 100644
index 0000000..71ce378
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/pyproject.toml
@@ -0,0 +1,31 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "openg2p-g2p-bridge-example-bank-models"
+authors = [
+ { name="OpenG2P", email="info@openg2p.org" },
+]
+description = "OpenG2P G2P Bridge Example Bank Models"
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+ "Operating System :: OS Independent",
+]
+dependencies = [
+ "openg2p-fastapi-common",
+ "openg2p-fastapi-auth",
+]
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://openg2p.org"
+Documentation = "https://docs.openg2p.org/"
+Repository = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-bank"
+Source = "https://github.com/OpenG2P/openg2p-g2p-bridge-example-bank"
+
+[tool.hatch.version]
+path = "src/openg2p_g2p_bridge_example_bank_models/__init__.py"
diff --git a/openg2p-g2p-bridge-example-bank-models/src/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/__init__.py
new file mode 100644
index 0000000..5becc17
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/__init__.py
@@ -0,0 +1 @@
+__version__ = "1.0.0"
diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py
new file mode 100644
index 0000000..497521c
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/__init__.py
@@ -0,0 +1,8 @@
+from .account import (
+ Account,
+ AccountingLog,
+ DebitCreditTypes,
+ FundBlock,
+ InitiatePaymentRequest,
+ PaymentStatus,
+)
diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py
new file mode 100644
index 0000000..5e94088
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/models/account.py
@@ -0,0 +1,106 @@
+from datetime import datetime
+from enum import Enum
+
+from openg2p_fastapi_common.models import BaseORMModelWithTimes
+from sqlalchemy import DateTime, Float, Integer, String
+from sqlalchemy import Enum as SqlEnum
+from sqlalchemy.orm import Mapped, mapped_column
+
+
+class PaymentStatus(Enum):
+ PENDING = "PENDING"
+ SUCCESS = "SUCCESS"
+ FAILED = "FAILED"
+
+
+class DebitCreditTypes(Enum):
+ DEBIT = "debit"
+ CREDIT = "credit"
+
+
+class Account(BaseORMModelWithTimes):
+ __tablename__ = "accounts"
+ account_holder_name: Mapped[str] = mapped_column(String)
+ account_number: Mapped[str] = mapped_column(String)
+ account_currency: Mapped[str] = mapped_column(String)
+ book_balance: Mapped[float] = mapped_column(Float)
+ available_balance: Mapped[float] = mapped_column(Float)
+ blocked_amount: Mapped[float] = mapped_column(Float, default=0)
+
+
+class FundBlock(BaseORMModelWithTimes):
+ __tablename__ = "fund_blocks"
+ block_reference_no: Mapped[str] = mapped_column(String, index=True, unique=True)
+ account_no: Mapped[str] = mapped_column(String)
+ currency: Mapped[str] = mapped_column(String)
+ amount: Mapped[float] = mapped_column(Float)
+ amount_released: Mapped[float] = mapped_column(Float, default=0)
+
+
+class InitiatePaymentRequest(BaseORMModelWithTimes):
+ __tablename__ = "initiate_payment_requests"
+ payment_reference_number = mapped_column(
+ String, index=True, unique=True
+ ) # disbursement id
+
+ remitting_account: Mapped[str] = mapped_column(String, nullable=False)
+ remitting_account_currency: Mapped[str] = mapped_column(String, nullable=False)
+ payment_amount: Mapped[float] = mapped_column(Float, nullable=False)
+ payment_date: Mapped[str] = mapped_column(String, nullable=False)
+ funds_blocked_reference_number: Mapped[str] = mapped_column(String, nullable=False)
+
+ beneficiary_name: Mapped[str] = mapped_column(String)
+ beneficiary_account: Mapped[str] = mapped_column(String)
+ beneficiary_account_currency: Mapped[str] = mapped_column(String)
+ beneficiary_account_type: Mapped[str] = mapped_column(String)
+ beneficiary_bank_code: Mapped[str] = mapped_column(String)
+ beneficiary_branch_code: Mapped[str] = mapped_column(String)
+
+ beneficiary_mobile_wallet_provider: Mapped[str] = mapped_column(
+ String, nullable=True
+ )
+ beneficiary_phone_no: Mapped[str] = mapped_column(String, nullable=True)
+
+ beneficiary_email: Mapped[str] = mapped_column(String, nullable=True)
+ beneficiary_email_wallet_provider: Mapped[str] = mapped_column(
+ String, nullable=True
+ )
+
+ narrative_1: Mapped[str] = mapped_column(
+ String, nullable=True
+ ) # disbursement narrative
+ narrative_2: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic
+ narrative_3: Mapped[str] = mapped_column(
+ String, nullable=True
+ ) # cycle code pneumonic
+ narrative_4: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id
+ narrative_5: Mapped[str] = mapped_column(String, nullable=True)
+ narrative_6: Mapped[str] = mapped_column(String, nullable=True)
+
+ payment_initiate_attempts: Mapped[int] = mapped_column(Integer, default=0)
+ payment_status: Mapped[PaymentStatus] = mapped_column(
+ SqlEnum(PaymentStatus), default=PaymentStatus.PENDING
+ )
+
+
+class AccountingLog(BaseORMModelWithTimes):
+ __tablename__ = "accounting_logs"
+ reference_no: Mapped[str] = mapped_column(String, index=True, unique=True)
+ customer_reference_no: Mapped[str] = mapped_column(String, index=True)
+ debit_credit: Mapped[DebitCreditTypes] = mapped_column(SqlEnum(DebitCreditTypes))
+ account_number: Mapped[str] = mapped_column(String, index=True)
+ transaction_amount: Mapped[float] = mapped_column(Float)
+ transaction_date: Mapped[datetime] = mapped_column(DateTime)
+ transaction_currency: Mapped[str] = mapped_column(String)
+ transaction_code: Mapped[str] = mapped_column(String, nullable=True)
+
+ narrative_1: Mapped[str] = mapped_column(String, nullable=True) # disbursement id
+ narrative_2: Mapped[str] = mapped_column(String, nullable=True) # beneficiary id
+ narrative_3: Mapped[str] = mapped_column(String, nullable=True) # program pneumonic
+ narrative_4: Mapped[str] = mapped_column(
+ String, nullable=True
+ ) # cycle code pneumonic
+ narrative_5: Mapped[str] = mapped_column(String, nullable=True) # beneficiary email
+ narrative_6: Mapped[str] = mapped_column(
+ String, nullable=True
+ ) # beneficiary phone number
diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py
new file mode 100644
index 0000000..3b47a75
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/__init__.py
@@ -0,0 +1,8 @@
+from .fund_schemas import (
+ BlockFundsRequest,
+ BlockFundsResponse,
+ CheckFundRequest,
+ CheckFundResponse,
+ InitiatePaymentPayload,
+ InitiatorPaymentResponse,
+)
diff --git a/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py
new file mode 100644
index 0000000..c623d9c
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/src/openg2p_g2p_bridge_example_bank_models/schemas/fund_schemas.py
@@ -0,0 +1,63 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class CheckFundRequest(BaseModel):
+ account_number: str
+ account_currency: str
+ total_funds_needed: float
+
+
+class CheckFundResponse(BaseModel):
+ status: str
+ account_number: str
+ has_sufficient_funds: bool
+ error_message: Optional[str] = None
+
+
+class BlockFundsRequest(BaseModel):
+ account_no: str
+ currency: str
+ amount: float
+
+
+class BlockFundsResponse(BaseModel):
+ status: str
+ block_reference_no: str
+ error_message: Optional[str] = None
+
+
+class InitiatePaymentPayload(BaseModel):
+ payment_reference_number: str
+ remitting_account: str
+ remitting_account_currency: str
+ payment_amount: float
+ funds_blocked_reference_number: str
+ beneficiary_name: str
+
+ beneficiary_account: str
+ beneficiary_account_currency: str
+ beneficiary_account_type: str
+ beneficiary_bank_code: str
+ beneficiary_branch_code: str
+
+ beneficiary_mobile_wallet_provider: Optional[str] = None
+ beneficiary_phone_no: Optional[str] = None
+
+ beneficiary_email: Optional[str] = None
+ beneficiary_email_wallet_provider: Optional[str] = None
+
+ narrative_1: Optional[str] = None
+ narrative_2: Optional[str] = None
+ narrative_3: Optional[str] = None
+ narrative_4: Optional[str] = None
+ narrative_5: Optional[str] = None
+ narrative_6: Optional[str] = None
+
+ payment_date: str
+
+
+class InitiatorPaymentResponse(BaseModel):
+ status: str
+ error_message: Optional[str] = None
diff --git a/openg2p-g2p-bridge-example-bank-models/test-requirements.txt b/openg2p-g2p-bridge-example-bank-models/test-requirements.txt
new file mode 100644
index 0000000..4f53afa
--- /dev/null
+++ b/openg2p-g2p-bridge-example-bank-models/test-requirements.txt
@@ -0,0 +1,3 @@
+pytest-cov
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-common
+git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-auth