From 527dde13c83a06b32a5072f78ec06d2ffb0eeffd Mon Sep 17 00:00:00 2001 From: Nils Le Roux Date: Mon, 19 Oct 2015 11:59:38 +0200 Subject: [PATCH 01/15] Typo --- payplug/test/test_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payplug/test/test_notifications.py b/payplug/test/test_notifications.py index d6843e1..475e0c8 100644 --- a/payplug/test/test_notifications.py +++ b/payplug/test/test_notifications.py @@ -27,7 +27,7 @@ def test_treat_unknown_api_resource(self): with pytest.raises(exceptions.UnknownAPIResource): notifications.treat('{"id": "payment_id", "object": "bouillabaisse"}') - def test_treat_invalide_api_resource(self): + def test_treat_invalid_api_resource(self): with pytest.raises(exceptions.UnknownAPIResource) as excinfo: notifications.treat('{"this_resource": "has_no_id", "object": "payment"}') assert str(excinfo.value) == 'The API resource provided is invalid.' From 2d0f3f39e1e1406b829a63d2b43a7a32d36db3c0 Mon Sep 17 00:00:00 2001 From: Nils Le Roux Date: Thu, 22 Oct 2015 19:06:34 +0200 Subject: [PATCH 02/15] Fixed tests and added ability to get item with square brackets for API resource collections --- payplug/__version__.py | 2 +- payplug/resources.py | 4 +++- payplug/test/test_network/test_urllib_requests.py | 2 +- .../test/test_resources/test_resource_collection.py | 13 +++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/payplug/__version__.py b/payplug/__version__.py index c1903b4..6fde8f8 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.1.0' +__version__ = '1.1.1' diff --git a/payplug/resources.py b/payplug/resources.py index 09c05ef..6213552 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -263,5 +263,7 @@ def __iter__(self): def next(self): return next(self._iterator) - __next__ = next # Python 3 compatibility + + def __getitem__(self, item): + return self.data[item] diff --git a/payplug/test/test_network/test_urllib_requests.py b/payplug/test/test_network/test_urllib_requests.py index b3b6d56..417753a 100644 --- a/payplug/test/test_network/test_urllib_requests.py +++ b/payplug/test/test_network/test_urllib_requests.py @@ -41,7 +41,7 @@ def test_do_request_ok(self, urllib_request_mock, config_mock, json_dumps_mock, response = request.do_request('GET', 'http://example.com', {}, {'some': 'data'}) urllib_request_mock.assert_called_once_with('http://example.com', '{"some":"data"}', {}) - urllib_urlopen_200_fixture.assert_called_once_with(urllib_request_object_mock, cafile='cacert_path') + urllib_urlopen_200_fixture.assert_called_once_with(urllib_request_object_mock) assert ('OK', 200, {'header': 'header_value'}) == response diff --git a/payplug/test/test_resources/test_resource_collection.py b/payplug/test/test_resources/test_resource_collection.py index c6dcb57..3a4cc43 100644 --- a/payplug/test/test_resources/test_resource_collection.py +++ b/payplug/test/test_resources/test_resource_collection.py @@ -68,3 +68,16 @@ def test_iter(self, api_response): resource = APIResourceCollection(Payment, **api_response) for payment in iter(resource): assert isinstance(payment, Payment) + + def test_get_item(self, api_response): + resource = APIResourceCollection(Payment, **api_response) + + assert resource[0].id == "pay_5iHMDxy4ABR4YBVW4UscIn" + assert resource[0].object == "payment" + assert resource[0].is_live is True + assert resource[0].amount == 3300 + + assert resource[1].id == "pay_3uFHHU3949uUF2A98s0CqE" + assert resource[1].object == "payment" + assert resource[1].is_live is False + assert resource[1].amount == 44012 From a8c8c29efa8fc356e1bdd4b9a39bf368652ef67a Mon Sep 17 00:00:00 2001 From: Nils Le Roux Date: Thu, 22 Oct 2015 19:09:37 +0200 Subject: [PATCH 03/15] PEP8 commit --- payplug/test/test_network/test_urllib_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/payplug/test/test_network/test_urllib_requests.py b/payplug/test/test_network/test_urllib_requests.py index 417753a..9908b09 100644 --- a/payplug/test/test_network/test_urllib_requests.py +++ b/payplug/test/test_network/test_urllib_requests.py @@ -7,6 +7,7 @@ from payplug import network from payplug.test import TestBase + @pytest.mark.xfail(sys.version_info < (2, 7, 9), reason="Can't set ca_file easily with urllib.") class TestUrllibRequest(TestBase): @pytest.fixture(scope='class') From 8aeece840decc45de617b35210d6c87432dc1bab Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 10:14:47 +0200 Subject: [PATCH 04/15] Improve routes + Add routes to customer and cards. --- payplug/__init__.py | 14 +++--- payplug/config.py | 2 - payplug/resources.py | 22 ++++++++-- payplug/routes.py | 23 +++++----- payplug/test/test_init/test_dao_payment.py | 8 ++-- payplug/test/test_init/test_dao_refund.py | 4 +- payplug/test/test_resources/test_card.py | 40 +++++++++++++++++ payplug/test/test_resources/test_customer.py | 46 ++++++++++++++++++++ payplug/test/test_resources/test_payment.py | 2 +- payplug/test/test_resources/test_refund.py | 5 ++- setup.py | 2 +- 11 files changed, 134 insertions(+), 34 deletions(-) create mode 100755 payplug/test/test_resources/test_card.py create mode 100755 payplug/test/test_resources/test_customer.py diff --git a/payplug/__init__.py b/payplug/__init__.py index ed47ffe..d6dd2ba 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -45,7 +45,7 @@ def retrieve(payment_id): :rtype: resources.Payment """ http_client = HttpClient() - response, __ = http_client.get(routes.url(routes.RETRIEVE_PAYMENT, payment_id=payment_id)) + response, __ = http_client.get(routes.url(routes.PAYMENT_RESOURCE, resource_id=payment_id)) return resources.Payment(**response) @staticmethod @@ -60,7 +60,7 @@ def abort(payment_id): :rtype: resources.Payment """ http_client = HttpClient() - response, __ = http_client.patch(routes.url(routes.ABORT_PAYMENT, payment_id=payment_id), {'abort': True}) + response, __ = http_client.patch(routes.url(routes.PAYMENT_RESOURCE, resource_id=payment_id), {'abort': True}) return resources.Payment(**response) @staticmethod @@ -74,7 +74,7 @@ def create(**data): :rtype resources.Payment """ http_client = HttpClient() - response, _ = http_client.post(routes.url(routes.CREATE_PAYMENT), data) + response, _ = http_client.post(routes.url(routes.PAYMENT_RESOURCE), data) return resources.Payment(**response) @staticmethod @@ -97,7 +97,7 @@ def list(per_page=None, page=None): pagination = dict((key, value) for (key, value) in [('page', page), ('per_page', per_page)] if value) http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.LIST_PAYMENTS, pagination)) + response, _ = http_client.get(routes.url(routes.PAYMENT_RESOURCE, pagination=pagination)) return resources.APIResourceCollection(resources.Payment, **response) @@ -122,7 +122,7 @@ def retrieve(payment, refund_id): payment = payment.id http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.RETRIEVE_REFUND, payment_id=payment, refund_id=refund_id)) + response, _ = http_client.get(routes.url(routes.REFUND_RESOURCE, resource_id=refund_id, payment_id=payment)) return resources.Refund(**response) @staticmethod @@ -141,7 +141,7 @@ def create(payment, **data): payment = payment.id http_client = HttpClient() - response, _ = http_client.post(routes.url(routes.CREATE_REFUND, payment_id=payment), data) + response, _ = http_client.post(routes.url(routes.REFUND_RESOURCE, payment_id=payment), data) return resources.Refund(**response) @staticmethod @@ -159,5 +159,5 @@ def list(payment): payment = payment.id http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.LIST_REFUNDS, payment_id=payment)) + response, _ = http_client.get(routes.url(routes.REFUND_RESOURCE, payment_id=payment)) return resources.APIResourceCollection(resources.Refund, **response) diff --git a/payplug/config.py b/payplug/config.py index f58f257..b683307 100644 --- a/payplug/config.py +++ b/payplug/config.py @@ -16,5 +16,3 @@ CACERT_PATH = os.path.join(os.path.dirname(__file__), 'certs', 'cacert.pem') secret_key = None - - diff --git a/payplug/resources.py b/payplug/resources.py index 6213552..60dce25 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -160,7 +160,7 @@ def get_consistent_resource(self): :rtype Payment """ http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.RETRIEVE_PAYMENT, payment_id=self.id)) + response, _ = http_client.get(routes.url(routes.PAYMENT_RESOURCE, resource_id=self.id)) return Payment(**response) def refund(self, **data): @@ -176,7 +176,7 @@ def refund(self, **data): def list_refunds(self): """ - List the refund of a payment. + List the refunds of a payment. :return The refunds iterable object :rtype APIResourceCollection @@ -226,10 +226,26 @@ def get_consistent_resource(self): :rtype Refund """ http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.RETRIEVE_REFUND, payment_id=self.payment_id, refund_id=self.id)) + response, _ = http_client.get( + routes.url(routes.REFUND_RESOURCE, resource_id=self.id, payment_id=self.payment_id) + ) return Refund(**response) +class Customer(APIResource, ReconstituableAPIResource): + """ + A Customer Resource. + """ + object_type = 'customer' + + +class Card(APIResource, ReconstituableAPIResource): + """ + A Customer Resource. + """ + object_type = 'card' + + class APIResourceCollection(APIResource): """ A class that contains multiple API resources diff --git a/payplug/routes.py b/payplug/routes.py index bb466c9..98f4ca6 100644 --- a/payplug/routes.py +++ b/payplug/routes.py @@ -1,28 +1,25 @@ # -*- coding: utf-8 -*- from six.moves.urllib.parse import urlencode -# Payments routes -CREATE_PAYMENT = '/payments' -RETRIEVE_PAYMENT = '/payments/{payment_id}' -ABORT_PAYMENT = '/payments/{payment_id}' -LIST_PAYMENTS = '/payments' - -# Refunds routes -CREATE_REFUND = '/payments/{payment_id}/refunds' -RETRIEVE_REFUND = '/payments/{payment_id}/refunds/{refund_id}' -LIST_REFUNDS = '/payments/{payment_id}/refunds' +# Resources URL +PAYMENT_RESOURCE = '/payments' +REFUND_RESOURCE = PAYMENT_RESOURCE + '/{payment_id}/refunds' +CUSTOMER_RESOURCE = '/customers' +CARD_RESOURCE = CUSTOMER_RESOURCE + '/{customer_id}/cards' # API base url API_BASE_URL = 'https://api.payplug.com' API_VERSION = 1 -def url(route, pagination=None, **parameters): +def url(route, resource_id=None, pagination=None, **parameters): """ Generates an absolute URL to an API resource. :param route: One of the routes available (see the header of this file) :type route: string + :param resource_id: The resource ID you want. If None, it will point to the endpoint. + :type resource_id: string|None :param pagination: parameters for pagination :type pagination: dict|None :param parameters: additional parameters required by the route @@ -32,13 +29,15 @@ def url(route, pagination=None, **parameters): """ route = route.format(**parameters) + resource_id_url = '/' + str(resource_id) if resource_id else '' + query_parameters = '' if pagination: query_parameters += urlencode(pagination) if query_parameters: query_parameters = '?' + query_parameters - return _base_url() + route + query_parameters + return _base_url() + route + resource_id_url + query_parameters def _base_url(): diff --git a/payplug/test/test_init/test_dao_payment.py b/payplug/test/test_init/test_dao_payment.py index d4c03ed..ad320fd 100644 --- a/payplug/test/test_init/test_dao_payment.py +++ b/payplug/test/test_init/test_dao_payment.py @@ -67,22 +67,22 @@ def teardown_class(cls): @patch('payplug.routes.url') def test_list_pagination_no_arguments(self, url_mock): payplug.Payment.list() - assert url_mock.call_args[0][1] == {} + assert url_mock.call_args[1]['pagination'] == {} @patch('payplug.routes.url') def test_list_pagination_page_argument(self, url_mock): payplug.Payment.list(page=1) - assert url_mock.call_args[0][1] == {'page': 1} + assert url_mock.call_args[1]['pagination'] == {'page': 1} @patch('payplug.routes.url') def test_list_pagination_per_page_argument(self, url_mock): payplug.Payment.list(per_page=1) - assert url_mock.call_args[0][1] == {'per_page': 1} + assert url_mock.call_args[1]['pagination'] == {'per_page': 1} @patch('payplug.routes.url') def test_list_pagination_page_and_per_page_arguments(self, url_mock): payplug.Payment.list(page=42, per_page=1) - assert url_mock.call_args[0][1] == {'page': 42, 'per_page': 1} + assert url_mock.call_args[1]['pagination'] == {'page': 42, 'per_page': 1} def test_list(self): payments = payplug.Payment.list() diff --git a/payplug/test/test_init/test_dao_refund.py b/payplug/test/test_init/test_dao_refund.py index 4a803cd..dfefbe6 100644 --- a/payplug/test/test_init/test_dao_refund.py +++ b/payplug/test/test_init/test_dao_refund.py @@ -24,7 +24,7 @@ def test_retrieve_with_payment_id(self, url_mock): refund = payplug.Refund.retrieve('pay_payment_id', 're_refund_id') assert url_mock.call_args[1]['payment_id'] == 'pay_payment_id' - assert url_mock.call_args[1]['refund_id'] == 're_refund_id' + assert url_mock.call_args[1]['resource_id'] == 're_refund_id' assert isinstance(refund, resources.Refund) @@ -34,7 +34,7 @@ def test_retrieve_with_payment_object(self, url_mock): refund = payplug.Refund.retrieve(payment, 're_refund_id') assert url_mock.call_args[1]['payment_id'] == 'pay_payment_id' - assert url_mock.call_args[1]['refund_id'] == 're_refund_id' + assert url_mock.call_args[1]['resource_id'] == 're_refund_id' assert isinstance(refund, resources.Refund) diff --git a/payplug/test/test_resources/test_card.py b/payplug/test/test_resources/test_card.py new file mode 100755 index 0000000..29399d0 --- /dev/null +++ b/payplug/test/test_resources/test_card.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from mock import patch +from payplug.resources import Card +from payplug.test import TestBase + + +@patch('payplug.config.secret_key', 'a_secret_key') +class TestCardResource(TestBase): + def test_initialize_card(self): + card_attributes = { + "id": "card_167oJVCpvtR9j8N85LraL2GA", + "object": "card", + "customer_id": "cus_6ESfofiMiLBjC6", + "created_at": 1434010787, + "last4": "1111", + "brand": "visa", + "exp_moth": 5, + "exp_year": 2019, + "country": "FR", + "metadata": { + "customer_id": 42710, + "customer_name": "Jean", + }, + } + + card = Card(**card_attributes) + + assert card.id == "card_167oJVCpvtR9j8N85LraL2GA" + assert card.object == "card" + assert card.customer_id == "cus_6ESfofiMiLBjC6" + assert card.created_at == 1434010787 + assert card.last4 == "1111" + assert card.brand == "visa" + assert card.exp_moth == 5 + assert card.exp_year == 2019 + assert card.country == "FR" + + assert isinstance(card.metadata, dict) + assert card.metadata["customer_id"] == 42710 + assert card.metadata["customer_name"] == "Jean" diff --git a/payplug/test/test_resources/test_customer.py b/payplug/test/test_resources/test_customer.py new file mode 100755 index 0000000..06267c2 --- /dev/null +++ b/payplug/test/test_resources/test_customer.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +from mock import patch +from payplug.resources import Customer +from payplug.test import TestBase + + +@patch('payplug.config.secret_key', 'a_secret_key') +class TestCustomerResource(TestBase): + def test_initialize_customer(self): + customer_attributes = { + "id": "cus_6ESfofiMiLBjC6", + "object": "customer", + "created_at": 1434010787, + "is_live": True, + "email": "john.watson@example.net", + "first_name": "John", + "last_name": "Watson", + "address1": "27 Rue Pasteur", + "address2": None, + "city": "Paris", + "postcode": "75018", + "country": "France", + "metadata": { + "customer_id": 42710, + "customer_name": "Jean", + }, + } + + customer = Customer(**customer_attributes) + + assert customer.id == "cus_6ESfofiMiLBjC6" + assert customer.object == "customer" + assert customer.created_at == 1434010787 + assert customer.is_live is True + assert customer.email == "john.watson@example.net" + assert customer.first_name == "John" + assert customer.last_name == "Watson" + assert customer.address1 == "27 Rue Pasteur" + assert customer.address2 is None + assert customer.city == "Paris" + assert customer.postcode == "75018" + assert customer.country == "France" + + assert isinstance(customer.metadata, dict) + assert customer.metadata["customer_id"] == 42710 + assert customer.metadata["customer_name"] == "Jean" diff --git a/payplug/test/test_resources/test_payment.py b/payplug/test/test_resources/test_payment.py index 96a2bca..604e809 100644 --- a/payplug/test/test_resources/test_payment.py +++ b/payplug/test/test_resources/test_payment.py @@ -138,5 +138,5 @@ def test_get_consistent_resource(self, routes_url_mock): safe_payment = unsafe_payment.get_consistent_resource() assert isinstance(safe_payment, Payment) - assert routes_url_mock.call_args[1]['payment_id'] == 'pay_5iHMDxy4ABR4YBVW4UscIn_unsafe' + assert routes_url_mock.call_args[1]['resource_id'] == 'pay_5iHMDxy4ABR4YBVW4UscIn_unsafe' assert safe_payment.id == 'pay_5iHMDxy4ABR4YBVW4UscIn' diff --git a/payplug/test/test_resources/test_refund.py b/payplug/test/test_resources/test_refund.py index 9e4d461..9b89b49 100644 --- a/payplug/test/test_resources/test_refund.py +++ b/payplug/test/test_resources/test_refund.py @@ -54,12 +54,13 @@ def teardown_class(cls): @patch('payplug.resources.routes.url') def test_get_consistent_resource(self, routes_url_mock): - unsafe_refund = Refund(id='re_5iHMDxy4ABR4YBVW4UscIn_unsafe', payment_id='pay_3fJie31HD5eF3dAjdI3903_unsafe', + unsafe_refund = Refund(id='re_5iHMDxy4ABR4YBVW4UscIn_unsafe', + payment_id='pay_3fJie31HD5eF3dAjdI3903_unsafe', object='refund') safe_refund = unsafe_refund.get_consistent_resource() assert isinstance(safe_refund, Refund) - assert routes_url_mock.call_args[1]['refund_id'] == 're_5iHMDxy4ABR4YBVW4UscIn_unsafe' + assert routes_url_mock.call_args[1]['resource_id'] == 're_5iHMDxy4ABR4YBVW4UscIn_unsafe' assert routes_url_mock.call_args[1]['payment_id'] == 'pay_3fJie31HD5eF3dAjdI3903_unsafe' assert safe_refund.id == 're_5iHMDxy4ABR4YBVW4UscIn' assert safe_refund.payment_id == 'pay_3fJie31HD5eF3dAjdI3903' diff --git a/setup.py b/setup.py index c172b99..e8cda71 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ def run_tests(self): packages=find_packages(exclude=['*.test*']), install_requires=['requests>=1.0.1,<3.0', 'six>=1.4.0'], - tests_require=['pytest>=2.7.0', 'mock>=1.0.1', 'six>=1.7.0'], + tests_require=['pytest>=2.7.0', 'mock>=1.0.1,<2.0', 'six>=1.7.0'], # If there are data files included in your packages that need to be # installed, specify them here. If using Python 2.6 or less, then these From eb81612937d74638ba8a4f2fa7a89f93aaa565f3 Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 13:56:12 +0200 Subject: [PATCH 05/15] Add customer-cards + Improve tests. --- payplug/__init__.py | 195 +++++++++++++++++- payplug/network.py | 19 ++ payplug/resources.py | 55 ++++- payplug/test/test_init/test_dao_card.py | 112 ++++++++++ payplug/test/test_init/test_dao_customer.py | 100 +++++++++ payplug/test/test_init/test_dao_payment.py | 66 +++--- payplug/test/test_init/test_dao_refund.py | 63 +++--- payplug/test/test_network/test_http_client.py | 6 + payplug/test/test_resources/test_customer.py | 18 ++ payplug/test/test_resources/test_payment.py | 29 +-- payplug/test/test_resources/test_refund.py | 25 +-- 11 files changed, 579 insertions(+), 109 deletions(-) create mode 100644 payplug/test/test_init/test_dao_card.py create mode 100644 payplug/test/test_init/test_dao_customer.py diff --git a/payplug/__init__.py b/payplug/__init__.py index d6dd2ba..0ec8430 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -31,7 +31,7 @@ def set_secret_key(token): class Payment(object): """ - A DAO for resources.Payment which provides a cromulent way to query payment resources. + A DAO for resources.Payment which provides a way to query payment resources. """ @staticmethod def retrieve(payment_id): @@ -49,18 +49,21 @@ def retrieve(payment_id): return resources.Payment(**response) @staticmethod - def abort(payment_id): + def abort(payment): """ Abort a payment from its id. - :param payment_id: The payment id - :type payment_id: string + :param payment: The payment id or payment object + :type payment: string|Payment :return: The payment resource :rtype: resources.Payment """ + if isinstance(payment, resources.Payment): + payment = payment.id + http_client = HttpClient() - response, __ = http_client.patch(routes.url(routes.PAYMENT_RESOURCE, resource_id=payment_id), {'abort': True}) + response, __ = http_client.patch(routes.url(routes.PAYMENT_RESOURCE, resource_id=payment), {'abort': True}) return resources.Payment(**response) @staticmethod @@ -103,7 +106,7 @@ def list(per_page=None, page=None): class Refund(object): """ - A DAO for resources.Refund which provides a cromulent way to query refund resources. + A DAO for resources.Refund which provides a way to query refund resources. """ @staticmethod def retrieve(payment, refund_id): @@ -161,3 +164,183 @@ def list(payment): http_client = HttpClient() response, _ = http_client.get(routes.url(routes.REFUND_RESOURCE, payment_id=payment)) return resources.APIResourceCollection(resources.Refund, **response) + + +class Customer(object): + """ + A DAO for resources.Customer which provides a way to query customer resources. + """ + @staticmethod + def retrieve(customer_id): + """ + Retrieve a customer from its id. + + :param customer_id: The customer id + :type customer_id: string + + :return: The customer resource + :rtype: resources.Customer + """ + http_client = HttpClient() + response, __ = http_client.get(routes.url(routes.CUSTOMER_RESOURCE, resource_id=customer_id)) + return resources.Customer(**response) + + @staticmethod + def delete(customer): + """ + Delete a customer from its id. + + :param customer: The customer id or object + :type customer: string|Customer + """ + if isinstance(customer, resources.Customer): + customer = customer.id + + http_client = HttpClient() + http_client.delete(routes.url(routes.CUSTOMER_RESOURCE, resource_id=customer)) + + @staticmethod + def update(customer, **data): + """ + Update a customer from its id. + + :param customer: The customer id or object + :type customer: string|Customer + :param data: The data you want to update + + :return: The customer resource + :rtype resources.Customer + """ + if isinstance(customer, resources.Customer): + customer = customer.id + + http_client = HttpClient() + response, _ = http_client.patch(routes.url(routes.CUSTOMER_RESOURCE, resource_id=customer), data) + return resources.Customer(**response) + + @staticmethod + def create(**data): + """ + Create a customer. + + :param data: data required to create the customer + + :return: The customer resource + :rtype resources.Customer + """ + http_client = HttpClient() + response, _ = http_client.post(routes.url(routes.CUSTOMER_RESOURCE), data) + return resources.Customer(**response) + + @staticmethod + def list(per_page=None, page=None): + """ + List of customers. You have to handle pagination manually for the moment. + + :param page: the page number + :type page: int|None + :param per_page: number of customers per page. It's a good practice to increase this number if you know that you + will need a lot of payments. + :type per_page: int|None + + :return A collection of customers + :rtype resources.APIResourceCollection + """ + # Comprehension dict are not supported in Python 2.6-. You can use this commented line instead of the current + # line when you drop support for Python 2.6. + # pagination = {key: value for (key, value) in [('page', page), ('per_page', per_page)] if value} + pagination = dict((key, value) for (key, value) in [('page', page), ('per_page', per_page)] if value) + + http_client = HttpClient() + response, _ = http_client.get(routes.url(routes.CUSTOMER_RESOURCE, pagination=pagination)) + return resources.APIResourceCollection(resources.Customer, **response) + + +class Card(object): + """ + A DAO for resources.Card which provides a way to query customer resources. + """ + @staticmethod + def retrieve(customer, card_id): + """ + Retrieve a card from its id. + + :param customer: The customer id or object + :type customer: string|Customer + :param card_id: The card id + :type card_id: string + + :return: The customer resource + :rtype: resources.Card + """ + if isinstance(customer, resources.Customer): + customer = customer.id + + http_client = HttpClient() + response, __ = http_client.get(routes.url(routes.CARD_RESOURCE, resource_id=card_id, customer_id=customer)) + return resources.Card(**response) + + @staticmethod + def delete(customer, card): + """ + Delete a card from its id. + + :param customer: The customer id or object + :type customer: string|Customer + :param card: The card id or object + :type card: string|Card + """ + if isinstance(customer, resources.Customer): + customer = customer.id + if isinstance(card, resources.Card): + card = card.id + + http_client = HttpClient() + http_client.delete(routes.url(routes.CARD_RESOURCE, resource_id=card, customer_id=customer)) + + @staticmethod + def create(customer, **data): + """ + Create a card instance. + + :param customer: the customer id or object + :type customer: string|Customer + :param data: data required to create the card + + :return: The card resource + :rtype resources.Card + """ + if isinstance(customer, resources.Customer): + customer = customer.id + + http_client = HttpClient() + response, _ = http_client.post(routes.url(routes.CARD_RESOURCE, customer_id=customer), data) + return resources.Card(**response) + + @staticmethod + def list(customer, per_page=None, page=None): + """ + List of cards. You have to handle pagination manually for the moment. + + :param customer: the customer id or object + :type customer: string|Customer + :param page: the page number + :type page: int|None + :param per_page: number of customers per page. It's a good practice to increase this number if you know that you + will need a lot of payments. + :type per_page: int|None + + :return A collection of cards + :rtype resources.APIResourceCollection + """ + if isinstance(customer, resources.Customer): + customer = customer.id + + # Comprehension dict are not supported in Python 2.6-. You can use this commented line instead of the current + # line when you drop support for Python 2.6. + # pagination = {key: value for (key, value) in [('page', page), ('per_page', per_page)] if value} + pagination = dict((key, value) for (key, value) in [('page', page), ('per_page', per_page)] if value) + + http_client = HttpClient() + response, _ = http_client.get(routes.url(routes.CARD_RESOURCE, customer_id=customer, pagination=pagination)) + return resources.APIResourceCollection(resources.Card, **response) diff --git a/payplug/network.py b/payplug/network.py index 31e27cd..c169400 100644 --- a/payplug/network.py +++ b/payplug/network.py @@ -212,6 +212,25 @@ def patch(self, url, data=None): """ return self._request('PATCH', url, data) + def delete(self, url, data=None): + """ + Send an authenticated DELETE request to the API. + + :param url: url to the remote resource + :type url: string + :param data: request data + :type data: dict|None + + :return: http response, http status + :rtype tuple(string, int) + + + :raises + exception.HttpError when http request returned bad HTTP status (≠ 2xx). + exception.ClientError on unexpected error + """ + return self._request('DELETE', url, data) + def get(self, url): """ Send an authenticated GET request to the API. diff --git a/payplug/resources.py b/payplug/resources.py index 60dce25..2b31383 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -183,6 +183,15 @@ def list_refunds(self): """ return payplug.Refund.list(self) + def abort(self): + """ + Abort a payment. + + :return The aborted payment object + :rtype Payment + """ + return payplug.Payment.abort(self) + class Card(APIResource): """ A credit card. @@ -238,13 +247,57 @@ class Customer(APIResource, ReconstituableAPIResource): """ object_type = 'customer' + def update(self, **data): + """ + Update a customer. + + :param data: the data to update. + """ + return payplug.Customer.update(self, **data) + + def delete(self): + """ + Delete the customer. + """ + payplug.Customer.delete(self) + + def add_card(self, **data): + """ + Add a card to the customer. + + :param data: The card data + :return: The new card object + :rtype Card + """ + return payplug.Card.create(self, **data) + + def list_cards(self, *args, **kwargs): + """ + List the cards of the customer. + + :param page: the page number + :type page: int|None + :param per_page: number of customers per page. It's a good practice to increase this number if you know that you + will need a lot of payments. + :type per_page: int|None + :return: The cards of the customer + :rtype APIResourceCollection + """ + return payplug.Card.list(self, *args, **kwargs) + class Card(APIResource, ReconstituableAPIResource): """ - A Customer Resource. + A Card Resource. """ object_type = 'card' + def delete(self): + """ + Delete the card. + """ + payplug.Card.delete(self) + class APIResourceCollection(APIResource): """ diff --git a/payplug/test/test_init/test_dao_card.py b/payplug/test/test_init/test_dao_card.py new file mode 100644 index 0000000..fc44de3 --- /dev/null +++ b/payplug/test/test_init/test_dao_card.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +import pytest +from mock import patch +import payplug +from payplug import resources +from payplug.test import TestBase + + +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({'id': 'card_card1'}, 201)) +@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'card_card1'}, 200)) +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'card_card1'}, 200)) +@patch.object(payplug.HttpClient, 'delete', lambda *args, **kwargs: ({}, 204)) +class TestCardCreateRetrieveDelete(TestBase): + @pytest.fixture + def customer_fixture(self): + return resources.Customer(id='cus_customer1') + + def test_retrieve(self): + card = payplug.Card.retrieve('cus_customer1', 'card_card1') + + assert isinstance(card, resources.Card) + assert card.id == 'card_card1' + + def test_retrieve_with_customer_object(self, customer_fixture): + card = payplug.Card.retrieve(customer_fixture, 'card_card1') + + assert isinstance(card, resources.Card) + assert card.id == 'card_card1' + + def test_create(self): + card = payplug.Card.create('cus_customer1', some='card', da='ta') + + assert isinstance(card, resources.Card) + assert card.id == 'card_card1' + + def test_create_with_customer_object(self, customer_fixture): + card = payplug.Card.create(customer_fixture, some='card', da='ta') + + assert isinstance(card, resources.Card) + assert card.id == 'card_card1' + + def test_delete(self): + res = payplug.Card.delete('cus_customer1', 'card_card1') + + assert res is None + + def test_delete_with_customer_object(self, customer_fixture): + res = payplug.Card.delete(customer_fixture, 'card_card1') + + assert res is None + + def test_delete_with_card_object(self): + card = resources.Card(id='card_card1') + res = payplug.Card.delete('cus_customer1', card) + + assert res is None + + +@pytest.fixture +def cards_list_fixture(): + return { + "type": "list", + "page": 0, + "per_page": 10, + "count": 2, + "data": [ + { + "id": "card_card1", + "object": "card", + }, + { + "id": "card_card2", + "object": "card", + }, + ] + } + + +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (cards_list_fixture(), 200)) +class TestCardsList(TestBase): + @pytest.fixture + def customer_fixture(self): + return resources.Customer(id='cus_customer1') + + @patch('payplug.routes.url') + def test_list_pagination_no_arguments(self, url_mock, customer_fixture): + payplug.Card.list(customer_fixture) + assert url_mock.call_args[1]['pagination'] == {} + + @patch('payplug.routes.url') + def test_list_pagination_page_argument(self, url_mock, customer_fixture): + payplug.Card.list(customer_fixture, page=1) + assert url_mock.call_args[1]['pagination'] == {'page': 1} + + @patch('payplug.routes.url') + def test_list_pagination_per_page_argument(self, url_mock, customer_fixture): + payplug.Card.list(customer_fixture, per_page=1) + assert url_mock.call_args[1]['pagination'] == {'per_page': 1} + + @patch('payplug.routes.url') + def test_list_pagination_page_and_per_page_arguments(self, url_mock, customer_fixture): + payplug.Card.list(customer_fixture, page=42, per_page=1) + assert url_mock.call_args[1]['pagination'] == {'page': 42, 'per_page': 1} + + def test_list(self, customer_fixture): + cards = payplug.Card.list(customer_fixture) + + assert isinstance(cards, resources.APIResourceCollection) + assert next(cards).id == 'card_card1' + assert next(cards).id == 'card_card2' diff --git a/payplug/test/test_init/test_dao_customer.py b/payplug/test/test_init/test_dao_customer.py new file mode 100644 index 0000000..e21b719 --- /dev/null +++ b/payplug/test/test_init/test_dao_customer.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +import pytest +from mock import patch +import payplug +from payplug import resources +from payplug.test import TestBase + + +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({'id': 'cus_customer_id'}, 201)) +@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'cus_customer_id'}, 200)) +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'cus_customer_id'}, 200)) +@patch.object(payplug.HttpClient, 'delete', lambda *args, **kwargs: ({}, 204)) +class TestCustomerCreateRetrieveUpdateDelete(TestBase): + def test_retrieve(self): + customer = payplug.Customer.retrieve('cus_customer_id') + + assert isinstance(customer, resources.Customer) + assert customer.id == 'cus_customer_id' + + def test_update(self): + customer = payplug.Customer.update('cus_customer_id', some='data') + + assert isinstance(customer, resources.Customer) + assert customer.id == 'cus_customer_id' + + def test_update_with_customer_object(self): + customer = payplug.Customer.retrieve('cus_customer_id') + customer = payplug.Customer.update(customer, some='data') + + assert isinstance(customer, resources.Customer) + assert customer.id == 'cus_customer_id' + + def test_create(self): + customer = payplug.Customer.create(some='customer', da='ta') + + assert isinstance(customer, resources.Customer) + assert customer.id == 'cus_customer_id' + + def test_delete(self): + res = payplug.Customer.delete('cus_customer_id') + + assert res is None + + def test_delete_with_customer_object(self): + customer = payplug.Customer.retrieve('cus_customer_id') + res = payplug.Customer.delete(customer) + + assert res is None + + +@pytest.fixture +def customers_list_fixture(): + return { + "type": "list", + "page": 0, + "per_page": 10, + "count": 2, + "data": [ + { + "id": "cus_customer1", + "object": "customer", + }, + { + "id": "cus_customer2", + "object": "customer", + }, + ] + } + + +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (customers_list_fixture(), 200)) +class TestCustomerList(TestBase): + @patch('payplug.routes.url') + def test_list_pagination_no_arguments(self, url_mock): + payplug.Customer.list() + assert url_mock.call_args[1]['pagination'] == {} + + @patch('payplug.routes.url') + def test_list_pagination_page_argument(self, url_mock): + payplug.Customer.list(page=1) + assert url_mock.call_args[1]['pagination'] == {'page': 1} + + @patch('payplug.routes.url') + def test_list_pagination_per_page_argument(self, url_mock): + payplug.Customer.list(per_page=1) + assert url_mock.call_args[1]['pagination'] == {'per_page': 1} + + @patch('payplug.routes.url') + def test_list_pagination_page_and_per_page_arguments(self, url_mock): + payplug.Customer.list(page=42, per_page=1) + assert url_mock.call_args[1]['pagination'] == {'page': 42, 'per_page': 1} + + def test_list(self): + customers = payplug.Customer.list() + + assert isinstance(customers, resources.APIResourceCollection) + assert next(customers).id == 'cus_customer1' + assert next(customers).id == 'cus_customer2' diff --git a/payplug/test/test_init/test_dao_payment.py b/payplug/test/test_init/test_dao_payment.py index ad320fd..8a5e0e5 100644 --- a/payplug/test/test_init/test_dao_payment.py +++ b/payplug/test/test_init/test_dao_payment.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from mock import patch import payplug from payplug import resources @@ -6,22 +7,10 @@ @patch('payplug.config.secret_key', 'a_secret_key') -class TestPaymentCreateRetrieve(TestBase): - @classmethod - def setup_class(cls): - cls.patcher_post = patch.object(payplug.HttpClient, 'post', return_value=({'id': 'pay_payment_id'}, 201)) - cls.patcher_patch = patch.object(payplug.HttpClient, 'patch', return_value=({'id': 'pay_payment_id'}, 200)) - cls.patcher_get = patch.object(payplug.HttpClient, 'get', return_value=({'id': 'pay_payment_id'}, 200)) - cls.patcher_post.start() - cls.patcher_patch.start() - cls.patcher_get.start() - - @classmethod - def teardown_class(cls): - cls.patcher_post.stop() - cls.patcher_patch.stop() - cls.patcher_get.stop() - +@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({'id': 'pay_payment_id'}, 201)) +@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'pay_payment_id'}, 200)) +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'pay_payment_id'}, 200)) +class TestPaymentCreateRetrieveAbort(TestBase): def test_retrieve(self): payment = payplug.Payment.retrieve('pay_payment_id') @@ -34,6 +23,13 @@ def test_abort(self): assert isinstance(payment, resources.Payment) assert payment.id == 'pay_payment_id' + def test_abort_with_payment_object(self): + payment = payplug.Payment.retrieve('pay_payment_id') + payment = payplug.Payment.abort(payment) + + assert isinstance(payment, resources.Payment) + assert payment.id == 'pay_payment_id' + def test_create(self): payment = payplug.Payment.create(some='payment', da='ta') @@ -41,29 +37,25 @@ def test_create(self): assert payment.id == 'pay_payment_id' +@pytest.fixture +def get_payments_fixture(): + return { + "type": "list", + "page": 0, + "per_page": 10, + "count": 2, + "data": [ + { + "id": "pay_5iHMDxy4ABR4YBVW4UscIn", + "object": "payment", + }, + ] + } + + @patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (get_payments_fixture(), 200)) class TestPaymentList(TestBase): - @classmethod - def setup_class(cls): - api_response = { - "type": "list", - "page": 0, - "per_page": 10, - "count": 2, - "data": [ - { - "id": "pay_5iHMDxy4ABR4YBVW4UscIn", - "object": "payment", - } - ] - } - cls.patcher_get = patch.object(payplug.HttpClient, 'get', return_value=(api_response, 200)) - cls.patcher_get.start() - - @classmethod - def teardown_class(cls): - cls.patcher_get.stop() - @patch('payplug.routes.url') def test_list_pagination_no_arguments(self, url_mock): payplug.Payment.list() diff --git a/payplug/test/test_init/test_dao_refund.py b/payplug/test/test_init/test_dao_refund.py index dfefbe6..8bb1d1a 100644 --- a/payplug/test/test_init/test_dao_refund.py +++ b/payplug/test/test_init/test_dao_refund.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from mock import patch import payplug from payplug import resources @@ -6,19 +7,9 @@ @patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({'id': 're_refund_id'}, 200)) +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 're_refund_id'}, 200)) class TestRefundCreateRetrieve(TestBase): - @classmethod - def setup_class(cls): - cls.patcher_post = patch.object(payplug.HttpClient, 'post', return_value=({'id': 're_refund_id'}, 200)) - cls.patcher_get = patch.object(payplug.HttpClient, 'get', return_value=({'id': 're_refund_id'}, 200)) - cls.patcher_post.start() - cls.patcher_get.start() - - @classmethod - def teardown_class(cls): - cls.patcher_post.stop() - cls.patcher_get.stop() - @patch('payplug.routes.url') def test_retrieve_with_payment_id(self, url_mock): refund = payplug.Refund.retrieve('pay_payment_id', 're_refund_id') @@ -56,35 +47,31 @@ def test_create_with_payment_object(self, url_mock): assert isinstance(refund, resources.Refund) -@patch('payplug.config.secret_key', 'a_secret_key') -class TestRefundList(TestBase): - @classmethod - def setup_class(cls): - api_response = { - "type": "list", - "data": [ - { - "id": "re_3NxGqPfSGMHQgLSZH0Mv3B", - "payment_id": "pay_5iHMDxy4ABR4YBVW4UscIn", - "object": "refund", - "is_live": True, - "amount": 358, - "currency": "EUR", - "created_at": 1434012358, - "metadata": { - "customer_id": 42710, - "reason": "The delivery was delayed" - } +@pytest.fixture +def get_refunds_fixture(): + return { + "type": "list", + "data": [ + { + "id": "re_3NxGqPfSGMHQgLSZH0Mv3B", + "payment_id": "pay_5iHMDxy4ABR4YBVW4UscIn", + "object": "refund", + "is_live": True, + "amount": 358, + "currency": "EUR", + "created_at": 1434012358, + "metadata": { + "customer_id": 42710, + "reason": "The delivery was delayed" } - ] - } - cls.patcher_get = patch.object(payplug.HttpClient, 'get', return_value=(api_response, 200)) - cls.patcher_get.start() + } + ] + } - @classmethod - def teardown_class(cls): - cls.patcher_get.stop() +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (get_refunds_fixture(), 200)) +class TestRefundList(TestBase): @patch('payplug.routes.url') def test_list_with_payment_id(self, url_mock): refunds = payplug.Refund.list('pay_payment_id') diff --git a/payplug/test/test_network/test_http_client.py b/payplug/test/test_network/test_http_client.py index a1b3a76..ce87eb5 100644 --- a/payplug/test/test_network/test_http_client.py +++ b/payplug/test/test_network/test_http_client.py @@ -54,6 +54,12 @@ def test_patch(self, _request_mock): http_client.patch('this_is_an_url', {'data': 'tada'}) _request_mock.assert_called_once_with('PATCH', 'this_is_an_url', {'data': 'tada'}) + @patch('payplug.network.HttpClient._request') + def test_delete(self, _request_mock): + http_client = HttpClient('a_secret_key', MagicMock()) + http_client.delete('this_is_an_url', {'data': 'tada'}) + _request_mock.assert_called_once_with('DELETE', 'this_is_an_url', {'data': 'tada'}) + @patch('payplug.network.HttpClient._request') def test_get(self, _request_mock): http_client = HttpClient('a_secret_key', MagicMock()) diff --git a/payplug/test/test_resources/test_customer.py b/payplug/test/test_resources/test_customer.py index 06267c2..c89f3dc 100755 --- a/payplug/test/test_resources/test_customer.py +++ b/payplug/test/test_resources/test_customer.py @@ -44,3 +44,21 @@ def test_initialize_customer(self): assert isinstance(customer.metadata, dict) assert customer.metadata["customer_id"] == 42710 assert customer.metadata["customer_name"] == "Jean" + + @patch('payplug.resources.payplug.Customer.update') + def test_update_payment(self, customer_update_mock): + customer = Customer(id='cus_customer1') + customer.update(da='ta') + customer_update_mock.assert_called_once_with(customer, da='ta') + + @patch('payplug.resources.payplug.Card.create') + def test_add_card(self, card_create_mock): + customer = Customer(id='cus_customer1') + customer.add_card(some='data') + card_create_mock.assert_called_once_with(customer, some='data') + + @patch('payplug.resources.payplug.Card.list') + def test_list_cards(self, card_list_mock): + customer = Customer(id='cus_customer1') + customer.list_cards(per_page=10, page=0) + card_list_mock.assert_called_once_with(customer, per_page=10, page=0) diff --git a/payplug/test/test_resources/test_payment.py b/payplug/test/test_resources/test_payment.py index 604e809..dd3d401 100644 --- a/payplug/test/test_resources/test_payment.py +++ b/payplug/test/test_resources/test_payment.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from mock import patch import payplug from payplug.resources import Payment @@ -116,22 +117,24 @@ def test_list_refunds_payment(self, refund_list_mock): payment.list_refunds() refund_list_mock.assert_called_once_with(payment) + @patch('payplug.resources.payplug.Payment.abort') + def test_abort_payment(self, payment_abort_mock): + payment = Payment(id='pay_5iHMDxy4ABR4YBVW4UscIn') + payment.abort() + payment_abort_mock.assert_called_once_with(payment) -@patch('payplug.config.secret_key', 'a_secret_key') -class TestConsistentPayment(TestBase): - @classmethod - def setup_class(cls): - api_response = { - "id": "pay_5iHMDxy4ABR4YBVW4UscIn", - "object": "payment", - } - cls.patcher_get = patch.object(payplug.HttpClient, 'get', return_value=(api_response, 200)) - cls.patcher_get.start() - @classmethod - def teardown_class(cls): - cls.patcher_get.stop() +@pytest.fixture +def payment_fixture(): + return { + "id": "pay_5iHMDxy4ABR4YBVW4UscIn", + "object": "payment", + } + +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (payment_fixture(), 200)) +class TestConsistentPayment(TestBase): @patch('payplug.resources.routes.url') def test_get_consistent_resource(self, routes_url_mock): unsafe_payment = Payment(id='pay_5iHMDxy4ABR4YBVW4UscIn_unsafe', object='payment') diff --git a/payplug/test/test_resources/test_refund.py b/payplug/test/test_resources/test_refund.py index 9b89b49..8235018 100644 --- a/payplug/test/test_resources/test_refund.py +++ b/payplug/test/test_resources/test_refund.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from mock import patch import payplug from payplug.resources import Refund @@ -36,22 +37,18 @@ def test_initialize_refund(self): assert refund_object.metadata['reason'] == "The delivery was delayed" -@patch('payplug.config.secret_key', 'a_secret_key') -class TestConsistentRefund(TestBase): - @classmethod - def setup_class(cls): - api_response = { - "id": "re_5iHMDxy4ABR4YBVW4UscIn", - "payment_id": "pay_3fJie31HD5eF3dAjdI3903", - "object": "refund", - } - cls.patcher_http_client = patch.object(payplug.HttpClient, 'get', return_value=(api_response, 200)) - cls.patcher_http_client.start() +@pytest.fixture +def refund_fixture(): + return { + "id": "re_5iHMDxy4ABR4YBVW4UscIn", + "payment_id": "pay_3fJie31HD5eF3dAjdI3903", + "object": "refund", + } - @classmethod - def teardown_class(cls): - cls.patcher_http_client.stop() +@patch('payplug.config.secret_key', 'a_secret_key') +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: (refund_fixture(), 200)) +class TestConsistentRefund(TestBase): @patch('payplug.resources.routes.url') def test_get_consistent_resource(self, routes_url_mock): unsafe_refund = Refund(id='re_5iHMDxy4ABR4YBVW4UscIn_unsafe', From 4bf5e9890a251a09b44dbb372d7b93c96b72a185 Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 14:26:23 +0200 Subject: [PATCH 06/15] Update version and python-version file. Add a changelog. --- .python-version | 8 +++----- CHANGELOG.rst | 7 +++++++ README.rst | 5 ++++- payplug/__version__.py | 2 +- tox.ini | 1 - 5 files changed, 15 insertions(+), 8 deletions(-) mode change 100644 => 100755 .python-version create mode 100644 CHANGELOG.rst diff --git a/.python-version b/.python-version old mode 100644 new mode 100755 index 90090f5..d4722a7 --- a/.python-version +++ b/.python-version @@ -1,9 +1,7 @@ -3.5.0 -2.7.10 +3.5.1 +2.7.11 3.4.3 3.3.6 -3.2.6 -3.1.5 2.6.9 pypy3-2.4.0 -pypy-2.6.1 +pypy-4.0.1 diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..11b839b --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,7 @@ +1.2.0 +----- + +- Support for Customers and Cards. +- Add ability to abort payment objects. +- Minor fixes in tests. +- Add this changelog. \ No newline at end of file diff --git a/README.rst b/README.rst index 484feaf..a022810 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,10 @@ Prerequisites ------------- PayPlug's library relies on **python-requests>=1.0.1** to perform HTTP requests and requires **OpenSSL** to secure -transactions. You also need either a **Python 2** newer than **Python 2.6** or a **Python 3** newer than **Python 3.1**. +transactions. You also need either a **Python 2.6+** or a **Python 3.3+**. The library is known to work with these +versions, pypy and pypy3. It may work on older versions or other Python implementations without warranty. If you use an +implementation that is not listed above, do not hesitate to let us known if it worked for you or not, so that we can +update this prerequisites. To ensure **Python 2** and **Python 3** compatibility, this library also depends on **six>=1.4.0**. Installation diff --git a/payplug/__version__.py b/payplug/__version__.py index 6fde8f8..36526b0 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.1.1' +__version__ = '1.2.0' diff --git a/tox.ini b/tox.ini index cf6cabf..18d74e1 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ envlist = py26 py27 - py32 py33 py34 py35 From f564bb9cb7a7b73b30c03362951bacd652763b3b Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 18:05:18 +0200 Subject: [PATCH 07/15] Fix delete card not working. --- payplug/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payplug/resources.py b/payplug/resources.py index 2b31383..105279d 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -296,7 +296,7 @@ def delete(self): """ Delete the card. """ - payplug.Card.delete(self) + payplug.Card.delete(self.customer_id, self) class APIResourceCollection(APIResource): From 6b1d5caea855ab7823f7df5d809d42fc676ebe27 Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 18:12:17 +0200 Subject: [PATCH 08/15] Move changelog to markdown. --- CHANGELOG.rst => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG.rst => CHANGELOG.md (100%) diff --git a/CHANGELOG.rst b/CHANGELOG.md similarity index 100% rename from CHANGELOG.rst rename to CHANGELOG.md From aa55fe634018aad28155c5b51d65aeac7c68ce0a Mon Sep 17 00:00:00 2001 From: NLR Date: Mon, 18 Apr 2016 18:13:47 +0200 Subject: [PATCH 09/15] Add example in changelog. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b839b..e378347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,8 @@ - Support for Customers and Cards. - Add ability to abort payment objects. + ``` + payment.abort() + ``` - Minor fixes in tests. - Add this changelog. \ No newline at end of file From d62321c5d1c5602587fa841690decbb87480c9b1 Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 15:04:06 +0200 Subject: [PATCH 10/15] Improve changelog. --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e378347..06d91ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ 1.2.0 ----- -- Support for Customers and Cards. -- Add ability to abort payment objects. +- **NEW**: Support for Customers and Cards. (see official documentation) +- **NEW**: Add ability to abort payment objects. + ``` payment.abort() ``` + +- **NEW**: This library is now under MIT Licence (Issue #4). - Minor fixes in tests. - Add this changelog. \ No newline at end of file From 63b9fab8718bd6c7bdd5708c8d8197624f7745da Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 15:04:19 +0200 Subject: [PATCH 11/15] Add LICENCE file. --- LICENCE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENCE.md diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..0e954bb --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Payplug + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 5ec4b407bd0846b7a044dddf6db6d6e300a755ee Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 17:24:22 +0200 Subject: [PATCH 12/15] Add Travis CI --- .travis.yml | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 27 +++++++++++++++++++-------- 2 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..72dc40e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,53 @@ +language: python + +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" + - "pypy" + - "pypy3" + +env: + - REQUESTS=1.0 + - REQUESTS=1.1 + - REQUESTS=1.2 + - REQUESTS=2.0 + - REQUESTS=2.1 + - REQUESTS=2.2 + - REQUESTS=2.3 + - REQUESTS=2.4 + - REQUESTS=2.5 + - REQUESTS=2.6 + - REQUESTS=2.7 + - REQUESTS=2.8 + - REQUESTS=2.9 + - REQUESTS=dev + +cache: pip + +matrix: + fast_finish: true + exclude: + # No support for Python 3.4+ in requests 1.x + - python: "3.4" + env: REQUESTS=1.0 + - python: "3.4" + env: REQUESTS=1.1 + - python: "3.4" + env: REQUESTS=1.2 + - python: "3.5" + env: REQUESTS=1.0 + - python: "3.5" + env: REQUESTS=1.1 + - python: "3.5" + env: REQUESTS=1.2 + +# No support for Python 3.2- in virtualenv 14+ +install: + - travis_retry pip install "virtualenv<14.0.0" "tox>=1.9" + +script: + - tox -e py$(echo $(echo $TRAVIS_PYTHON_VERSION | sed -e 's/pypy/py/')-requests$REQUESTS | tr -d .) -- --cov=payplug diff --git a/tox.ini b/tox.ini index 18d74e1..5e6e6eb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,30 @@ [tox] +; No support for Python 3.4+ in requests 1.x envlist = - py26 - py27 - py33 - py34 - py35 - pypy - pypy3 + py{26,27,32,33,py,py3}-requests{10,11,12,20,21,22,23,24,25,26,27,28,29,dev}, + py{34,35}-requests{20,21,22,23,24,25,26,27,28,29,dev} [testenv] deps = pytest>=2.7.0 + pytest-cov>=2.2 ; Uncomment this if you want to test with a six version lower than 1.7.0 since mock depends on six.wraps which was ; released a bit later. ; mock==1.0.1 mock>=1.0.1 six>=1.4.0 -commands=py.test \ No newline at end of file + requests10: requests>=1.0.1,<1.1 + requests11: requests>=1.1,<1.2 + requests12: requests>=1.2,<1.3 + requests20: requests>=2.0,<2.1 + requests21: requests>=2.1,<2.2 + requests22: requests>=2.2,<2.3 + requests23: requests>=2.3,<2.4 + requests24: requests>=2.4,<2.5 + requests25: requests>=2.5,<2.6 + requests26: requests>=2.6,<2.7 + requests27: requests>=2.7,<2.8 + requests28: requests>=2.8,<2.9 + requests29: requests>=2.9,<2.10 + requestsdev: https://github.com/kennethreitz/requests/tarball/master +commands=py.test {posargs} \ No newline at end of file From 7c0795ff1b84364fa378c7dbb6baa2cfc8f613bb Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 18:03:58 +0200 Subject: [PATCH 13/15] Add embedded status image for CI and pypi. --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index a022810..49a148c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,12 @@ Python library for the PayPlug API ================================== +.. image:: https://travis-ci.org/payplug/payplug-python.svg?branch=master +:target: https://travis-ci.org/payplug/payplug-python + +.. image:: https://img.shields.io/pypi/v/payplug.svg?maxAge=2592000 +:target: https://pypi.python.org/pypi/payplug/ + This is the documentation of PayPlug's Python library. It is designed to help developers to use PayPlug as payment solution in a simple, yet robust way. From 170dcbc5f0d8c84da24ed5d5056dd6b15e2effe1 Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 18:05:11 +0200 Subject: [PATCH 14/15] Drop officiel support for Python 3.2. --- .travis.yml | 1 - tox.ini | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 72dc40e..ed040af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python python: - "2.6" - "2.7" - - "3.2" - "3.3" - "3.4" - "3.5" diff --git a/tox.ini b/tox.ini index 5e6e6eb..13afd76 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] ; No support for Python 3.4+ in requests 1.x envlist = - py{26,27,32,33,py,py3}-requests{10,11,12,20,21,22,23,24,25,26,27,28,29,dev}, + py{26,27,33,py,py3}-requests{10,11,12,20,21,22,23,24,25,26,27,28,29,dev}, py{34,35}-requests{20,21,22,23,24,25,26,27,28,29,dev} [testenv] From 4d08e859b717d318e145061ae3e42f93cff0eee5 Mon Sep 17 00:00:00 2001 From: NLR Date: Tue, 19 Apr 2016 18:08:07 +0200 Subject: [PATCH 15/15] Fix RST. --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 49a148c..62433d9 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,12 @@ Python library for the PayPlug API ================================== .. image:: https://travis-ci.org/payplug/payplug-python.svg?branch=master -:target: https://travis-ci.org/payplug/payplug-python + :target: https://travis-ci.org/payplug/payplug-python + :alt: CI Status .. image:: https://img.shields.io/pypi/v/payplug.svg?maxAge=2592000 -:target: https://pypi.python.org/pypi/payplug/ + :target: https://pypi.python.org/pypi/payplug/ + :alt: PyPi This is the documentation of PayPlug's Python library. It is designed to help developers to use PayPlug as payment solution in a simple, yet robust way.