├── .DS_Store ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .readthedocs.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── modules.rst ├── python_flutterwave.charge.rst ├── python_flutterwave.rst └── python_flutterwave.tokenization.rst ├── pyproject.toml ├── python_flutterwave ├── __init__.py ├── charge │ ├── __init__.py │ ├── bank.py │ ├── card.py │ ├── mobile.py │ └── validation.py ├── decorators.py ├── exceptions.py ├── objects.py └── tokenization │ ├── __init__.py │ └── tokenized_charge.py ├── requirements.txt └── setup.cfg /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WilliamOtieno/python-flutterwave/dd73a184e92a845470b712c9bf29c9bd47857bf4/.DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | 4 | custom: ["https://dashboard.flutterwave.com/donate/zvapzky1ozls"] 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Python Flutterwave Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install flake8 pytest requests 28 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 29 | - name: Lint with flake8 30 | run: | 31 | # stop the build if there are Python syntax errors or undefined names 32 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 33 | - name: Format with black 34 | run: | 35 | black . 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.9' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install build 22 | - name: Build package 23 | run: python -m build 24 | - name: Publish package 25 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.PYPI_API_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | venv 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | 134 | # Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,python,visualstudiocode 135 | # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,python,visualstudiocode 136 | 137 | ### JetBrains+all ### 138 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 139 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 140 | 141 | # User-specific stuff 142 | .idea/**/workspace.xml 143 | .idea/**/tasks.xml 144 | .idea/**/usage.statistics.xml 145 | .idea/**/dictionaries 146 | .idea/**/shelf 147 | 148 | # AWS User-specific 149 | .idea/**/aws.xml 150 | 151 | # Generated files 152 | .idea/**/contentModel.xml 153 | 154 | # Sensitive or high-churn files 155 | .idea/**/dataSources/ 156 | .idea/**/dataSources.ids 157 | .idea/**/dataSources.local.xml 158 | .idea/**/sqlDataSources.xml 159 | .idea/**/dynamic.xml 160 | .idea/**/uiDesigner.xml 161 | .idea/**/dbnavigator.xml 162 | 163 | # Gradle 164 | .idea/**/gradle.xml 165 | .idea/**/libraries 166 | 167 | # Gradle and Maven with auto-import 168 | # When using Gradle or Maven with auto-import, you should exclude module files, 169 | # since they will be recreated, and may cause churn. Uncomment if using 170 | # auto-import. 171 | # .idea/artifacts 172 | # .idea/compiler.xml 173 | # .idea/jarRepositories.xml 174 | # .idea/modules.xml 175 | # .idea/*.iml 176 | # .idea/modules 177 | # *.iml 178 | # *.ipr 179 | 180 | # CMake 181 | cmake-build-*/ 182 | 183 | # Mongo Explorer plugin 184 | .idea/**/mongoSettings.xml 185 | 186 | # File-based project format 187 | *.iws 188 | 189 | # IntelliJ 190 | out/ 191 | 192 | # mpeltonen/sbt-idea plugin 193 | .idea_modules/ 194 | 195 | # JIRA plugin 196 | atlassian-ide-plugin.xml 197 | 198 | # Cursive Clojure plugin 199 | .idea/replstate.xml 200 | 201 | # Crashlytics plugin (for Android Studio and IntelliJ) 202 | com_crashlytics_export_strings.xml 203 | crashlytics.properties 204 | crashlytics-build.properties 205 | fabric.properties 206 | 207 | # Editor-based Rest Client 208 | .idea/httpRequests 209 | 210 | # Android studio 3.1+ serialized cache file 211 | .idea/caches/build_file_checksums.ser 212 | 213 | ### JetBrains+all Patch ### 214 | # Ignores the whole .idea folder and all .iml files 215 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 216 | 217 | .idea/ 218 | 219 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 220 | 221 | *.iml 222 | modules.xml 223 | .idea/misc.xml 224 | *.ipr 225 | 226 | # Sonarlint plugin 227 | .idea/sonarlint 228 | 229 | ### Python ### 230 | # Byte-compiled / optimized / DLL files 231 | __pycache__/ 232 | *.py[cod] 233 | *$py.class 234 | 235 | # C extensions 236 | *.so 237 | 238 | # Distribution / packaging 239 | .Python 240 | build/ 241 | develop-eggs/ 242 | dist/ 243 | downloads/ 244 | eggs/ 245 | .eggs/ 246 | lib/ 247 | lib64/ 248 | parts/ 249 | sdist/ 250 | var/ 251 | wheels/ 252 | share/python-wheels/ 253 | *.egg-info/ 254 | .installed.cfg 255 | *.egg 256 | MANIFEST 257 | 258 | # PyInstaller 259 | # Usually these files are written by a python script from a template 260 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 261 | *.manifest 262 | *.spec 263 | 264 | # Installer logs 265 | pip-log.txt 266 | pip-delete-this-directory.txt 267 | 268 | # Unit test / coverage reports 269 | htmlcov/ 270 | .tox/ 271 | .nox/ 272 | .coverage 273 | .coverage.* 274 | .cache 275 | nosetests.xml 276 | coverage.xml 277 | *.cover 278 | *.py,cover 279 | .hypothesis/ 280 | .pytest_cache/ 281 | cover/ 282 | 283 | # Translations 284 | *.mo 285 | *.pot 286 | 287 | # Django stuff: 288 | *.log 289 | local_settings.py 290 | db.sqlite3 291 | db.sqlite3-journal 292 | 293 | # Flask stuff: 294 | instance/ 295 | .webassets-cache 296 | 297 | # Scrapy stuff: 298 | .scrapy 299 | 300 | # Sphinx documentation 301 | docs/_build/ 302 | 303 | # PyBuilder 304 | .pybuilder/ 305 | target/ 306 | 307 | # Jupyter Notebook 308 | .ipynb_checkpoints 309 | 310 | # IPython 311 | profile_default/ 312 | ipython_config.py 313 | 314 | # pyenv 315 | # For a library or package, you might want to ignore these files since the code is 316 | # intended to run in multiple environments; otherwise, check them in: 317 | # .python-version 318 | 319 | # pipenv 320 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 321 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 322 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 323 | # install all needed dependencies. 324 | #Pipfile.lock 325 | 326 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 327 | __pypackages__/ 328 | 329 | # Celery stuff 330 | celerybeat-schedule 331 | celerybeat.pid 332 | 333 | # SageMath parsed files 334 | *.sage.py 335 | 336 | # Environments 337 | .env 338 | .venv 339 | env/ 340 | venv/ 341 | ENV/ 342 | env.bak/ 343 | venv.bak/ 344 | 345 | # Spyder project settings 346 | .spyderproject 347 | .spyproject 348 | 349 | # Rope project settings 350 | .ropeproject 351 | 352 | # mkdocs documentation 353 | /site 354 | 355 | # mypy 356 | .mypy_cache/ 357 | .dmypy.json 358 | dmypy.json 359 | 360 | # Pyre type checker 361 | .pyre/ 362 | 363 | # pytype static type analyzer 364 | .pytype/ 365 | 366 | # Cython debug symbols 367 | cython_debug/ 368 | 369 | ### VisualStudioCode ### 370 | .vscode/* 371 | !.vscode/settings.json 372 | !.vscode/tasks.json 373 | !.vscode/launch.json 374 | !.vscode/extensions.json 375 | *.code-workspace 376 | 377 | # Local History for Visual Studio Code 378 | .history/ 379 | 380 | ### VisualStudioCode Patch ### 381 | # Ignore all local history of files 382 | .history 383 | .ionide 384 | 385 | # Support for Project snippet scope 386 | !.vscode/*.code-snippets 387 | 388 | # End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,python,visualstudiocode 389 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: ./requirements.txt 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Python Flutterwave 2 | 3 | Thank you for showing interest in contributing to this project. Following these guidelines help to communicate that you respect the time of the developers managing and working on this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes and also helping you finalise your pull requests. 4 | 5 | ### What do I do? How do I get started? 6 | 7 | Follow the [README.md](/README.md) guide. If you've noticed a bug, want to add a feature or have a question, [search the issue tracker](https://github.com/WilliamOtieno/python-flutterwave/issues) to see if someone else already created a ticket. If that has not yet been done, go ahead and [make one](https://github.com/WilliamOtieno/python-flutterwave/issues/new) then assign it to any of the other developers or even yourself. 8 | The issue should also be assigned to the project at hand and one should also add tags to the issue to demystify it. 9 | 10 | 11 | ### Fork the repository 12 | Each developer will have his/her own remote fork(copy) of the repository. Developers are therefore encouraged to create said forks in order to work on the forks rather than on the main repository directly. 13 | 14 | 15 | 16 | ### Clone & create a branch 17 | 18 | If there is something you think you can fix or contribute to, [clone your project](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) and create a branch with a descriptive name. Always checkout from the **master branch**, that is the default and up-to-date stable branch. I know, not the best practice, but I'm sure we won't break stuff. 19 | 20 | A good branch name would be: 21 | 22 | ```sh 23 | git checkout -b 13-fix-url-endpoints 24 | ``` 25 | 26 | ### Commits 27 | The goal is that each commit has a **single focus**. Each commit should record a single-unit change. Now this can be a bit subjective (which is totally fine), but each commit should make a change to just one aspect of the project. 28 | This essentially means that you should commit a change and not a file. A file may have many unrelated changes. 29 | 30 | Conversely, a commit shouldn't include unrelated changes - changes to the url routes and changes to the authentication service. These two aren't related to each other and shouldn't be included in the same commit. Work on one change first, commit that, and then change the second one. That way, if it turns out that one change had a bug and you have to undo it, you don't have to undo the other change too. 31 | 32 | If you have to use "and" in your commit message, your commit is probably doing too many changes - break the changes into separate commits. 33 | 34 | #### Commit Messages 35 | 36 | Please follow the below format while writing commit messages: 37 | 38 | ``` 39 | [Title e.g] One line description about your change 40 | 41 | [Description e.g] An optional detailed description of your changes. 42 | ``` 43 | 44 | Explain what the commit does (not how or why). 45 | 46 | ### Implement your fix or feature 47 | 48 | At this point, you're ready to make your changes. Feel free to ask for help; everyone was once a beginner; everyone is learning. 49 | 50 | Submit a Pull Request to the branch you had checked out from in order for the maintainers to review your commit and proceed with the necessary action. 51 | 52 | Please ensure that the issue you've fixed is related to the branch you're currently working from. If you want to fix something else unrelated to whatever you've worked on, do another checkout from the staging branch and give the new branch an appropriate name.This makes it easy for the maintainers to track your fixes. 53 | 54 | If your fix or contribution involved installing a 3rd Party package that has not been implemented yet, be sure to add the dependency to the [requirements](/requirements.txt) file by executing: 55 | ```shell 56 | pip freeze > requirements.txt 57 | ``` 58 | 59 | ### Keeping your Pull Request updated 60 | 61 | Related to the above, if a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch, so it's easier to merge. 62 | 63 | #### Further Steps 64 | 65 | After a feature has been merged into master successfully, developers are encouraged to pull those changes from the origin/upstream to their forks to avoid unnecessary merge conflicts down the line. 66 | 67 | 68 | ### Merging a PR (for maintainers only) 69 | 70 | A PR can only be merged into staging by a maintainer if: 71 | - It has no requested changes 72 | - It is up-to-date with current staging branch 73 | - It is a valid fix/feature. 74 | - It has been reviewed and accepted by another maintainer. 75 | 76 | Any maintainer is allowed to merge a PR if all of these conditions are met. 77 | 78 | Thank you again for your support, happy coding and remember it is your [GNU-given](https://www.gnu.org/home.en.html) right to fork. 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 William Otieno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### python-flutterwave 2 | 3 | - This lib aims to be the middleman between `Flutterwave API` and a python backend. Ergo, it directly makes the API requests to FW and returns the necessary info to the backend. 4 | 5 | - One should thoroughly go through the [official docs here](https://developer.flutterwave.com/reference/introduction) in order to have a meaningful insight on how to use the params provided by the functions in the lib. 6 | 7 | - The lib will also follow the same project structure as the official docs for ease of use and consistency. 8 | 9 | - NB: Set `FW_SECRET_KEY` environnment variable obtained from the dashboard. 10 | 11 | - Full package documentation can be accessed from [here](https://python-flutterwave.readthedocs.io/en/latest/) 12 | 13 | 14 | - More contributors needed, refer to [the contribution gude](/CONTRIBUTING.md) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | import os 9 | import sys 10 | 11 | sys.path.insert(0, os.path.abspath("..")) 12 | 13 | 14 | project = "python-flutterwave" 15 | copyright = "2024, William Otieno" 16 | author = "William Otieno" 17 | release = "1.2.1" 18 | 19 | # -- General configuration --------------------------------------------------- 20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 21 | 22 | extensions = ["sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.autodoc"] 23 | 24 | autodoc_decorators = True 25 | 26 | templates_path = ["_templates"] 27 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 28 | 29 | 30 | # -- Options for HTML output ------------------------------------------------- 31 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 32 | 33 | html_theme = "sphinx_rtd_theme" 34 | html_static_path = ["_static"] 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. python-flutterwave documentation master file, created by 2 | sphinx-quickstart on Mon Jan 1 08:59:27 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-flutterwave's documentation! 7 | ============================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | modules 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | python-flutterwave 2 | ================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | python_flutterwave 8 | -------------------------------------------------------------------------------- /docs/python_flutterwave.charge.rst: -------------------------------------------------------------------------------- 1 | python\_flutterwave.charge package 2 | ================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | python\_flutterwave.charge.bank module 8 | -------------------------------------- 9 | 10 | .. automodule:: python_flutterwave.charge.bank 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | python\_flutterwave.charge.card module 16 | -------------------------------------- 17 | 18 | .. automodule:: python_flutterwave.charge.card 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | python\_flutterwave.charge.mobile module 24 | ---------------------------------------- 25 | 26 | .. automodule:: python_flutterwave.charge.mobile 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | python\_flutterwave.charge.validation module 32 | -------------------------------------------- 33 | 34 | .. automodule:: python_flutterwave.charge.validation 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: python_flutterwave.charge 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/python_flutterwave.rst: -------------------------------------------------------------------------------- 1 | python\_flutterwave package 2 | =========================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | python_flutterwave.charge 11 | python_flutterwave.tokenization 12 | 13 | Submodules 14 | ---------- 15 | 16 | python\_flutterwave.decorators module 17 | ------------------------------------- 18 | 19 | .. automodule:: python_flutterwave.decorators 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | python\_flutterwave.exceptions module 25 | ------------------------------------- 26 | 27 | .. automodule:: python_flutterwave.exceptions 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | python\_flutterwave.objects module 33 | ---------------------------------- 34 | 35 | .. automodule:: python_flutterwave.objects 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: python_flutterwave 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/python_flutterwave.tokenization.rst: -------------------------------------------------------------------------------- 1 | python\_flutterwave.tokenization package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | python\_flutterwave.tokenization.tokenized\_charge module 8 | --------------------------------------------------------- 9 | 10 | .. automodule:: python_flutterwave.tokenization.tokenized_charge 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: python_flutterwave.tokenization 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=54", 4 | "wheel", 5 | "requests" 6 | ] 7 | build-backend = "setuptools.build_meta" 8 | -------------------------------------------------------------------------------- /python_flutterwave/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper around Flutterwave Payments API""" 2 | -------------------------------------------------------------------------------- /python_flutterwave/charge/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The charge APIs help you to collect payments using different payment methods. 3 | """ 4 | from .bank import ( 5 | initiate_bank_charge, 6 | initiate_uk_eu_bank_charge, 7 | initiate_ach_bank_charge, 8 | initiate_nigeria_bank_charge 9 | ) 10 | from .card import initiate_card_charge 11 | from .mobile import ( 12 | initiate_ussd_charge, 13 | initiate_mpesa_charge, 14 | initiate_enaira_charge, 15 | initiate_paypal_charge, 16 | initiate_apple_pay_charge, 17 | initiate_fawry_pay_charge, 18 | initiate_google_pay_charge, 19 | initiate_ghana_mobile_charge, 20 | initiate_uganda_mobile_charge, 21 | initiate_franco_mobile_charge, 22 | initiate_rwanda_mobile_charge, 23 | initiate_zambia_mobile_charge, 24 | initiate_tanzania_mobile_charge 25 | ) 26 | from .validation import validate_charge 27 | -------------------------------------------------------------------------------- /python_flutterwave/charge/bank.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | 5 | from python_flutterwave.decorators import handle_api_exceptions 6 | 7 | 8 | token = os.environ.get("FW_SECRET_KEY") 9 | base_url = "https://api.flutterwave.com/v3/charges" 10 | 11 | 12 | @handle_api_exceptions 13 | def initiate_bank_charge( 14 | amount: int, 15 | email: str, 16 | tx_ref: str, 17 | ) -> dict: 18 | """ 19 | Collect payments from your customers via bank transfers 20 | 21 | Args: 22 | tx_ref (int): This is a unique reference peculiar to the transaction being carried out. 23 | 24 | amount (int): This is the amount to be charged for the transaction. 25 | 26 | email (str): The customer's email address. 27 | 28 | Returns: 29 | dict: Response Details 30 | """ 31 | 32 | params = {"type": "bank_transfer"} 33 | payload = json.dumps( 34 | { 35 | "tx_ref": f"{tx_ref}", 36 | "amount": f"{amount}", 37 | "email": f"{email}", 38 | } 39 | ) 40 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 41 | 42 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 43 | 44 | return dict(response.json()) 45 | 46 | 47 | @handle_api_exceptions 48 | def initiate_nigeria_bank_charge( 49 | amount: int, 50 | email: str, 51 | tx_ref: str, 52 | ) -> dict: 53 | """ 54 | Charge Nigerian bank accounts using Flutterwave 55 | 56 | Args: 57 | tx_ref (int): This is a unique reference peculiar to the transaction being carried out. 58 | 59 | amount (int): This is the amount to be charged for the transaction. 60 | 61 | email (str): The customer's email address. 62 | 63 | Returns: 64 | dict: Response Details 65 | """ 66 | 67 | params = {"type": "mono"} 68 | payload = json.dumps( 69 | { 70 | "tx_ref": f"{tx_ref}", 71 | "amount": f"{amount}", 72 | "email": f"{email}", 73 | } 74 | ) 75 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 76 | 77 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 78 | 79 | return dict(response.json()) 80 | 81 | 82 | @handle_api_exceptions 83 | def initiate_uk_eu_bank_charge( 84 | amount: int, email: str, tx_ref: str, phone_number: str, is_token_io: int 85 | ) -> dict: 86 | """ 87 | Charge Customers UK and EU bank accounts 88 | 89 | Args: 90 | tx_ref (int): Unique reference peculiar to the transaction. 91 | 92 | amount (int): Amount to be charged for the transaction. 93 | 94 | email (str): The customer's email address. 95 | 96 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 97 | 98 | is_token_io (int): 99 | 100 | Returns: 101 | dict: Response Details 102 | """ 103 | 104 | params = {"type": "account-ach-uk"} 105 | payload = json.dumps( 106 | { 107 | "tx_ref": f"{tx_ref}", 108 | "amount": f"{amount}", 109 | "email": f"{email}", 110 | "phone_number": f"{phone_number}", 111 | "is_token_io": is_token_io, 112 | } 113 | ) 114 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 115 | 116 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 117 | 118 | return dict(response.json()) 119 | 120 | 121 | @handle_api_exceptions 122 | def initiate_ach_bank_charge( 123 | amount: int, 124 | email: str, 125 | tx_ref: str, 126 | phone_number: str, 127 | currency: str, 128 | ) -> dict: 129 | """ 130 | Collect ACH payments for USD and ZAR transactions 131 | 132 | Args: 133 | tx_ref (int): Unique reference peculiar to the transaction. 134 | 135 | amount (int): Amount to be charged for the transaction. 136 | 137 | email (str): The customer's email address. 138 | 139 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 140 | 141 | currency (str): Currency to charge in. Expected values are ZAR for ZA ACH and USD for US ACH. 142 | 143 | Returns: 144 | dict: Response Details 145 | """ 146 | 147 | params = {"type": "account-ach-uk"} 148 | payload = json.dumps( 149 | { 150 | "tx_ref": f"{tx_ref}", 151 | "amount": f"{amount}", 152 | "currency": f"{currency}", 153 | "email": f"{email}", 154 | "phone_number": f"{phone_number}", 155 | } 156 | ) 157 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 158 | 159 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 160 | 161 | return dict(response.json()) 162 | -------------------------------------------------------------------------------- /python_flutterwave/charge/card.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | 5 | from python_flutterwave.decorators import handle_api_exceptions 6 | 7 | 8 | token = os.environ.get("FW_SECRET_KEY") 9 | base_url = "https://api.flutterwave.com/v3/charges" 10 | 11 | 12 | @handle_api_exceptions 13 | def initiate_card_charge( 14 | amount: int, 15 | card_number: int, 16 | cvv: int, 17 | expiry_month: int, 18 | expiry_year: int, 19 | email: str, 20 | tx_ref: str, 21 | ) -> dict: 22 | """ 23 | This is used to initiate card payments. 24 | 25 | Args: 26 | tx_ref (int): This is a unique reference peculiar to the transaction being carried out. 27 | 28 | amount (int): This is the amount to be charged for the transaction. 29 | 30 | email (str): The customer's email address. 31 | 32 | card_number (int): The customer's card. 33 | 34 | cvv (int): Card CVV. 35 | 36 | expiry_month (int): Card expiry month 37 | 38 | expiry_year (int): Card expiry year 39 | 40 | Returns: 41 | dict: Response Details 42 | """ 43 | 44 | params = {"type": "card"} 45 | payload = json.dumps( 46 | { 47 | "tx_ref": f"{tx_ref}", 48 | "amount": f"{amount}", 49 | "card_number": f"{card_number}", 50 | "cvv": f"{cvv}", 51 | "expirty_month": f"{expiry_month}", 52 | "expirty_year": f"{expiry_year}", 53 | "email": f"{email}", 54 | } 55 | ) 56 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 57 | 58 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 59 | 60 | return dict(response.json()) 61 | -------------------------------------------------------------------------------- /python_flutterwave/charge/mobile.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | 5 | from python_flutterwave.decorators import handle_api_exceptions 6 | 7 | 8 | token = os.environ.get("FW_SECRET_KEY") 9 | base_url = "https://api.flutterwave.com/v3/charges" 10 | 11 | 12 | @handle_api_exceptions 13 | def initiate_mpesa_charge( 14 | amount: int, email: str, tx_ref: str, phone_number: str 15 | ) -> dict: 16 | """ 17 | Collect Mpesa payments from customers in Kenya 18 | 19 | Args: 20 | tx_ref (int): Unique reference peculiar to the transaction. 21 | 22 | amount (int): Amount to be charged for the transaction. 23 | 24 | email (str): The customer's email address. 25 | 26 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 27 | 28 | Returns: 29 | dict: Response Details 30 | """ 31 | 32 | params = {"type": "mpesa"} 33 | payload = json.dumps( 34 | { 35 | "tx_ref": f"{tx_ref}", 36 | "amount": f"{amount}", 37 | "email": f"{email}", 38 | "phone_number": f"{phone_number}", 39 | "currency": "KES", 40 | } 41 | ) 42 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 43 | 44 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 45 | 46 | return dict(response.json()) 47 | 48 | 49 | @handle_api_exceptions 50 | def initiate_ghana_mobile_charge( 51 | amount: int, email: str, tx_ref: str, phone_number: str, network: str 52 | ) -> dict: 53 | """ 54 | Collect mobile money payments from customers in Ghana 55 | 56 | Args: 57 | tx_ref (int): Unique reference peculiar to the transaction. 58 | 59 | amount (int): Amount to be charged for the transaction. 60 | 61 | email (str): The customer's email address. 62 | 63 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 64 | 65 | network (str): Mobile money network provider (MTN, VODAFONE, TIGO) 66 | 67 | Returns: 68 | dict: Response Details 69 | """ 70 | 71 | params = {"type": "mobile_money_ghana"} 72 | payload = json.dumps( 73 | { 74 | "tx_ref": f"{tx_ref}", 75 | "amount": f"{amount}", 76 | "email": f"{email}", 77 | "phone_number": f"{phone_number}", 78 | "network": f"{network}", 79 | "currency": "GHS", 80 | } 81 | ) 82 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 83 | 84 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 85 | 86 | return dict(response.json()) 87 | 88 | 89 | @handle_api_exceptions 90 | def initiate_uganda_mobile_charge( 91 | amount: int, 92 | email: str, 93 | tx_ref: str, 94 | phone_number: str, 95 | ) -> dict: 96 | """ 97 | Collect mobile money payments from customers in Uganda 98 | 99 | Args: 100 | tx_ref (int): Unique reference peculiar to the transaction. 101 | 102 | amount (int): Amount to be charged for the transaction. 103 | 104 | email (str): The customer's email address. 105 | 106 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 107 | 108 | Returns: 109 | dict: Response Details 110 | """ 111 | 112 | params = {"type": "mobile_money_uganda"} 113 | payload = json.dumps( 114 | { 115 | "tx_ref": f"{tx_ref}", 116 | "amount": f"{amount}", 117 | "email": f"{email}", 118 | "phone_number": f"{phone_number}", 119 | "currency": "UGX", 120 | } 121 | ) 122 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 123 | 124 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 125 | 126 | return dict(response.json()) 127 | 128 | 129 | @handle_api_exceptions 130 | def initiate_franco_mobile_charge( 131 | amount: int, 132 | email: str, 133 | tx_ref: str, 134 | phone_number: str, 135 | currency: str, 136 | franco_country_code: str, 137 | ) -> dict: 138 | """ 139 | Collect mobile money payments from customers in Francophone countries 140 | 141 | Args: 142 | tx_ref (int): Unique reference peculiar to the transaction. 143 | 144 | amount (int): Amount to be charged for the transaction. 145 | 146 | email (str): The customer's email address. 147 | 148 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 149 | 150 | currency (str): Currency to charge in. 151 | 152 | franco_country_code (str): Country code (BF, CI, CM, SN) 153 | 154 | Returns: 155 | dict: Response Details 156 | """ 157 | 158 | params = {"type": "mobile_money_franco"} 159 | payload = json.dumps( 160 | { 161 | "tx_ref": f"{tx_ref}", 162 | "amount": f"{amount}", 163 | "email": f"{email}", 164 | "phone_number": f"{phone_number}", 165 | "currency": f"{currency}", 166 | "country": franco_country_code, 167 | } 168 | ) 169 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 170 | 171 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 172 | 173 | return dict(response.json()) 174 | 175 | 176 | @handle_api_exceptions 177 | def initiate_tanzania_mobile_charge( 178 | amount: int, 179 | email: str, 180 | tx_ref: str, 181 | phone_number: str, 182 | ) -> dict: 183 | """ 184 | Collect mobile money payments from customers in Tanzania 185 | 186 | Args: 187 | tx_ref (int): Unique reference peculiar to the transaction. 188 | 189 | amount (int): Amount to be charged for the transaction. 190 | 191 | email (str): The customer's email address. 192 | 193 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 194 | 195 | Returns: 196 | dict: Response Details 197 | """ 198 | 199 | params = {"type": "mobile_money_tanzania"} 200 | payload = json.dumps( 201 | { 202 | "tx_ref": f"{tx_ref}", 203 | "amount": f"{amount}", 204 | "email": f"{email}", 205 | "phone_number": f"{phone_number}", 206 | "currency": "TZS", 207 | } 208 | ) 209 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 210 | 211 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 212 | 213 | return dict(response.json()) 214 | 215 | 216 | @handle_api_exceptions 217 | def initiate_rwanda_mobile_charge( 218 | amount: int, email: str, tx_ref: str, phone_number: str, order_id: str 219 | ) -> dict: 220 | """ 221 | Collect mobile money payments from customers in Rwanda 222 | 223 | Args: 224 | tx_ref (int): Unique reference peculiar to the transaction. 225 | 226 | amount (int): Amount to be charged for the transaction. 227 | 228 | email (str): The customer's email address. 229 | 230 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 231 | 232 | order_id (str): Unique ref for the mobilemoney transaction to be provided by the merchant 233 | 234 | Returns: 235 | dict: Response Details 236 | """ 237 | 238 | params = {"type": "mobile_money_rwanda"} 239 | payload = json.dumps( 240 | { 241 | "tx_ref": f"{tx_ref}", 242 | "amount": f"{amount}", 243 | "email": f"{email}", 244 | "phone_number": f"{phone_number}", 245 | "order_id": f"{order_id}", 246 | "currency": "RWF", 247 | } 248 | ) 249 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 250 | 251 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 252 | 253 | return dict(response.json()) 254 | 255 | 256 | @handle_api_exceptions 257 | def initiate_zambia_mobile_charge( 258 | amount: int, 259 | email: str, 260 | tx_ref: str, 261 | phone_number: str, 262 | ) -> dict: 263 | """ 264 | Collect mobile money payments from customers in Zambia 265 | 266 | Args: 267 | tx_ref (int): Unique reference peculiar to the transaction. 268 | 269 | amount (int): Amount to be charged for the transaction. 270 | 271 | email (str): The customer's email address. 272 | 273 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 274 | 275 | Returns: 276 | dict: Response Details 277 | """ 278 | 279 | params = {"type": "mobile_money_zambia"} 280 | payload = json.dumps( 281 | { 282 | "tx_ref": f"{tx_ref}", 283 | "amount": f"{amount}", 284 | "email": f"{email}", 285 | "phone_number": f"{phone_number}", 286 | "currency": "ZMW", 287 | } 288 | ) 289 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 290 | 291 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 292 | 293 | return dict(response.json()) 294 | 295 | 296 | @handle_api_exceptions 297 | def initiate_ussd_charge( 298 | tx_ref: str, 299 | account_bank: str, 300 | amount: int, 301 | email: str, 302 | phone_number: str, 303 | ) -> dict: 304 | """ 305 | Collect USSD payments from customers in Nigeria 306 | 307 | Args: 308 | tx_ref (int): Unique reference peculiar to the transaction. 309 | 310 | amount (int): Amount to be charged for the transaction. 311 | 312 | email (str): The customer's email address. 313 | 314 | account_bank (str): Bank numeric code. It can be gotten from the banks endpoint. 315 | 316 | phone_number (str): Phone number linked to the customer's bank account or mobile money account 317 | 318 | Returns: 319 | dict: Response Details 320 | """ 321 | 322 | params = {"type": "ussd"} 323 | payload = json.dumps( 324 | { 325 | "tx_ref": f"{tx_ref}", 326 | "account_bank": f"{account_bank}", 327 | "amount": f"{amount}", 328 | "currency": "NGN", 329 | "email": f"{email}", 330 | "phone_number": f"{phone_number}", 331 | } 332 | ) 333 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 334 | 335 | response = requests.post(base_url, headers=headers, data=payload, params=params) 336 | 337 | return dict(response.json()) 338 | 339 | 340 | @handle_api_exceptions 341 | def initiate_apple_pay_charge( 342 | tx_ref: str, 343 | amount: int, 344 | email: str, 345 | currency: str, 346 | ) -> dict: 347 | """ 348 | Accept payments from your customers with Apple Pay 349 | 350 | Args: 351 | tx_ref (int): Unique reference peculiar to the transaction. 352 | 353 | amount (int): Amount to be charged for the transaction. 354 | 355 | email (str): The customer's email address. 356 | 357 | currency (str): Currency to charge in. 358 | 359 | Returns: 360 | dict: Response Details 361 | """ 362 | 363 | params = {"type": "applepay"} 364 | payload = json.dumps( 365 | { 366 | "tx_ref": f"{tx_ref}", 367 | "amount": f"{amount}", 368 | "currency": f"{currency}", 369 | "email": f"{email}", 370 | } 371 | ) 372 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 373 | 374 | response = requests.post(base_url, headers=headers, data=payload, params=params) 375 | 376 | return dict(response.json()) 377 | 378 | 379 | @handle_api_exceptions 380 | def initiate_google_pay_charge( 381 | tx_ref: str, 382 | amount: int, 383 | email: str, 384 | currency: str, 385 | ) -> dict: 386 | """ 387 | Accept payments from your customers with Google Pay 388 | 389 | Args: 390 | tx_ref (int): Unique reference peculiar to the transaction. 391 | 392 | amount (int): Amount to be charged for the transaction. 393 | 394 | email (str): The customer's email address. 395 | 396 | currency (str): Currency to charge in. 397 | 398 | Returns: 399 | dict: Response Details 400 | """ 401 | 402 | params = {"type": "googlepay"} 403 | payload = json.dumps( 404 | { 405 | "tx_ref": f"{tx_ref}", 406 | "amount": f"{amount}", 407 | "currency": f"{currency}", 408 | "email": f"{email}", 409 | } 410 | ) 411 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 412 | 413 | response = requests.post(base_url, headers=headers, data=payload, params=params) 414 | 415 | return dict(response.json()) 416 | 417 | 418 | @handle_api_exceptions 419 | def initiate_enaira_charge( 420 | tx_ref: str, 421 | amount: int, 422 | email: str, 423 | ) -> dict: 424 | """ 425 | Accept payment from eNaira wallets 426 | 427 | Args: 428 | tx_ref (int): This is a unique reference peculiar to the transaction being carried out. 429 | 430 | amount (int): This is the amount to be charged for the transaction. 431 | 432 | email (str): The customer's email address. 433 | 434 | Returns: 435 | dict: Response Details 436 | """ 437 | 438 | params = {"type": "enaira"} 439 | payload = json.dumps( 440 | { 441 | "tx_ref": f"{tx_ref}", 442 | "amount": f"{amount}", 443 | "email": f"{email}", 444 | } 445 | ) 446 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 447 | 448 | response = requests.post(base_url, headers=headers, data=payload, params=params) 449 | 450 | return dict(response.json()) 451 | 452 | 453 | @handle_api_exceptions 454 | def initiate_fawry_pay_charge( 455 | tx_ref: str, 456 | amount: int, 457 | email: str, 458 | ) -> dict: 459 | """ 460 | Receive Fawry payments from customers in Egypt 461 | 462 | Args: 463 | tx_ref (int): This is a unique reference peculiar to the transaction being carried out. 464 | 465 | amount (int): This is the amount to be charged for the transaction. 466 | 467 | email (str): The customer's email address. 468 | 469 | Returns: 470 | dict: Response Details 471 | """ 472 | 473 | params = {"type": "fawry_pay"} 474 | payload = json.dumps( 475 | { 476 | "tx_ref": f"{tx_ref}", 477 | "amount": f"{amount}", 478 | "currency": "EGP", 479 | "email": f"{email}", 480 | } 481 | ) 482 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 483 | 484 | response = requests.post(base_url, headers=headers, data=payload, params=params) 485 | 486 | return dict(response.json()) 487 | 488 | 489 | @handle_api_exceptions 490 | def initiate_paypal_charge( 491 | tx_ref: str, 492 | amount: int, 493 | email: str, 494 | currency: str, 495 | ) -> dict: 496 | """ 497 | Collect payments from customers with PayPal 498 | 499 | Args: 500 | tx_ref (int): Unique reference peculiar to the transaction. 501 | 502 | amount (int): Amount to be charged for the transaction. 503 | 504 | email (str): The customer's email address. 505 | 506 | currency (str): Currency to charge in. 507 | 508 | Returns: 509 | dict: Response Details 510 | """ 511 | 512 | params = {"type": "paypal"} 513 | payload = json.dumps( 514 | { 515 | "tx_ref": f"{tx_ref}", 516 | "amount": f"{amount}", 517 | "currency": f"{currency}", 518 | "email": f"{email}", 519 | } 520 | ) 521 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 522 | 523 | response = requests.post(base_url, headers=headers, data=payload, params=params) 524 | 525 | return dict(response.json()) 526 | -------------------------------------------------------------------------------- /python_flutterwave/charge/validation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | from python_flutterwave.decorators import handle_api_exceptions 5 | 6 | token = os.environ.get("FW_SECRET_KEY") 7 | 8 | 9 | @handle_api_exceptions 10 | def validate_charge(otp: str, flw_ref: str) -> dict: 11 | """ 12 | Validate a charge 13 | 14 | Args: 15 | flw_ref (str): Reference returned in the initiate charge call as `data.flw_ref` 16 | 17 | otp (str): Random number of at least 6 characters sent to customers phone number. 18 | 19 | Returns: 20 | dict: Response Details 21 | """ 22 | 23 | params = {"type": "mpesa"} 24 | payload = json.dumps( 25 | { 26 | "flw_ref": f"{flw_ref}", 27 | "otp": f"{otp}", 28 | } 29 | ) 30 | headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} 31 | 32 | base_url = "https://api.flutterwave.com/v3/validate-charge" 33 | response = requests.post(url=base_url, headers=headers, data=payload, params=params) 34 | 35 | return dict(response.json()) 36 | -------------------------------------------------------------------------------- /python_flutterwave/decorators.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from functools import wraps 4 | from .exceptions import TokenException, FlutterwaveAPIException 5 | 6 | 7 | token = os.environ.get("FW_SECRET_KEY") 8 | 9 | 10 | def require_token(func): 11 | """Ascertain existence of the auth token""" 12 | @wraps(func) 13 | def wrapper(*args, **kwargs): 14 | if token == "" or token is None: 15 | raise TokenException(token=token, message="Authentication token absent") 16 | return func(*args, **kwargs) 17 | 18 | return wrapper 19 | 20 | 21 | def handle_api_exceptions(func): 22 | """Raise exceptions whenever necessary""" 23 | @wraps(func) 24 | @require_token 25 | def wrapper(*args, **kwargs): 26 | try: 27 | return func(*args, **kwargs) 28 | except requests.exceptions.RequestException as e: 29 | raise FlutterwaveAPIException(f"Request Exception: {str(e)}") 30 | except Exception as ex: 31 | raise FlutterwaveAPIException(str(ex)) 32 | 33 | return wrapper 34 | -------------------------------------------------------------------------------- /python_flutterwave/exceptions.py: -------------------------------------------------------------------------------- 1 | class FlutterwaveAPIException(Exception): 2 | pass 3 | 4 | 5 | class TokenException(FlutterwaveAPIException): 6 | def __init__(self, token, message): 7 | super().__init__(f"Token Error: {message}") 8 | self.token = token 9 | -------------------------------------------------------------------------------- /python_flutterwave/objects.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class RetryStrategy: 6 | """ 7 | This object defines retries for failed tokenization attempts. 8 | 9 | Args: 10 | retry_interval (int): Number in minutes for the next retry attempt. 11 | retry_amount_variable (int): Amount to be retried after the specified number of attempts in percentage. 12 | retry_attempt_variable (int): Number of retries to make after the initial tokenization attempt. 13 | last_retry_attempt (int): Maximum number of retries to attempt. If unspecified, It is set to 10 by default. 14 | """ 15 | 16 | retry_interval: int 17 | retry_amount_variable: int 18 | retry_attempt_variable: int 19 | last_retry_attempt: int = 10 20 | 21 | 22 | @dataclass 23 | class ChargeData: 24 | """ 25 | Object containing your charge data 26 | 27 | Args: 28 | currency (str): Currency to charge in 29 | token (str): Token id obtained 30 | country (str): Country Code 31 | amount (int): Charge Amount 32 | email (str): Customer Email 33 | tx_ref (str): Unique ref 34 | """ 35 | 36 | currency: str 37 | token: str 38 | country: str 39 | amount: int 40 | email: str 41 | tx_ref: str 42 | -------------------------------------------------------------------------------- /python_flutterwave/tokenization/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tokenize your customers' cards with Flutterwave for faster payments. 3 | """ 4 | from .tokenized_charge import ( 5 | initiate_tokenized_charge, 6 | initiate_bulk_tokenized_charges, 7 | fetch_bulk_tokenized_charges, 8 | fetch_bulk_tokenized_charges_status, 9 | update_card_token 10 | ) 11 | -------------------------------------------------------------------------------- /python_flutterwave/tokenization/tokenized_charge.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | from dataclasses import asdict 5 | from python_flutterwave.decorators import handle_api_exceptions 6 | from python_flutterwave.objects import ChargeData, RetryStrategy 7 | 8 | auth_token = os.environ.get("FW_SECRET_KEY") 9 | 10 | 11 | @handle_api_exceptions 12 | def initiate_tokenized_charge( 13 | amount: int, email: str, tx_ref: str, currency: str, token: str 14 | ) -> dict: 15 | """ 16 | This endpoint helps you tokenize a customer's card 17 | 18 | Args: 19 | tx_ref (int): Unique reference peculiar to the transaction. 20 | 21 | amount (int): Amount to be charged for the transaction. 22 | 23 | email (str): The customer's email address. 24 | 25 | currency (str): Currency to be used 26 | 27 | token (str): Card token returned from the transaction verification endpoint as data.card.token 28 | 29 | Returns: 30 | dict: Response Details 31 | """ 32 | 33 | payload = json.dumps( 34 | { 35 | "tx_ref": f"{tx_ref}", 36 | "amount": f"{amount}", 37 | "email": f"{email}", 38 | "token": f"{token}", 39 | "currency": f"{currency}", 40 | } 41 | ) 42 | headers = { 43 | "Authorization": f"Bearer {auth_token}", 44 | "Content-Type": "application/json", 45 | } 46 | 47 | response = requests.post( 48 | url="https://api.flutterwave.com/v3/tokenized-charges", 49 | headers=headers, 50 | data=payload, 51 | ) 52 | 53 | return dict(response.json()) 54 | 55 | 56 | @handle_api_exceptions 57 | def initiate_bulk_tokenized_charges( 58 | retry_strategy: RetryStrategy, bulk_data: list[ChargeData] 59 | ) -> dict: 60 | """ 61 | Tokenize multiple cards at once 62 | 63 | Args: 64 | retry_strategy: RetryStrategy 65 | 66 | bulk_data: list[ChargeData] 67 | 68 | Returns: 69 | dict: Response Details 70 | """ 71 | 72 | payload = json.dumps( 73 | { 74 | "retry_strategy": asdict(retry_strategy), 75 | "bulk_data": [asdict(i) for i in bulk_data], 76 | } 77 | ) 78 | headers = { 79 | "Authorization": f"Bearer {auth_token}", 80 | "Content-Type": "application/json", 81 | } 82 | 83 | response = requests.post( 84 | url="https://api.flutterwave.com/v3/bulk-tokenized-charges", 85 | headers=headers, 86 | data=payload, 87 | ) 88 | 89 | return dict(response.json()) 90 | 91 | 92 | @handle_api_exceptions 93 | def fetch_bulk_tokenized_charges(bulk_id: int): 94 | """ 95 | This endpoint allows you to get bulk tokenized charges transactions 96 | 97 | Args: 98 | bulk_id (int): id returned in the bulk charge response 99 | 100 | Returns: 101 | dict: Response Details 102 | """ 103 | 104 | headers = { 105 | "Authorization": f"Bearer {auth_token}", 106 | "Content-Type": "application/json", 107 | } 108 | 109 | response = requests.get( 110 | url=f"https://api.flutterwave.com/v3/bulk-tokenized-charges/{bulk_id}/transactions", 111 | headers=headers, 112 | ) 113 | 114 | return dict(response.json()) 115 | 116 | 117 | @handle_api_exceptions 118 | def fetch_bulk_tokenized_charges_status(bulk_id: int): 119 | """ 120 | This endpoint allows you to query the status of a bulk tokenized charge 121 | 122 | Args: 123 | bulk_id (int): id returned in the bulk charge response 124 | 125 | Returns: 126 | dict: Response Details 127 | """ 128 | 129 | headers = { 130 | "Authorization": f"Bearer {auth_token}", 131 | "Content-Type": "application/json", 132 | } 133 | 134 | response = requests.get( 135 | url=f"https://api.flutterwave.com/v3/bulk-tokenized-charges/{bulk_id}", 136 | headers=headers, 137 | ) 138 | 139 | return dict(response.json()) 140 | 141 | 142 | @handle_api_exceptions 143 | def update_card_token(email: str, full_name: str, phone_number: str, token: str): 144 | """ 145 | This endpoints allow developers update the details tied to a customer's card token. 146 | 147 | Args: 148 | email (str): The customer's email address. 149 | 150 | full_name (str): The customer's email address. 151 | 152 | phone_number (str): Customer's phone number. 153 | 154 | token (str): Card token returned from the transaction verification endpoint as data.card.token 155 | 156 | Returns: 157 | dict: Response Details 158 | """ 159 | 160 | payload = json.dumps( 161 | { 162 | "email": f"{email}", 163 | "full_name": f"{full_name}", 164 | "phone_number": f"{phone_number}", 165 | } 166 | ) 167 | 168 | headers = { 169 | "Authorization": f"Bearer {auth_token}", 170 | "Content-Type": "application/json", 171 | } 172 | 173 | response = requests.get( 174 | url=f"https://api.flutterwave.com/v3/tokens/{token}", 175 | headers=headers, 176 | data=payload, 177 | ) 178 | 179 | return dict(response.json()) 180 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi 2 | charset-normalizer 3 | idna 4 | requests 5 | urllib3 6 | sphinx 7 | autodoc 8 | sphinx-rtd-theme 9 | black 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-flutterwave 3 | version = 1.2.2 4 | author = William Otieno 5 | author_email = jimmywilliamotieno@gmail.com 6 | description = Python Wrapper for interacting with the Flutterwave Payments API 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/WilliamOtieno/python-flutterwave 10 | classifiers = 11 | Programming Language :: Python :: 3 12 | License :: OSI Approved :: MIT License 13 | Operating System :: OS Independent 14 | 15 | [options] 16 | packages = find: 17 | install_requires = requests; 18 | python_requires = >=3.8 19 | include_package_data = True 20 | --------------------------------------------------------------------------------