├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── README.rst ├── docs ├── index.md └── license.md ├── fabfile.py ├── mkdocs.yml ├── paystack ├── __init__.py ├── client.py ├── data │ ├── paystack.crt │ └── paystack.key ├── error.py ├── resource.py ├── util.py └── version.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt └── tests ├── __init__.py └── paystack_test.py /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: xmibvBCXz9BAHTc7eciiaQOV3xWAHBJBn -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *$py.class 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | s.py 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | dist/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | parts/ 17 | var/ 18 | *.egg-info/ 19 | *.egg 20 | 21 | # PyInstaller 22 | # Usually these files are written by a python script from a template 23 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 24 | *.manifest 25 | *.spec 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .coverage.* 36 | .cache 37 | nosetests.xml 38 | coverage.xml 39 | *,cover 40 | .hypothesis/ 41 | 42 | # Translations 43 | *.mo 44 | *.pot 45 | 46 | # Django stuff: 47 | *.log 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | # PyBuilder 53 | target/ 54 | 55 | #Ipython Notebook 56 | .ipynb_checkpoints 57 | # pycharm 58 | .idea/ 59 | .idea 60 | 61 | # Packages 62 | *.egg 63 | *.egg-info 64 | build 65 | eggs 66 | parts 67 | bin 68 | var 69 | sdist 70 | develop-eggs 71 | .installed.cfg 72 | lib 73 | lib64 74 | .DS_Store 75 | # Installer logs 76 | pip-log.txt 77 | 78 | # Unit test / coverage reports 79 | .coverage 80 | .tox 81 | nosetests.xml 82 | 83 | # Complexity 84 | output/*.html 85 | output/*/index.html 86 | 87 | # Sphinx 88 | docs/_build 89 | mkdocs.yml 90 | # Cookiecutter 91 | output/ 92 | site/ 93 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | - "3.7" 9 | - "pypy" 10 | 11 | before_install: 12 | - pip install codecov 13 | 14 | install: 15 | - pip install -r requirements.txt 16 | - pip install -r test-requirements.txt 17 | 18 | script: 19 | nosetests --with-coverage --cover-package=paystack/ 20 | 21 | after_success: 22 | - coveralls 23 | - codecov 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bernard Ojengwa 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst requirements.txt LICENSE.md 2 | 3 | recursive-include paystack/data * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | paystack 2 | =============================== 3 | 4 | [![Build Status](https://travis-ci.org/ojengwa/paystack.svg?branch=master)](https://travis-ci.org/ojengwa/paystack) [![Coverage Status](https://coveralls.io/repos/github/ojengwa/paystack/badge.svg?branch=master)](https://coveralls.io/github/ojengwa/paystack?branch=master) [![PyPi Version](https://badge.fury.io/py/paystack.svg)](https://badge.fury.io/py/paystack) [![PyPI](https://img.shields.io/pypi/dm/paystack.svg)](https://pypi.python.org/pypi/paystack) 5 | 6 | 7 | Overview 8 | -------- 9 | 10 | Paystack API bindings in Python. 11 | 12 | Installation / Usage 13 | -------------------- 14 | 15 | To install use pip: 16 | 17 | $ pip install --upgrade paystack 18 | 19 | or 20 | 21 | $ easy_install --upgrade paystack 22 | 23 | See http://www.pip-installer.org/en/latest/index.html for instructions 24 | on installing pip. If you are on a system with easy_install but not 25 | pip, you can use easy_install instead. If you're not using virtualenv, 26 | you may have to prefix those commands with `sudo`. You can learn more 27 | about virtualenv at http://www.virtualenv.org/ 28 | 29 | To install from source, clone this repo and run: 30 | 31 | $ git clone https://github.com/ojengwa/paystack.git 32 | $ python setup.py install 33 | 34 | 35 | Documentation 36 | ------------- 37 | 38 | Please see https://developers.paystack.co/docs for the most up-to-date documentation for the Paystack API. 39 | 40 | 41 | API Anatomy 42 | ------------- 43 | 44 | The API resource are exposed via a single interface `paystack.resource`. 45 | 46 | Classes exposed via the interface includes: 47 | `'BaseAPIResource', 'CustomerResource', 'PlanResource', 'RequestsClient', 'TransactionResource', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'error', 'util', 'version'` 48 | 49 | Documentation and signature for each of the methods defined in the API follows: 50 | 51 | **TransactionResource**: 52 | 53 | """ 54 | Base transaction resource Class. 55 | 56 | Encapsulate everything about a transaction instant. 57 | 58 | Attributes: 59 | access_code (string): Paystack access_code for initiating payment 60 | amount (int): Amount to pay in Kobo 61 | authorization_code (string): Paystack verification authorization_code 62 | authorization_url (string): Paystack verification authorization_url 63 | email (string): Client's email address 64 | reference (string): Unique transaction reference 65 | """ 66 | 67 | 68 | def __init__(self, api_secret, reference=None, 69 | resource_path='transaction', *args, **kwargs): 70 | """ 71 | Create a TransactionResource instance. 72 | 73 | Args: 74 | api_secret (string): Developer's API SECRET_KEY. 75 | reference (string, optional): Unique transaction reference. 76 | resource_path (str, optional): API resource_path. Do not change. 77 | *args: Extra positional arguments. 78 | **kwargs: Extra keyworded arguments. 79 | """ 80 | 81 | 82 | def initialize(self, amount, email, 83 | plan=None, ref=None): # pragma no cover 84 | """ 85 | Transaction resource initialisation method. 86 | 87 | Args: 88 | amount (int): Amount to pay in Kobo. 89 | email (string): Client's email address. 90 | plan (string, optional): You customer billing plan. 91 | ref (string, optional): Unique transaction reference. 92 | 93 | Raises: 94 | error.APIError: Something generally bad happened... :() 95 | error.ValidationError: Bad input. 96 | 97 | Returns: 98 | response (dict): Response data from Paystack 99 | """ 100 | 101 | 102 | def verify(self, ref=None): # pragma no cover 103 | """ 104 | Verify transaction instance. 105 | 106 | Args: 107 | ref (string, optional): Unique transaction reference 108 | 109 | Raises: 110 | error.APIError: Something generally bad happened... :() 111 | error.ValidationError: Bad input. 112 | 113 | Returns: 114 | response (dict): Dictionary containing payment verification details 115 | """ 116 | 117 | 118 | def charge(self, auth_code=None, amount=None, 119 | email=None, reference=None): # pragma no cover 120 | """ 121 | Bill a transaction to a customer's account. 122 | 123 | Args: 124 | auth_code (string, optional): Paystack verification authorization_code 125 | amount (int, optional): Amount to pay in Kobo. 126 | email (string, optional): Client's email address. 127 | reference (string, optional): Unique transaction reference. 128 | 129 | Raises: 130 | error.APIError: Something generally bad happened... :() 131 | error.ValidationError: Bad input. 132 | 133 | Returns: 134 | response (dict): Response data from Paystack 135 | """ 136 | 137 | 138 | def authorize(self, auth_url=None): # pragma: no cover 139 | """ 140 | Open a browser window for client to enter card details. 141 | 142 | Args: 143 | auth_url (string, optional): Paystack verification authorization_url 144 | 145 | Raises: 146 | e: Browser Error :( 147 | error.ValidationError: Bad input. 148 | 149 | Returns: 150 | None 151 | """ 152 | 153 | 154 | Testing 155 | ------------- 156 | 157 | The package is compatible with Python 2.6+, Python 3.1+ and PyPy. We need to test against all of these environments to ensure compatibility. Travis CI will automatically run our tests on push. For local testing, we use [nose](http://nose2.readthedocs.org/en/latest/) to handle testing across environments. 158 | 159 | To run the included test: 160 | 161 | 1. Clone the repo: 162 | 163 | ```$ git clone https://github.com/ojengwa/paystack.git``` 164 | 165 | 2. Cd into project directory: 166 | 167 | ```$ cd paystack``` 168 | 169 | 3. Install dependencies: 170 | 171 | ```$ pip install -r requirements.txt``` 172 | 173 | 4. Run the includded test using fabric: 174 | 175 | ```$ fab test``` 176 | 177 | TODO 178 | ------------ 179 | 180 | 1. Add Event hooks 181 | 2. Create Consumer Resource 182 | 3. Create Plan Resource 183 | 184 | Example 185 | ------- 186 | 187 | ``` 188 | 189 | from paystack.resource import TransactionResource 190 | 191 | import random 192 | import string 193 | 194 | def main(): 195 | rand = ''.join( 196 | [random.choice( 197 | string.ascii_letters + string.digits) for n in range(16)]) 198 | secret_key = 'YOUR_SECRET_KEY' 199 | random_ref = rand 200 | test_email = 'TEST_EMAIL' 201 | test_amount = 'TEST_AMOUNT' 202 | plan = 'Basic' 203 | client = TransactionResource(secret_key, random_ref) 204 | response = c****lient.initialize(test_amount, 205 | test_email, 206 | plan) 207 | print(response) 208 | client.authorize() # Will open a browser window for client to enter card details 209 | verify = client.verify() # Verify client credentials 210 | print(verify) 211 | print(client.charge()) # Charge an already exsiting client 212 | 213 | ``` 214 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Paystack 2 | ======== 3 | 4 | Installation / Usage 5 | -------------------- 6 | 7 | To install use pip: 8 | 9 | :: 10 | 11 | $ pip install --upgrade paystack 12 | 13 | or 14 | 15 | :: 16 | 17 | $ easy_install --upgrade paystack 18 | 19 | 20 | Info 21 | ---- 22 | 23 | | See http://www.pip-installer.org/en/latest/index.html for instructions 24 | | on installing pip. If you are on a system with easy\_install but not 25 | | pip, you can use easy\_install instead. If you’re not using 26 | virtualenv, 27 | | you may have to prefix those commands with ``sudo``. You can learn 28 | more 29 | | about virtualenv at http://www.virtualenv.org/ 30 | 31 | 32 | To install from source, clone this repo and run: 33 | 34 | :: 35 | 36 | $ git clone https://github.com/ojengwa/paystack.git 37 | 38 | $ python setup.py install 39 | 40 | 41 | Documentation 42 | ------------- 43 | 44 | Please see https://developers.paystack.co/docs for the most up-to-date 45 | documentation for the Paystack API. 46 | 47 | 48 | API Anatomy 49 | ----------------- 50 | 51 | Please see https://ojengwa.github.io/paystack/ for the most up-to-date 52 | documentation of the API Anatomy. 53 | 54 | 55 | Testing 56 | ------- 57 | 58 | The package is compatible with Python 2.6+, Python 3.1+ and PyPy. We 59 | need to test against all of these environments to ensure compatibility. 60 | Travis CI will automatically run our tests on push. For local testing, 61 | we use `nose`_ to handle testing across environments. 62 | 63 | To run the included test: 64 | 65 | #. Clone the repo: 66 | 67 | :: 68 | 69 | $ git clone https://github.com/ojengwa/paystack.git 70 | 71 | #. Enter project directory: 72 | 73 | :: 74 | 75 | $ cd paystack 76 | 77 | #. Install dependencies using fabric: 78 | 79 | :: 80 | 81 | $ fab install 82 | 83 | #. Run the includded test using fabric: 84 | 85 | :: 86 | 87 | $ fab test 88 | 89 | 90 | TODO 91 | ---- 92 | 93 | #. Add Event hooks 94 | #. Create Consumer Resource 95 | #. Create Plan Resource 96 | 97 | 98 | Example 99 | ------- 100 | 101 | :: 102 | 103 | from paystack.resource import TransactionResource 104 | 105 | import random 106 | import string 107 | 108 | def main(): 109 | rand = ''.join( 110 | [random.choice( 111 | string.ascii_letters + string.digits) for n in range(16)]) 112 | secret_key = 'YOUR_SECRET_KEY' 113 | random_ref = rand 114 | test_email = 'TEST_EMAIL' 115 | test_amount = 'TEST_AMOUNT' 116 | plan = 'Basic' 117 | client = TransactionResource(secret_key, random_ref) 118 | response = client.initialize(test_amount, 119 | test_email, 120 | plan) 121 | print(response) 122 | client.authorize() # Will open a browser window for client to enter card details 123 | verify = client.verify() # Verify client credentials 124 | print(verify) 125 | print(client.charge()) # Charge an already exsiting client 126 | 127 | 128 | 129 | .. _nose: http://nose2.readthedocs.org/en/latest/ 130 | 131 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | Paystack API bindings in Python. 5 | 6 | [![Build Status](https://travis-ci.org/ojengwa/paystack.svg?branch=master)](https://travis-ci.org/ojengwa/paystack) [![Coverage Status](https://coveralls.io/repos/github/ojengwa/paystack/badge.svg?branch=master)](https://coveralls.io/github/ojengwa/paystack?branch=master) [![PyPI version](https://badge.fury.io/py/paystack.svg)](https://badge.fury.io/py/paystack) [![PyPi Downloads](https://img.shields.io/pypi/dm/paystack)](https://pypi.python.org/pypi/paystack) 7 | 8 | 9 | Installation / Usage 10 | -------------------- 11 | 12 | To install use pip: 13 | 14 | $ pip install --upgrade paystack 15 | 16 | or 17 | 18 | $ easy_install --upgrade paystack 19 | 20 | See [pip-installer](http://www.pip-installer.org/en/latest/index.html) for instructions 21 | on installing pip. If you are on a system with easy_install but not 22 | pip, you can use easy_install instead. If you're not using virtualenv, 23 | you may have to prefix those commands with `sudo`. You can learn more 24 | about virtualenv at [http://www.virtualenv.org/](http://www.virtualenv.org/) 25 | 26 | To install from source, clone this repo and run: 27 | 28 | $ git clone https://github.com/ojengwa/paystack.git 29 | 30 | $ python setup.py install 31 | 32 | 33 | Documentation 34 | ------------- 35 | 36 | Please see [https://developers.paystack.co/docs](https://developers.paystack.co/docs) for the most up-to-date documentation for the Paystack API. 37 | 38 | 39 | API Anatomy 40 | ------------- 41 | 42 | The API resource are exposed via a single interface `paystack.resource`. 43 | 44 | Classes exposed via the interface includes: 45 | `'BaseAPIResource', 'CustomerResource', 'PlanResource', 'RequestsClient', 'TransactionResource', 'version'` 46 | 47 | Documentation and signature for each of the methods defined in the API follows: 48 | 49 | 50 | **TransactionResource**: 51 | 52 | """ 53 | Base transaction resource Class. 54 | 55 | Encapsulate everything about a transaction instant. 56 | 57 | Attributes: 58 | access_code (string): Paystack access_code for initiating payment 59 | amount (int): Amount to pay in Kobo 60 | authorization_code (string): Paystack verification authorization_code 61 | authorization_url (string): Paystack verification authorization_url 62 | email (string): Client's email address 63 | reference (string): Unique transaction reference 64 | """ 65 | 66 | 67 | def __init__(self, api_secret, reference=None, resource_path='transaction'): 68 | """ 69 | Create a TransactionResource instance. 70 | 71 | Args: 72 | api_secret (string): Developer's API SECRET_KEY. 73 | reference (string, optional): Unique transaction reference. 74 | resource_path (str, optional): API resource_path. Do not change. 75 | *args: Extra positional arguments. 76 | **kwargs: Extra keyworded arguments. 77 | """ 78 | 79 | 80 | def initialize(self, amount, email, plan=None, ref=None): 81 | """ 82 | Transaction resource initialisation method. 83 | 84 | Args: 85 | amount (int): Amount to pay in Kobo. 86 | email (string): Client's email address. 87 | plan (string, optional): You customer billing plan. 88 | ref (string, optional): Unique transaction reference. 89 | 90 | Raises: 91 | error.APIError: Something generally bad happened... :() 92 | error.ValidationError: Bad input. 93 | 94 | Returns: 95 | response (dict): Response data from Paystack 96 | """ 97 | 98 | 99 | def verify(self, ref=None): 100 | """ 101 | Verify transaction instance. 102 | 103 | Args: 104 | ref (string, optional): Unique transaction reference 105 | 106 | Raises: 107 | error.APIError: Something generally bad happened... :() 108 | error.ValidationError: Bad input. 109 | 110 | Returns: 111 | response (dict): Dictionary containing payment verification details 112 | """ 113 | 114 | 115 | def charge(self, auth_code=None, amount=None, email=None, reference=None): 116 | """ 117 | Bill a transaction to a customer's account. 118 | 119 | Args: 120 | auth_code (string, optional): Paystack verification authorization_code 121 | amount (int, optional): Amount to pay in Kobo. 122 | email (string, optional): Client's email address. 123 | reference (string, optional): Unique transaction reference. 124 | 125 | Raises: 126 | error.APIError: Something generally bad happened... :() 127 | error.ValidationError: Bad input. 128 | 129 | Returns: 130 | response (dict): Response data from Paystack 131 | """ 132 | 133 | 134 | def authorize(self, auth_url=None): 135 | """ 136 | Open a browser window for client to enter card details. 137 | 138 | Args: 139 | auth_url (string, optional): Paystack verification authorization_url 140 | 141 | Raises: 142 | e: Browser Error :( 143 | error.ValidationError: Bad input. 144 | 145 | Returns: 146 | None 147 | """ 148 | 149 | 150 | Workflows 151 | ------------- 152 | 153 | This library was designed with with pluggablility and ease of use in mind. Two of the most common workflows are well supported: 154 | 155 | 1. New Transaction Workflow: 156 | 157 | Most times, we might need to initiate a completely new transaction. The API provides us with simple methods that map to the various endpoints exposed by the [Paystack](https://developers.paystack.co/docs) platform. Also, most of this methods comes with sane defaults so you don't have to repeat the obvious. 158 | 159 | 2. Partial Transaction Workflow: 160 | 161 | Example of when you might have some partial transactions to complete includes (incuring bills, intances where you initiate the transaction using the frontend SDKs, etc...). With this in mind, we wrote the method to be as independent and self-containing as possible. 162 | 163 | **NB: The TransactionResource#authorize method can only be called on the dev environment(your computer) or any environment where there is access to a web browser.** 164 | 165 | Testing 166 | ------------- 167 | 168 | The package is compatible with Python 2.6+, Python 3.1+ and PyPy. We need to test against all of these environments to ensure compatibility. Travis CI will automatically run our tests on push. For local testing, we use [nose](http://nose2.readthedocs.org/en/latest/) to handle testing across environments. 169 | 170 | To run the included test using [fabric](http://fabfile.org): 171 | 172 | 1. Clone the repo: 173 | 174 | ```$ git clone https://github.com/ojengwa/paystack.git``` 175 | 176 | 2. CD into project directory: 177 | 178 | ```$ cd paystack``` 179 | 180 | 3. Install dependencies: 181 | 182 | ```$ fab install``` 183 | 184 | 4. Run the includded test: 185 | 186 | ```$ fab test``` 187 | 188 | 189 | TODO 190 | ------------ 191 | 192 | 1. Add Event hooks 193 | 2. Create Consumer Resource 194 | 3. Create Plan Resource 195 | 196 | 197 | Example 198 | ------- 199 | 200 | ```python 201 | import string 202 | import random 203 | 204 | from paystack.resource import TransactionResource 205 | 206 | 207 | def main(): 208 | rand = ''.join( 209 | [random.choice( 210 | string.ascii_letters + string.digits) for n in range(16)]) 211 | secret_key = 'YOUR_SECRET_KEY' 212 | random_ref = rand 213 | test_email = 'TEST_EMAIL' 214 | test_amount = 'TEST_AMOUNT' 215 | plan = 'Basic' 216 | client = TransactionResource(secret_key, random_ref) 217 | response = client.initialize(test_amount, 218 | test_email, 219 | plan) 220 | print(response) 221 | client.authorize() # Will open a browser window for client to enter card details 222 | verify = client.verify() # Verify client credentials 223 | print(verify) 224 | print(client.charge()) # Charge an already exsiting client 225 | 226 | ``` 227 | Please see the [project license](license.md) for further details. -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bernard Ojengwa 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | """Summary.""" 2 | 3 | from fabric.api import local, task 4 | 5 | from paystack.version import VERSION 6 | 7 | 8 | @task 9 | def install(version=""): 10 | """Install project artifacts. 11 | 12 | Args: 13 | version (str, optional): Description 14 | """ 15 | local("pip install -r requirements.txt") 16 | local("pip install -r test-requirements.txt") 17 | 18 | 19 | @task 20 | def clean(): 21 | """Remove all the .pyc files.""" 22 | local("find . -name '*.pyc' -print0|xargs -0 rm", capture=False) 23 | # Remove the dist folder 24 | local("rm -rf ./dist && rm -rf paystack.egg-info") 25 | local("rm -rf **__pycache__* && rm -rf coverage*") 26 | local("rm -rf ./temp && rm -rf ./build") 27 | 28 | 29 | @task 30 | def push(msg): 31 | """Push to github. 32 | 33 | Args: 34 | msg (str, required): Description 35 | """ 36 | clean() 37 | local("git add . && git commit -m '{}'".format(msg)) 38 | local("git push") 39 | 40 | 41 | @task 42 | def publish(msg="checkpoint: publish package"): 43 | """Deploy the app to PYPI. 44 | 45 | Args: 46 | msg (str, optional): Description 47 | """ 48 | test = check() 49 | if test.succeeded: 50 | clean() 51 | push(msg) 52 | sdist = local("python setup.py sdist") 53 | if sdist.succeeded: 54 | build = local( 55 | 'python setup.py build && python setup.py bdist_egg') 56 | if build.succeeded: 57 | upload = local("twine upload dist/*") 58 | if upload.succeeded: 59 | tag() 60 | 61 | 62 | @task 63 | def check(): 64 | """Test project.""" 65 | test = local("coverage erase && nosetests --with-coverage --cover-package=paystack/" 66 | ) 67 | if test.succeeded: 68 | return test 69 | 70 | 71 | @task 72 | def tag(version=VERSION): 73 | """Deploy a version tag.""" 74 | build = local("git tag {0}".format(version)) 75 | if build.succeeded: 76 | local("git push --tags") 77 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Paystack API Documentation. 2 | theme: mkdocs 3 | repo_url: https://github.com/ojengwa/paystack/ 4 | repo_name: Github Repo 5 | site_description: A Paystack API wrapper in Python. 6 | site_author: Bernard Ojengwa 7 | copyright: ©2016, Bernard Ojengwa 8 | 9 | extra: 10 | version: 1.5.0 11 | links: 12 | - https://github.com/mkdocs 13 | - https://docs.readthedocs.org/en/latest/builds.html#mkdocs 14 | - http://www.mkdocs.org/ -------------------------------------------------------------------------------- /paystack/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Paystack API wrapper. 4 | 5 | @author Bernard Ojengwa. 6 | 7 | Copyright (c) 2015, Bernard Ojengwa 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OF 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | """ 35 | # from paystack.resource import (TransactionResource, 36 | # CustomerResource, 37 | # PlanResource) 38 | 39 | # from paystack.version import VERSION as __version__ 40 | 41 | # __all__ = [ 42 | # 'TransactionResource', 43 | # 'CustomerResource', 44 | # 'PlanResource', 45 | # '__version__' 46 | # ] 47 | -------------------------------------------------------------------------------- /paystack/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Paystack API wrapper. 4 | 5 | @author Bernard Ojengwa. 6 | 7 | Copyright (c) 2015, Bernard Ojengwa 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | """ 35 | import os 36 | import textwrap 37 | import requests 38 | 39 | from paystack import error 40 | 41 | try: 42 | import json 43 | except Exception as e: # pragma: no cover 44 | import simplejson as json 45 | 46 | 47 | class HTTPClient(object): 48 | """Base API Request Client.""" 49 | 50 | def __init__(self, verify_ssl_certs=True): 51 | """ 52 | Summary. 53 | 54 | Args: 55 | verify_ssl_certs (bool, optional): SSL is not yet supported. 56 | """ 57 | self._verify_ssl_certs = verify_ssl_certs 58 | 59 | def request(self, method, url, headers, post_data=None): 60 | """ 61 | Summary. 62 | 63 | Args: 64 | method (TYPE): Description 65 | url (TYPE): Description 66 | headers (TYPE): Description 67 | post_data (TYPE, optional): Description 68 | 69 | Raises: 70 | NotImplementedError: Description 71 | 72 | Returns: 73 | TYPE: Description 74 | """ 75 | raise NotImplementedError( 76 | 'HTTPClient subclasses must implement `request`' 77 | ) # pragma: no cover 78 | 79 | 80 | class RequestsClient(HTTPClient): # pragma: no cover 81 | """ 82 | Summary. 83 | 84 | Attributes: 85 | name (str): Description 86 | """ 87 | 88 | def request(self, method, url, headers, post_data=None): 89 | """ 90 | Summary. 91 | 92 | Args: 93 | method (TYPE): Description 94 | url (TYPE): Description 95 | headers (TYPE): Description 96 | post_data (TYPE, optional): Description 97 | 98 | Raises: 99 | TypeError: Description 100 | 101 | Returns: 102 | TYPE: Description 103 | """ 104 | self.kwargs = {} 105 | 106 | if self._verify_ssl_certs: 107 | key = os.path.join( 108 | os.path.dirname(__file__), 'data/paystack.key') 109 | cert = os.path.join( 110 | os.path.dirname(__file__), 'data/paystack.crt') 111 | self.kwargs['cert'] = (cert, key) 112 | else: 113 | self.kwargs['verify'] = False 114 | 115 | try: 116 | try: 117 | result = requests.request(method, 118 | url, 119 | headers=headers, 120 | data=json.dumps(post_data), 121 | timeout=80, 122 | **self.kwargs) 123 | except TypeError as e: # pragma: no cover 124 | raise TypeError( 125 | 'Warning: It looks like your installed version of the ' 126 | '"requests" library is not compatible with Paystack\'s ' 127 | 'usage thereof. (HINT: The most likely cause is that ' 128 | 'your "requests" library is out of date. You can fix ' 129 | 'that by running "pip install -U requests".) The ' 130 | 'underlying error was: %s' % (e,)) 131 | 132 | # This causes the content to actually be read, which could cause 133 | # e.g. a socket timeout. TODO: The other fetch methods probably 134 | # are susceptible to the same and should be updated. 135 | self._content = (lambda cont: json 136 | .loads(cont) if cont else None)(result.content) 137 | self._status_code = result.status_code 138 | 139 | except Exception as e: 140 | # Would catch just requests.exceptions.RequestException, but can 141 | # also raise ValueError, RuntimeError, etc. 142 | self._handle_request_error(e) 143 | return self._content, self._status_code, result.headers 144 | 145 | def _handle_request_error(self, e): 146 | """ 147 | Summary. 148 | 149 | Args: 150 | e (TYPE): Description 151 | 152 | Raises: 153 | error.APIConnectionError: Description 154 | 155 | Returns: 156 | TYPE: Description 157 | """ 158 | if isinstance(e, requests.exceptions.RequestException): 159 | msg = ("Unexpected error communicating with Paystack. " 160 | "If this problem persists, let me know at " 161 | "bernardojengwa@gmail.com.") 162 | err = "%s: %s" % (type(e).__name__, str(e)) 163 | else: # pragma: no cover 164 | msg = ("Unexpected error communicating with Paystack. " 165 | "It looks like there's probably a configuration " 166 | "issue locally. If this problem persists, let me " 167 | "know at bernardojengwa@gmail.com.") 168 | err = "A %s was raised" % (type(e).__name__,) 169 | if str(e): 170 | err += " with error message %s" % (str(e),) 171 | else: 172 | err += " with no error message" 173 | msg = textwrap.fill(msg) + "\n\n(Network error: %s)" % (err,) 174 | raise error.APIConnectionError(msg) 175 | -------------------------------------------------------------------------------- /paystack/data/paystack.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkDCCAngCCQC7Yv5CTumYJzANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC 3 | TkcxDjAMBgNVBAgMBUxhZ29zMQ0wCwYDVQQHDARZYWJhMRgwFgYDVQQKDA9CZXJu 4 | YXJkIE9qZW5nd2ExGDAWBgNVBAMMD0Jlcm5hcmQgT2plbmd3YTEnMCUGCSqGSIb3 5 | DQEJARYYYmVybmFyZG9qZW5nd2FAZ21haWwuY29tMB4XDTE2MDIyMjA3NTQ1NloX 6 | DTE3MDIyMTA3NTQ1NlowgYkxCzAJBgNVBAYTAk5HMQ4wDAYDVQQIDAVMYWdvczEN 7 | MAsGA1UEBwwEWWFiYTEYMBYGA1UECgwPQmVybmFyZCBPamVuZ3dhMRgwFgYDVQQD 8 | DA9CZXJuYXJkIE9qZW5nd2ExJzAlBgkqhkiG9w0BCQEWGGJlcm5hcmRvamVuZ3dh 9 | QGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALXskKGG 10 | B1PbB+P+PDa3IlW6o+pOPJOxqKeNCs4uyCoYbB33mkmL5xvqbfsVz2SBfP93V+tS 11 | 487aeZ6jdjcWlx2gHICBx9rEtLsFBMUpxLZrbGyEPnU0N+uuWJjDrlXpqn2SElkM 12 | PuDu9OGqhv/XcSOwTUVC828Tiujb0oSqQgTMSBZaHTvuAQjywVxeiZloUx5TkBLz 13 | 3p6DRuWwFBz1Ucu0ylh3s/IfUGCT0KifaKjEuLJDjCo0Ra0N1lJ9h5EsbQDeEvsD 14 | ufUVnF6nnzgmPwA3oIYtAnM+i5e2g+Wxa1oKrYGBCtI7FFNkbDUCoMr0O+xDvk3I 15 | hkgE1JIONpyAGJ8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAihNw5MMVwf2tRB4t 16 | LdMiI/sSXBsgbJnGHU2LKgiO4BgxC/MKNRj3SRo7zYT5GeAvId/F4Eoz4QL6+Eus 17 | ZEEgNo5wRW6mZ4+lIK3XS6jX5SqJQSmYFyB9EoLWzOoVvJ1V2+HxdRxCl6mvZRgv 18 | nfVbJlnS0N7a+QvQvk9BkwEJ/pDuXn/lGrsbVgYz7ErF8DwbkX5X+QUxUGSi/J0z 19 | 4z58UyKOisK6nOTUw2ahVRExACHAaSVgEG2WbXtvWqc6C+zfOhYCPxMYxqxDqKll 20 | OC2Tj8E6Ue6AC8kmRr2vBWONyGjXctK3Lsx6eUYZU9Xh9ebbzFoIKX27/Znns/kg 21 | Rg8HCg== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /paystack/data/paystack.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAteyQoYYHU9sH4/48NrciVbqj6k48k7Gop40Kzi7IKhhsHfea 3 | SYvnG+pt+xXPZIF8/3dX61Ljztp5nqN2NxaXHaAcgIHH2sS0uwUExSnEtmtsbIQ+ 4 | dTQ3665YmMOuVemqfZISWQw+4O704aqG/9dxI7BNRULzbxOK6NvShKpCBMxIFlod 5 | O+4BCPLBXF6JmWhTHlOQEvPenoNG5bAUHPVRy7TKWHez8h9QYJPQqJ9oqMS4skOM 6 | KjRFrQ3WUn2HkSxtAN4S+wO59RWcXqefOCY/ADeghi0Ccz6Ll7aD5bFrWgqtgYEK 7 | 0jsUU2RsNQKgyvQ77EO+TciGSATUkg42nIAYnwIDAQABAoIBACc5NA2/SgEoblEX 8 | 1X+uiHaXYRP5flIRsd0+KaA+seVxko37foZw5Yk9SC0kjKyMxpiF8KzKGFfW/vXX 9 | JHvIIRjAFqUWVjKyyoywDlE8Ki9yhdP3x0eZ1Jy54Fw/hGz9jka4ocAVRdFVc/Lb 10 | w3ORzdD2CBLw1m+SdRaznnYAB2/lBiYOw3uinYP4ZXlew6QtQniGBKAkXjXULTE1 11 | aHCZ9N6uVJBL5yYTmUPsiyooQ4qKxA+QH3NJklpD86kDsvPKzaBGNKHujQkP0Te9 12 | 7mZUPCLXYYK12p8qNPxFLZWEOUB/N5JTDxuQlSJWADY6ll8RZ3+SCuT/NsuLQgy9 13 | E9coRlECgYEA6VhB9NYFVM7K6/f4MRtT0jWIKkNOJ5QPLe+Y9FNUNkAwRQebVXWb 14 | XDrhpOvbehcAeolSYje1ALCQdoG7zXr8eBvAGwA2keNe8zIyWH6gZrbba5IPU7Gj 15 | 6ctIkOlwmBFCVHVaNn3pmdLmEqxdlpL5S0W2pObzFbxCQYMXWar2P7UCgYEAx5ZB 16 | cBBIrLAHjute5jC9BUeTie856Nw/NlKfoKzaAY2Psd+ePHZOQ/H9/auHIpaUi/NP 17 | nCuoS0IE5PYgaNIkew20CZZfdK1KfpHAEAqa7Nk4wz8iQqYfd3esRqOph8s4UAiE 18 | Sid3CqDCIqt8/Un+OidWqne3nz0zooflbG4A44MCgYEAgeetiGVhwG4DkEyTMpt5 19 | FazRql2f8+68aKFbsKF3X2BqvvNWKcR4gmcRWaQoopaNoKo0YBONQ94H/sGI0QMn 20 | 7KHeV+nNd3hyUsfeOV/9YlFoZEGDIN/jVfPi6THcifwJxkUyvOVqGEXN6hVlYL9f 21 | 4+TionkkWTReWmRlqni/8IUCgYAZ2Kx6vbEbo+QmrvZoKvGfiGhj2XYPYM5S05pW 22 | bWwB/XoZV9MfE1cJV4JD4n2qjgOIqCasG788l70YhYMm7+fHg7vH7hBIhNR71ZYg 23 | va7Yczz1k+x04xx6FSZbM4CV1ExjmObAz6GL38ukP7788iMZFtUTWFOQLJXOO3kL 24 | dKdqzwKBgQDZt2jqUHml8X9WalEBDO9I1rNDNbMyI2lXQz3emTzskKujhXbX6BjA 25 | CZ7v5PVd8V4ncnBrAZCnmIyWPDCZEG1lZRR0Hl2LpKS5s8/BNm/FA2ZRgOLpyDXQ 26 | 7/nrYPXOtO0csQ1M+CvjZ9DCbRLJQ//z5asm9Xtr5NxDs1bPQpoB9Q== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /paystack/error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Paystack API wrapper. 4 | 5 | @author Bernard Ojengwa. 6 | 7 | Copyright (c) 2015, Bernard Ojengwa 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | """ 35 | from requests import RequestException, ConnectionError 36 | 37 | 38 | class Error(RequestException): 39 | """Summary.""" 40 | 41 | pass 42 | 43 | 44 | class APIError(Error): 45 | """Summary.""" 46 | 47 | pass 48 | 49 | 50 | class APIConnectionError(Error, ConnectionError): 51 | """Summary.""" 52 | 53 | pass 54 | 55 | 56 | class ValidationError(Error): 57 | """Summary.""" 58 | 59 | pass 60 | 61 | 62 | class AuthorizationError(Error): 63 | """Summary.""" 64 | 65 | pass 66 | 67 | 68 | class InvalidRequestError(Error): 69 | """Summary.""" 70 | 71 | pass 72 | -------------------------------------------------------------------------------- /paystack/resource.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Paystack API wrapper. 4 | 5 | @author Bernard Ojengwa. 6 | 7 | Copyright (c) 2015, Bernard Ojengwa 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | """ 35 | 36 | import webbrowser 37 | 38 | from paystack.client import RequestsClient 39 | from paystack import error 40 | from paystack import util 41 | from paystack import version 42 | 43 | 44 | class BaseAPIResource(object): # pragma no cover 45 | """ 46 | Abstract resource class. 47 | 48 | Attributes: 49 | api_host (TYPE): Description 50 | api_secret (TYPE): Description 51 | client (TYPE): Description 52 | protocol (str): Description 53 | request_headers (TYPE): Description 54 | resource_path (TYPE): Description 55 | """ 56 | 57 | def __init__(self, api_secret=None, resource_path=None, verify_ssl=True): 58 | """ 59 | Summary. 60 | 61 | Args: 62 | api_secret (TYPE, optional): Description 63 | resource_path (TYPE, optional): Description 64 | verify_ssl (bool, optional): Description 65 | 66 | Raises: 67 | error.ValidationError: Description 68 | """ 69 | self.protocol = util.utf8('https') 70 | self.api_host = util.utf8(self.protocol + '://api.paystack.co/') 71 | 72 | if not api_secret: # pragma: no cover 73 | raise error.ValidationError("You must provide your API SECRET_KEY " 74 | "during object initialisation") 75 | 76 | if not resource_path: # pragma: no cover 77 | raise error.ValidationError("You must provide the API endpoint " 78 | "for the resource you want to access.") 79 | 80 | self.api_secret = util.utf8(api_secret) 81 | self.resource_path = self._class_name(resource_path) 82 | self.client = RequestsClient(verify_ssl_certs=verify_ssl) 83 | self.request_headers = { 84 | "Authorization": "Bearer {0}".format(self.api_secret), 85 | "Content-Type": "application/json", 86 | "user-agent": "PaystackSDK - {0}".format(version.VERSION) 87 | } 88 | self._result = {} 89 | self._status_code = None 90 | self._response_headers = {} 91 | 92 | def all(self): # pragma: no cover 93 | """ 94 | Summary. 95 | 96 | Raises: 97 | NotImplementedError: Description 98 | 99 | Returns: 100 | name (TYPE): Description 101 | """ 102 | raise NotImplementedError( 103 | 'BaseAPIResource subclass must implement this method.') 104 | 105 | def one(self, id): # pragma: no cover 106 | """ 107 | Summary. 108 | 109 | Args: 110 | id (TYPE): Description 111 | 112 | Raises: 113 | NotImplementedError: Description 114 | 115 | Returns: 116 | name (TYPE): Description 117 | """ 118 | raise NotImplementedError( 119 | 'BaseAPIResource subclass must implement this method.') 120 | 121 | def post(self, data): # pragma: no cover 122 | """ 123 | Summary. 124 | 125 | Args: 126 | data (TYPE): Description 127 | 128 | Raises: 129 | NotImplementedError: Description 130 | 131 | Returns: 132 | name (TYPE): Description 133 | """ 134 | raise NotImplementedError( 135 | 'BaseAPIResource subclass must implement this method.') 136 | 137 | def delete(self, id): # pragma: no cover 138 | """ 139 | Summary. 140 | 141 | Args: 142 | id (TYPE): Description 143 | 144 | Raises: 145 | NotImplementedError: Description 146 | 147 | Returns: 148 | name (TYPE): Description 149 | """ 150 | raise NotImplementedError( 151 | 'BaseAPIResource subclass must implement this method.') 152 | 153 | def update(self, id, data): # pragma: no cover 154 | """ 155 | Summary. 156 | 157 | Args: 158 | id (TYPE): Description 159 | data (TYPE): Description 160 | 161 | Raises: 162 | NotImplementedError: Description 163 | 164 | Returns: 165 | name (TYPE): Description 166 | """ 167 | raise NotImplementedError( 168 | 'BaseAPIResource subclass must implement this method.') 169 | 170 | @property 171 | def status(self): 172 | """ 173 | Summary. 174 | 175 | Returns: 176 | name (TYPE): Description 177 | """ 178 | return self._status_code 179 | 180 | @property 181 | def response(self): 182 | """ 183 | Summary. 184 | 185 | Returns: 186 | name (TYPE): Description 187 | """ 188 | return self._result 189 | 190 | @property 191 | def headers(self): 192 | """ 193 | Summary. 194 | 195 | Returns: 196 | name (TYPE): Description 197 | """ 198 | self._response_headers 199 | 200 | @classmethod 201 | def _class_name(cls, resource_path): 202 | """ 203 | Summary. 204 | 205 | Args: 206 | resource_path (TYPE): Description 207 | 208 | Raises: 209 | error.APIError: Description 210 | 211 | Returns: 212 | name (TYPE): Description 213 | """ 214 | if cls == BaseAPIResource: 215 | raise error.APIError( 216 | 'You cannot instantiate the API Base class directory.' 217 | 'This is an abstract class. You should perform ' 218 | 'actions on its subclasses (e.g. Charge, Customer).') 219 | return util.utf8(resource_path) 220 | 221 | 222 | class CustomerResource(BaseAPIResource): # pragma: no cover 223 | """Summary.""" 224 | 225 | def __init__(self, *args, **kwargs): 226 | """ 227 | Summary. 228 | 229 | Args: 230 | *args: Description 231 | **kwargs: Description 232 | 233 | Raises: 234 | NotImplementedError: Description 235 | """ 236 | raise NotImplementedError( 237 | 'CustomerResource: hasn\'t being implemented yet') 238 | 239 | 240 | class TransactionResource(BaseAPIResource): 241 | """ 242 | Base transaction resource Class. 243 | 244 | Encapsulate everything about a transaction instant. 245 | 246 | Attributes: 247 | access_code (string): Paystack access_code for initiating payment 248 | amount (int): Amount to pay in Kobo 249 | authorization_code (string): Paystack verification authorization_code 250 | authorization_url (string): Paystack verification authorization_url 251 | email (string): Client's email address 252 | reference (string): Unique transaction reference 253 | """ 254 | 255 | def __init__(self, api_secret, reference=None, 256 | resource_path='transaction', *args, **kwargs): 257 | """ 258 | Create a TransactionResource instance. 259 | 260 | Args: 261 | api_secret (string): Developer's API SECRET_KEY. 262 | reference (string, optional): Unique transaction reference. 263 | resource_path (str, optional): API resource_path. Do not change. 264 | *args: Extra positional arguments. 265 | **kwargs: Extra keyworded arguments. 266 | """ 267 | super(TransactionResource, self)\ 268 | .__init__(api_secret, resource_path, *args, **kwargs) 269 | self.reference = util.utf8(reference) # pragma no cover 270 | self.authorization_url = None # pragma no cover 271 | self.access_code = None # pragma no cover 272 | self.email = None # pragma no cover 273 | self.amount = None # pragma no cover 274 | self.authorization_code = None # pragma no cover 275 | 276 | def initialize(self, amount, email, 277 | plan=None, ref=None): # pragma no cover 278 | """ 279 | Transaction resource initialisation method. 280 | 281 | Args: 282 | amount (int): Amount to pay in Kobo. 283 | email (string): Client's email address. 284 | plan (string, optional): You customer billing plan. 285 | ref (string, optional): Unique transaction reference. 286 | 287 | Raises: 288 | error.APIError: Something generally bad happened... :() 289 | error.ValidationError: Bad input. 290 | 291 | Returns: 292 | response (dict): Response data from Paystack 293 | """ 294 | endpoint = '/initialize' 295 | method = 'POST' 296 | 297 | self.reference = (lambda ref: ref if ref else self.reference)(ref) 298 | self.email = email 299 | self.amount = amount 300 | payload = { 301 | "amount": amount, 302 | "email": email, 303 | "plan": plan 304 | } 305 | if self.reference: 306 | payload.update(reference=self.reference) 307 | 308 | url = self.api_host + self.resource_path + endpoint 309 | response, status, headers = self.client.request(method, url, 310 | self.request_headers, 311 | post_data=payload) 312 | self._response_headers = headers 313 | self._status_code = status 314 | self._result = response 315 | if not response.get('status', False): 316 | raise error.APIError(response.get('message')) 317 | 318 | self.authorization_url = self._result['data'].get('authorization_url') 319 | self.access_code = self._result['data'].get('access_code') 320 | 321 | return response 322 | 323 | def verify(self, ref=None): # pragma no cover 324 | """ 325 | Verify transaction instance. 326 | 327 | Args: 328 | ref (string, optional): Unique transaction reference 329 | 330 | Raises: 331 | error.APIError: Something generally bad happened... :() 332 | error.ValidationError: Bad input. 333 | 334 | Returns: 335 | response (dict): Dictionary containing payment verification details 336 | """ 337 | method = 'GET' 338 | 339 | if not ref and not self.reference: 340 | raise error.ValidationError('A unique object reference was not ' 341 | 'provided during instantiation. You' 342 | ' must provide a reference for this' 343 | ' transaction.') 344 | 345 | self.reference = (lambda ref: ref if ref else self.reference)(ref) 346 | 347 | endpoint = '/verify/' + self.reference 348 | url = self.api_host + self.resource_path + endpoint 349 | 350 | response, status, headers = self.client.request(method, url, 351 | self.request_headers 352 | ) 353 | self._response_headers = headers 354 | self._status_code = status 355 | self._result = response['data'] 356 | if not response.get('status', False): 357 | raise error.APIError(response.get('message')) 358 | 359 | self.authorization_code = self\ 360 | ._result['authorization']['authorization_code'] 361 | 362 | return response 363 | 364 | def charge(self, auth_code=None, amount=None, 365 | email=None, reference=None): # pragma no cover 366 | """ 367 | Bill a transaction to a customer's account. 368 | 369 | Args: 370 | auth_code (string, optional): Paystack verified authorization_code 371 | amount (int, optional): Amount to pay in Kobo. 372 | email (string, optional): Client's email address. 373 | reference (string, optional): Unique transaction reference. 374 | 375 | Raises: 376 | error.APIError: Something generally bad happened... :() 377 | error.ValidationError: Bad input. 378 | 379 | Returns: 380 | response (dict): Response data from Paystack 381 | """ 382 | endpoint = '/charge_authorization' 383 | method = 'POST' 384 | 385 | if not auth_code and not self.authorization_code: 386 | raise error.ValidationError('This transaction object does not' 387 | ' have an authorization code.You must' 388 | ' provide an authorization code for' 389 | ' this transaction.') 390 | if not amount and not self.amount: 391 | raise error.ValidationError('There is no amount specified for this' 392 | ' transaction. You must provide the ' 393 | ' transaction amount in Kobo.') 394 | if not email and not self.email: 395 | raise error.ValidationError('The customer\'s email address wss not' 396 | ' specified.') 397 | 398 | authorization_code = ( 399 | lambda auth_code: auth_code if auth_code else self 400 | .authorization_code)(auth_code) 401 | 402 | email = ( 403 | lambda email: email if email else self.email)(email) 404 | 405 | amount = ( 406 | lambda amount: amount if amount else self.amount)(amount) 407 | 408 | payload = { 409 | "reference": reference, 410 | "amount": amount, 411 | "email": email, 412 | "authorization_code": authorization_code 413 | } 414 | 415 | url = self.api_host + self.resource_path + endpoint 416 | response, status, headers = self.client.request(method, url, 417 | self.request_headers, 418 | post_data=payload 419 | ) 420 | self._response_headers = headers 421 | self._status_code = status 422 | self._result = response 423 | if not response.get('status', False): 424 | raise error.APIError(response.get('message')) 425 | 426 | return response 427 | 428 | def authorize(self, auth_url=None): # pragma: no cover 429 | """ 430 | Open a browser window for client to enter card details. 431 | 432 | Args: 433 | auth_url (string, optional): Paystack verified authorization_url 434 | 435 | Raises: 436 | e: Browser Error :( 437 | error.ValidationError: Bad input. 438 | 439 | Returns: 440 | None 441 | """ 442 | if not auth_url and not self.authorization_url: 443 | raise error.ValidationError('This transaction object does not' 444 | ' have an authorization url.You must' 445 | ' provide an authorization url or' 446 | 'for call the `initialize` method' 447 | ' this transaction first.') 448 | 449 | authorization_url = ( 450 | lambda auth_url: auth_url if auth_url else self 451 | .authorization_url)(auth_url) 452 | 453 | try: 454 | webbrowser.open_new_tab(authorization_url) 455 | except webbrowser.Error as e: 456 | raise e 457 | 458 | 459 | class PlanResource(BaseAPIResource): # pragma: no cover 460 | """Summary.""" 461 | 462 | def __init__(self, *args, **kwargs): 463 | """ 464 | Summary. 465 | 466 | Args: 467 | *args: Description 468 | **kwargs: Description 469 | 470 | Raises: 471 | NotImplementedError: Description 472 | """ 473 | raise NotImplementedError( 474 | 'PlanResource: hasn\'t being implemented yet') 475 | -------------------------------------------------------------------------------- /paystack/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Paystack API wrapper. 4 | 5 | @author Bernard Ojengwa. 6 | 7 | Copyright (c) 2015, Bernard Ojengwa 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | 3. Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | """ 35 | import sys 36 | 37 | try: 38 | import unicode 39 | except Exception as e: 40 | # Using Python < 3 41 | pass 42 | 43 | 44 | def utf8(value): 45 | """String encoding utility.""" 46 | if sys.version_info < (3, 0): 47 | if isinstance(value, unicode): # pragma: no cover 48 | return value.encode('utf-8') 49 | 50 | return value 51 | -------------------------------------------------------------------------------- /paystack/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '1.5.0' 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 2 | simplejson==3.8.2 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | description-file = README.rst 6 | 7 | [easy_install] 8 | 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | from codecs import open 4 | from os import path 5 | from paystack.version import VERSION 6 | 7 | here = path.abspath(path.dirname(__file__)) 8 | print(here) 9 | # Get the long description from the README file 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | 14 | setup( 15 | name='paystack', 16 | version=VERSION, 17 | description='A Paystack API wrapper in Python', 18 | long_description=long_description, 19 | url='https://github.com/ojengwa/paystack', 20 | download_url='https://github.com/ojengwa/paystack/tarball/' + VERSION, 21 | license='BSD', 22 | classifiers=[ 23 | "Development Status :: 5 - Production/Stable", 24 | 'Intended Audience :: Developers', 25 | 'Topic :: Software Development :: Libraries', 26 | 'Programming Language :: Python :: 2', 27 | 'Programming Language :: Python :: 2.6', 28 | 'Programming Language :: Python :: 2.7', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.2', 31 | 'Programming Language :: Python :: 3.3', 32 | 'Programming Language :: Python :: 3.4', 33 | 'Programming Language :: Python :: 3.5', 34 | 35 | ], 36 | keywords='', 37 | packages=find_packages(exclude=['docs', 'tests', 'site']), 38 | package_data={ 39 | 'paystack': [ 40 | 'data/paystack.crt', 41 | 'data/paystack.key' 42 | ] 43 | }, 44 | include_package_data=True, 45 | author='Bernard Ojengwa', 46 | install_requires=[ 47 | 'requests', 48 | 'simplejson' 49 | ], 50 | author_email='bernardojengwa@gmail.com' 51 | ) 52 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==4.0.3 2 | coveralls==1.1 3 | docopt==0.6.2 4 | cov-core==1.15.0 5 | nose2==0.6.0 6 | nose2-cov==1.0a4 7 | six==1.10.0 8 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojengwa/paystack/9c461d6ed3423432eb4913bbbe153680135bf760/tests/__init__.py -------------------------------------------------------------------------------- /tests/paystack_test.py: -------------------------------------------------------------------------------- 1 | """TestCases for the PaystackSDK.""" 2 | 3 | import unittest 4 | import random 5 | import string 6 | 7 | from paystack import util 8 | from paystack.resource import (TransactionResource, 9 | ) 10 | from paystack.client import (HTTPClient, 11 | RequestsClient) 12 | 13 | 14 | class PaystackSDKTest(unittest.TestCase): 15 | """TestCase class for the PaystackSDK.""" 16 | 17 | def setUp(self): 18 | """ 19 | Test set up method. 20 | 21 | Returns: 22 | None 23 | """ 24 | pass 25 | 26 | def tearDown(self): 27 | """ 28 | Test tear down method. 29 | 30 | Returns: 31 | None 32 | """ 33 | pass 34 | 35 | def test_version(self): 36 | """ 37 | Summary. 38 | 39 | Returns: 40 | name (TYPE): Description 41 | """ 42 | from paystack.version import VERSION 43 | self.assertIsInstance(VERSION, str) 44 | 45 | 46 | class TransactionResourceTest(unittest.TestCase): 47 | """TestCase class for the PaystackSDK. 48 | 49 | Attributes: 50 | client (TYPE): Description 51 | plan (str): Description 52 | random_ref (TYPE): Description 53 | response (TYPE): Description 54 | secret_key (str): Description 55 | test_amount (int): Description 56 | test_email (str): Description 57 | """ 58 | 59 | def setUp(self): 60 | """ 61 | Test set up method. 62 | 63 | Returns: 64 | None 65 | """ 66 | rand = ''.join( 67 | [random 68 | .choice(string.ascii_letters + string.digits) for n in range(16)]) 69 | self.secret_key = 'sk_test_16c58271c29a007970de0353d8a47868df727cd0' 70 | self.random_ref = util.utf8(rand) 71 | self.test_email = 'bernard@disgui.se' 72 | self.test_amount = 5000 73 | self.plan = 'Basic' 74 | self.client = TransactionResource(self.secret_key, self.random_ref) 75 | # self.client.initialize(util.utf8(self.test_amount), 76 | # util.utf8(self.test_email), 77 | # util.utf8(self.plan)) 78 | 79 | def tearDown(self): 80 | """ 81 | Test tear down method. 82 | 83 | Returns: 84 | None 85 | """ 86 | pass 87 | 88 | def test_instance_of(self): 89 | """ 90 | Summary. 91 | 92 | Returns: 93 | name (TYPE): Description 94 | """ 95 | self.assertIsInstance(self.client, TransactionResource) 96 | 97 | def test_has_resource_path(self): 98 | """ 99 | Summary. 100 | 101 | Returns: 102 | name (TYPE): Description 103 | """ 104 | self.assertEqual(self.client.resource_path, 'transaction') 105 | 106 | def test_has_reference(self): 107 | """ 108 | Summary. 109 | 110 | Returns: 111 | name (TYPE): Description 112 | """ 113 | self.assertEqual(self.client.reference, self.random_ref) 114 | 115 | @unittest.skip 116 | def test_has_response(self): 117 | """ 118 | Summary. 119 | 120 | Returns: 121 | name (TYPE): Description 122 | """ 123 | self.response = self.client.initialize(self.test_amount, 124 | self.test_email, 125 | self.plan) 126 | self.assertIsNotNone(self.response) 127 | 128 | 129 | class UtilTest(unittest.TestCase): 130 | """TestCase class for the util module. 131 | 132 | Attributes: 133 | value (TYPE): Description 134 | """ 135 | 136 | def setUp(self): 137 | """ 138 | Test set up method. 139 | 140 | Returns: 141 | None 142 | """ 143 | self.value = util.utf8('138186') 144 | 145 | def tearDown(self): 146 | """ 147 | Test tear down method. 148 | 149 | Returns: 150 | None 151 | """ 152 | pass 153 | 154 | def test_utf8(self): 155 | """ 156 | Summary. 157 | 158 | Returns: 159 | name (TYPE): Description 160 | """ 161 | self.assertIsInstance(self.value, str) 162 | 163 | def test_equal(self): 164 | """ 165 | Summary. 166 | 167 | Returns: 168 | name (TYPE): Description 169 | """ 170 | self.value = util.utf8(138186) 171 | self.assertEqual(self.value, 138186) 172 | 173 | 174 | class HTTPClientTest(unittest.TestCase): 175 | """TestCase class for the HTTPClient class. 176 | 177 | Attributes: 178 | client (TYPE): Description 179 | headers (dict): Description 180 | method (str): Description 181 | url (str): Description 182 | """ 183 | 184 | def setUp(self): 185 | """ 186 | Test set up method. 187 | 188 | Returns: 189 | None 190 | """ 191 | self.client = HTTPClient() 192 | self.method = 'GET' 193 | self.url = 'http://github.com/ojengwa' 194 | self.headers = {} 195 | 196 | def tearDown(self): 197 | """ 198 | Test tear down method. 199 | 200 | Returns: 201 | None 202 | """ 203 | pass 204 | 205 | def test_isinstance(self): 206 | """ 207 | Summary. 208 | 209 | Returns: 210 | name (TYPE): Description 211 | """ 212 | self.assertIsInstance(self.client, HTTPClient) 213 | 214 | def test_verify_ssl(self): 215 | """ 216 | Summary. 217 | 218 | Returns: 219 | name (TYPE): Description 220 | """ 221 | self.assertEqual(self.client._verify_ssl_certs, True) 222 | 223 | 224 | class RequestsClientTest(unittest.TestCase): 225 | """TestCase class for the HTTPClient class. 226 | 227 | Attributes: 228 | client (TYPE): Description 229 | headers (dict): Description 230 | method (str): Description 231 | url (str): Description 232 | """ 233 | 234 | def setUp(self): 235 | """ 236 | Test set up method. 237 | 238 | Returns: 239 | None 240 | """ 241 | self.client = RequestsClient() 242 | self.method = 'GET' 243 | self.url = 'http://github.com/ojengwa' 244 | self.headers = {} 245 | 246 | def tearDown(self): 247 | """ 248 | Test tear down method. 249 | 250 | Returns: 251 | None 252 | """ 253 | pass 254 | 255 | def test_isinstance(self): 256 | """ 257 | Summary. 258 | 259 | Returns: 260 | name (TYPE): Description 261 | """ 262 | self.assertIsInstance(self.client, RequestsClient) 263 | 264 | def test_verify_ssl(self): 265 | """ 266 | Summary. 267 | 268 | Returns: 269 | name (TYPE): Description 270 | """ 271 | self.assertEqual(self.client._verify_ssl_certs, True) 272 | 273 | @unittest.skip 274 | def test_kwargs_is_verify(self): 275 | """ 276 | Summary. 277 | 278 | Returns: 279 | name (TYPE): Description 280 | """ 281 | verify = self.kwargs['verify'] 282 | self.assertTrue(verify) 283 | 284 | @unittest.skip 285 | def test_kwargs_is_not_verify(self): 286 | """ 287 | Summary. 288 | 289 | Returns: 290 | name (TYPE): Description 291 | """ 292 | self._verify_ssl_certs = False 293 | self.client.request(self.method, self.url, self.headers) 294 | verify = self.kwargs['verify'] 295 | self.assertFalse(verify) 296 | 297 | @unittest.skip 298 | def test_status_code(self): 299 | """ 300 | Summary. 301 | 302 | Returns: 303 | name (TYPE): Description 304 | """ 305 | self.client.request(self.method, self.url, self.headers) 306 | self.assertIsInstance(self._status_code, int) 307 | 308 | @unittest.skip 309 | def test_response(self): 310 | """ 311 | Summary. 312 | 313 | Returns: 314 | name (TYPE): Description 315 | """ 316 | response = self.client.request(self.method, self.url, self.headers) 317 | self.assertIsInstance(response, tuple) 318 | 319 | @unittest.skip 320 | def test_not_implemented(self): 321 | """ 322 | Summary. 323 | 324 | Returns: 325 | name (TYPE): Description 326 | """ 327 | self.assertRaises(self.client.request(self.method, 328 | self.url, 329 | self.headers 330 | ), NotImplementedError) 331 | --------------------------------------------------------------------------------