├── .gitignore ├── LICENSE ├── README.md ├── RELEASE ├── examples ├── bank_list.py └── name_enquiry.py ├── kuda ├── __author__.py ├── __init__.py ├── __version__.py ├── api.py ├── error.py └── savings.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | main.py 2 | keys.json 3 | test.py 4 | __pycache__ 5 | .gitattributes 6 | build 7 | dist 8 | kuda_python.egg-info 9 | deploy.bat -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Manuel InfoSec 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 | # Kuda OpenAPI Python Library 2 | [![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://kuda-python.readthedocs.io/)[![Code Style](https://img.shields.io/badge/code_style-black-black)](https://black.readthedocs.io/en/stable/)[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | 4 | This is a lightweight library that works as a connector to [Kuda OpenAPI](https://kudabank.gitbook.io/kudabank/). 5 | 6 | - Customizable base URL. 7 | - Response metadata can be displayed. 8 | - Included examples. 9 | 10 | 11 | ## Upcoming 12 | - Savings endpoints, 13 | - Example cases, 14 | - HTTP proxy. 15 | 16 | 17 | ## Installation 18 | 19 | ``` 20 | pip install kuda-python 21 | ``` 22 | 23 | 24 | ## Documentation 25 | 26 | **Docs**:
27 | [https://kuda-python.readthedocs.io](https://kuda-python.readthedocs.io) (Coming Soon) 28 | 29 | **Article on Medium:**
30 | https://manuelinfosec.medium.com/python-integration-with-the-kuda-open-banking-api-kuda-python-112606ff989d 31 | 32 | 33 | ## Getting started 34 | 35 | > - Login to your [Kuda dashboard](https://developer.kuda.com) and generate your apiKey. 36 | > - Load your email and apiKey to the Kuda client (JSON or dotenv is recommended). 37 | > - JWT tokens are automatically generated. 38 | 39 | 40 | Usage examples: 41 | 42 | ```python 43 | from kuda import Kuda 44 | 45 | # email and apiKey are generated from your Kuda developer dashboard 46 | Kuda = Kuda(email='', apiKey='', show_request=True, sandbox=False, base_url="") 47 | 48 | # Get bank list 49 | print(Kuda.bank_list()) 50 | 51 | # Retrieve main account balance 52 | print(Kuda.get_main_account_balance()) 53 | 54 | # Perform name enquiry 55 | print(Kuda.name_enquiry(2005161838, "090267")) 56 | 57 | # Create virtual account 58 | params = { 59 | 'lastName': 'Manuel', 60 | 'firstName': 'Infosec', 61 | 'email': 'manuelinfosec@gmail.cm', 62 | 'phoneNumber': "09131103073", 63 | 'trackingRef': Kuda.get_ref(10) # you can generate your trackingReference some other way you choose. 64 | } 65 | 66 | print(Kuda.create_virtual_account(**params)) 67 | ``` 68 | 69 | 70 | ### Kuda OpenAPI Test 71 | 72 | [Kuda Test](https://kuda-openapi-uat.kudabank.com/v2) is available too. 73 | 74 | To use Kuda OpenAPI on Test, switch to test on your dashboard and copy apiKey (Live apiKey still remains valid): 75 | 76 | ```python 77 | from kuda import Kuda 78 | 79 | # email and apiKey are generated from your Kuda developer dashboard 80 | Kuda = Kuda(email='', apiKey='', show_request=True, sandbox=False, base_url="") 81 | 82 | 83 | # Get bank list 84 | print(Kuda.bank_list()) 85 | 86 | # Retrieve main account balance 87 | print(Kuda.get_main_account_balance()) 88 | 89 | # Perform name enquiry 90 | print(Kuda.name_enquiry(2005161838, "999129")) 91 | 92 | 93 | # Create virtual account 94 | params = { 95 | 'lastName': 'Manuel', 96 | 'firstName': 'Infosec', 97 | 'email': 'manuelinfosec@gmail.com', 98 | 'phoneNumber': "+2349131103073", 99 | 'trackingRef': Kuda.get_ref(10) # you can generate your trackingReference some other way you choose. 100 | } 101 | 102 | 103 | print(Kuda.create_virtual_account(**params)) 104 | ``` 105 | 106 | 107 | ### Base URL 108 | 109 | If not provided, `base_url` defaults to `https://kuda-openapi.kuda.com/v2` if `sandbox=False`, or `https://kuda-openapi-uat.kudabank.com/v2` if `sandbox=True`.
110 | 111 | Changes to the Kuda OpenAPI URL will reflect in future updates. In the case of late update, it is recommended to pass in the `base_url` parameter. 112 | 113 | 114 | ### Request and Response Metadata 115 | 116 | This library provies requests and header data for debugging purposes. 117 | 118 | You can display them by initializing the client with `show_request=True` or `show_header=True` respectively: 119 | 120 | ```python 121 | from kuda import Kuda 122 | 123 | # email and apiKey are generated from your Kuda developer dashboard 124 | Kuda = Kuda(email='', apiKey='', show_request=True, show_header=True, sandbox=False, base_url="") 125 | 126 | # Get bank list 127 | print(Kuda.bank_list()) 128 | ``` 129 | 130 | returns: 131 | 132 | ```python 133 | {'headers': {'User-Agent': 'python-requests/2.27.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Authorization': 'Bearer ', 'Content-Length': '77', 'Content-Type': 'application/json'}, 'body': {'data': '', 'password': ''}} 134 | ``` 135 | 136 | You can also display full response metadata to help in debugging: 137 | 138 | ```python 139 | Kuda = Kuda(email='', apiKey='', show_request=True, sandbox=False, base_url="") 140 | 141 | # Get bank list 142 | print(Kuda.bank_list()) 143 | ``` 144 | 145 | returns: 146 | 147 | ```python 148 | { 149 | "data": "{'serviceType': 'BANK_LIST', 'requestRef': '9903712', 'data': {}}" 150 | } 151 | # 152 | ``` 153 | > Refer to documentation for respective data types for each fields in the payload 154 | 155 | If `ClientError` is received, it'll display full header meta information. 156 | 157 | 158 | ### Error 159 | 160 | There are 2 types of errors returned from the library: 161 | 162 | - `kuda.error.ClientError` 163 | 164 |     - This is thrown when server returns `4XX`, it's an issue from client side. 165 | 166 |     - It has 3 properties: 167 | 168 |         - `status_code` - HTTP status code, e.g. `-404` 169 | 170 |         - `error_message` - Server's error message, e.g. `Something went wrong` 171 | 172 |         - `header` - Full response header. 173 | 174 | - `kuda.error.ServerError` 175 | 176 |     - This is thrown when server returns `5XX`, it's an issue from server side. 177 | 178 | 179 | ## Contributing 180 | 181 | Contributions are welcome.
182 | 183 | If you've found a bug within this project, please open an issue to discuss what you would like to change.
184 | 185 | If you have any questions, feature requests, or notice any errors with the OpenAPI, please reach out to [Kuda](mailto:openapi@kuda.com), creating a //Skype link and someone will respond. 186 | 187 | ## Authors 188 | [Manuel](https://twitter.com/manuelinfosec) 189 | 190 | ## Acknowledgments 191 | [Kuda Bank Team](https://kudabank.com/) 192 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | v1.0.0 (Major): 2 | - First Release on PyPI 3 | 4 | v1.0.1 (Patch): 5 | - Fixed issue with displaying request metadata. 6 | 7 | v1.0.2 (Patch): 8 | - Fixed issue with creating virtual accounts. 9 | -------------------------------------------------------------------------------- /examples/bank_list.py: -------------------------------------------------------------------------------- 1 | from kuda import Kuda 2 | from kuda.error import ClientError 3 | 4 | email = "" 5 | apiKey = "" 6 | 7 | Kuda = Kuda(email, apiKey, 8 | sandbox=False, 9 | show_request=False, 10 | show_header=False) 11 | 12 | banks = Kuda.bank_list()['data']['Data']['banks'] 13 | banks = json.dumps(banks, indent=4) 14 | 15 | print(banks) -------------------------------------------------------------------------------- /examples/name_enquiry.py: -------------------------------------------------------------------------------- 1 | from kuda import Kuda 2 | from kuda.error import ClientError 3 | 4 | email = "" 5 | apiKey = "" 6 | 7 | Kuda = Kuda(email, apiKey, 8 | sandbox=False, 9 | show_request=False, 10 | show_header=False) 11 | 12 | beneficiaryAccountNumber = "" 13 | beneficiaryBankCode = "" 14 | 15 | name_enquiry = Kuda.name_enquiry(beneficiaryAccountNumber, 16 | beneficiaryBankCode) 17 | 18 | 19 | 20 | name = name_enquiry['data']['Data']['BeneficiaryName'] 21 | -------------------------------------------------------------------------------- /kuda/__author__.py: -------------------------------------------------------------------------------- 1 | __author__ = "Manuel, For Kuda MicroFinance Bank Inc." -------------------------------------------------------------------------------- /kuda/__init__.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import requests 4 | import json 5 | from json import JSONDecodeError 6 | from kuda.error import ClientError, ServerError 7 | 8 | class Kuda(): 9 | from kuda.api import bank_list 10 | from kuda.api import name_enquiry 11 | from kuda.api import create_virtual_account 12 | from kuda.api import get_virtual_single_account 13 | from kuda.api import single_fund_transfer 14 | from kuda.api import virtual_fund_transfer 15 | from kuda.api import txn_status_query 16 | from kuda.api import get_main_account_balance 17 | from kuda.api import get_virtual_account_balance 18 | from kuda.api import txn_logs 19 | from kuda.api import main_txn_logs 20 | from kuda.api import filter_main_txn_logs 21 | from kuda.api import virtual_txn_logs 22 | from kuda.api import filter_virtual_txn_logs 23 | from kuda.api import fund_virtual_account 24 | from kuda.api import withdraw_virtual_account 25 | 26 | def __init__(self, 27 | email, 28 | apiKey, 29 | sandbox=False, 30 | show_request=False, 31 | show_header=False, 32 | base_url = "",**kwargs): 33 | 34 | if sandbox and base_url == "": 35 | self.url = "https://kuda-openapi-uat.kudabank.com/v2".replace("\u200b", "") 36 | elif not sandbox and base_url == "": 37 | self.url = "​https://kuda-openapi.kuda.com/v2".replace("\u200b", "") 38 | else: 39 | self.url = base_url.replace("\u200b", "") 40 | 41 | if show_request: 42 | self.show_request = True 43 | else: 44 | self.show_request = False 45 | 46 | if show_header: 47 | self.show_header = True 48 | else: 49 | self.show_header = False 50 | 51 | self.token = self.getToken(email, apiKey) 52 | 53 | def getToken(self, email, apiKey): 54 | token = requests.post(f"{self.url}/Account/GetToken", 55 | json={"email" : email, "apiKey":apiKey}) 56 | self._handle_exception(token) 57 | return token.text 58 | 59 | def get_ref(self, length: int) -> int: 60 | """Generate a unique reference per request (requestRef) 61 | 62 | Args: 63 | length (int): length of reference 64 | 65 | Returns: 66 | int: random requestRef 67 | """ 68 | digits = string.digits 69 | result_int = ''.join(random.choice(digits) for i in range(length)) 70 | return int(result_int) 71 | 72 | def koboToNGN(self, kobo): 73 | return kobo/100 74 | 75 | def ngntoKobo(self, naira): 76 | return naira*100 77 | 78 | def sign_request(self, url_path: str, payload={}): 79 | 80 | request = { 81 | "header": { 82 | "Authorization": f"Bearer {self.token}", 83 | }, 84 | "body" : { 85 | "data": { 86 | "serviceType": url_path, 87 | "requestRef" : str(self.get_ref(7)), 88 | "data" : payload 89 | } 90 | } 91 | } 92 | 93 | request['body']['data'] = str(request['body']['data']) 94 | if self.show_request: 95 | print(json.dumps(request['body'], indent=4)) 96 | return self.send_request(request) 97 | 98 | def send_request(self, request): 99 | # Make request to Kuda API 100 | resp = requests.post(self.url, headers=request['header'], json=request['body']) 101 | 102 | # Handle exceptions in response 103 | self._handle_exception(resp) 104 | 105 | try: 106 | data = resp.json() 107 | except ValueError: 108 | data = resp.text 109 | 110 | result = {} 111 | 112 | # Display header for debugging purpose 113 | if self.show_header: 114 | result['headers'] = resp.request.headers 115 | result['body'] = data 116 | return result 117 | 118 | result['data'] = json.loads(data['data']) 119 | return result 120 | 121 | def _handle_exception(self, response): 122 | # http status code 123 | code = response.status_code 124 | if code < 400: 125 | return 126 | if 400 <= code <= 500: 127 | try: 128 | err = json.loads(response.text) 129 | except JSONDecodeError: 130 | raise ClientError(code, response.text, response.headers) 131 | raise ClientError(code, err, f"Response Headers: {response.headers}") 132 | raise ServerError(code, response.text) -------------------------------------------------------------------------------- /kuda/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0" -------------------------------------------------------------------------------- /kuda/api.py: -------------------------------------------------------------------------------- 1 | def bank_list(self): 2 | """The Bank list covered here are for all Nigerian Financial Institutions supported by NIBSS 3 | https://kudabank.gitbook.io/kudabank/single-fund-transfer/bank-list 4 | 5 | Returns: 6 | dict 7 | """ 8 | serviceType = "BANK_LIST" 9 | return self.sign_request("BANK_LIST") 10 | 11 | def name_enquiry(self, 12 | account_number: int, 13 | bank_code: str, 14 | SenderTrackingReference: str = "") -> dict: 15 | """Validate and retrieve information on a NUBAN account number 16 | https://kudabank.gitbook.io/kudabank/single-fund-transfer/name-enquiry 17 | 18 | Args: 19 | account_number (int): Destination Account Number 20 | bank_code (int): Destination Bank Code. Defaults to Kuda bank code 21 | 22 | Returns: 23 | dict 24 | """ 25 | serviceType = "NAME_ENQUIRY" 26 | 27 | if not SenderTrackingReference: 28 | SenderTrackingReference = "" 29 | isRequestFromVirtualAccount = "false" 30 | else: 31 | isRequestFromVirtualAccount = "true" 32 | 33 | data = { 34 | "beneficiaryAccountNumber": account_number, 35 | "beneficiaryBankCode": bank_code, 36 | "SenderTrackingReference": SenderTrackingReference, 37 | "isRequestFromVirtualAccount" : isRequestFromVirtualAccount 38 | } 39 | 40 | return self.sign_request(serviceType, data) 41 | 42 | def create_virtual_account(self, 43 | lastName: str, 44 | firstName: str, 45 | email: str, 46 | phoneNumber: str, 47 | trackingRef: int) -> dict: 48 | 49 | """Create a Virtual Account 50 | https://kudabank.gitbook.io/kudabank/virtual-account-creation#create-a-virtual-account 51 | 52 | Args: 53 | lastName (str): User's last name 54 | firstName (str): User's first name 55 | emailAddress (str): User's email address 56 | phoneNumber (str): User's phone number 57 | trackingRef (int): Unique identifier for the account 58 | 59 | Returns: 60 | dict 61 | """ 62 | serviceType = "ADMIN_CREATE_VIRTUAL_ACCOUNT" 63 | 64 | data = { 65 | "email": email, 66 | "phoneNumber": phoneNumber, 67 | "lastName": lastName, 68 | "firstName": firstName, 69 | "trackingReference": trackingRef 70 | } 71 | 72 | return self.sign_request(serviceType, data) 73 | 74 | def get_virtual_single_account(self, 75 | trackingRef: int) -> dict: 76 | """Retrieve Virtual Account information 77 | https://kudabank.gitbook.io/kudabank/virtual-account-creation/retrieve-virtual-account 78 | 79 | Args: 80 | trackingRef (int): Unique Identifier for virtual Account 81 | 82 | Returns: 83 | dict 84 | """ 85 | serviceType = "ADMIN_RETRIEVE_SINGLE_VIRTUAL_ACCOUNT" 86 | 87 | data = { 88 | "trackingReference": trackingRef 89 | } 90 | 91 | return self.sign_request(serviceType, data) 92 | 93 | 94 | def single_fund_transfer(self, 95 | trackingRef: int, 96 | beneficiaryAccountNumber: int, 97 | beneficiaryBankCode: int, 98 | beneficiaryName: str, 99 | amount: int, 100 | SenderName: str, 101 | narration: str, 102 | accountID: int, 103 | NameEnquirySessionID: str = None) -> dict: 104 | """Bank transfer from a Kuda Account to any bank account 105 | https://kudabank.gitbook.io/kudabank/single-fund-transfer/send-money-from-a-kuda-account 106 | 107 | Args: 108 | trackingRef (int): Request Reference ID 109 | beneficiaryAccountNumber (int): Destination bank account number 110 | beneficiaryBankCode (int): Destination bank code 111 | beneficiaryName (str): Name of the recipient 112 | amount (int): Amount to be transferred (in kobo) 113 | SenderName (str): Name of the person sending money 114 | narration (str): User defined reason for the transaction. 115 | accountID (int): Main Account number 116 | NameEnquirySessionID (str, optional): Session ID generated from the nameEnquiry request. Defaults to None. 117 | 118 | Returns: 119 | dict 120 | """ 121 | 122 | serviceType = "SINGLE_FUND_TRANSFER" 123 | 124 | # TODO: Check Request Body 125 | if not NameEnquirySessionID: 126 | accountInfo = self.name_enquiry(beneficiaryAccountNumber, beneficiaryBankCode) 127 | NameEnquirySessionID = accountInfo["data"]["Data"]["SessionID"] 128 | accountName = accountInfo["data"]["Data"]["BeneficiaryName"] 129 | else: 130 | accountInfo = self.name_enquiry(beneficiaryAccountNumber, beneficiaryBankCode) 131 | NameEnquirySessionID = accountInfo["data"]["Data"]["SessionID"] 132 | accountName = accountInfo["data"]["Data"]["BeneficiaryName"] 133 | 134 | data = { 135 | "ClientAccountNumber": accountID, 136 | "beneficiarybankCode": beneficiaryBankCode, 137 | "beneficiaryAccount": beneficiaryAccountNumber, 138 | "beneficiaryName": accountName, 139 | "amount": amount, 140 | "narration": narration, 141 | "nameEnquirySessionID": NameEnquirySessionID, 142 | "trackingReference": trackingRef, 143 | "senderName": SenderName, 144 | } 145 | 146 | return self.sign_request(serviceType, data) 147 | 148 | def virtual_fund_transfer(self, 149 | trackingRef: int, 150 | beneficiaryAccount: int, 151 | amount: int, 152 | narration: str, 153 | beneficiaryBankCode: int, 154 | beneficiaryName: str, 155 | senderName: str, 156 | nameEnquiryId: str = None) -> dict: 157 | 158 | """Bank transfer from a KUDA Virtual Account to any bank account 159 | https://kudabank.gitbook.io/kudabank/single-fund-transfer/virtual-account-fund-transfer 160 | 161 | Args: 162 | trackingRef (int): Unique Identifier for virtual Account 163 | beneficiaryAccount (int): Destination bank account number 164 | beneficiaryBankCode (int): Destination bank code 165 | beneficiaryName (str): Name of the recipient 166 | amount (int): Amount to be transferred (in kobo) 167 | narration (str): User defined reason for the transaction. 168 | SenderName (str): Name of the person sending money 169 | nameEnquiryId (str, optional): Session ID generated from the nameEnquiry request. Defaults to None. 170 | 171 | Returns: 172 | dict 173 | """ 174 | serviceType = "VIRTUAL_ACCOUNT_FUND_TRANSFER" 175 | 176 | # TODO: Check Request Body 177 | if not nameEnquiryId: 178 | accountInfo = self.name_enquiry(beneficiaryAccount, beneficiaryBankCode, trackingRef) 179 | nameEnquiryId = accountInfo["data"]["Data"]["SessionID"] 180 | accountName = accountInfo["data"]["Data"]["BeneficiaryName"] 181 | else: 182 | accountInfo = self.name_enquiry(beneficiaryAccount, beneficiaryBankCode, trackingRef) 183 | nameEnquiryId = accountInfo["data"]["Data"]["SessionID"] 184 | accountName = accountInfo["data"]["Data"]["BeneficiaryName"] 185 | 186 | data = { 187 | "trackingReference": trackingRef, 188 | "beneficiarybankCode": beneficiaryBankCode, 189 | "beneficiaryAccount": beneficiaryAccount, 190 | "beneficiaryName": accountName, 191 | "amount": amount, 192 | "narration": narration, 193 | "nameEnquiryId": nameEnquiryId, 194 | "senderName": senderName, 195 | } 196 | 197 | return self.sign_request(serviceType, data) 198 | 199 | def txn_status_query(self, 200 | third_party: bool, 201 | txnRef: int) -> dict: 202 | 203 | """Retrieve information on a the status of a transaction 204 | https://kudabank.gitbook.io/kudabank/single-fund-transfer/transactions-query 205 | 206 | Args: 207 | txnRef (int): Unique identifier for the transfer 208 | third_party (bool, optional): Does it concern a third-party bank? Defaults to False 209 | 210 | Returns: 211 | dict 212 | """ 213 | 214 | serviceType = "TRANSACTION_STATUS_QUERY" 215 | if third_party: 216 | isThirdPartyBankTransfer = "true" 217 | else: 218 | isThirdPartyBankTransfer = "true" 219 | 220 | data = { 221 | "isThirdPartyBankTransfer": isThirdPartyBankTransfer, 222 | "transactionRequestReference": txnRef 223 | } 224 | return self.sign_request(serviceType, data) 225 | 226 | def get_main_account_balance(self) -> dict: 227 | 228 | """Check the balance on your KUDA main account 229 | https://kudabank.gitbook.io/kudabank/check-admin-account-balance 230 | 231 | Returns: 232 | dict 233 | """ 234 | serviceType = "ADMIN_RETRIEVE_MAIN_ACCOUNT_BALANCE" 235 | return self.sign_request(serviceType) 236 | 237 | def get_virtual_account_balance(self, 238 | trackingRef: int) -> dict: 239 | 240 | """Retrieve Virtual Account Balance 241 | https://kudabank.gitbook.io/kudabank/check-virtual-account-balance 242 | 243 | Args: 244 | trackingRef (int) 245 | 246 | Returns: 247 | dict 248 | """ 249 | serviceType = "RETRIEVE_VIRTUAL_ACCOUNT_BALANCE" 250 | 251 | data = { 252 | "trackingReference": trackingRef 253 | } 254 | 255 | return self.sign_request(serviceType, data) 256 | 257 | def txn_logs(self, 258 | RequestReference: int, 259 | ResponseReference: int, 260 | startDate: str, 261 | endDate: str, 262 | txnDate = "", 263 | HasTransactionDateRangeFilter: bool = False, 264 | FetchSuccessfulRecords: bool = True, 265 | pageSize: int = 10, 266 | pageNumber: int = 1) -> dict: 267 | 268 | """Get Transaction Logs 269 | 270 | Args: 271 | Refer to documentaion: https://kudabank.gitbook.io/kudabank/view-transaction-history/get-transaction-logs 272 | 273 | Returns: 274 | dict 275 | """ 276 | 277 | serviceType = "RETRIEVE_TRANSACTION_LOGS" 278 | 279 | if HasTransactionDateRangeFilter: 280 | HasTransactionDateRangeFilter = "true" 281 | else: 282 | startDate = txnDate 283 | endDate = txnDate 284 | HasTransactionDateRangeFilter = "false" 285 | 286 | if FetchSuccessfulRecords: 287 | FetchSuccessfulRecords = "true" 288 | else: 289 | FetchSuccessfulRecords = "false" 290 | 291 | data = { 292 | "RequestReference": RequestReference, 293 | "ResponseReference": ResponseReference, 294 | "TransactionDate": txnDate, 295 | "HasTransactionDateRangeFilter": HasTransactionDateRangeFilter, 296 | "StartDate": startDate, 297 | "EndDate": endDate, 298 | "PageSize": pageSize, 299 | "PageNumber": pageNumber, 300 | "FetchSuccessfulRecords": FetchSuccessfulRecords 301 | } 302 | return self.sign_request(serviceType, data) 303 | 304 | def main_txn_logs(self, 305 | pageSize: int = 10, pageNumber: int = 1) -> dict: 306 | """Retrieve a list of all transactions for the currently authenticated user. 307 | https://kudabank.gitbook.io/kudabank/view-transaction-history/kuda-account-transaction-history 308 | 309 | Args: 310 | pageSize (int) 311 | pageNumber (int) 312 | """ 313 | serviceType = "ADMIN_MAIN_ACCOUNT_TRANSACTIONS" 314 | 315 | data = { 316 | "pageSize" : pageSize, 317 | "pageNumber" : pageNumber 318 | } 319 | 320 | return self.sign_request(serviceType, data) 321 | 322 | def filter_main_txn_logs(self, 323 | startDate: str, 324 | endDate: str, 325 | pageSize: int = 10, 326 | pageNumber: int = 1) -> dict: 327 | """Retrieve a filtered list of all transactions for the currently authenticated user. 328 | https://kudabank.gitbook.io/kudabank/view-transaction-history/filtered-kuda-account-transaction-history 329 | 330 | Args: 331 | startDate (str): Ex: 2020-10-27T09:58:23.4740446Z 332 | endDate (str): Ex: 2020-12-27T09:58:23.4740446Z 333 | pageSize (int, optional) 334 | pageNumber (int, optional) 335 | 336 | Returns: 337 | dict 338 | """ 339 | serviceType = "ADMIN_MAIN_ACCOUNT_FILTERED_TRANSACTIONS" 340 | 341 | data = { 342 | "pageSize": pageSize, 343 | "pageNumber": pageNumber, 344 | "startDate": startDate, 345 | "endDate": endDate 346 | } 347 | return self.sign_request(serviceType, data) 348 | 349 | def virtual_txn_logs(self, 350 | trackingRef: int, 351 | pageSize: int = 10, 352 | pageNumber: int = 1) -> dict: 353 | """Retrieve a list of all transactions for a specified virtual account 354 | https://kudabank.gitbook.io/kudabank/view-transaction-history/virtual-accounttransactionhistory 355 | 356 | Args: 357 | trackingRef (int): Unique Identifier for virtual Account 358 | pageSize (int, optional) 359 | pageNumber (int, optional) 360 | 361 | Returns: 362 | dict 363 | """ 364 | serviceType = "ADMIN_VIRTUAL_ACCOUNT_TRANSACTIONS" 365 | 366 | data = { 367 | "trackingReference": trackingRef, 368 | "pageSize" : pageSize, 369 | "pageNumber" : pageNumber, 370 | } 371 | 372 | return self.sign_request(serviceType, data) 373 | 374 | def filter_virtual_txn_logs(self, 375 | trackingRef: int, 376 | startDate: str, 377 | endDate: str, 378 | pageSize: int = 10, 379 | pageNumber: int = 1) -> dict: 380 | """Retrieve a filtered list of all transactions for a specified virtual account 381 | https://kudabank.gitbook.io/kudabank/view-transaction-history/virtual-account-transaction-history 382 | 383 | Args: 384 | trackingRef (int): Unique Identifier for virtual Account 385 | startDate (str): Ex: 2020-10-27T09:58:23.4740446Z 386 | endDate (str): Ex: 2020-12-27T09:58:23.4740446Z 387 | pageSize (int, optional) 388 | pageNumber (int, optional) 389 | 390 | Returns: 391 | dict 392 | """ 393 | serviceType = "ADMIN_MAIN_ACCOUNT_FILTERED_TRANSACTIONS" 394 | 395 | data = { 396 | "trackingReference": trackingRef, 397 | "pageSize": pageSize, 398 | "pageNumber": pageNumber, 399 | "startDate": startDate, 400 | "endDate": endDate 401 | } 402 | return self.sign_request(serviceType, data) 403 | 404 | def fund_virtual_account(self, 405 | trackingRef: int, 406 | amount: int, 407 | narration: str) -> dict: 408 | 409 | """Deposit to a virtual account 410 | https://kudabank.gitbook.io/kudabank/add-remove-money-from-a-virtual-account 411 | 412 | Args: 413 | trackingRef (int): Unique Identifier for virtual Account 414 | amount (int): Amount to fund (in kobo) 415 | narration (str, optional): Transaction Description. Defaults to "". 416 | 417 | Returns: 418 | dict: 419 | """ 420 | serviceType = "FUND_VIRTUAL_ACCOUNT" 421 | 422 | data = { 423 | "trackingReference": trackingRef, 424 | "amount": amount, 425 | "narration": narration 426 | } 427 | return self.sign_request(serviceType, data) 428 | 429 | def withdraw_virtual_account(self, 430 | trackingRef: int, 431 | amount: int, 432 | narration: str = "") -> dict: 433 | """Withdrawing funds from a virtual account means to transfer funds from a virtual account to an associated KUDA account or to any other Nigerian Bank account. 434 | https://kudabank.gitbook.io/kudabank/add-remove-money-from-a-virtual-account#withdraw-from-virtual-account 435 | 436 | Args: 437 | trackingRef (int): Unique Identifier for virtual Account 438 | amount (int): Amount to be withdrawn (in kobo) 439 | narration (str, optional): Transaction description. Defaults to "". 440 | 441 | Returns: 442 | dict 443 | """ 444 | serviceType = "WITHDRAW_VIRTUAL_ACCOUNT" 445 | 446 | data = { 447 | "trackingReference": trackingRef, 448 | "amount": amount, 449 | "narration": narration 450 | } 451 | 452 | return self.sign_request(serviceType, data) -------------------------------------------------------------------------------- /kuda/error.py: -------------------------------------------------------------------------------- 1 | class Err(Exception): 2 | pass 3 | 4 | class ClientError(Err): 5 | def __init__(self, status_code, error_message, header): 6 | # response status code 7 | self.status_code = status_code 8 | # error message returned from API 9 | self.error_message = error_message 10 | # the whole response header returned from API 11 | self.header = header 12 | 13 | class ServerError(Err): 14 | def __init__(self, status_code, message): 15 | # response status code 16 | self.status_code = status_code 17 | # error message returned from API 18 | self.message = message 19 | 20 | class InsufficientBalance(Err): 21 | def __init__(self, status_code, message): 22 | # response status code 23 | self.status_code = status_code 24 | # error message returned from API 25 | self.error_message = message -------------------------------------------------------------------------------- /kuda/savings.py: -------------------------------------------------------------------------------- 1 | def get_spend_savings_txns(self, 2 | trackingRef: int, 3 | pageNumber: int, 4 | pageSize: int) -> dict: 5 | serviceType = "RETRIEVE_SPEND_AND_SAVE_TRANSACTIONS" 6 | return 7 | 8 | def create_plain_savings(self, 9 | savingsTrackingRef: int, 10 | name: str, 11 | virtualAccTrackingRef: int) -> dict: 12 | serviceType = "CREATE_PLAIN_SAVINGS" 13 | return 14 | 15 | def get_plain_savings(self, 16 | trackingRef: int) -> dict: 17 | serviceType = "GET_PLAIN_SAVINGS" 18 | return 19 | 20 | def get_all_customer_plain_savings(self, 21 | accountNumber: int) -> dict: 22 | return 23 | 24 | def get_all_plain_savings(self) -> dict: 25 | return 26 | 27 | def update_plain_savings_account(self, 28 | trackingRef: int, 29 | status: int) -> dict: 30 | return 31 | 32 | def plain_savings_txn(self, 33 | trackingRef: int, 34 | amount: int, 35 | transactionType: str, 36 | narration: str = "") -> dict: 37 | serviceType = "PLAIN_SAVINGS_TRANSACTIONS" 38 | return 39 | 40 | def plain_savings_txn_logs(self, 41 | trackingRef: int, 42 | pageSize: int, 43 | pageNumber: int) -> dict: 44 | serviceType = "RETRIEVE_PLAIN_SAVINGS_TRANSACTIONS" 45 | return 46 | 47 | def create_open_flexible_savings(self, 48 | savingsTrackingRef: str, 49 | name: str, 50 | virtualAccTrackingRef: str, 51 | amount: int, 52 | duration: int, 53 | frequency: int, 54 | startNow: str, 55 | startDate: str = True, 56 | isInterestEarning: bool = True) -> dict: 57 | serviceType = "CREATE_OPEN_FLEXIBLE_SAVE" 58 | return 59 | 60 | def create_closed_flexible_save(self, 61 | savingsTrackingRef: str, 62 | name: str, 63 | virtualAccTrackingRef: str, 64 | amount: int, 65 | duration: int, 66 | frequency: int, 67 | startDate: str, 68 | startNow: bool = True, 69 | isInterestEarning: bool = True) -> dict: 70 | serviceType = "CREATE_CLOSED_FLEXIBLE_SAVE" 71 | return 72 | 73 | def get_open_flexible_save(self, 74 | trackingRef: int) -> dict: 75 | serviceType = "GET_OPEN_FLEXIBLE_SAVE" 76 | return 77 | 78 | def get_closed_flexible_savings(self, 79 | trackingRef: int) -> dict: 80 | serviceType = "GET_CLOSED_FLEXIBLE_SAVE" 81 | return 82 | 83 | def get_all_customer_open_flexible_savings(self, 84 | primaryAccountNumber: int) -> dict: 85 | serviceType = "GET_ALL_CUSTOMER_OPEN_FLEXIBLE_SAVE" 86 | return 87 | 88 | def get_all_customer_closed_flexible_savings(self, 89 | primaryAccountNumber: int) -> dict: 90 | serviceType = "GET_ALL_CUSTOMER_CLOSED_FLEXIBLE_SAVE" 91 | return 92 | 93 | def get_all_open_flexible_savings(self) -> dict: 94 | serviceType = "GET_ALL_OPEN_FLEXIBLE_SAVE" 95 | return 96 | 97 | def get_all_closed_flexible_savings(self) -> dict: 98 | serviceType = "GET_ALL_CLOSED_FLEXIBLE_SAVE" 99 | return 100 | 101 | def withdraw_open_flexible_savings(self) -> dict: 102 | serviceType = "COMPLETE_OPEN_FLEXIBLE_SAVE_WITHDRAWAL" 103 | return 104 | 105 | def withdraw_closed_flexible_savings(self) -> dict: 106 | serviceType = "COMPLETE_CLOSED_FLEXIBLE_SAVE_WITHDRAWAL" 107 | return 108 | 109 | def open_flexible_savings_txn_logs(self, 110 | trackingRef: int, 111 | pageSize: int, 112 | pageNumber: int) -> dict: 113 | serviceType = "RETRIEVE_OPEN_FLEXIBLE_SAVINGS_TRANSACTIONS" 114 | return 115 | 116 | def closed_flexible_savings_txn_logs(self, 117 | trackingRef: int, 118 | pageSize: int, 119 | pageNumber: int) -> dict: 120 | serviceType = "RETRIEVE_CLOSED_FLEXIBLE_SAVINGS_TRANSACTIONS" 121 | return 122 | 123 | def create_fixed_savings(self, 124 | savingsTrackingRef: int, 125 | name: str, 126 | virtualAccTrackingRef: int, 127 | amount: int, 128 | duration: int, 129 | startDate: str, 130 | startNow: bool = True, 131 | isInterestEarning: bool = True) -> dict: 132 | serviceType = "CREATE_FIXED_SAVINGS" 133 | return 134 | 135 | def get_fixed_savings(self, 136 | trackingRef: int) -> dict: 137 | serviceType = "GET_FIXED_SAVINGS" 138 | return 139 | 140 | def get_all_customer_fixed_savings(self, 141 | primaryAccountNumber: int) -> dict: 142 | serviceType = "GET_ALL_CUSTOMER_FIXED_SAVINGS " 143 | return 144 | 145 | def get_all_fixed_savings(self) -> dict: 146 | serviceType = "GET_ALL_FIXED_SAVINGS" 147 | return 148 | 149 | def terminate_fixed_deposit(self, 150 | trackingRef: int, 151 | amount: int) -> dict: 152 | """Terminate a fixed deposit 153 | 154 | https://kudabank.gitbook.io/kudabank/savings/fixed-deposit/terminate-a-fixed-deposit 155 | 156 | Args: 157 | trackingRef (int) 158 | amount (int) 159 | 160 | Returns: 161 | dict 162 | """ 163 | serviceType = "COMPLETE_FIXED_SAVINGS_WITHDRAWAL" 164 | 165 | data = { 166 | "trackingReference" : trackingRef, 167 | "amount" : amount 168 | } 169 | 170 | return self.sign_request(serviceType, data) 171 | 172 | def fixed_savings_txn_logs(self, 173 | trackingRef: int, 174 | pageSize: int = 10, 175 | pageNumber: int = 1) -> dict: 176 | """View all transactions on a fixed savings account 177 | 178 | https://kudabank.gitbook.io/kudabank/savings/fixed-deposit/view-all-transactions-on-a-fixed-savings-account 179 | 180 | Args: 181 | trackingRef (int) 182 | pageSize (int, optional): Defaults to 10 183 | pageNumber (int, optional): Defualts to 1 184 | 185 | Returns: 186 | dict 187 | """ 188 | serviceType = "RETRIEVE_FIXED_SAVINGS_TRANSACTIONS" 189 | 190 | data = { 191 | "trackingReference" : trackingRef, 192 | "pageSize": pageSize, 193 | "pageNumber" : pageNumber 194 | } 195 | return self.sign_request(serviceType, data) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | # For consistent encoding 4 | from codecs import open 5 | from os import path 6 | 7 | HERE = path.abspath(path.dirname(__file__)) 8 | 9 | with open(path.join(HERE, 'README.md'), encoding='utf-8') as f: 10 | long_description = f.read() 11 | 12 | setup( 13 | name="kuda-python", 14 | version="1.0.2", 15 | description="Kuda OpenAPI Python Library", 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url="https://kuda-python.readthedocs.io/", 19 | author="Chiemezie Njoku", 20 | author_email="njokuchiemezie01@gmail.com", 21 | license="MIT", 22 | classifiers=[ 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: MIT License", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.6", 28 | "Programming Language :: Python :: 3.7", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | "Operating System :: OS Independent" 32 | ], 33 | packages=["kuda"], 34 | include_package_data=True, 35 | install_requires=["requests"] 36 | ) --------------------------------------------------------------------------------