├── .github
└── FUNDING.yml
├── .gitignore
├── Contributing.md
├── LICENSE.txt
├── MANIFEST
├── README.md
├── assets
├── banner.jpeg
├── banner.png
├── become_a_patron_button.png
└── pypesa-banner.png
├── description.md
├── keys.json
├── pypesa
├── __init__.py
├── mpesa.py
├── mpesa_exceptions.py
└── service_urls.py
├── requirements.txt
├── setup.py
└── tests
├── __init__.py
└── test_pypesa.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: kalebujordan
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | __pycache__/
3 | dist/
4 | build/
5 | *.egg-info/
6 | *.py[cod]
--------------------------------------------------------------------------------
/Contributing.md:
--------------------------------------------------------------------------------
1 | ## Guide to Contributing to Pypesa Package
2 | # Contributing
3 |
4 | When contributing to this repository, please first discuss the change you wish to make via issue,
5 | email, or any other method with the owners of this repository before making a change.
6 |
7 | Please note we have a code of conduct, please follow it in all your interactions with the project.
8 |
9 | ## Pull Request Process
10 |
11 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
12 | build.
13 | 2. Update the README.md with details of changes to the interface, this includes new environment
14 | variables, exposed ports, useful file locations and container parameters.
15 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
16 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
17 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
18 | do not have permission to do that, you may request the second reviewer to merge it for you.
19 |
20 | ## Code of Conduct
21 |
22 | ### Our Pledge
23 |
24 | In the interest of fostering an open and welcoming environment, we as
25 | contributors and maintainers pledge to making participation in our project and
26 | our community a harassment-free experience for everyone, regardless of age, body
27 | size, disability, ethnicity, gender identity and expression, level of experience,
28 | nationality, personal appearance, race, religion, or sexual identity and
29 | orientation.
30 |
31 | ### Our Standards
32 |
33 | Examples of behavior that contributes to creating a positive environment
34 | include:
35 |
36 | * Using welcoming and inclusive language
37 | * Being respectful of differing viewpoints and experiences
38 | * Gracefully accepting constructive criticism
39 | * Focusing on what is best for the community
40 | * Showing empathy towards other community members
41 |
42 | Examples of unacceptable behavior by participants include:
43 |
44 | * The use of sexualized language or imagery and unwelcome sexual attention or
45 | advances
46 | * Trolling, insulting/derogatory comments, and personal or political attacks
47 | * Public or private harassment
48 | * Publishing others' private information, such as a physical or electronic
49 | address, without explicit permission
50 | * Other conduct which could reasonably be considered inappropriate in a
51 | professional setting
52 |
53 | ### Our Responsibilities
54 |
55 | Project maintainers are responsible for clarifying the standards of acceptable
56 | behavior and are expected to take appropriate and fair corrective action in
57 | response to any instances of unacceptable behavior.
58 |
59 | Project maintainers have the right and responsibility to remove, edit, or
60 | reject comments, commits, code, wiki edits, issues, and other contributions
61 | that are not aligned to this Code of Conduct, or to ban temporarily or
62 | permanently any contributor for other behaviors that they deem inappropriate,
63 | threatening, offensive, or harmful.
64 |
65 | ### Scope
66 |
67 | This Code of Conduct applies both within project spaces and in public spaces
68 | when an individual is representing the project or its community. Examples of
69 | representing a project or community include using an official project e-mail
70 | address, posting via an official social media account, or acting as an appointed
71 | representative at an online or offline event. Representation of a project may be
72 | further defined and clarified by project maintainers.
73 |
74 | ### Enforcement
75 |
76 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
77 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
78 | complaints will be reviewed and investigated and will result in a response that
79 | is deemed necessary and appropriate to the circumstances. The project team is
80 | obligated to maintain confidentiality with regard to the reporter of an incident.
81 | Further details of specific enforcement policies may be posted separately.
82 |
83 | Project maintainers who do not follow or enforce the Code of Conduct in good
84 | faith may face temporary or permanent repercussions as determined by other
85 | members of the project's leadership.
86 |
87 | ### Attribution
88 |
89 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
90 | available at [http://contributor-covenant.org/version/1/4][version]
91 |
92 | [homepage]: http://contributor-covenant.org
93 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Kalebu Gwalugano
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 |
--------------------------------------------------------------------------------
/MANIFEST:
--------------------------------------------------------------------------------
1 | # file GENERATED by distutils, do NOT edit
2 | setup.py
3 | pypesa/__init__.py
4 | pypesa/mpesa_exceptions.py
5 | pypesa/service_urls.py
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # [pypesa](http://kalebu.github.io/pypesa)
4 |
5 | [](https://kalebu.github.io/pypesa/)
6 | [](https://badge.fury.io/py/python-pesa)
7 | [](https://github.com/Kalebu/pypesa)
8 | [](https://pepy.tech/project/python-pesa)
9 | [](https://pepy.tech/project/python-pesa)
10 | [](https://pepy.tech/project/python-pesa)
11 | [](https://opensource.org/licenses/MIT)
12 |
13 | Python wrapper on **Mpesa public API** for mobile Payment Integration made with care to offer easy and elegant integration made by [kalebu](https://github.com/kalebu)
14 |
15 | [](https://www.patreon.com/kalebujordan)
16 |
17 | ## Implemented Features
18 |
19 | The following are the features that are supported by the **Mpesa** public API and require the **python** implementation.
20 |
21 | - [x] Customer to Bussiness (C2B) Single Payment
22 | - [x] Bussiness to Customer (B2C)
23 | - [x] Bussiness to Bussiness (B2B)
24 | - [x] Payment Reversal
25 | - [x] Query Transaction status
26 | - [x] Direct debit creation and Payment
27 |
28 | ## Documentation
29 |
30 | Full documentation can be found on [pypesa](http://kalebu.github.io/pypesa)
31 |
32 | ## Getting started
33 |
34 | Getting started with **pypesa** is pretty straight forward and can be categorized
35 |
36 | into steps shown below.
37 |
38 | - Sign up for Mpesa Developer portal
39 |
40 | - Install the [pypesa](http://kalebu.github.io/pypesa) package using **pip**
41 |
42 | - Build your services with **pypesa**
43 |
44 | ## Signing up
45 |
46 | To sign up for Mpesa public API visit [Mpesa-API](https://openapiportal.m-pesa.com/sign-up) and then
47 |
48 | you can go through [getting started Mpesa Developer portal](https://dev.to/alphaolomi/getting-started-with-mpesa-developer-portal-46a4)
49 | by [alphaolomi](https://github.com/alphaolomi) to see how.
50 |
51 | ## Installation
52 |
53 | To install the **pypesa** package to your machine you can either
54 |
55 | install directly from github or use pip to install it.
56 |
57 | - Using github
58 |
59 | ```bash
60 | $~ git clone https://github.com/Kalebu/pypesa
61 | $~ cd pypesa
62 | $ pypesa ~ python setup.py install
63 | ```
64 |
65 | - Using pip
66 |
67 | ```bash
68 |
69 | pip install python-pesa
70 |
71 | ```
72 |
73 | ## Usage
74 |
75 | To begin using the package is pretty straight forward
76 |
77 | 1. You need to have a json file named **keys.json** on your project directly
78 | and then fill put in it your public and api keys in json format shown below
79 |
80 | ```python
81 |
82 | {
83 | 'api_key': 'xxx',
84 | 'public_key': 'xxxxxxxxxxxxxx'
85 | }
86 | ```
87 |
88 | 2.Once done you're ready to go, just make sure you have active internet connection
89 |
90 | ### Example of Usage (Customer to Bussiness Transaction)
91 |
92 | ```python
93 | >>> from pypesa import Mpesa
94 | >>> mpesa = Mpesa()
95 | >>> transaction_query = {"input_Amount": "10",
96 | "input_Country": "TZN",
97 | "input_Currency": "TZS",
98 | "input_CustomerMSISDN": "000000000001",
99 | "input_ServiceProviderCode": "000000",
100 | "input_ThirdPartyConversationID":'2edf7a0206d848f6b6fedea26accdc3a',
101 | "input_TransactionReference": 'T23434ZE5',
102 | "input_PurchasedItemsDesc": "Python Book"
103 | }
104 | >>> mpesa.customer_to_bussiness(transaction_query)
105 |
106 | Request processed successfully INS-0
107 | {'output_ResponseCode': 'INS-0', 'output_ResponseDesc': 'Request processed successfully',
108 | 'output_TransactionID': 'uGnPxFoXT2W0', 'output_ConversationID': '1d1e38495dc946729a8cffb136ab8391', 'output_ThirdPartyConversationID': '2edf7a0206d848f6b6fedea26accdc3a'}
109 |
110 | ```
111 |
112 | ### Naming the authentication json
113 |
114 | If you named your authentication json in other name than **keys** you might to
115 | specify it while creating an instance for mpesa just as shown below;
116 |
117 | ```python
118 | >>> from pypesa import Mpesa
119 | >>> mpesa = Mpesa(auth_path = filename)
120 | ```
121 |
122 | ## production environment
123 |
124 | The package run by default using sandbox environment, If you wanna use it to real production
125 | environment you can specify it while creating an instance as shown below
126 |
127 | ```python
128 | >>> from pypesa import Mpesa
129 | >>> mpesa = Mpesa(environment="production")
130 | ```
131 |
132 | ## To do list
133 |
134 | - [x] Adding a well structured documentation
135 | - [ ] Adding a detailed test case to the implementation
136 | - [ ] Fixing rising bugs
137 |
138 | ## Contributing
139 |
140 | Wanna contribute to Pypesa ? then please [contributing.md](https://github.com/Kalebu/pypesa/blob/main/Contributing.md) to see how
141 |
142 | ## Give it a star
143 |
144 | If you found this repository useful, give it a star, You can also keep in touch with on [twitter](https://twitter.com/j_kalebu).
145 |
146 | ## Bug bounty?
147 |
148 | If you encounter **issue** with the usage of the package, feel free raise an **issue** so as
149 | we can fix it as soon as possible(ASAP) or just reach me directly through my email isaackeinstein(at)gmail.com
150 |
151 | ## Credit
152 |
153 | All the credits to
154 | 1. [kalebu](https://github.com/Kalebu/)
155 | 2. [SentielWarren](https://github.com/SentinelWarren)
156 | 3. and all the future contributors
157 |
158 |
159 |
--------------------------------------------------------------------------------
/assets/banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurotech-HQ/pypesa/3b787157b12ff1f176724e3d55fc08d59412a9fa/assets/banner.jpeg
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurotech-HQ/pypesa/3b787157b12ff1f176724e3d55fc08d59412a9fa/assets/banner.png
--------------------------------------------------------------------------------
/assets/become_a_patron_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurotech-HQ/pypesa/3b787157b12ff1f176724e3d55fc08d59412a9fa/assets/become_a_patron_button.png
--------------------------------------------------------------------------------
/assets/pypesa-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurotech-HQ/pypesa/3b787157b12ff1f176724e3d55fc08d59412a9fa/assets/pypesa-banner.png
--------------------------------------------------------------------------------
/description.md:
--------------------------------------------------------------------------------
1 | # [pypesa](http://kalebu.github.io/pypesa)
2 |
3 | Python wrapper on **Mpesa public API** for mobile Payment Integration made with care to offer easy and elegant integration made by [kalebu](https://github.com/kalebu)
4 |
5 | ## Implemented Features
6 |
7 | The following are the features that are supported by the **Mpesa** public API and require the **python** implementation.
8 |
9 | - Customer to Bussiness (C2B) Single Payment
10 | - Bussiness to Customer (B2C)
11 | - Bussiness to Bussiness (B2B)
12 | - Payment Reversal
13 | - Query Transaction status
14 | - Direct debit creation and Payment
15 |
16 | ## Documentation
17 |
18 | Full documentation can be found on [pypesa](http://kalebu.github.io/pypesa)
19 |
20 | ## Getting started
21 |
22 | Getting started with **pypesa** is pretty straight forward and can be categorized
23 |
24 | into steps shown below.
25 |
26 | - Sign up for Mpesa Developer portal
27 |
28 | - Install the [pypesa](http://kalebu.github.io/pypesa) package using **pip**
29 |
30 | - Build your services with **pypesa**
31 |
32 | ## Signing up
33 |
34 | To sign up for Mpesa public API visit [Mpesa-API](https://openapiportal.m-pesa.com/sign-up) and then
35 |
36 | you can go through [getting started Mpesa Developer portal](https://dev.to/alphaolomi/getting-started-with-mpesa-developer-portal-46a4)
37 | by [alphaolomi](https://github.com/alphaolomi) to see how.
38 |
39 | ## Installation
40 |
41 | To install the **pypesa** package to your machine you can either
42 |
43 | install directly from github or use pip to install it.
44 |
45 | - Using github
46 |
47 | ```bash
48 | $~ git clone https://github.com/Kalebu/pypesa
49 | $~ cd pypesa
50 | $ pypesa ~ python setup.py install
51 | ```
52 |
53 | - Using pip
54 |
55 | ```bash
56 |
57 | pip install python-pesa
58 |
59 | ```
60 |
61 | ## Usage
62 |
63 | To begin using the package is pretty straight forward
64 |
65 | 1. You need to have a json file named **keys.json** on your project directly
66 | and then fill put in it your public and api keys in json format shown below
67 |
68 | ```python
69 |
70 | {
71 | 'api_key': 'xxx',
72 | 'public_key': 'xxxxxxxxxxxxxx'
73 | }
74 | ```
75 |
76 | 2.Once done you're ready to go, just make sure you have active internet connection
77 |
78 | ### Example of Usage (Customer to Bussiness Transaction)
79 |
80 | ```python
81 | >>> from pypesa import Mpesa
82 | >>> mpesa = Mpesa()
83 | >>> transaction_query ={"input_Amount": "10",
84 | "input_Country": "TZN",
85 | "input_Currency": "TZS",
86 | "input_CustomerMSISDN": "000000000001",
87 | "input_ServiceProviderCode": "000000",
88 | "input_ThirdPartyConversationID":'2edf7a0206d848f6b6fedea26accdc3a',
89 | "input_TransactionReference": 'T23434ZE5',
90 | "input_PurchasedItemsDesc": "Python Book"
91 | }
92 | >>> mpesa.customer_to_bussiness(transaction_query)
93 |
94 | Request processed successfully INS-0
95 | {'output_ResponseCode': 'INS-0', 'output_ResponseDesc': 'Request processed successfully',
96 | 'output_TransactionID': 'uGnPxFoXT2W0', 'output_ConversationID': '1d1e38495dc946729a8cffb136ab8391', 'output_ThirdPartyConversationID': '2edf7a0206d848f6b6fedea26accdc3a'}
97 |
98 | ```
99 |
100 | ### Naming the authentication json
101 |
102 | If you named your authentication json in other name than **keys** you might to
103 | specify it while creating an instance for mpesa just as shown below;
104 |
105 | ```python
106 | >>> from pypesa import Mpesa
107 | >>> mpesa = Mpesa(auth_path = filename)
108 | ```
109 |
110 | ## production environment
111 |
112 | The package run by default using sandbox environment, If you wanna use it to real production
113 | environment you can specify it while creating an instance as shown below
114 |
115 | ```python
116 | >>> from pypesa import Mpesa
117 | >>> mpesa = Mpesa(environment="production")
118 | ```
119 |
120 | ## Contributing
121 |
122 | Wanna contribute to Pypesa ? then please [contributing.md](https://github.com/Kalebu/pypesa/blob/main/Contributing.md) to see how.
123 |
124 | ## Bug bounty?
125 |
126 | If you encounter **issue** with the usage of the package, feel free raise an **issue** so as we can fix it as soon as possible(ASAP) or just reach me directly through my email isaackeinstein(at)gmail.com.
127 |
128 | ## Credit
129 |
130 | All the credits to [kalebu](https://github.com/Kalebu/) and all the future contributors.
131 |
--------------------------------------------------------------------------------
/keys.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "api_key" : "tdWtOTaKSCVGDCoqKM4zqPgnM70IyVe6",
4 | "public_key" : "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArv9yxA69XQKBo24BaF/D+fvlqmGdYjqLQ5WtNBb5tquqGvAvG3WMFETVUSow/LizQalxj2ElMVrUmzu5mGGkxK08bWEXF7a1DEvtVJs6nppIlFJc2SnrU14AOrIrB28ogm58JjAl5BOQawOXD5dfSk7MaAA82pVHoIqEu0FxA8BOKU+RGTihRU+ptw1j4bsAJYiPbSX6i71gfPvwHPYamM0bfI4CmlsUUR3KvCG24rB6FNPcRBhM3jDuv8ae2kC33w9hEq8qNB55uw51vK7hyXoAa+U7IqP1y6nBdlN25gkxEA8yrsl1678cspeXr+3ciRyqoRgj9RD/ONbJhhxFvt1cLBh+qwK2eqISfBb06eRnNeC71oBokDm3zyCnkOtMDGl7IvnMfZfEPFCfg5QgJVk1msPpRvQxmEsrX9MQRyFVzgy2CWNIb7c+jPapyrNwoUbANlN8adU1m6yOuoX7F49x+OjiG2se0EJ6nafeKUXw/+hiJZvELUYgzKUtMAZVTNZfT8jjb58j8GVtuS+6TM2AutbejaCV84ZK58E2CRJqhmjQibEUO6KPdD7oTlEkFy52Y1uOOBXgYpqMzufNPmfdqqqSM4dU70PO8ogyKGiLAIxCetMjjm6FCMEA3Kc8K0Ig7/XtFm9By6VxTJK1Mg36TlHaZKP6VzVLXMtesJECAwEAAQ=="
5 | }
--------------------------------------------------------------------------------
/pypesa/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | pypesa: A Python wrapper on Mpesa public API for mobile payments Integration.
3 | =============================================================================
4 |
5 | Documentation is available in the docstrings
6 | and online at http://kalebu.github.io/pypesa
7 |
8 | Contents
9 | --------
10 | pypesa contents goes here!
11 |
12 | Subpackages
13 | -----------
14 | Using any of the pypesa subpackages requires either
15 | an explicit or implicit import. For example,
16 | ``import pypesa`` | ``import pypesa.mpesa`` | ``from pypesa import Mpesa``.
17 |
18 | ::
19 |
20 | mpesa --- Mpesa class
21 | mpesa_exceptions --- Exception classes
22 | service_urls --- Services urls
23 |
24 |
25 | Utility tools
26 | -------------
27 | ::
28 |
29 | tests --- Run pypesa unittests
30 |
31 | Changelog
32 | ---------
33 | **Version 0.4** - 2021 April 30
34 | Improved auth decorator with wraps
35 | Added example in methods docstring to ease the usage
36 |
37 | **Version 0.3** - 2021 February 14
38 | Fixed Query transaction status bug
39 | Improved docstring documentation
40 | Added json response to debit creation and payments
41 |
42 | **Version 0.2** - 2021 February 13
43 | Fixed bug | added doc string | changed import format
44 |
45 | **Version 0.1** - 2021 February 12
46 | Initial release.
47 |
48 | """
49 |
50 | __version__ = '0.4'
51 | __all__ = ['Mpesa',]
52 |
53 | from pypesa.mpesa import Mpesa
54 |
--------------------------------------------------------------------------------
/pypesa/mpesa.py:
--------------------------------------------------------------------------------
1 | """Python package to easy integration with Vodacom's Mpesa Public API.
2 | """
3 |
4 | import os
5 | import json
6 | import base64
7 | import requests
8 |
9 | from pathlib import Path
10 | from functools import wraps
11 | from typing import Dict, Set, Optional, Union
12 | from Crypto.PublicKey import RSA
13 | from Crypto.Cipher import PKCS1_v1_5 as rsa_cipher
14 |
15 | from pypesa.service_urls import Sandbox, Production
16 | from pypesa.mpesa_exceptions import AuthenticationError, LoadingKeyError, MpesaConnectionError
17 |
18 |
19 | class Mpesa:
20 | """Mpesa API client.
21 | """
22 |
23 | __slots__ = [
24 | "auth_path",
25 | "auth_keys",
26 | "_encrypted_api_key",
27 | "_origin_ip",
28 | "urls",
29 | ]
30 |
31 | def __init__(
32 | self,
33 | auth_path: Optional[str] = "keys.json",
34 | environment: Optional[str] = "sandbox",
35 | ) -> None:
36 | self.auth_path = auth_path
37 | self.auth_keys: Dict = dict()
38 | self._encrypted_api_key = None
39 | self._origin_ip = "*"
40 | self.urls = Production() if environment == "production" else Sandbox()
41 | print(self.urls)
42 |
43 | @property
44 | def authenticate(self) -> bool:
45 | """Property to check If the user is has has included auth keys
46 | either manually or through auth json file(keys.json).
47 |
48 | >> import pypesa
49 | >> mpesa = pypesa()
50 | >> mpesa.authenticate
51 |
52 | Return True if the environment has auth keys else
53 | False if auth are not included.
54 | """
55 |
56 | if self.auth_keys.get("public_key") and self.auth_keys.get("api_key"):
57 | self._encrypted_api_key = self.__generate_encrypted_key()
58 | return True
59 |
60 | elif os.path.isfile(self.auth_path):
61 | # print("loading from file")
62 | self.auth_keys = self.load_keys(self.auth_path)
63 | if self.auth_keys:
64 | self._encrypted_api_key = self.__generate_encrypted_key()
65 | return True
66 | return False
67 |
68 | else:
69 | return False
70 |
71 | def authenticated(method):
72 | @wraps(method)
73 | def authorized_method(self, *args, **kwargs):
74 | if self.authenticate:
75 | return method(self, *args, **kwargs)
76 | else:
77 | raise AuthenticationError
78 |
79 | return authorized_method
80 |
81 | @staticmethod
82 | def load_keys(keys_filename: Union[str, Path]) -> Dict:
83 | """Pypesa internal method to load the auth file
84 | >> import pypesa
85 | >> mpesa = pypesa()
86 | >> pypesa.load_keys()
87 |
88 | Return keys Dict if auth file is present else,
89 | Raise FileNotFoundError if auth file is absent.
90 | """
91 | try:
92 |
93 | with open(keys_filename, "r") as auth:
94 | return json.load(auth)
95 |
96 | except FileNotFoundError:
97 | raise FileNotFoundError(
98 | f"{keys_filename} is not found on the current directory"
99 | )
100 |
101 | except Exception as bug:
102 | print(bug)
103 | raise LoadingKeyError
104 |
105 | def __generate_encrypted_key(self, session: Optional[bool] = False) -> str:
106 | """Method use to Encrypt the api key and public key
107 | so as to get a secure RSA Encrypted key.
108 | """
109 | try:
110 | pub_key = self.auth_keys.get("public_key")
111 | raw_key = self.auth_keys.get("api_key")
112 |
113 | if session:
114 | raw_key = self.get_session_id()
115 |
116 | public_key_string = base64.b64decode(pub_key)
117 | rsa_public_key = RSA.importKey(public_key_string)
118 | raw_cipher = rsa_cipher.new(rsa_public_key)
119 | encrypted_key = raw_cipher.encrypt(raw_key.encode())
120 | return base64.b64encode(encrypted_key).decode("utf-8")
121 |
122 | except Exception as bug:
123 | print(bug)
124 | """raise AuthenticationError(
125 | "Exceptions thrown while generating encrypted key\nPlease make sure you have the right public key"
126 | )"""
127 |
128 | @property
129 | def path_to_auth(self) -> str:
130 | """
131 | Return Path to Authentication file
132 | """
133 | return self.auth_path
134 |
135 | @path_to_auth.setter
136 | def path_to_auth(self, auth_path: Union[str, Path]) -> str:
137 | """Setting new path to the authentication file.
138 | """
139 | if isinstance(auth_path, str):
140 | self.auth_path = auth_path
141 | return self.auth_path
142 | raise TypeError(
143 | f"Path to auth file must of type Path or String not {type(auth_path)}"
144 | )
145 |
146 | @property
147 | def environment(self) -> Union[Sandbox, Production]:
148 | """Return the Environment the Pypesa is running
149 | whether its Sandbox | Production.
150 | """
151 | return self.urls
152 |
153 | @environment.setter
154 | def environment(self, env: str) -> Union[Sandbox, Production]:
155 | """Set new pypesa environment
156 |
157 | Eg. changing env to production;
158 |
159 | >> import pypesa
160 | >> mpesa = pypesa()
161 | >> mpesa.environment = "production"
162 |
163 | """
164 | if isinstance(env):
165 | if env in ["Sandbox", "sandbox", "Production", "production"]:
166 | if env == "Sandbox" or "sandbox":
167 | self.urls = Sandbox()
168 | elif env == "Production" or "production":
169 | self.urls = Production()
170 | print(self.urls)
171 | return self.urls
172 | raise ValueError(
173 | "Environment must be either sandbox or production")
174 | raise TypeError(
175 | f"environment must be of type string not {type(env)}")
176 |
177 | @property
178 | def api_key(self) -> str:
179 | """Return current api key.
180 | """
181 | return self.auth_keys.get("api_key")
182 |
183 | @api_key.setter
184 | def api_key(self, Api_key: str) -> str:
185 | """Use this property to explicit set a api_key
186 |
187 | >> import pypesa
188 | >> wallet = pypesa()
189 | >> wallet.api_key = " Your api key" #here
190 | """
191 | if isinstance(Api_key, str):
192 | self.auth_keys["api_key"] = Api_key
193 | return self.auth_keys["api_key"]
194 | raise TypeError(
195 | f"API key must be a of type String not {type(Api_key)}")
196 |
197 | @property
198 | def public_key(self) -> str:
199 | """Return the current Public key.
200 | """
201 | return self.auth_keys.get("public_key")
202 |
203 | @public_key.setter
204 | def public_key(self, pb_key: str) -> str:
205 | """Set a new public key.
206 | """
207 | if isinstance(pb_key, str):
208 | self.auth_keys["public_key"] = pb_key
209 | return self.auth_keys["public_key"]
210 | raise TypeError(f"Public key must be a string not a {type(pb_key)}")
211 |
212 | @property
213 | def origin_address(self) -> str:
214 | """Return the current origin address.
215 | """
216 | return self._origin_ip
217 |
218 | @origin_address.setter
219 | def origin_address(self, ip_address: str) -> str:
220 | """Set a new origin address.
221 | """
222 | if isinstance(ip_address, str):
223 | self._origin_ip = ip_address
224 | return self._origin_ip
225 | raise TypeError(
226 | f"Address must be of type string not {type(ip_address)}")
227 |
228 | @authenticated
229 | def default_headers(self, auth_key: Optional[str] = "") -> Dict:
230 | """Generate Default header to be used during a Request.
231 | """
232 | if not auth_key:
233 | auth_key = self.__generate_encrypted_key(session=True)
234 | return {
235 | "Content-Type": "application/json",
236 | "Authorization": "Bearer {}".format(auth_key),
237 | "Host": "openapi.m-pesa.com",
238 | "Origin": self.origin_address,
239 | }
240 |
241 | @authenticated
242 | def get_session_id(self) -> str:
243 | try:
244 | headers = self.default_headers(auth_key=self._encrypted_api_key)
245 | response = requests.get(self.urls.session_id, headers=headers)
246 | response = response.json()
247 | session_id = response["output_SessionID"]
248 | response_code = response["output_ResponseCode"]
249 | description = response["output_ResponseDesc"]
250 | # print(description, " ", response_code)
251 | if response_code == "INS-989":
252 | # print("Session creation failed!!")
253 | raise AuthenticationError
254 | return session_id
255 | except Exception as bug:
256 | print(bug)
257 | raise AuthenticationError
258 |
259 | @staticmethod
260 | def verify_query(transaction_query: Dict, required_fields: Set) -> bool:
261 | """Raise KeyError if transaction query has a missing key.
262 | """
263 | query_keys = set(transaction_query.keys())
264 | missing_keys = required_fields.difference(query_keys)
265 | if missing_keys:
266 | raise KeyError(
267 | "These keys {} are missing in your transaction query".format(
268 | missing_keys
269 | )
270 | )
271 | return True
272 |
273 | @authenticated
274 | def customer_to_bussiness(self, transaction_query: Dict) -> Dict:
275 | """Customer to bussiness method.
276 |
277 | Example;
278 | >> import pypesa
279 | >> mpesa = pypesa()
280 | >> transaction_query = {
281 | "input_Amount": "10",
282 | "input_Country": "TZN",
283 | "input_Currency": "TZS",
284 | "input_CustomerMSISDN": "000000000001",
285 | "input_ServiceProviderCode": "000000",
286 | "input_ThirdPartyConversationID":'2edf7a0206d848f6b6fedea26accdc3a',
287 | "input_TransactionReference": 'T23434ZE5',
288 | "input_PurchasedItemsDesc": "Python Book"
289 | }
290 | >> mpesa.customer_to_bussiness(transaction_query)
291 | """
292 |
293 | self.verify_query(transaction_query,
294 | self.urls.re_customer_to_bussiness)
295 |
296 | try:
297 | return requests.post(
298 | self.urls.single_stage_c2b,
299 | json=transaction_query,
300 | headers=self.default_headers(),
301 | verify=True,
302 | ).json()
303 |
304 | except (requests.ConnectTimeout, requests.ConnectionError):
305 | raise MpesaConnectionError
306 |
307 | @authenticated
308 | def bussiness_to_customer(self, transaction_query: Dict) -> Dict:
309 | """Bussiness to customer method.
310 |
311 | Example;
312 | >> import pypesa
313 | >> mpesa = pypesa()
314 | >> transaction_query = {
315 | 'input_Amount': 250,
316 | 'input_Country': 'TZN',
317 | 'input_Currency': 'TZS',
318 | 'input_CustomerMSISDN': '000000000001',
319 | 'input_ServiceProviderCode': '000000',
320 | 'input_ThirdPartyConversationID':'f5e420e99594a9c496d8600',
321 | 'input_TransactionReference': 'T12345C',
322 | 'input_PaymentItemsDesc': 'Donation',
323 | }
324 |
325 | >> mpesa.bussiness_to_customer(transaction_query)
326 | """
327 |
328 | self.verify_query(transaction_query,
329 | self.urls.re_bussiness_to_customer)
330 |
331 | try:
332 |
333 | return requests.post(
334 | self.urls.single_stage_b2c,
335 | json=transaction_query,
336 | headers=self.default_headers(),
337 | verify=True,
338 | ).json()
339 |
340 | except (requests.ConnectTimeout, requests.ConnectionError):
341 | raise MpesaConnectionError
342 |
343 | @authenticated
344 | def bussiness_to_bussiness(self, transaction_query: Dict) -> Dict:
345 | """Bussiness to bussiness method
346 |
347 | Example;
348 | >> import pypesa
349 | >> mpesa = pypesa()
350 | >> transaction_query = {
351 | 'input_Amount': 10,
352 | 'input_Country': 'TZN',
353 | 'input_Currency': 'TZS',
354 | 'input_PrimaryPartyCode':'000000',
355 | 'input_ReceiverPartyCode':'000001',
356 | 'input_ServiceProviderCode': '000000',
357 | 'input_ThirdPartyConversationID': '8a89835c71f15e99396',
358 | 'input_TransactionReference': 'T1234C',
359 | 'input_PurchasedItemsDesc': 'Shoes',
360 | }
361 | >> mpesa.bussiness_to_bussiness(transaction_query)
362 | """
363 |
364 | self.verify_query(transaction_query,
365 | self.urls.re_bussiness_to_bussiness)
366 |
367 | try:
368 | return requests.post(
369 | self.urls.single_stage_b2b,
370 | json=transaction_query,
371 | headers=self.default_headers(),
372 | verify=True,
373 | ).json()
374 |
375 | except (requests.ConnectTimeout, requests.ConnectionError):
376 | raise MpesaConnectionError
377 |
378 | @authenticated
379 | def payment_reversal(self, transaction_query: Dict) -> Dict:
380 | """Payment reversal method.
381 |
382 | Example;
383 | >> import pypesa
384 | >> mpesa = pypesa()
385 | >> transaction_query = {
386 | 'input_ReversalAmount':10,
387 | 'input_Country': 'TZN',
388 | 'input_ServiceProviderCode': '000000',
389 | 'input_ThirdPartyConversationID':'asvf7ba228d83d0d689761',
390 | 'input_TransactionID':'4iUThBRRWXMG'
391 | }
392 | >> mpesa.payment_reversal(transaction_query)
393 | """
394 |
395 | self.verify_query(transaction_query, self.urls.re_payment_reversal)
396 |
397 | try:
398 | return requests.put(
399 | self.urls.payment_reversal,
400 | json=transaction_query,
401 | headers=self.default_headers(),
402 | verify=True,
403 | ).json()
404 |
405 | except (requests.ConnectTimeout, requests.ConnectionError):
406 | raise MpesaConnectionError
407 |
408 | @authenticated
409 | def query_transaction_status(self, transaction_query: Dict) -> Dict:
410 | """Query transaction status method.
411 |
412 | Example;
413 | >> import pypesa
414 | >> mpesa = pypesa()
415 | >> transaction_query = {
416 | 'input_QueryReference': '000000000000000000001',
417 | 'input_ServiceProviderCode': '000000',
418 | 'input_ThirdPartyConversationID': 'asv02e5958774f7ba228d83d0d689761',
419 | 'input_Country': 'TZN',
420 | }
421 | >> mpesa.query_transaction_status(transaction_query)
422 | """
423 |
424 | self.verify_query(transaction_query, self.urls.re_transaction_status)
425 |
426 | try:
427 | return requests.get(
428 | self.urls.transaction_status,
429 | json=transaction_query,
430 | headers=self.default_headers(),
431 | verify=True,
432 | ).json()
433 |
434 | except (requests.ConnectTimeout, requests.ConnectionError):
435 | raise MpesaConnectionError
436 |
437 | @authenticated
438 | def create_direct_debit(self, transaction_query: Dict) -> Dict:
439 | """Create direct Debit method.
440 |
441 | Example;
442 | >> import pypesa
443 | >> mpesa = pypesa()
444 | >> transaction_query = {
445 | "input_AgreedTC": "1",
446 | "input_Country": "TZN",
447 | "input_CustomerMSISDN": "000000000001",
448 | "input_EndRangeOfDays": "22",
449 | "input_ExpiryDate": "20211126",
450 | "input_FirstPaymentDate": "20160324",
451 | "input_Frequency": "06",
452 | "input_ServiceProviderCode": "000000",
453 | "input_StartRangeOfDays": "01",
454 | "input_ThirdPartyConversationID": "5334a912jbsj1j2kk1",
455 | "input_ThirdPartyReference": "3333",
456 | }
457 | >> mpesa.create_direct_debit(transaction_query)
458 | """
459 |
460 | self.verify_query(transaction_query, self.urls.re_create_direct_debit)
461 |
462 | try:
463 | return requests.post(
464 | self.urls.direct_debit,
465 | json=transaction_query,
466 | headers=self.default_headers(),
467 | verify=True,
468 | ).json()
469 |
470 | except (requests.ConnectTimeout, requests.ConnectionError):
471 | raise MpesaConnectionError
472 |
473 | @authenticated
474 | def direct_debit_payment(self, transaction_query: Dict) -> Dict:
475 | """Direct debit payment method.
476 |
477 | Example;
478 | >> import pypesa
479 | >> mpesa = pypesa()
480 | >> transaction_query = {
481 | "input_Amount": "10",
482 | "input_Country": "TZN",
483 | "input_Currency": "TZS",
484 | "input_CustomerMSISDN": "000000000001",
485 | "input_ServiceProviderCode": "000000",
486 | "input_ThirdPartyConversationID": "v2de053v4912jbasdj1j2kk",
487 | "input_ThirdPartyReference": "5db410b459bd433ca8e5"
488 | }
489 | >> mpesa.direct_debit_payment(transaction_query)
490 | """
491 | self.verify_query(transaction_query, self.urls.re_direct_debit_payment)
492 |
493 | try:
494 | return requests.post(
495 | self.urls.direct_debit_payment,
496 | json=transaction_query,
497 | headers=self.default_headers(),
498 | verify=True,
499 | ).json()
500 | except (requests.ConnectTimeout, requests.ConnectionError):
501 | raise MpesaConnectionError
502 |
503 | #sys.modules[__name__] = Mpesa
--------------------------------------------------------------------------------
/pypesa/mpesa_exceptions.py:
--------------------------------------------------------------------------------
1 | key_format = {"api_key": "xxxxxxxxx", "public_key": "xxxxxxx"}
2 |
3 |
4 | class AuthenticationError(Exception):
5 | """Exception class that throws exception for invalid keys.
6 |
7 | Please try entering again carefully.
8 | """
9 |
10 | error_message = f"""
11 | Could not verify your authentication keys
12 |
13 | keys.json should be formatted as shown below
14 |
15 | {key_format}
16 | """
17 |
18 | def __init__(self, error_message=error_message):
19 | super().__init__(error_message)
20 |
21 |
22 | class LoadingKeyError(Exception):
23 | """Exception thrown while loading authentication keys.
24 |
25 | """
26 |
27 | error_message = f"""
28 |
29 | Exception thrown while loading authentication keys from file
30 |
31 | Please make sure you format correctly in json format as shown below
32 |
33 | {key_format}
34 | """
35 |
36 | def __init__(self, error_message=error_message):
37 | super().__init__(error_message)
38 |
39 |
40 | class MpesaConnectionError(Exception):
41 | """This is exception will be thrown where there is no or slow internet connection
42 | """
43 |
44 | error_message = f"""
45 |
46 | Transaction couldn\'t be processed due to connection timeout
47 |
48 | Please make sure you have a stable internet Connection
49 | """
50 |
51 | def __init__(self, error_message=error_message):
52 | super().__init__(error_message)
53 |
--------------------------------------------------------------------------------
/pypesa/service_urls.py:
--------------------------------------------------------------------------------
1 | class Required:
2 | """A class holding dictionaries of required fields
3 | for variety of transaction supported by pypesa.
4 | """
5 | re_customer_to_bussiness = {
6 | "input_Amount",
7 | "input_Country",
8 | "input_Currency",
9 | "input_CustomerMSISDN",
10 | "input_ServiceProviderCode",
11 | "input_ThirdPartyConversationID",
12 | "input_TransactionReference",
13 | "input_PurchasedItemsDesc",
14 | }
15 |
16 | re_bussiness_to_customer = {
17 | "input_Amount",
18 | "input_Country",
19 | "input_Currency",
20 | "input_CustomerMSISDN",
21 | "input_ServiceProviderCode",
22 | "input_ThirdPartyConversationID",
23 | "input_TransactionReference",
24 | "input_PaymentItemsDesc",
25 | }
26 |
27 | re_bussiness_to_bussiness = {
28 | "input_Amount",
29 | "input_Country",
30 | "input_Currency",
31 | "input_PrimaryPartyCode",
32 | "input_ReceiverPartyCode",
33 | "input_ThirdPartyConversationID",
34 | "input_TransactionReference",
35 | "input_PurchasedItemsDesc",
36 | }
37 |
38 | re_payment_reversal = {
39 | "input_Country",
40 | "input_ReversalAmount",
41 | "input_ServiceProviderCode",
42 | "input_ThirdPartyConversationID",
43 | "input_TransactionID",
44 | }
45 |
46 | re_transaction_status = {
47 | "input_Country",
48 | "input_QueryReference",
49 | "input_ServiceProviderCode",
50 | "input_ThirdPartyConversationID",
51 | }
52 |
53 | re_create_direct_debit = {
54 | "input_AgreedTC",
55 | "input_Country",
56 | "input_CustomerMSISDN",
57 | "input_EndRangeOfDays",
58 | "input_ExpiryDate",
59 | "input_FirstPaymentDate",
60 | "input_Frequency",
61 | "input_ServiceProviderCode",
62 | "input_StartRangeOfDays",
63 | "input_ThirdPartyConversationID",
64 | "input_ThirdPartyReference",
65 | }
66 |
67 | re_direct_debit_payment = {
68 | "input_Amount",
69 | "input_Country",
70 | "input_Currency",
71 | "input_CustomerMSISDN",
72 | "input_ServiceProviderCode",
73 | "input_ThirdPartyConversationID",
74 | "input_ThirdPartyReference",
75 | }
76 |
77 |
78 | class Sandbox(Required):
79 | """Service URL to be used during sandbox Development.
80 | """
81 |
82 | def __init__(self) -> None:
83 | self.base_url = "https://openapi.m-pesa.com:443/sandbox/ipg/v2/vodacomTZN"
84 |
85 | self.session_id = f"{self.base_url}/getSession/"
86 | self.single_stage_c2b = f"{self.base_url}/c2bPayment/singleStage/"
87 | self.single_stage_b2c = f"{self.base_url}/b2cPayment/"
88 | self.single_stage_b2b = f"{self.base_url}/b2bPayment/"
89 |
90 | self.payment_reversal = f"{self.base_url}/reversal/"
91 | self.transaction_status = f"{self.base_url}/queryTransactionStatus/"
92 |
93 | self.direct_debit = f"{self.base_url}/directDebitCreation/"
94 | self.direct_debit_payment = f"{self.base_url}/directDebitPayment/"
95 |
96 | def __str__(self) -> str:
97 | return ''
98 |
99 | def __repr__(self) -> str:
100 | return ''
101 |
102 |
103 | class Production(Required):
104 | """Service URL to be used for Production Development
105 | """
106 |
107 | def __init__(self) -> None:
108 | self.base_url = "https://openapi.m-pesa.com:443/openapi/ipg/v2/vodacomTZN"
109 |
110 | self.session_id = f"{self.base_url}/getSession/"
111 | self.single_stage_c2b = f"{self.base_url}/c2bPayment/singleStage/"
112 | self.single_stage_b2c = f"{self.base_url}/b2cPayment/"
113 | self.single_stage_b2b = f"{self.base_url}/b2bPayment/"
114 |
115 | self.payment_reversal = f"{self.base_url}/reversal/"
116 | self.transaction_status = f"{self.base_url}/queryTransactionStatus/"
117 |
118 | self.direct_debit = f"{self.base_url}/directDebitCreation/"
119 | self.direct_debit_payment = f"{self.base_url}/directDebitPayment/"
120 |
121 | def __str__(self) -> str:
122 | return ''
123 |
124 | def __repr__(self) -> str:
125 | return ''
126 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | appdirs==1.4.3
2 | bleach==3.3.0
3 | CacheControl==0.12.6
4 | certifi==2019.11.28
5 | cffi==1.14.5
6 | chardet==3.0.4
7 | colorama==0.4.3
8 | contextlib2==0.6.0
9 | cryptography==3.4.4
10 | distlib==0.3.0
11 | distro==1.4.0
12 | docutils==0.16
13 | html5lib==1.0.1
14 | idna==2.8
15 | ipaddr==2.2.0
16 | jeepney==0.6.0
17 | keyring==22.0.1
18 | lockfile==0.12.2
19 | msgpack==0.6.2
20 | packaging==20.3
21 | pep517==0.8.2
22 | pkginfo==1.7.0
23 | progress==1.5
24 | pycparser==2.20
25 | pycrypto==2.6.1
26 | Pygments==2.7.4
27 | pyparsing==2.4.6
28 | pytoml==0.1.21
29 | readme-renderer==28.0
30 | requests==2.22.0
31 | requests-toolbelt==0.9.1
32 | retrying==1.3.3
33 | rfc3986==1.4.0
34 | SecretStorage==3.3.1
35 | six==1.14.0
36 | tqdm==4.56.2
37 | twine==3.3.0
38 | urllib3==1.26.5
39 | webencodings==0.5.1
40 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | from setuptools import setup
3 |
4 | # read the contents of your description file
5 |
6 | this_directory = path.abspath(path.dirname(__file__))
7 | with open(path.join(this_directory, 'description.md'), encoding='utf-8') as f:
8 | long_description = f.read()
9 |
10 |
11 | setup(
12 | name="python-pesa",
13 | version="0.6",
14 | description='Python package for Vodacom Mpesa API Integration',
15 | long_description=long_description,
16 | long_description_content_type='text/markdown',
17 | url='https://github.com/Kalebu/pypesa',
18 | download_url="https://github.com/Kalebu/pypesa/archive/0.4.tar.gz",
19 | author="Jordan Kalebu",
20 | author_email="isaackeinstein@gmail.com",
21 | license="MIT",
22 | packages=["pypesa"],
23 | keywords=[
24 | "pypesa",
25 | "python-pesa",
26 | "mpesa python",
27 | "vodacom python",
28 | "pypesa package",
29 | "pypesa python package",
30 | "vodacom python package",
31 | "pypesa github",
32 | "python pypesa",
33 | ],
34 |
35 | install_requires=[
36 | 'requests',
37 | 'pycryptodome'
38 | ],
39 |
40 | include_package_data=True,
41 | python_requires=">=3.6",
42 | classifiers=[
43 | "Development Status :: 3 - Alpha",
44 | "Intended Audience :: Developers",
45 | "Topic :: Software Development :: Build Tools",
46 | "License :: OSI Approved :: MIT License",
47 | "Programming Language :: Python :: 3.6",
48 | "Programming Language :: Python :: 3.7",
49 | "Programming Language :: Python :: 3.8",
50 | "Programming Language :: Python :: 3.9",
51 | ],
52 | )
53 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurotech-HQ/pypesa/3b787157b12ff1f176724e3d55fc08d59412a9fa/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_pypesa.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | import unittest
3 | from pypesa import Mpesa
4 |
5 | class MpesaTest(unittest.TestCase):
6 | def setUp(self):
7 | self.mpesa = Mpesa()
8 | self.expected_output_keys = {
9 | 'output_ResponseCode',
10 | 'output_ResponseDesc',
11 | 'output_TransactionID',
12 | 'output_ConversationID',
13 | 'output_ThirdPartyConversationID'
14 | }
15 | self.c2b_transaction_query = {
16 | "input_Amount": "10",
17 | "input_Country": "TZN",
18 | "input_Currency": "TZS",
19 | "input_CustomerMSISDN": "000000000001",
20 | "input_ServiceProviderCode": "000000",
21 | "input_ThirdPartyConversationID":self.random_id,
22 | "input_TransactionReference": 'T23434ZE5',
23 | "input_PurchasedItemsDesc": "Python Book"
24 | }
25 |
26 | self.b2c_transaction_query = {
27 | "input_Amount": "10",
28 | "input_Country": "TZN",
29 | "input_Currency": "TZS",
30 | "input_CustomerMSISDN": "000000000001",
31 | "input_ServiceProviderCode": "000000",
32 | "input_ThirdPartyConversationID": self.random_id,
33 | "input_TransactionReference": "T12344C",
34 | "input_PaymentItemsDesc": "Salary payment"
35 | }
36 |
37 | self.b2b_transaction_query = {
38 | "input_Amount": "10",
39 | "input_Country": "TZN",
40 | "input_Currency": "TZS",
41 | "input_PrimaryPartyCode": "000000",
42 | "input_ReceiverPartyCode": "000001",
43 | "input_ThirdPartyConversationID": self.random_id,
44 | "input_TransactionReference": "T12344C",
45 | "input_PurchasedItemsDesc": "Apartment"
46 | }
47 |
48 | self.payment_reversal_query = {
49 | "input_Country": "TZN",
50 | "input_ReversalAmount": "25",
51 | "input_ServiceProviderCode": "000000",
52 | "input_ThirdPartyConversationID": self.random_id,
53 | "input_TransactionID": "0000000000001"
54 | }
55 |
56 | self.transaction_status_query = {
57 | "input_Country": "TZN",
58 | "input_QueryReference":"000000000000000000001" ,
59 | "input_ServiceProviderCode": "000000",
60 | "input_ThirdPartyConversationID": self.random_id
61 | }
62 |
63 | @property
64 | def random_id(self):
65 | unique_id = str(uuid.uuid4())
66 | unique_id = unique_id.replace('-', '')
67 | return unique_id
68 |
69 | def test_customer_to_bussiness(self):
70 | print('Testing customer_to_bussiness Feature')
71 | response = self.mpesa.customer_to_bussiness(self.c2b_transaction_query)
72 | response_keys = set(response.json().keys())
73 | self.assertEqual(response_keys, self.expected_output_keys)
74 |
75 | def test_bussiness_to_customer(self):
76 | print('Testing bussiness_to_customer Feature')
77 | response = self.mpesa.bussiness_to_customer(self.b2c_transaction_query)
78 | response_keys = set(response.json().keys())
79 | self.assertEqual(response_keys, self.expected_output_keys)
80 | print('Finished testing bussiness_to_customer Feature')
81 |
82 | def test_bussiness_to_bussiness(self):
83 | print('Testing bussiness_to_bussiness Feature')
84 | response = self.mpesa.bussiness_to_bussiness(self.b2b_transaction_query)
85 | response_keys = set(response.json().keys())
86 | self.assertEqual(response_keys, self.expected_output_keys)
87 |
88 | def test_payment_reversal(self):
89 | print('Testing payment Reversal Feature')
90 | response = self.mpesa.payment_reversal(self.payment_reversal_query)
91 | response_keys = set(response.json().keys())
92 | self.assertEqual(response_keys, self.expected_output_keys)
93 | print('Finished testing Payment reversal')
94 |
95 | def test_query_transaction_status(self):
96 | print('Testing query transaction status Feature')
97 | response = self.mpesa.query_transaction_status(self.transaction_status_query)
98 | response_keys = set(response.json().keys())
99 | self.assertEqual(response_keys, self.expected_output_keys)
100 | print('Finished Testing transaction status Feature')
101 |
102 |
103 | if __name__ == "__main__":
104 | unittest.main()
--------------------------------------------------------------------------------