diff --git a/.gitignore b/.gitignore index 53179ac..14fdd76 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# IDE +*.idea/ + # Distribution / packaging .Python env/ @@ -62,4 +65,4 @@ target/ .ipynb_checkpoints # Vscode config -.vscode \ No newline at end of file +.vscode diff --git a/README.md b/README.md index 254f8da..57add01 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ response = transaction.verify(refcode) #Verify a transaction given a reference customer = Customer(authorization_key="sk_myauthorizationkeyfromthepaystackguys") response = customer.create("customer2@gmail.com", "John", "Doe", phone="080123456789") #Add new customer -response = customer.getone(1234) #Get customer with id of 1234 +response = customer.getone("CUS_xxxxyy") #Get customer with customer code of CUS_xxxxyy response = customer.getall() #Get all customers @@ -53,7 +53,4 @@ response = plan.getone(240) #Get plan with id of 240 response = plan.getall() #Get all plans ``` -#Todo -Write more tests - diff --git a/pypaystack/baseapi.py b/pypaystack/baseapi.py index 2f5f44f..f3a016f 100644 --- a/pypaystack/baseapi.py +++ b/pypaystack/baseapi.py @@ -6,7 +6,6 @@ class BaseAPI(object): - """ Base class for the pypaystack python API wrapper for paystack Not to be instantiated directly. @@ -15,7 +14,6 @@ class BaseAPI(object): _CONTENT_TYPE = "application/json" _BASE_END_POINT = "https://api.paystack.co" - def __init__(self, authorization_key=None): if authorization_key: self._PAYSTACK_AUTHORIZATION_KEY = authorization_key @@ -24,18 +22,15 @@ def __init__(self, authorization_key=None): if not self._PAYSTACK_AUTHORIZATION_KEY: raise MissingAuthKeyError("Missing Authorization key argument or env variable") - def _url(self, path): return self._BASE_END_POINT + path - def _headers(self): - return { - "Content-Type": self._CONTENT_TYPE, - "Authorization": "Bearer " + self._PAYSTACK_AUTHORIZATION_KEY, - "user-agent": "pyPaystack-{}".format(version.__version__) - } - + return { + "Content-Type": self._CONTENT_TYPE, + "Authorization": "Bearer " + self._PAYSTACK_AUTHORIZATION_KEY, + "user-agent": "pyPaystack-{}".format(version.__version__) + } def _parse_json(self, response_obj): """ @@ -49,11 +44,8 @@ def _parse_json(self, response_obj): status = parsed_response.get('status', None) message = parsed_response.get('message', None) data = parsed_response.get('data', None) - #if data: - # message = data.get('gateway_response', None) return response_obj.status_code, status, message, data - def _handle_request(self, method, url, data=None): """ @@ -61,11 +53,11 @@ def _handle_request(self, method, url, data=None): Returns a python tuple of status code, status(bool), message, data """ method_map = { - 'GET':requests.get, - 'POST':requests.post, - 'PUT':requests.put, - 'DELETE':requests.delete - } + 'GET': requests.get, + 'POST': requests.post, + 'PUT': requests.put, + 'DELETE': requests.delete + } payload = json.dumps(data) if data else data request = method_map.get(method) @@ -81,6 +73,4 @@ def _handle_request(self, method, url, data=None): return self._parse_json(response) else: body = response.json() - return response.status_code, body.get('status'), body.get('message'), body.get('errors') - - + return response.status_code, body.get('status'), body.get('message'), body.get('errors') diff --git a/pypaystack/customers.py b/pypaystack/customers.py index 5535634..0544459 100644 --- a/pypaystack/customers.py +++ b/pypaystack/customers.py @@ -1,26 +1,25 @@ from .baseapi import BaseAPI + class Customer(BaseAPI): - def create(self, email, first_name=None, last_name=None, phone=None): """ Creates a new paystack customer account - + args: email -- Customer's email address first_name-- Customer's first name (Optional) last_name-- Customer's last name (Optional) - phone -- optional + phone -- optional """ url = self._url("/customer/") payload = { - "first_name": first_name, - "last_name": last_name, - "email": email, - "phone": phone - } - return self._handle_request('POST', url, payload) - + "first_name": first_name, + "last_name": last_name, + "email": email, + "phone": phone, + } + return self._handle_request("POST", url, payload) def update(self, user_id, email, first_name=None, last_name=None, phone=None): """ @@ -31,37 +30,34 @@ def update(self, user_id, email, first_name=None, last_name=None, phone=None): email -- Customer's email address first_name-- Customer's first name (Optional) last_name-- Customer's last name (Optional) - phone -- optional + phone -- optional """ url = self._url("/customer/{}/".format(user_id)) payload = { - "first_name": first_name, - "last_name": last_name, - "email": email, - "phone": phone - } - return self._handle_request('PUT', url, payload) - + "first_name": first_name, + "last_name": last_name, + "email": email, + "phone": phone, + } + return self._handle_request("PUT", url, payload) def getall(self, pagination=10): """ Gets all the customers we have at paystack in steps of (default) 50 records per page. We can provide an optional pagination to indicate how many customer records we want to fetch per time - + args: pagination -- Count of data to return per call """ - url = self._url("/customer/?perPage="+str(pagination)) - return self._handle_request('GET', url) + url = self._url("/customer/?perPage=" + str(pagination)) + return self._handle_request("GET", url) - - def getone(self, user_id): + def getone(self, customer_code): """ Gets the customer with the given user id - + args: - user_id -- The customer's user id + customer_code -- The customer's code """ - url = self._url("/customer/{}/".format(user_id)) - return self._handle_request('GET', url) - + url = self._url("/customer/{}/".format(customer_code)) + return self._handle_request("GET", url) diff --git a/pypaystack/tests/__init__.py b/pypaystack/tests/__init__.py deleted file mode 100644 index 71c2f13..0000000 --- a/pypaystack/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -import time -from unittest import TestCase -from uuid import uuid4 - -from pypaystack import Customer, Plan, Transaction - -test_auth_key = os.getenv('PAYSTACK_AUTHORIZATION_KEY') diff --git a/pypaystack/tests/test_00_transaction.py b/pypaystack/tests/test_00_transaction.py deleted file mode 100644 index 83e19c6..0000000 --- a/pypaystack/tests/test_00_transaction.py +++ /dev/null @@ -1,37 +0,0 @@ -from . import test_auth_key, Transaction, TestCase - - -class TestTransaction(TestCase): - def setUp(self): - super(TestTransaction, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.transaction = Transaction(authorization_key=test_auth_key) - - def test_charge_and_verify(self): - """ - Integration test for initiating and verifying transactions - """ - transaction_details = { - "amount": 1000*100, - "email": "test_customer@mail.com" - } - - def initialize_transaction(): - (status_code, status, response_msg, - initialized_transaction_data) = self.transaction.initialize(**transaction_details) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Authorization URL created') - return initialized_transaction_data - - def verify_transaction(): - (status_code, status, response_msg, response_data) = self.transaction.verify( - reference=initialized_transaction_data['reference']) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Verification successful') - self.assertEqual(response_data.get('customer') - ['email'], transaction_details['email']) - - initialized_transaction_data = initialize_transaction() - verify_transaction() diff --git a/pypaystack/tests/test_01_transactions_records.py b/pypaystack/tests/test_01_transactions_records.py deleted file mode 100644 index ff39d93..0000000 --- a/pypaystack/tests/test_01_transactions_records.py +++ /dev/null @@ -1,43 +0,0 @@ -from . import test_auth_key, Transaction, TestCase - - -class TestTransactionRecords(TestCase): - - def setUp(self): - super(TestTransactionRecords, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.transaction = Transaction(authorization_key=test_auth_key) - - def test_transaction_records(self): - """ - Integration test for retriving all transactions and getting single transaction details - """ - def retrieve_all_transactions(): - (status_code, status, response_msg, - all_transactions) = self.transaction.getall() - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Transactions retrieved') - self.assertIsInstance(all_transactions, list) - return all_transactions - - def retrieve_one_transaction(): - one_transaction = all_transactions[0] - (status_code, status, response_msg, transaction_data) = self.transaction.getone( - transaction_id=one_transaction['id']) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Transaction retrieved') - - # removing authorization field as content is not concurrent in transaction_list and transaction_data - if 'authorization' in transaction_data.keys(): - transaction_data.pop('authorization') - if 'authorization' in one_transaction.keys(): - one_transaction.pop('authorization') - - # assert if equal transaction keys are equal - self.assertEqual(transaction_data.keys(), - one_transaction.keys()) - - all_transactions = retrieve_all_transactions() - retrieve_one_transaction() diff --git a/pypaystack/tests/test_02_customer.py b/pypaystack/tests/test_02_customer.py deleted file mode 100644 index bac4c3b..0000000 --- a/pypaystack/tests/test_02_customer.py +++ /dev/null @@ -1,48 +0,0 @@ -from . import test_auth_key, uuid4, Customer, TestCase - - -class TestCustomer(TestCase): - def setUp(self): - super(TestCustomer, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.customer = Customer(authorization_key=test_auth_key) - - def test_customer_setup_and_update(self): - """ - Integration test for creating customer and updating created customer details - """ - # using random generator for email id to ensure email is unique, thus ensuring success on retests - user_email = f"{uuid4()}@mail.com" - user_details = {"email": user_email, - "first_name": "Test", - "last_name": "Customer", - "phone": "08012345678"} - updated_user_details = { - "email": user_email, - "first_name": "Updated", - "last_name": "Customer", - "phone": "080987654321"} - - def create_customer(): - (status_code, status, response_msg, - created_customer_data) = self.customer.create(**user_details) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Customer created') - # assert if subset - self.assertLessEqual( - user_details.items(), created_customer_data.items()) - return created_customer_data - - def update_customer(): - (status_code, status, response_msg, updated_customer_data) = self.customer.update( - user_id=created_customer_data['id'], **updated_user_details) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Customer updated') - # assert if subset - self.assertLessEqual( - updated_user_details.items(), updated_customer_data.items()) - - created_customer_data = create_customer() - update_customer() diff --git a/pypaystack/tests/test_03_customer_records.py b/pypaystack/tests/test_03_customer_records.py deleted file mode 100644 index 68d6270..0000000 --- a/pypaystack/tests/test_03_customer_records.py +++ /dev/null @@ -1,34 +0,0 @@ -from . import test_auth_key, Customer, TestCase - - -class TestCustomerRecords(TestCase): - def setUp(self): - super(TestCustomerRecords, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.customer = Customer(authorization_key=test_auth_key) - - def test_customers_records(self): - """ - Integration test for getting all customers and getting single customer details - """ - def retrieve_all_customers(): - (status_code, status, response_msg, - customers_list) = self.customer.getall() - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Customers retrieved') - self.assertIsInstance(customers_list, list) - return customers_list - - def retrieve_one_customer(): - one_customer = customers_list[0] - (status_code, status, response_msg, - customer_data) = self.customer.getone(one_customer['id']) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Customer retrieved') - # assert if subset - self.assertLessEqual(one_customer.items(), customer_data.items()) - - customers_list = retrieve_all_customers() - retrieve_one_customer() diff --git a/pypaystack/tests/test_04_plan.py b/pypaystack/tests/test_04_plan.py deleted file mode 100644 index 540f119..0000000 --- a/pypaystack/tests/test_04_plan.py +++ /dev/null @@ -1,44 +0,0 @@ -from . import test_auth_key, Plan, TestCase - - -class TestPlan(TestCase): - def setUp(self): - super(TestPlan, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.plan = Plan(authorization_key=test_auth_key) - - def test_plan_setup_and_update(self): - """ - Integration test for creating plan and updating created plan - """ - - initial_plan_detail = {'name': 'test_plan_1', - 'amount': 1000*100, - 'interval': 'weekly'} - - updated_plan_details = {'name': 'test_plan_1', - 'amount': 300*100, - 'interval': 'daily'} - - def create_plan(): - (status_code, status, response_msg, - created_plan_data) = self.plan.create(**initial_plan_detail) - self.assertEqual(status_code, 201) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Plan created') - # assert if subset - self.assertLessEqual( - initial_plan_detail.items(), created_plan_data.items()) - return created_plan_data - - def update_plan(): - (status_code, status, response_msg, updated_plan_data) = self.plan.update( - plan_id=created_plan_data['id'], **updated_plan_details) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual( - response_msg, 'Plan updated. 0 subscription(s) affected') - self.assertEqual(updated_plan_data, None) - - created_plan_data = create_plan() - update_plan() diff --git a/pypaystack/tests/test_05_plans_records.py b/pypaystack/tests/test_05_plans_records.py deleted file mode 100644 index 414801a..0000000 --- a/pypaystack/tests/test_05_plans_records.py +++ /dev/null @@ -1,34 +0,0 @@ -from . import test_auth_key, Plan, TestCase - - -class TestPlansRecord(TestCase): - def setUp(self): - super(TestPlansRecord, self).setUp() - self.assertNotEqual(test_auth_key, None) - self.plan = Plan(authorization_key=test_auth_key) - - def test_plans_records(self): - """ - Integration test for getting all plans and getting single plan details - """ - def retrieve_all_plans(): - (status_code, status, response_msg, - plans_list) = self.plan.getall() - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Plans retrieved') - self.assertIsInstance(plans_list, list) - return plans_list - - def retrieve_one_plan(): - one_plan = plans_list[0] - (status_code, status, response_msg, - plan_data) = self.plan.getone(one_plan['id']) - self.assertEqual(status_code, 200) - self.assertEqual(status, True) - self.assertEqual(response_msg, 'Plan retrieved') - # assert if subset - self.assertLessEqual(one_plan.items(), plan_data.items()) - - plans_list = retrieve_all_plans() - retrieve_one_plan() diff --git a/pypaystack/transactions.py b/pypaystack/transactions.py index 7d41da7..94e7af7 100644 --- a/pypaystack/transactions.py +++ b/pypaystack/transactions.py @@ -2,49 +2,47 @@ from . import utils from .errors import InvalidDataError - -class Transaction(BaseAPI): +class Transaction(BaseAPI): def getall(self, start_date=None, end_date=None, status=None, pagination=10): """ Gets all your transactions - + args: - pagination -- Count of data to return per call + pagination -- Count of data to return per call from: start date to: end date """ url = self._url("/transaction/?perPage={}".format(pagination)) - url = url+"&status={}".format(status) if status else url - url = url+"&from={}".format(start_date) if start_date else url - url = url+"&to={}".format(end_date) if end_date else url - - return self._handle_request('GET', url) + url = url + "&status={}".format(status) if status else url + url = url + "&from={}".format(start_date) if start_date else url + url = url + "&to={}".format(end_date) if end_date else url + return self._handle_request("GET", url) def getone(self, transaction_id): """ Gets one customer with the given transaction id - + args: Transaction_id -- transaction we want to get """ url = self._url("/transaction/{}/".format(transaction_id)) - return self._handle_request('GET', url) - + return self._handle_request("GET", url) def totals(self): """ Gets transaction totals """ url = self._url("/transaction/totals/") - return self._handle_request('GET', url) + return self._handle_request("GET", url) - - def initialize(self, email, amount, plan=None, reference=None, channel=None, metadata=None): + def initialize( + self, email, amount, plan=None, reference=None, channel=None, metadata=None + ): """ Initialize a transaction and returns the response - + args: email -- Customer's email address amount -- Amount to charge @@ -56,24 +54,29 @@ def initialize(self, email, amount, plan=None, reference=None, channel=None, met amount = utils.validate_amount(amount) if not email: - raise InvalidDataError("Customer's Email is required for initialization") + raise InvalidDataError("Customer's Email is required for initialization") url = self._url("/transaction/initialize") payload = { - "email":email, + "email": email, "amount": amount, - "reference": reference, - "plan": plan, - "channels": channel, - "metadata": {"custom_fields":metadata} } - return self._handle_request('POST', url, payload) + if plan: + payload.update({"plan": plan}) + if channel: + payload.update({"channels": channel}) + if reference: + payload.update({"reference": reference}) + if metadata: + payload.update({"metadata": {"custom_fields": metadata}}) + + return self._handle_request("POST", url, payload) def charge(self, email, auth_code, amount, reference=None, metadata=None): """ Charges a customer and returns the response - + args: auth_code -- Customer's auth code email -- Customer's email address @@ -87,20 +90,22 @@ def charge(self, email, auth_code, amount, reference=None, metadata=None): raise InvalidDataError("Customer's Email is required to charge") if not auth_code: - raise InvalidDataError("Customer's Auth code is required to charge") - + raise InvalidDataError("Customer's Auth code is required to charge") + url = self._url("/transaction/charge_authorization") payload = { - "authorization_code":auth_code, - "email":email, + "authorization_code": auth_code, + "email": email, "amount": amount, - "reference": reference, - "metadata": {"custom_fields":metadata} } - return self._handle_request('POST', url, payload) + if reference: + payload.update({"reference": reference}) + if metadata: + payload.update({"metadata": {"custom_fields": metadata}}) + + return self._handle_request("POST", url, payload) - def verify(self, reference): """ Verifies a transaction using the provided reference number @@ -108,20 +113,18 @@ def verify(self, reference): args: reference -- reference of the transaction to verify """ - + reference = str(reference) url = self._url("/transaction/verify/{}".format(reference)) - return self._handle_request('GET', url) - + return self._handle_request("GET", url) def fetch_transfer_banks(self): """ Fetch transfer banks """ - - url = self._url("/bank") - return self._handle_request('GET', url) + url = self._url("/bank") + return self._handle_request("GET", url) def create_transfer_customer(self, bank_code, account_number, account_name): """ @@ -129,15 +132,13 @@ def create_transfer_customer(self, bank_code, account_number, account_name): """ url = self._url("/transferrecipient") payload = { - "type":"nuban", - "currency":"NGN", + "type": "nuban", + "currency": "NGN", "bank_code": bank_code, "account_number": account_number, - "name":account_name, + "name": account_name, } - return self._handle_request('POST', url, payload) - - + return self._handle_request("POST", url, payload) def transfer(self, recipient_code, amount, reason, reference=None): """ @@ -147,15 +148,12 @@ def transfer(self, recipient_code, amount, reason, reference=None): url = self._url("/transfer") payload = { "amount": amount, - "reason": reason, - "recipient": recipient_code, + "reason": reason, + "recipient": recipient_code, "source": "balance", - "currency":"NGN", + "currency": "NGN", } if reference: - payload.update({"reference": reference}) - - return self._handle_request('POST', url, payload) - - + payload.update({"reference": reference}) + return self._handle_request("POST", url, payload) diff --git a/pypaystack/version.py b/pypaystack/version.py index 1eac56e..141f7db 100644 --- a/pypaystack/version.py +++ b/pypaystack/version.py @@ -1,5 +1,5 @@ __title__ = 'pypaystack' -__version__ = '1.23' +__version__ = '1.24' __author__ = 'Edward Popoola' __license__ = 'MIT' __copyright__ = 'Copyright 2016. Edward Popoola' diff --git a/tests/test_03_customer_records.py b/tests/test_03_customer_records.py index 68d6270..1b43ecd 100644 --- a/tests/test_03_customer_records.py +++ b/tests/test_03_customer_records.py @@ -23,7 +23,7 @@ def retrieve_all_customers(): def retrieve_one_customer(): one_customer = customers_list[0] (status_code, status, response_msg, - customer_data) = self.customer.getone(one_customer['id']) + customer_data) = self.customer.getone(one_customer['customer_code']) self.assertEqual(status_code, 200) self.assertEqual(status, True) self.assertEqual(response_msg, 'Customer retrieved')