├── .gitignore ├── LICENSE ├── README.md ├── errors.py ├── examples ├── init_transaction_with_options.py ├── list_transactions.py ├── simple_init_transaction.py └── verify_transaction.py ├── main.py ├── new.py ├── paystackpyAPI ├── __init__.py ├── base.py └── transaction.py ├── requirements.txt ├── setup.py └── tests └── test_transaction.py /.gitignore: -------------------------------------------------------------------------------- 1 | pay_venv 2 | paystackpyAPI/__pycache__ 3 | tests/__pycache__ 4 | /build/ 5 | /dist/ 6 | /paystackpyAPI.egg-info/ 7 | .idea 8 | __pycache__ 9 | pay.py 10 | res.json 11 | export.csv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] [Al-Areef](https://github.com/NUCCASJNR) 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 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaystackPyAPI 2 | 3 | PaystackPyAPI is a Python package designed to simplify and streamline Paystack API integration, enabling secure online payment processing in your Python applications. 4 | 5 | ## Installation 6 | 7 | You can install the package using pip: 8 | 9 | ```bash 10 | pip install paystackpyAPI 11 | ``` 12 | 13 | ## Getting Started 14 | 15 | **1. Obtain API Key:** 16 | 17 | Sign up for a Paystack account if you don't have one: [Paystack Signup](https://dashboard.paystack.com/#/signup). 18 | 19 | _Log in to your Paystack dashboard and obtain your API key._ 20 | 21 | 2. **Initialize PaystackAPI:** 22 | 23 | ```bash 24 | from paystackpyAPI.transaction import Transaction 25 | 26 | # Replace 'your_api_key' with your actual Paystack API key 27 | api_key = 'your_api_key' 28 | transaction = Transaction(api_key) 29 | 30 | ``` 31 | 32 | 3. **Initialize a Transaction** 33 | 34 | ```bash 35 | email = 'user@example.com' 36 | amount = 100 37 | initialize_response = transaction.initialize_transaction(email, amount) 38 | print("Initialization Response:", initialize_response) 39 | ``` 40 | 41 | 4. **Verify a Transaction** 42 | ```bash 43 | reference = initialize_response['data']['data']['reference'] 44 | verify_response = transaction.verify_transaction(reference) 45 | print("Verification Response:", verify_response) 46 | 47 | ``` 48 | 49 | 5. **Optional parameters** 50 | 51 | The initialize_transaction method supports the following optional parameters: 52 | 53 | 1. currency 54 | 55 | 2. reference 56 | 57 | 3. callback_url 58 | 59 | 4. plan 60 | 61 | 5. invoice_limit 62 | 63 | 6. metadata 64 | 65 | 7. channels 66 | 67 | 8. split_code 68 | 69 | 9. subaccount 70 | 71 | 10. transaction_charge 72 | 73 | 11. bearer 74 | 75 | Pass these parameters as keyword arguments when calling the initialize_transaction method. 76 | 77 | 6. **Examples** 78 | 79 | Check the [examples](./examples) directory for sample scripts demonstrating various use cases. 80 | 81 | 7. **Contributing** 82 | 83 | _If you find a bug or have a feature request, please open an issue. Contributions are welcome!_ 84 | 85 | 1. Fork the repository 86 | 2. Create a new branch (git checkout -b feature/awesome-feature). 87 | 3. Commit your changes (git commit -am 'Add awesome feature'). 88 | 4. Push to the branch (git push origin feature/awesome-feature). 89 | 5. Open a Pull Request. 90 | 91 | 8. **License** 92 | 93 | _This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details._ -------------------------------------------------------------------------------- /errors.py: -------------------------------------------------------------------------------- 1 | 2 | class PaystackError(Exception): 3 | """Base class for Paystack API errors.""" 4 | 5 | 6 | class APIError(PaystackError): 7 | """Exception raised for errors in the Paystack API. 8 | 9 | Attributes: 10 | status_code -- the HTTP status code indicating the error 11 | error_message -- a description of the error 12 | """ 13 | 14 | def __init__(self, status_code, error_message): 15 | self.status_code = status_code 16 | self.error_message = error_message 17 | super().__init__(self.error_message) 18 | -------------------------------------------------------------------------------- /examples/init_transaction_with_options.py: -------------------------------------------------------------------------------- 1 | from paystackpyAPI.transaction import Transaction 2 | from os import getenv 3 | """ 4 | Purpose: Shows how to include optional parameters 5 | in the transaction initialization. 6 | """ 7 | api_key = getenv("PAYSTACK_KEY") 8 | transaction = Transaction(api_key) 9 | 10 | email = 'user@example.com' 11 | amount = 100 12 | options = { 13 | 'currency': 'USD', 14 | 'callback_url': 'https://example.com/callback', 15 | # ... other optional parameters ... 16 | } 17 | initialize_response = transaction.initialize_transaction(email, amount, **options) 18 | print("Initialization Response:", initialize_response) 19 | -------------------------------------------------------------------------------- /examples/list_transactions.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NUCCASJNR/PaystackPyAPI/918ea9ff864d38003ffe0ada515d62e7f5b75e0f/examples/list_transactions.py -------------------------------------------------------------------------------- /examples/simple_init_transaction.py: -------------------------------------------------------------------------------- 1 | from paystackpyAPI.transaction import Transaction 2 | """ 3 | Purpose: Demonstrates a basic transaction initialization without 4 | optional parameters. 5 | """ 6 | api_key = 'your_api_key' 7 | transaction = Transaction(api_key) 8 | 9 | email = 'user@example.com' 10 | amount = 100 11 | initialize_response = transaction.initialize_transaction(email, amount) 12 | print("Initialization Response:", initialize_response) 13 | -------------------------------------------------------------------------------- /examples/verify_transaction.py: -------------------------------------------------------------------------------- 1 | from paystackpyAPI.transaction import Transaction 2 | """ 3 | Purpose: Illustrates how to verify a transaction using the verify_transaction method. 4 | """ 5 | api_key = 'your_api_key' 6 | transaction = Transaction(api_key) 7 | 8 | # Replace 'your_reference' with an actual reference from a previous initialization 9 | reference = 'your_reference' 10 | verify_response = transaction.verify_transaction(reference) 11 | print("Verification Response:", verify_response) 12 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from paystackpyAPI.transaction import Transaction 2 | from os import getenv 3 | api_key = getenv("PAYSTACK_KEY") 4 | 5 | transaction = Transaction(api_key) 6 | 7 | # Example: Initialize a transaction 8 | email = 'customer@example.com' 9 | amount = 5000 # Replace with your desired amount 10 | optional_params = {'currency': 'NGN', 'callback_url': 'https://example.com/callback'} 11 | 12 | try: 13 | initialization_response = transaction.initialize_transaction(email, amount, **optional_params) 14 | print("Initialization Response:", initialization_response) 15 | 16 | # Extract the reference from the initialization response 17 | reference_to_verify = initialization_response["response_from_api"]['data']['reference'] 18 | 19 | try: 20 | auth_code = initialization_response["response_from_api"]['data']['authorization_url'] 21 | auth_code = auth_code.split('/')[-1] 22 | auth_transaction = transaction.charge_authorization(email, amount, auth_code, **optional_params) 23 | print("Authorization Response:", auth_transaction) 24 | except Exception as e: 25 | print("Error authorizing transaction:", str(e)) 26 | # Example: Verify the transaction using the obtained reference 27 | try: 28 | verification_response = transaction.verify_transaction(reference_to_verify) 29 | print("Verification Response:", verification_response) 30 | except Exception as e: 31 | print("Error verifying transaction:", str(e)) 32 | try: 33 | transactions = transaction.list_transactions() 34 | print("Transactions:", transactions) 35 | except Exception as e: 36 | print("Error listing transactions:", str(e)) 37 | try: 38 | transaction_id = verification_response["response_from_api"]['data']['id'] 39 | fetch_transaction = transaction.fetch_transaction(transaction_id) 40 | print("Transaction:", fetch_transaction) 41 | except Exception as e: 42 | print("Error fetching transaction:", str(e)) 43 | try: 44 | total_transactions = transaction.get_transaction_totals() 45 | print("Total Transactions:", total_transactions) 46 | except Exception as e: 47 | print("Error getting total transactions:", str(e)) 48 | 49 | try: 50 | export = transaction.export_transactions() 51 | print("export result :", export) 52 | except Exception as e: 53 | print("Error Exporting transactions:", str(e)) 54 | except Exception as e: 55 | print("Error initializing transaction:", str(e)) 56 | -------------------------------------------------------------------------------- /new.py: -------------------------------------------------------------------------------- 1 | from paystackpyAPI.transaction import Transaction 2 | from os import getenv 3 | 4 | api_key = getenv("PAYSTACK_KEY") -------------------------------------------------------------------------------- /paystackpyAPI/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NUCCASJNR/PaystackPyAPI/918ea9ff864d38003ffe0ada515d62e7f5b75e0f/paystackpyAPI/__init__.py -------------------------------------------------------------------------------- /paystackpyAPI/base.py: -------------------------------------------------------------------------------- 1 | 2 | class PaystackAPI: 3 | 4 | def __init__(self, api_key: str) -> None: 5 | self.api_key = api_key 6 | -------------------------------------------------------------------------------- /paystackpyAPI/transaction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Handles All Paystack related tasks""" 4 | import requests 5 | from .base import PaystackAPI 6 | from typing import Dict, Union 7 | from errors import APIError 8 | from decimal import Decimal 9 | import datetime 10 | import webbrowser 11 | 12 | class Transaction(PaystackAPI): 13 | INITIALIZATION_OPTIONAL_PARAMS = [ 14 | "currency", 15 | "reference", 16 | "callback_url", 17 | "plan", 18 | "invoice_limit", 19 | "metadata", 20 | "channels", 21 | "split_code", 22 | "subaccount", 23 | "transaction_charge", 24 | "bearer" 25 | ] 26 | 27 | TRANSACTION_LIST_OPTIONAL_PARAMS = [ 28 | "customer", 29 | "terminalid", 30 | "status", 31 | "from", 32 | "to", 33 | "amount" 34 | ] 35 | CHARGE_AUTHORIZATION_OPTIONAL_PARAMS = [ 36 | "reference", 37 | "currency", 38 | "metadata", 39 | "channels", 40 | "subaccount", 41 | "transaction_charge", 42 | "bearer", 43 | "queue" 44 | ] 45 | 46 | EXPORT_OPTIONAL_PARAMS = [ 47 | 'from', 48 | 'to', 49 | 'customer', 50 | 'status', 51 | 'currency', 52 | 'amount', 53 | 'settled', 54 | 'settlement', 55 | 'payment_page' 56 | ] 57 | 58 | def __init__(self, api_key: str): 59 | super().__init__(api_key) 60 | self.paystack_initialization_url = "https://api.paystack.co/transaction/initialize" 61 | self.paystack_verification_url = "https://api.paystack.co/transaction/verify" 62 | self.list_transaction_url = "https://api.paystack.co/transaction" 63 | self.fetch_transaction_url = "https://api.paystack.co/transaction" 64 | self.charge_authorization_url = "https://api.paystack.co/transaction/charge_authorization" 65 | self.transaction_timeline_url = "https://api.paystack.co/transaction/timeline" 66 | self.transaction_totals_url = "https://api.paystack.co/transaction/totals" 67 | self.export_transactions_url = "https://api.paystack.co/transaction/export" 68 | 69 | 70 | def initialize_transaction(self, email: str, amount: int, **kwargs): 71 | """ 72 | Initialize a Paystack transaction. 73 | 74 | :param email: Customer's email address. 75 | :param amount: Transaction amount. 76 | :param kwargs: Optional parameters for the transaction. 77 | Example: `currency`, `callback_url`, etc. 78 | :return: JSON response from Paystack API. 79 | :raises APIError: If required parameters are missing or the API key is invalid. 80 | """ 81 | if not email or not amount: 82 | raise APIError(400, "Missing required parameters: email and/or amount") 83 | 84 | valid_kwargs = {key: value for key, value in kwargs.items() if key in self.INITIALIZATION_OPTIONAL_PARAMS} 85 | data = { 86 | "email": email, 87 | "amount": amount * 100, 88 | **valid_kwargs 89 | } 90 | 91 | if not self.api_key: 92 | raise APIError(401, "Invalid API key") 93 | 94 | headers = { 95 | 'Authorization': f'Bearer {self.api_key}', 96 | 'Content-Type': 'application/json', 97 | } 98 | response = requests.post(self.paystack_initialization_url, headers=headers, json=data) 99 | if response.status_code == 200: 100 | custom_response = { 101 | "status_code": response.status_code, 102 | "message": "Transaction initialized successfully", 103 | "response_from_api": response.json() 104 | } 105 | else: 106 | error_message = response.text 107 | raise APIError(response.status_code, error_message) 108 | return custom_response 109 | 110 | def verify_transaction(self, reference: Union[int, str]) -> Dict: 111 | """ 112 | Verify a Paystack transaction. 113 | 114 | :param reference: Reference id of the transaction (int or str). 115 | :return: Customized response from Paystack API. 116 | :raises APIError: If the reference is missing or the API key is invalid. 117 | """ 118 | if not reference: 119 | raise APIError(400, "Missing required parameter: reference") 120 | 121 | if not self.api_key: 122 | raise APIError(401, "Invalid API key") 123 | 124 | url = f"{self.paystack_verification_url}/{reference}" 125 | headers = { 126 | 'Authorization': f'Bearer {self.api_key}' 127 | } 128 | response = requests.get(url, headers=headers) 129 | 130 | if response.status_code == 200: 131 | custom_response = { 132 | "status_code": response.status_code, 133 | "message": "Transaction details retrieved successfully", 134 | "response_from_api": response.json() 135 | } 136 | else: 137 | error_message = response.text 138 | raise APIError(response.status_code, error_message) 139 | 140 | return custom_response 141 | 142 | def list_transactions(self, **kwargs: Dict) -> Dict: 143 | """ 144 | Retrieve a list of transactions based on optional parameters. 145 | 146 | :param kwargs: Optional parameters for filtering the list of transactions. 147 | Supported parameters: 148 | - `perPage`: Number of transactions to retrieve per page. 149 | - `page`: Page number for pagination. 150 | - `from`: Start date for transactions in the format 'YYYY-MM-DD'. 151 | - `to`: End date for transactions in the format 'YYYY-MM-DD'. 152 | - `customer`: Customer's email or identification. 153 | - `status`: Transaction status (e.g., 'success', 'failed'). 154 | - `currency`: Currency code (e.g., 'NGN', 'USD'). 155 | - `amount`: Transaction amount. 156 | - `reference`: Transaction reference. 157 | - `gateway`: Payment gateway used (e.g., 'card', 'bank'). 158 | - `channel`: Transaction channel (e.g., 'card', 'bank'). 159 | - `plan`: Plan code associated with the transaction. 160 | 161 | :return: Customized response with the list of transactions. 162 | Format: { 163 | "status_code": int, 164 | "message": str, 165 | "data": dict 166 | } 167 | 168 | :raises APIError: If the API key is invalid or if there's an issue with the request. 169 | """ 170 | if not self.api_key: 171 | raise APIError(401, "Invalid API Key") 172 | 173 | valid_kwargs = {key: value for key, value in kwargs.items() if key in self.TRANSACTION_LIST_OPTIONAL_PARAMS} 174 | 175 | headers = { 176 | 'Authorization': f'Bearer {self.api_key}' 177 | } 178 | 179 | data = { 180 | **valid_kwargs 181 | } 182 | 183 | response = requests.get(self.list_transaction_url, headers=headers, params=data) 184 | 185 | if response.status_code == 200: 186 | custom_response = { 187 | "status_code": response.status_code, 188 | "message": "Transactions details below", 189 | "response_from_api": response.json() 190 | } 191 | else: 192 | error_message = response.text 193 | raise APIError(response.status_code, error_message) 194 | 195 | return custom_response 196 | 197 | def fetch_transaction(self, id: int) -> Dict: 198 | """ 199 | Fetches the details of a transaction using the id provided 200 | :param id: 201 | Transaction Id 202 | """ 203 | if not self.api_key: 204 | raise APIError(401, "Invalid Api Key") 205 | url = f"{self.fetch_transaction_url}/{id}" 206 | headers = { 207 | 'Authorization': f'Bearer {self.api_key}' 208 | } 209 | response = requests.get(url, headers=headers) 210 | if response.status_code == 200: 211 | custom_response = { 212 | "status_code": response.status_code, 213 | "message": "Transaction Successfully fetched", 214 | "response_from_api": response.json() 215 | } 216 | else: 217 | error_message = response.text 218 | raise APIError(response.status_code, error_message) 219 | return custom_response 220 | 221 | def charge_authorization(self, email: str, amount: int, authorization_code: str, **kwargs: Dict) -> Dict: 222 | """charge a transaction""" 223 | 224 | if not self.api_key: 225 | raise APIError(401, "Invalid API Key") 226 | valid_kwargs = {key: value for key, value in kwargs.items() if key in self.CHARGE_AUTHORIZATION_OPTIONAL_PARAMS} 227 | if not amount: 228 | raise APIError(400, "Missing required parameter amount") 229 | if not email: 230 | raise APIError(400, "Missing required parameter email") 231 | if not authorization_code: 232 | raise APIError(400, "Missing required parameter authorization_code") 233 | headers = { 234 | 'Authorization': f'Bearer {self.api_key}', 235 | 'Content-Type': 'application/json', 236 | } 237 | data = { 238 | "amount": amount * 100, 239 | "email": email, 240 | "authorization_code": f"AUTH_{authorization_code}", 241 | **valid_kwargs 242 | } 243 | response = requests.post(self.charge_authorization_url, headers=headers, json=data) 244 | if response.status_code == 200: 245 | custom_response = { 246 | "status_code": response.status_code, 247 | "message": "Transaction initialized successfully", 248 | "response_from_api": response.json() 249 | } 250 | else: 251 | error_message = response.text 252 | raise APIError(response.status_code, error_message) 253 | return custom_response 254 | 255 | def show_transaction_timeline(self, id_or_reference: str) -> Dict: 256 | """ 257 | SHow a transaction timeline 258 | """ 259 | headers = { 260 | 'Authorization': f'Bearer {self.api_key}' 261 | } 262 | url = f"{self.transaction_timeline_url}/{id_or_reference}" 263 | response = requests.get(url, headers=headers) 264 | if response.status_code == 200: 265 | custom_response = { 266 | "status_code": response.status_code, 267 | "message": "Transaction timeline retrieved", 268 | "response_from_api": response.json() 269 | } 270 | else: 271 | error_message = response.text 272 | raise APIError(response.status_code, error_message) 273 | return custom_response 274 | 275 | def get_total_transactions(self, per_page=50, page=1, from_date=None, to_date=None): 276 | """ 277 | Retrieve the total amount received on your account based on specified parameters. 278 | 279 | :param per_page: Number of records to retrieve per page (default is 50). 280 | :param page: Page number to retrieve (default is 1). 281 | :param from_date: Start date for listing transactions in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ'. 282 | :param to_date: End date for listing transactions in the format 'YYYY-MM-DDTHH:mm:ss.SSSZ'. 283 | 284 | :return: Customized response with the total amount received. 285 | Format: { 286 | "status_code": int, 287 | "message": str, 288 | "data": { 289 | "total_amount": float 290 | } 291 | } 292 | 293 | :raises APIError: If the API key is invalid or if there's an issue with the request. 294 | """ 295 | if not self.api_key: 296 | raise APIError(401, "Invalid API Key") 297 | 298 | headers = { 299 | 'Authorization': f'Bearer {self.api_key}' 300 | } 301 | 302 | params = { 303 | 'perPage': per_page, 304 | 'page': page, 305 | 'from': from_date, 306 | 'to': to_date 307 | } 308 | 309 | response = requests.get(self.transaction_totals_url, headers=headers, params=params) 310 | 311 | if response.status_code == 200: 312 | custom_response = { 313 | "status_code": response.status_code, 314 | "message": "Transaction totals retrieved successfully", 315 | "response_from_api": response.json() 316 | } 317 | else: 318 | error_message = response.text 319 | raise APIError(response.status_code, error_message) 320 | 321 | return custom_response 322 | 323 | def download_csv(self, url, output_filename='exported_file.csv'): 324 | response = requests.get(url) 325 | response.raise_for_status() 326 | 327 | with open(output_filename, 'wb') as file: 328 | file.write(response.content) 329 | 330 | print(f'File downloaded successfully: {output_filename}') 331 | 332 | def export_transactions(self, per_page=50, page=1, filename="export.csv", **kwargs): 333 | """ 334 | initiate the export, and download the CSV file. 335 | 336 | :param per_page: Number of records to retrieve per page (default is 50). 337 | :param page: Page number to retrieve (default is 1). 338 | :param filename: Optional filename for the exported CSV file. 339 | 340 | :return: Customized response indicating the success of the export. 341 | Format: { 342 | "status_code": int, 343 | "message": str, 344 | "data": { 345 | "exported_file": str # File path or URL 346 | } 347 | } 348 | 349 | :raises APIError: If the API key is invalid, export initiation fails, or if there's an issue with the request. 350 | """ 351 | optional_kwargs = {key: value for key, value in kwargs.items() if key in self.EXPORT_OPTIONAL_PARAMS} 352 | if not self.api_key: 353 | raise APIError(401, "Invalid API key") 354 | headers = { 355 | 'Authorization': f'Bearer {self.api_key}' 356 | } 357 | 358 | params = { 359 | 'perPage': per_page, 360 | 'page': page, 361 | **optional_kwargs 362 | } 363 | try: 364 | response = requests.get(self.export_transactions_url, headers=headers, params=params) 365 | if response.status_code == 200: 366 | data = response.json() 367 | url_to_visit = data['data']['path'] 368 | # webbrowser.open(url_to_visit) 369 | self.download_csv(url_to_visit, output_filename=filename) 370 | 371 | custom_response = { 372 | "status_code": response.status_code, 373 | "message": f"Transactions exported successfully to {filename or url_to_visit}", 374 | "data": { 375 | "exported_file": filename or url_to_visit 376 | } 377 | } 378 | 379 | return custom_response 380 | 381 | 382 | except requests.exceptions.HTTPError as errh: 383 | raise APIError(errh.response.status_code, f"HTTP Error: {errh}") 384 | except requests.exceptions.ConnectionError as errc: 385 | raise APIError(500, f"Error Connecting: {errc}") 386 | except requests.exceptions.Timeout as errt: 387 | raise APIError(500, f"Timeout Error: {errt}") 388 | except requests.exceptions.RequestException as err: 389 | raise APIError(500, f"An error occurred: {err}") 390 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2023.7.22 2 | cffi==1.16.0 3 | charset-normalizer==3.3.2 4 | cryptography==41.0.5 5 | docutils==0.20.1 6 | idna==3.4 7 | importlib-metadata==6.8.0 8 | jaraco.classes==3.3.0 9 | jeepney==0.8.0 10 | keyring==24.2.0 11 | markdown-it-py==3.0.0 12 | mdurl==0.1.2 13 | more-itertools==10.1.0 14 | nh3==0.2.14 15 | pkginfo==1.9.6 16 | pycparser==2.21 17 | Pygments==2.16.1 18 | readme-renderer==42.0 19 | requests==2.31.0 20 | requests-toolbelt==1.0.0 21 | rfc3986==2.0.0 22 | rich==13.6.0 23 | SecretStorage==3.3.3 24 | twine==4.0.2 25 | urllib3==2.0.7 26 | zipp==3.17.0 27 | 28 | responses~=0.24.0 29 | setuptools~=59.6.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r") as readme_file: 4 | long_description = readme_file.read() 5 | 6 | setup( 7 | name="paystackpyAPI", 8 | version="1.0.0", 9 | author="Al-Areef", 10 | description="A Python package designed to simplify and streamline Paystack API integration, enabling secure \ 11 | online payment processing in your Python applications.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/NUCCASJNR/PaystackPy", # Replace with your GitHub repository URL 15 | packages=find_packages(), 16 | install_requires=["requests"], 17 | entry_points={ 18 | 'console_scripts': [ 19 | 'paystack-transaction=paystackpyAPI.transaction:Transaction', 20 | ], 21 | }, 22 | classifiers=[ 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /tests/test_transaction.py: -------------------------------------------------------------------------------- 1 | import tracemalloc 2 | import unittest 3 | from unittest.mock import Mock, patch 4 | from paystackpyAPI.transaction import Transaction 5 | from errors import APIError 6 | from os import getenv 7 | import secrets 8 | import responses 9 | 10 | REFERENCE = secrets.token_hex(16) 11 | ID = '' 12 | print(ID) 13 | 14 | 15 | class TestPaystackAPI(unittest.TestCase): 16 | def setUp(self): 17 | # Set up any necessary test data or configurations 18 | self.api = Transaction(api_key=getenv("PAYSTACK_KEY")) 19 | 20 | def tearDown(self): 21 | # Clean up any resources used for testing 22 | responses.stop() 23 | responses.reset() 24 | tracemalloc.stop() 25 | 26 | def test_non_200_response(self): 27 | with responses.RequestsMock() as rsps: 28 | rsps.add( 29 | responses.POST, 30 | self.api.paystack_initialization_url, 31 | status=400, 32 | json={"status": False, "message": "Invalid request"}, 33 | ) 34 | data = { 35 | "email": "test@example.com", 36 | "amount": 1000, 37 | "reference": REFERENCE, 38 | } 39 | with self.assertRaises(APIError) as context: 40 | self.api.initialize_transaction(**data) 41 | self.assertEqual(context.exception.status_code, 400) 42 | 43 | def test_initialize_transaction(self): 44 | data = { 45 | "email": "test@example.com", 46 | "amount": 1000, 47 | "reference": REFERENCE, 48 | } 49 | 50 | response = self.api.initialize_transaction(**data) 51 | self.assertEqual(response["status_code"], 200) 52 | self.assertEqual(response["message"], "Transaction initialized successfully") 53 | print(response["message"]) 54 | 55 | def test_verify_transaction(self): 56 | reference = REFERENCE 57 | response = self.api.verify_transaction(reference) 58 | ID = response["response_from_api"]['data']['id'] 59 | self.assertEqual(response["status_code"], 200) 60 | self.assertEqual(response["message"], "Transaction details retrieved successfully") 61 | 62 | def test_invalid_reference_key(self): 63 | reference = "invalid_reference" 64 | with self.assertRaises(APIError): 65 | self.api.verify_transaction(reference) 66 | 67 | def test_missing_email_initialize(self): 68 | with self.assertRaises(APIError) as context: 69 | self.api.initialize_transaction(amount=1000, email=None) 70 | # self.assertEqual(context.exception.status_code, 400) 71 | self.assertIn("Missing required parameters: email and/or amount", str(context.exception)) 72 | 73 | def test_missing_amount_initialize(self): 74 | with self.assertRaises(APIError) as context: 75 | self.api.initialize_transaction(amount=None, email="idan@gmail.com") 76 | # self.assertEqual(context.exception.status_code, 400) 77 | self.assertIn("Missing required parameters: email and/or amount", str(context.exception)) 78 | 79 | def test_missing_reference_verify(self): 80 | with self.assertRaises(APIError) as context: 81 | self.api.verify_transaction(reference=None) 82 | self.assertEqual(context.exception.status_code, 400) 83 | self.assertIn("Missing required parameter: reference", str(context.exception)) 84 | 85 | def test_list_transactions(self): 86 | response = self.api.list_transactions() 87 | if response["status_code"] == 401: 88 | self.assertEqual(response["message"], "Invalid API key") 89 | elif response["status_code"] == 200: 90 | self.assertEqual(response["status_code"], 200) 91 | self.assertEqual(response["message"], "Transactions details below") 92 | 93 | def test_with_str_id(self): 94 | with self.assertRaises(APIError) as context: 95 | self.api.fetch_transaction("wrong_id") 96 | self.assertEqual(context.exception.status_code, 400) 97 | self.assertIn("Transaction ID should be numeric", str(context.exception)) 98 | 99 | def test_with_int_id(self): 100 | with self.assertRaises(APIError) as context: 101 | self.api.fetch_transaction(123456789) 102 | self.assertEqual(context.exception.status_code, 404) 103 | self.assertIn("Transaction not found", str(context.exception)) 104 | print(str(context.exception)) 105 | 106 | def test_with_valid_id(self): 107 | response = self.api.fetch_transaction(ID) 108 | self.assertEqual(response["status_code"], 200) 109 | self.assertEqual(response["message"], "Transaction Successfully fetched") 110 | print(response["message"]) 111 | 112 | def test_authorize_transaction_with_missing_amount(self): 113 | data = { 114 | "email": "alareefadegbite@gmail.com", 115 | "authorization_code": "AUTH_8dfhjjdt", 116 | "amount": None 117 | } 118 | with self.assertRaises(APIError) as context: 119 | self.api.charge_authorization(**data) 120 | print(context.exception) 121 | self.assertEqual(context.exception.status_code, 400) 122 | self.assertIn("Missing required parameter amount", str(context.exception)) 123 | 124 | def test_authorize_transaction_with_missing_email(self): 125 | data = { 126 | "email": None, 127 | "authorization_code": "AUTH_8dfhjjdt", 128 | "amount": 2000 129 | } 130 | with self.assertRaises(APIError) as context: 131 | self.api.charge_authorization(**data) 132 | print(context.exception) 133 | self.assertEqual(context.exception.status_code, 400) 134 | self.assertIn("Missing required parameter email", str(context.exception)) 135 | 136 | def test_authorize_transaction_with_missing_auth_code(self): 137 | data = { 138 | "email": 'alareefadegbite@gmail.com', 139 | "authorization_code": None, 140 | "amount": 2000 141 | } 142 | with self.assertRaises(APIError) as context: 143 | self.api.charge_authorization(**data) 144 | print(context.exception) 145 | self.assertEqual(context.exception.status_code, 400) 146 | self.assertIn("Missing required parameter auth", str(context.exception)) 147 | 148 | def test_show_transaction_timeline_with_wrongId(self): 149 | with self.assertRaises(APIError) as context: 150 | self.api.show_transaction_timeline("wrong_id") 151 | self.assertEqual(context.exception.status_code, 404) 152 | self.assertIn("Transaction ID or reference is invalid", str(context.exception)) 153 | 154 | def test_show_transaction_timeline_with_validId(self): 155 | response = self.api.show_transaction_timeline(REFERENCE) 156 | self.assertEqual(response["status_code"], 200) 157 | self.assertEqual(response["message"], "Transaction timeline retrieved") 158 | print(response["message"]) 159 | 160 | def test_get_transaction_totals(self): 161 | response = self.api.get_total_transactions() 162 | self.assertEqual(response["status_code"], 200) 163 | self.assertEqual(response["message"], "Transaction totals retrieved successfully") 164 | print(response["message"]) 165 | 166 | def test_get_transaction_totals_with_400(self): 167 | with responses.RequestsMock() as rsps: 168 | rsps.add( 169 | responses.GET, 170 | self.api.transaction_totals_url, 171 | status=400, 172 | json={"status": False, "message": "Invalid request"}, 173 | ) 174 | with self.assertRaises(APIError) as context: 175 | self.api.get_total_transactions() 176 | self.assertEqual(context.exception.status_code, 400) 177 | self.assertIn("Invalid request", str(context.exception)) 178 | 179 | def test_get_transaction_totals_with_401(self): 180 | with responses.RequestsMock() as rsps: 181 | rsps.add( 182 | responses.GET, 183 | self.api.transaction_totals_url, 184 | status=401, 185 | json={"status": False, "message": "Invalid API Key"}, 186 | ) 187 | with self.assertRaises(APIError) as context: 188 | self.api.get_total_transactions() 189 | self.assertEqual(context.exception.status_code, 401) 190 | self.assertIn("Invalid API Key", str(context.exception)) 191 | 192 | def test_export_transactions(self): 193 | response = self.api.export_transactions() 194 | self.assertEqual(response["status_code"], 200) 195 | self.assertEqual(response["message"], 'Transactions exported successfully to export.csv') 196 | self.assertEqual(response['data'], {'exported_file': 'export.csv'}) 197 | 198 | 199 | @patch('paystackpyAPI.transaction.requests.get') 200 | def test_export_transaction_failure(self, mock_get): 201 | mock_get.return_value.status = 404 202 | mock_get.return_value.text = 'Not Found' 203 | try: 204 | self.api.export_transactions() 205 | except APIError as e: 206 | self.assertEqual(e.status_code, 404) 207 | self.assertEqual(e.message, 'Not Found') 208 | 209 | if __name__ == '__main__': 210 | unittest.main() 211 | --------------------------------------------------------------------------------