Skip to content

Commit fb17d98

Browse files
authored
Merge branch 'main' into production
2 parents 89474bc + 4c23b93 commit fb17d98

File tree

4 files changed

+140
-50
lines changed

4 files changed

+140
-50
lines changed

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ $ pypesa ~ python setup.py install
5757
```
5858

5959
- Using pip
60-
6160
```
6261
6362
$~ pip install python-pesa
@@ -83,8 +82,8 @@ To begin using the package is pretty straight forward
8382
### Example of Usage (Customer to Bussiness Transaction)
8483

8584
```python
86-
>>>from pypesa import Mpesa
87-
>>>mpesa = Mpesa()
85+
>>>import pypesa
86+
>>>mpesa = pypesa()
8887
>>>transaction_query = {"input_Amount": "10",
8988
"input_Country": "TZN",
9089
"input_Currency": "TZS",
@@ -119,9 +118,15 @@ environmnent you can specify it while creating an instance as shown below
119118
>>>mpesa = Mpesa(environmnent="production")
120119
```
121120

121+
## To do list
122+
123+
- Adding a well structured documentation
124+
- Adding a detailed test case to the implementation
125+
- Fixing rising bugs
126+
122127
## Contributing
123128

124-
Wanna contribute ? then please [contributing.md](https://github.com/Kalebu/pypesa/blob/main/Contributing.md) to see how
129+
Wanna contribute to Pypesa ? then please [contributing.md](https://github.com/Kalebu/pypesa/blob/main/Contributing.md) to see how
125130

126131

127132
## Give it a star
@@ -134,7 +139,7 @@ You can also keep in touch with on [Twitter](https://twitter.com/j_kalebu).
134139
## Bug bounty?
135140

136141
If you encounter **issue** with the usage of the package, feel free raise an **issue** so as
137-
we can fix it as soon as possible(ASAP) or just reach me directly through [email](isaackeinstein@gmail.com)
142+
we can fix it as soon as possible(ASAP) or just reach me directly through my email isaackeinstein(at)gmail.com
138143

139144

140145

pypesa/__init__.py

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
"""
2+
Python Package to Easy the Integration with Vodacom Public API
3+
"""
4+
15
import os
26
import json
7+
import sys
38
import base64
49
import socket
510
import requests
@@ -24,7 +29,7 @@ class Mpesa(object):
2429
def __init__(
2530
self,
2631
auth_path: Optional[str] = "keys.json",
27-
environment: Optional[str] = "testing",
32+
environment: Optional[str] = "sandbox",
2833
):
2934
"""
3035
Mpesa API client for Python
@@ -39,14 +44,24 @@ def __init__(
3944

4045
@property
4146
def authenticate(self) -> bool:
42-
""""""
47+
"""
48+
Property to check If the user is has has included auth keys
49+
either manually or through auth json file(keys.json)
50+
51+
>> import pypesa
52+
>> mpesa = pypesa()
53+
>> mpesa.authenticate
54+
55+
Return True if the environment has auth keys
56+
False if auth are not included
57+
"""
4358

4459
if self.auth_keys.get("public_key") and self.auth_keys.get("api_key"):
4560
self._encrypted_api_key = self.__generate_encrypted_key()
4661
return True
4762

4863
elif os.path.isfile(self.auth_path):
49-
print("loading from file")
64+
# print("loading from file")
5065
self.auth_keys = self.load_keys(self.auth_path)
5166
if self.auth_keys:
5267
self._encrypted_api_key = self.__generate_encrypted_key()
@@ -67,7 +82,17 @@ def authorized_method(self, *args, **kwargs):
6782

6883
@staticmethod
6984
def load_keys(keys_filename: Union[str, Path]) -> dict:
70-
""""""
85+
"""
86+
Pypesa internal method to load the auth file
87+
88+
>> import pypesa
89+
>> mpesa = pypesa()
90+
>> pypesa.load_keys()
91+
92+
Return keys Dict if auth file is present
93+
94+
Raise FileNotFoundError if auth file is absent
95+
"""
7196
try:
7297

7398
with open(keys_filename, "r") as auth:
@@ -83,7 +108,11 @@ def load_keys(keys_filename: Union[str, Path]) -> dict:
83108
raise LoadingKeyError
84109

85110
def __generate_encrypted_key(self, session: Optional[bool] = False) -> str:
86-
""""""
111+
"""
112+
113+
Method use to Encrypt the api key and public key
114+
so as to get a secure RSA Encrypted key
115+
"""
87116
try:
88117
pub_key = self.auth_keys.get("public_key")
89118
raw_key = self.auth_keys.get("api_key")
@@ -95,7 +124,6 @@ def __generate_encrypted_key(self, session: Optional[bool] = False) -> str:
95124
rsa_public_key = RSA.importKey(public_key_string)
96125
raw_cipher = rsa_cipher.new(rsa_public_key)
97126
encrypted_key = raw_cipher.encrypt(raw_key.encode())
98-
99127
return base64.b64encode(encrypted_key).decode("utf-8")
100128

101129
except Exception as bug:
@@ -106,10 +134,16 @@ def __generate_encrypted_key(self, session: Optional[bool] = False) -> str:
106134

107135
@property
108136
def path_to_auth(self) -> str:
137+
"""
138+
Return Path to Authentication file
139+
"""
109140
return self.auth_path
110141

111142
@path_to_auth.setter
112143
def path_to_auth(self, auth_path: Union[str, Path]) -> str:
144+
"""
145+
Setting new path to the authentication file
146+
"""
113147
if isinstance(auth_path, str):
114148
self.auth_path = auth_path
115149
return self.auth_path
@@ -119,56 +153,102 @@ def path_to_auth(self, auth_path: Union[str, Path]) -> str:
119153

120154
@property
121155
def environment(self) -> Union[sandbox, production]:
156+
"""
157+
Return the Environment the Pypesa is running
158+
159+
whether its Sandbox | Production
160+
"""
122161
return self.urls
123162

124163
@environment.setter
125164
def environment(self, enviro: str) -> Union[sandbox, production]:
165+
"""
166+
Set new pypesa environment
167+
168+
Eg. changing env to production;
169+
170+
>> import pypesa
171+
>> mpesa = pypesa()
172+
>> mpesa.environment = "production"
173+
<Using Production Urls>
174+
"""
126175
if isinstance(enviro, str):
127-
if enviro in ["testing", "production"]:
128-
if enviro == "testing":
176+
if enviro in ["sandbox", "production"]:
177+
if enviro == "sandbox":
129178
self.urls = sandbox()
130179
else:
131180
self.urls = production()
132181
print(self.urls)
133182
return self.urls
134-
return ValueError("Environment must be either testing or production")
135-
return TypeError(f"environment must be of type string not {type(enviro)}")
183+
raise ValueError(
184+
"Environment must be either sandbox or production")
185+
raise TypeError(
186+
f"environment must be of type string not {type(enviro)}")
136187

137188
@property
138189
def api_key(self) -> str:
139-
return self.auth_keys["api_key"]
190+
'''
191+
Return current api key
192+
'''
193+
return self.auth_keys.get("api_key")
140194

141195
@api_key.setter
142196
def api_key(self, Api_key: str) -> str:
197+
'''
198+
Use this propery to explicit set a api_key
199+
200+
>> import pypesa
201+
>> wallet = pypesa()
202+
>> wallet.api_key = " Your api key" #here
203+
204+
'''
143205
if isinstance(Api_key, str):
144206
self.auth_keys["api_key"] = Api_key
145207
return self.auth_keys["api_key"]
146-
raise TypeError(f"API key must be a of type String not {type(Api_key)}")
208+
raise TypeError(
209+
f"API key must be a of type String not {type(Api_key)}")
147210

148211
@property
149212
def public_key(self) -> str:
150-
return self.auth_keys["public_key"]
213+
"""
214+
Return the current Public key
215+
"""
216+
return self.auth_keys.get("public_key")
151217

152218
@public_key.setter
153219
def public_key(self, pb_key: str) -> str:
220+
"""
221+
Set a new public key
222+
"""
154223
if isinstance(pb_key, str):
155224
self.auth_keys["public_key"] = pb_key
156225
return self.auth_keys["public_key"]
157226
raise TypeError(f"Public key must be a string not a {type(pb_key)}")
158227

159228
@property
160229
def origin_address(self) -> str:
230+
"""
231+
Return the current origin address
232+
"""
161233
return self._origin_ip
162234

163235
@origin_address.setter
164236
def origin_address(self, ip_address: str) -> str:
237+
"""
238+
Set a new origin address
239+
"""
165240
if isinstance(ip_address, str):
166241
self._origin_ip = ip_address
167242
return self._origin_ip
168-
raise TypeError(f"Address must be of type string not {type(ip_address)}")
243+
raise TypeError(
244+
f"Address must be of type string not {type(ip_address)}")
169245

170246
@authenticated
171247
def default_headers(self, auth_key: Optional[str] = "") -> dict:
248+
"""
249+
Generate Default header to be used during a Request
250+
251+
"""
172252
if not auth_key:
173253
auth_key = self.__generate_encrypted_key(session=True)
174254
return {
@@ -187,9 +267,9 @@ def get_session_id(self) -> str:
187267
session_id = response["output_SessionID"]
188268
response_code = response["output_ResponseCode"]
189269
description = response["output_ResponseDesc"]
190-
print(description, " ", response_code)
270+
# print(description, " ", response_code)
191271
if response_code == "INS-989":
192-
print("Session creation failed!!")
272+
# print("Session creation failed!!")
193273
raise AuthenticationError
194274
return session_id
195275
except Exception as bug:
@@ -216,7 +296,8 @@ def verify_query(transaction_query: dict, required_fields: set) -> bool:
216296
def customer_to_bussiness(self, transaction_query: dict) -> dict:
217297
""""""
218298

219-
self.verify_query(transaction_query, self.urls.re_customer_to_bussiness)
299+
self.verify_query(transaction_query,
300+
self.urls.re_customer_to_bussiness)
220301

221302
try:
222303
return requests.post(
@@ -233,7 +314,8 @@ def customer_to_bussiness(self, transaction_query: dict) -> dict:
233314
def bussiness_to_customer(self, transaction_query: dict) -> dict:
234315
""""""
235316

236-
self.verify_query(transaction_query, self.urls.re_bussiness_to_customer)
317+
self.verify_query(transaction_query,
318+
self.urls.re_bussiness_to_customer)
237319

238320
try:
239321

@@ -251,7 +333,8 @@ def bussiness_to_customer(self, transaction_query: dict) -> dict:
251333
def bussiness_to_bussiness(self, transaction_query: dict) -> dict:
252334
""""""
253335

254-
self.verify_query(transaction_query, self.urls.re_bussiness_to_bussiness)
336+
self.verify_query(transaction_query,
337+
self.urls.re_bussiness_to_bussiness)
255338

256339
try:
257340
return requests.post(
@@ -328,4 +411,7 @@ def direct_debit_payment(self, transaction_query: dict) -> dict:
328411
verify=True,
329412
)
330413
except (requests.ConnectTimeout, requests.ConnectionError):
331-
raise MpesaConnectionError
414+
raise MpesaConnectionError
415+
416+
417+
sys.modules[__name__] = Mpesa

pypesa/service_urls.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ class Required(object):
7373

7474

7575
class sandbox(Required):
76-
def __init__(self):
76+
"""
77+
Service URL to be used during sandbox Development
7778
78-
"""
79-
Service URL to be used during sandbox Development
79+
"""
8080

81-
"""
81+
def __init__(self):
8282
self.session_id = (
8383
"https://openapi.m-pesa.com/sandbox/ipg/v2/vodacomTZN/getSession/"
8484
)
@@ -98,14 +98,21 @@ def __init__(self):
9898

9999
self.direct_debit_payment = "https://openapi.m-pesa.com:443/sandbox/ipg/v2/vodacomTZN/directDebitPayment/"
100100

101+
def __str__(self) -> str:
102+
return '<Using Sandbox Urls>'
103+
104+
def __str__(self) -> str:
105+
return '<Using Sandbox Urls>'
106+
101107

102108
class production(Required):
103-
def __init__(self):
104-
"""
109+
"""
110+
111+
Service URL to be used for Production Development
105112
106-
Service URL to be used for Production Development
113+
"""
107114

108-
"""
115+
def __init__(self):
109116
self.session_id = (
110117
"https://openapi.m-pesa.com/openapi/ipg/v2/vodacomTZN/getSession/"
111118
)
@@ -120,7 +127,11 @@ def __init__(self):
120127
"https://openapi.m-pesa.com:443/openapi/ipg/v2/vodacomTZN/reversal/"
121128
)
122129
self.transaction_status = "https://openapi.m-pesa.com:443/openapi/ipg/v2/vodacomTZN/queryTransactionStatus/"
123-
124130
self.direct_debit = "https://openapi.m-pesa.com:443/openapi/ipg/v2/vodacomTZN/directDebitCreation/"
125-
126131
self.direct_debit_payment = "https://openapi.m-pesa.com:443/openapi/ipg/v2/vodacomTZN/directDebitPayment/"
132+
133+
def __str__(self) -> str:
134+
return '<Using Production Urls>'
135+
136+
def __repr__(self) -> str:
137+
return '<Using Production Urls>'

setup.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
1-
from distutils.core import setup
1+
2+
from setuptools import setup
23

34
setup(
45
name="python-pesa",
56
version="0.1",
67
description='Python package for Vodacom Mpesa API Integration',
78
url='https://github.com/Kalebu/pypesa',
8-
download_url="https://github.com/Kalebu/pypesa/archive/0.1.tar.gz",
99
author="Jordan Kalebu",
1010
author_email="isaackeinstein@gmail.com",
1111
license="MIT",
12-
packages=["pypesa"],
13-
keywords=[
14-
"pypesa",
15-
"python-pesa",
16-
"mpesa python",
17-
"vodacom python",
18-
"pypesa package",
19-
"pypesa python package",
20-
"vodacom python package",
21-
"pypesa github",
22-
"python pypesa",
23-
],
24-
12+
packages=['pypesa'],
2513
install_requires=[
2614
'requests',
2715
'pycryptodome'

0 commit comments

Comments
 (0)