From 9605b58ca04267817bc0e319c6edcde26451d414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Tue, 29 Sep 2020 19:45:27 +0200 Subject: [PATCH 01/13] Add `Billing` and `Shipping` entities to payment resource --- payplug/resources.py | 14 +++ payplug/test/test_resources/test_payment.py | 125 +++++++++++++++++++- 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/payplug/resources.py b/payplug/resources.py index 871f965..0d0dd9d 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -152,6 +152,8 @@ def _mapper(self): 'hosted_payment': Payment.HostedPayment, 'notification': Payment.Notification, 'failure': Payment.Failure, + 'billing': Payment.Billing, + 'shipping': Payment.Shipping, } def get_consistent_resource(self): @@ -222,6 +224,18 @@ class Failure(APIResource): """ pass + class Billing(APIResource): + """ + Billing information + """ + pass + + class Shipping(APIResource): + """ + Shipping information + """ + pass + class Refund(APIResource, VerifiableAPIResource, ReconstituableAPIResource): """ diff --git a/payplug/test/test_resources/test_payment.py b/payplug/test/test_resources/test_payment.py index 14aa849..5e68970 100644 --- a/payplug/test/test_resources/test_payment.py +++ b/payplug/test/test_resources/test_payment.py @@ -8,7 +8,7 @@ @patch('payplug.config.secret_key', 'a_secret_key') class TestPaymentResource(TestBase): - def test_initialize_payment(self): + def test_initialize_payment_old(self): payment_attributes = { "id": "pay_5iHMDxy4ABR4YBVW4UscIn", "object": "payment", @@ -105,6 +105,129 @@ def test_initialize_payment(self): assert isinstance(payment.metadata, dict) assert payment.metadata['customer_id'] == 42710 + def test_initialize_payment_2019(self): + payment_attributes = { + "id": "pay_5iHMDxy4ABR4YBVW4UscIn", + "object": "payment", + "is_live": True, + "amount": 3300, + "amount_refunded": 0, + "currency": "EUR", + "created_at": 1434010787, + "is_paid": True, + "is_refunded": False, + "is_3ds": False, + "save_card": False, + "card": { + "last4": "1800", + "country": "FR", + "exp_month": 9, + "exp_year": 2017, + "brand": "Mastercard" + }, + "hosted_payment": { + "payment_url": "hosted_payment_payment_url", + "return_url": "hosted_payment_return_url", + "cancel_url": "hosted_payment_cancel_url", + "paid_at": 1434010827 + }, + "notification": { + "url": "notification_url", + "response_code": 200 + }, + "failure": { + "code": "a_failure_code", + "message": 'A weird failure message ®±' + }, + "metadata": { + "customer_id": 42710 + }, + 'billing': { + 'title': 'mr', + 'first_name': 'John', + 'last_name': 'Watson', + 'email': 'john.watson@example.net', + 'address1': '221B Baker Street', + 'postcode': 'NW16XE', + 'city': 'London', + 'country': 'GB', + 'language': 'en' + }, + 'shipping': { + 'title': 'mr', + 'first_name': 'John', + 'last_name': 'Watson', + 'email': 'john.watson@example.net', + 'address1': '221B Baker Street', + 'postcode': 'NW16XE', + 'city': 'London', + 'country': 'GB', + 'language': 'en', + 'delivery_type': 'BILLING' + }, + } + + payment = Payment(**payment_attributes) + + assert payment.id == 'pay_5iHMDxy4ABR4YBVW4UscIn' + assert payment.object == 'payment' + assert payment.is_live is True + assert payment.amount == 3300 + assert payment.amount_refunded == 0 + assert payment.currency == "EUR" + assert payment.created_at == 1434010787 + assert payment.is_paid is True + assert payment.is_refunded is False + assert payment.is_3ds is False + assert payment.save_card is False + + assert type(payment.card) == Payment.Card + assert payment.card.last4 == "1800" + assert payment.card.country == "FR" + assert payment.card.exp_month == 9 + assert payment.card.exp_year == 2017 + assert payment.card.brand == "Mastercard" + + assert type(payment.billing) == Payment.Billing + assert payment.billing.title == 'mr' + assert payment.billing.first_name == "John" + assert payment.billing.last_name == "Watson" + assert payment.billing.email == "john.watson@example.net" + assert payment.billing.address1 == '221B Baker Street' + assert payment.billing.postcode == 'NW16XE' + assert payment.billing.city == 'London' + assert payment.billing.country == 'GB' + assert payment.billing.language == 'en' + + assert type(payment.shipping) == Payment.Shipping + assert payment.shipping.title == 'mr' + assert payment.shipping.first_name == "John" + assert payment.shipping.last_name == "Watson" + assert payment.shipping.email == "john.watson@example.net" + assert payment.shipping.address1 == '221B Baker Street' + assert payment.shipping.postcode == 'NW16XE' + assert payment.shipping.city == 'London' + assert payment.shipping.country == 'GB' + assert payment.shipping.language == 'en' + assert payment.shipping.delivery_type == 'BILLING' + + assert type(payment.hosted_payment) == Payment.HostedPayment + assert payment.hosted_payment.payment_url == "hosted_payment_payment_url" + assert payment.hosted_payment.return_url == "hosted_payment_return_url" + assert payment.hosted_payment.cancel_url == "hosted_payment_cancel_url" + assert payment.hosted_payment.paid_at == 1434010827 + + assert type(payment.notification) == Payment.Notification + assert payment.notification.url == "notification_url" + assert payment.notification.response_code == 200 + + assert type(payment.failure) == Payment.Failure + assert payment.failure.code == "a_failure_code" + assert payment.failure.message == 'A weird failure message ®±' + + assert isinstance(payment.metadata, dict) + assert payment.metadata['customer_id'] == 42710 + @patch('payplug.resources.payplug.Refund.create') def test_refund_payment(self, refund_create_mock): payment = Payment(id='pay_5iHMDxy4ABR4YBVW4UscIn') From 5a0cdfcfad579d38cd8509282c9f995870841f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Tue, 29 Sep 2020 19:47:31 +0200 Subject: [PATCH 02/13] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a09d6d..30e4d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.3.1 +----- +- Add missing `Billing` and `Shipping` entities to payment resource + 1.3.0 ----- - Add AccountingReport class to handle the new /accounting\_reports API endpoint From 76e89b4d1f6f3e3a14be4eded6627f40f7440553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Wed, 30 Sep 2020 14:06:19 +0200 Subject: [PATCH 03/13] Update readme --- README.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 18e1d35..1bb0fac 100644 --- a/README.rst +++ b/README.rst @@ -71,14 +71,20 @@ Here's how simple it is to create a payment request: .. sourcecode :: python + customer = { + 'email': 'john.watson@example.net', + 'first_name': 'John', + 'last_name': 'Watson', + 'address1': '221B Baker Street', + 'postcode': 'NW16XE', + 'city': 'London', + 'country': 'GB', + } payment_data = { 'amount': 3300, 'currency': 'EUR', - 'customer': { - 'email': 'john.watson@example.net', - 'first_name': 'John', - 'last_name': 'Watson', - }, + 'billing': customer, + 'shipping': customer, 'hosted_payment': { 'return_url': 'https://www.example.net/success?id=42710', 'cancel_url': 'https://www.example.net/cancel?id=42710', From b61dfffa600c05eed34c5bcc30a2da61f0f6bd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Turpin?= Date: Thu, 1 Oct 2020 15:27:36 +0200 Subject: [PATCH 04/13] Update __version__.py Bump version --- payplug/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payplug/__version__.py b/payplug/__version__.py index ea1a019..d14fa05 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.3.0' +__version__ = '1.3.1' From a7495d9633f8463e0c2e8c61bcf99f39c07943b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Thu, 1 Oct 2020 16:36:47 +0200 Subject: [PATCH 05/13] Update version number --- payplug/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payplug/__version__.py b/payplug/__version__.py index ea1a019..d14fa05 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.3.0' +__version__ = '1.3.1' From 35fa9e530e982bea4980a675ed16a4dfd1268ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Labeyrie?= Date: Thu, 8 Oct 2020 18:54:57 +0200 Subject: [PATCH 06/13] Set correct API version in README example --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 1bb0fac..8d9aef8 100644 --- a/README.rst +++ b/README.rst @@ -71,6 +71,8 @@ Here's how simple it is to create a payment request: .. sourcecode :: python + payplug.set_api_version("2019-08-06") + customer = { 'email': 'john.watson@example.net', 'first_name': 'John', @@ -80,6 +82,7 @@ Here's how simple it is to create a payment request: 'city': 'London', 'country': 'GB', } + payment_data = { 'amount': 3300, 'currency': 'EUR', @@ -94,6 +97,7 @@ Here's how simple it is to create a payment request: 'customer_id': 42710, }, } + payment = payplug.Payment.create(**payment_data) Go further: From e6bbafa5b06734a58b7c41d7d5a22c7cab9f467d Mon Sep 17 00:00:00 2001 From: Achraf El Khachchai Date: Mon, 11 Jan 2021 12:28:03 +0100 Subject: [PATCH 07/13] add OneyPaymentSimulation class, resource & tests --- CHANGELOG.md | 4 ++ payplug/__init__.py | 19 ++++++++ payplug/__version__.py | 2 +- payplug/resources.py | 23 ++++++++++ payplug/routes.py | 1 + .../test_dao_oney_payment_simulation.py | 15 +++++++ .../test_oney_payment_simulation.py | 44 +++++++++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 payplug/test/test_init/test_dao_oney_payment_simulation.py create mode 100644 payplug/test/test_resources/test_oney_payment_simulation.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e4d08..5723baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.4.0 +----- +- Add OneyPaymentSimulation class to handle the /oney_payment_simulations API endpoint + 1.3.1 ----- - Add missing `Billing` and `Shipping` entities to payment resource diff --git a/payplug/__init__.py b/payplug/__init__.py index 143abd7..d2b64b9 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -397,3 +397,22 @@ def create(**data): http_client = HttpClient() response, _ = http_client.post(routes.url(routes.ACCOUNTING_REPORT_RESOURCE), data) return resources.AccountingReport(**response) + + +class OneyPaymentSimulation: + """ + A DAO for resources.OneyPaymentSimulation which provides a way to query oney payment simulations. + """ + @staticmethod + def get_simulation(**data): + """ + Get an oney payment simulation. + + :param data: data required to get a simulation + + :return: The oney payment simulation + :rtype resources.OneyPaymentSimulation + """ + http_client = HttpClient() + response, _ = http_client.post(routes.url(routes.ONEY_PAYMENT_SIMULATION), data) + return resources.OneyPaymentSimulation(**response) diff --git a/payplug/__version__.py b/payplug/__version__.py index d14fa05..cf0bccd 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.3.1' +__version__ = '1.4.0' diff --git a/payplug/resources.py b/payplug/resources.py index 0d0dd9d..81706a2 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -368,3 +368,26 @@ def get_consistent_resource(self): routes.url(routes.ACCOUNTING_REPORT_RESOURCE, resource_id=self.id) ) return AccountingReport(**response) + + +class OneyPaymentSimulation(APIResource): + """ + An OneyPaymentSimulation Resource. + """ + @property + def _mapper(self): + """ + Maps each oney payment simuation item to an operation. + + :see :func:`~APIResource._mapper` + """ + return { + 'x3_with_fees': OneyPaymentSimulation.Operation, + 'x4_with_fees': OneyPaymentSimulation.Operation, + } + + class Operation(APIResource): + """ + An operation. + """ + pass diff --git a/payplug/routes.py b/payplug/routes.py index 8f3b522..60fe7e1 100644 --- a/payplug/routes.py +++ b/payplug/routes.py @@ -7,6 +7,7 @@ CUSTOMER_RESOURCE = '/customers' CARD_RESOURCE = CUSTOMER_RESOURCE + '/{customer_id}/cards' ACCOUNTING_REPORT_RESOURCE = '/accounting_reports' +ONEY_PAYMENT_SIMULATION = '/oney_payment_simulations' # API base url API_BASE_URL = 'https://api.payplug.com' diff --git a/payplug/test/test_init/test_dao_oney_payment_simulation.py b/payplug/test/test_init/test_dao_oney_payment_simulation.py new file mode 100644 index 0000000..b14546f --- /dev/null +++ b/payplug/test/test_init/test_dao_oney_payment_simulation.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +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': 'simulation_id'}, 201)) +class TestAccountingReportCreateRetrieve(TestBase): + def test_retrieve(self): + simulation = payplug.OneyPaymentSimulation.get_simulation(key='val') + + assert isinstance(simulation, resources.OneyPaymentSimulation) + assert simulation.id == 'simulation_id' diff --git a/payplug/test/test_resources/test_oney_payment_simulation.py b/payplug/test/test_resources/test_oney_payment_simulation.py new file mode 100644 index 0000000..b40a2b7 --- /dev/null +++ b/payplug/test/test_resources/test_oney_payment_simulation.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from payplug.resources import OneyPaymentSimulation +from payplug.test import TestBase + + +class TestOneyPaymentSimulationResource(TestBase): + def test_initializer_oney_payment_simulation(self): + simulation_attributes = { + "x3_with_fees": { + "down_payment_amount": 67667, + "nominal_annual_percentage_rate": 6.04, + "effective_annual_percentage_rate": 6.21, + "installments": [ + { + "date": "2019-12-29T01:00:00.000Z", + "amount": 66667 + }, + { + "date": "2020-01-29T01:00:00.000Z", + "amount": 66666 + } + ], + "total_cost": 1000 + }, + } + + simulation_object = OneyPaymentSimulation(**simulation_attributes) + operation = simulation_object.x3_with_fees + + assert isinstance(operation, OneyPaymentSimulation.Operation) + assert operation.down_payment_amount == 67667 + assert operation.nominal_annual_percentage_rate == 6.04 + assert operation.effective_annual_percentage_rate == 6.21 + assert operation.installments == [ + { + "date": "2019-12-29T01:00:00.000Z", + "amount": 66667 + }, + { + "date": "2020-01-29T01:00:00.000Z", + "amount": 66666 + } + ] + assert operation.total_cost == 1000 From b34de446d48a12add25b0cb00ed7ad6d091d3751 Mon Sep 17 00:00:00 2001 From: Florian Richard Date: Mon, 10 May 2021 17:56:33 +0200 Subject: [PATCH 08/13] add InstallmentPlan class, resource and tests --- payplug/__init__.py | 43 +++++++ payplug/resources.py | 29 +++++ payplug/routes.py | 1 + .../test_init/test_dao_installment_plans.py | 29 +++++ .../test_resources/test_installment_plans.py | 109 ++++++++++++++++++ 5 files changed, 211 insertions(+) create mode 100644 payplug/test/test_init/test_dao_installment_plans.py create mode 100644 payplug/test/test_resources/test_installment_plans.py diff --git a/payplug/__init__.py b/payplug/__init__.py index d2b64b9..6faedae 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -416,3 +416,46 @@ def get_simulation(**data): http_client = HttpClient() response, _ = http_client.post(routes.url(routes.ONEY_PAYMENT_SIMULATION), data) return resources.OneyPaymentSimulation(**response) + + +class InstallmentPlan: + """ + A DAO for resources.InstallmentPlans which provides a way to query installment plans. + """ + @staticmethod + def create(**data): + """ + Create an installment plan. + + :param data: data required to create an installment plan + + :return: The installment plan + :rtype resources.InstallmentPlan + """ + http_client = HttpClient() + response, _ = http_client.post(routes.url(routes.INSTALLMENT_PLANS), data) + return resources.InstallmentPlan(**response) + + @staticmethod + def update(installment_plan, **data): + + if isinstance(installment_plan, resources.InstallmentPlan): + installment_plan = installment_plan.id + + http_client = HttpClient() + response, _ = http_client.patch(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan), data) + return resources.InstallmentPlan(**response) + + @staticmethod + def get(installment_plan_id): + """ + Get an installment plan. + + :param data: data required to get an installment plan + + :return: The installment plan + :rtype resources.InstallmentPlan + """ + http_client = HttpClient() + response, _ = http_client.get(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan_id)) + return resources.InstallmentPlan(**response) diff --git a/payplug/resources.py b/payplug/resources.py index 81706a2..8615fdd 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -391,3 +391,32 @@ class Operation(APIResource): An operation. """ pass + + +class InstallmentPlan(APIResource): + """ + An InstallmentPlans Resource + """ + object_type = 'intallment_plan' + + """ + Create an InstallmentPlan + + :param data: the data to create the installment plan + """ + def create(self, **data): + return payplug.InstallmentPlans.create_installment_plan_endpoint(data) + + """ + Update an InstallmentPlan + + :param data: the data needed to update the installment plan + """ + def update(self, **data): + return payplug.InstallmentPlans.update_installment_plan_endpoint(self, data) + + """ + Get an InstallmentPlan + """ + def get(self): + return payplug.InstallmentPlans.get_installment_plan_endpoint(self) \ No newline at end of file diff --git a/payplug/routes.py b/payplug/routes.py index 60fe7e1..562c27b 100644 --- a/payplug/routes.py +++ b/payplug/routes.py @@ -8,6 +8,7 @@ CARD_RESOURCE = CUSTOMER_RESOURCE + '/{customer_id}/cards' ACCOUNTING_REPORT_RESOURCE = '/accounting_reports' ONEY_PAYMENT_SIMULATION = '/oney_payment_simulations' +INSTALLMENT_PLANS = '/installment_plans' # API base url API_BASE_URL = 'https://api.payplug.com' diff --git a/payplug/test/test_init/test_dao_installment_plans.py b/payplug/test/test_init/test_dao_installment_plans.py new file mode 100644 index 0000000..4bacc22 --- /dev/null +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +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: ({"some":"installment_plan", "da":"ta"}, 201)) +@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 200)) +@patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 200)) +class TestInstallmentPlanRetrieve(TestBase): + def test_get(self): + installment_plan = payplug.InstallmentPlan.get('installment_plan_id') + + assert isinstance(installment_plan, resources.InstallmentPlan) + assert installment_plan.id == 'installment_plan_id' + + def test_update(self): + installment_plan = payplug.InstallmentPlan.get('installment_plan_id') + installment_plan = payplug.InstallmentPlan.update(installment_plan) + + assert isinstance(installment_plan, resources.InstallmentPlan) + assert installment_plan.id == 'installment_plan_id' + + def test_create(self): + installment_plan = payplug.InstallmentPlan.create() + + assert isinstance(installment_plan, resources.InstallmentPlan) + assert installment_plan.some == 'installment_plan' \ No newline at end of file diff --git a/payplug/test/test_resources/test_installment_plans.py b/payplug/test/test_resources/test_installment_plans.py new file mode 100644 index 0000000..38b9dfb --- /dev/null +++ b/payplug/test/test_resources/test_installment_plans.py @@ -0,0 +1,109 @@ +from payplug.resources import InstallmentPlan +from payplug.test import TestBase + + +class TestInstallmentPlansResource(TestBase): + def test_initializer_installment_plans(self): + + null = None + true = True + false = False + + installment_plan_attributes = { + "id": "inst_1FTGSRWYHla7eDkfTo2Usd", + "object": "installment_plan", + "is_live": true, + "is_active": true, + "currency": "EUR", + "created_at": 1548326773, + "is_fully_paid": false, + "billing": { + "title": "mr", + "first_name": "John", + "last_name": "Watson", + "email": "john.watson@example.net", + "mobile_phone_number": null, + "landline_phone_number": null, + "address1": "221B Baker Street", + "address2": null, + "postcode": "NW16XE", + "city": "London", + "state": null, + "country": "GB", + "language": "en", + }, + "shipping": { + "title": "mr", + "first_name": "John", + "last_name": "Watson", + "email": "john.watson@example.net", + "mobile_phone_number": null, + "landline_phone_number": null, + "address1": "221B Baker Street", + "address2": null, + "postcode": "NW16XE", + "city": "London", + "state": null, + "country": "GB", + "language": "en", + "delivery_type": "BILLING", + }, + "hosted_payment": { + "payment_url": "https://secure.payplug.com/pay/1FTGSRWYHla7eDkfTo2Usd", + "return_url": "https://example.net/success?id=42", + "cancel_url": "https://example.net/cancel?id=42", + }, + "notification": {"url": "https://example.net/notifications?id=42"}, + "schedule": [ + { + "date": "2019-01-24", + "amount": 15000, + "payment_ids": ["pay_62VazeAbq5ttYietdC2QxR"], + }, + { + "date": "2019-02-24", + "amount": 15000, + "payment_ids": ["pay_12uazeAbq5ttYietdC2QxP"], + }, + {"date": "2019-03-24", "amount": 10000, "payment_ids": []}, + ], + "failure": null, + "metadata": {"customer_id": 42}, + } + + installment_plan_object = InstallmentPlan(**installment_plan_attributes) + + assert isinstance(installment_plan_object, InstallmentPlan) + assert installment_plan_object.created_at == 1548326773 + assert installment_plan_object.currency == "EUR" + assert installment_plan_object.billing == { + "title": "mr", + "first_name": "John", + "last_name": "Watson", + "email": "john.watson@example.net", + "mobile_phone_number": null, + "landline_phone_number": null, + "address1": "221B Baker Street", + "address2": null, + "postcode": "NW16XE", + "city": "London", + "state": null, + "country": "GB", + "language": "en", + } + assert installment_plan_object.shipping == { + "title": "mr", + "first_name": "John", + "last_name": "Watson", + "email": "john.watson@example.net", + "mobile_phone_number": null, + "landline_phone_number": null, + "address1": "221B Baker Street", + "address2": null, + "postcode": "NW16XE", + "city": "London", + "state": null, + "country": "GB", + "language": "en", + "delivery_type": "BILLING", + } From 88c1ae84bc7bb3f9be755d9ab4e866d81db677e2 Mon Sep 17 00:00:00 2001 From: Florian Richard Date: Tue, 18 May 2021 11:22:29 +0200 Subject: [PATCH 09/13] fix consistent resource --- payplug/__init__.py | 36 ++++--------------- payplug/resources.py | 33 ++++++----------- .../test_init/test_dao_installment_plans.py | 21 ++--------- 3 files changed, 21 insertions(+), 69 deletions(-) diff --git a/payplug/__init__.py b/payplug/__init__.py index 6faedae..bd27d59 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -422,40 +422,18 @@ class InstallmentPlan: """ A DAO for resources.InstallmentPlans which provides a way to query installment plans. """ - @staticmethod - def create(**data): - """ - Create an installment plan. - - :param data: data required to create an installment plan - - :return: The installment plan - :rtype resources.InstallmentPlan - """ - http_client = HttpClient() - response, _ = http_client.post(routes.url(routes.INSTALLMENT_PLANS), data) - return resources.InstallmentPlan(**response) - - @staticmethod - def update(installment_plan, **data): - - if isinstance(installment_plan, resources.InstallmentPlan): - installment_plan = installment_plan.id - - http_client = HttpClient() - response, _ = http_client.patch(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan), data) - return resources.InstallmentPlan(**response) @staticmethod - def get(installment_plan_id): + def retrieve(installment_plan_id): """ - Get an installment plan. + Retrieve an installment plan from its id. - :param data: data required to get an installment plan + :param installment_plan_id: The installment plan id + :type installment_plan_id: string - :return: The installment plan - :rtype resources.InstallmentPlan + :return: The installment plan resource + :rtype: resources.InstallmentPlan """ http_client = HttpClient() - response, _ = http_client.get(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan_id)) + response, __ = http_client.get(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan_id)) return resources.InstallmentPlan(**response) diff --git a/payplug/resources.py b/payplug/resources.py index 8615fdd..050c39e 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -393,30 +393,19 @@ class Operation(APIResource): pass -class InstallmentPlan(APIResource): +class InstallmentPlan(APIResource, VerifiableAPIResource, ReconstituableAPIResource): """ An InstallmentPlans Resource """ object_type = 'intallment_plan' - """ - Create an InstallmentPlan - - :param data: the data to create the installment plan - """ - def create(self, **data): - return payplug.InstallmentPlans.create_installment_plan_endpoint(data) - - """ - Update an InstallmentPlan - - :param data: the data needed to update the installment plan - """ - def update(self, **data): - return payplug.InstallmentPlans.update_installment_plan_endpoint(self, data) - - """ - Get an InstallmentPlan - """ - def get(self): - return payplug.InstallmentPlans.get_installment_plan_endpoint(self) \ No newline at end of file + def get_consistent_resource(self): + """ + :return an Installment Plan that you can trust. + :rtype InstallmentPlan + """ + http_client = HttpClient() + response, _ = http_client.get( + routes.url(routes.INSTALLMENT_PLANS, resource_id=self.id) + ) + return InstallmentPlan(**response) \ No newline at end of file diff --git a/payplug/test/test_init/test_dao_installment_plans.py b/payplug/test/test_init/test_dao_installment_plans.py index 4bacc22..9715d12 100644 --- a/payplug/test/test_init/test_dao_installment_plans.py +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -5,25 +5,10 @@ from payplug.test import TestBase @patch('payplug.config.secret_key', 'a_secret_key') -@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({"some":"installment_plan", "da":"ta"}, 201)) -@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 200)) @patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 200)) class TestInstallmentPlanRetrieve(TestBase): - def test_get(self): - installment_plan = payplug.InstallmentPlan.get('installment_plan_id') + def test_retrieve(self): + installment_plan = payplug.InstallmentPlan.retrieve('installment_plan_id') assert isinstance(installment_plan, resources.InstallmentPlan) - assert installment_plan.id == 'installment_plan_id' - - def test_update(self): - installment_plan = payplug.InstallmentPlan.get('installment_plan_id') - installment_plan = payplug.InstallmentPlan.update(installment_plan) - - assert isinstance(installment_plan, resources.InstallmentPlan) - assert installment_plan.id == 'installment_plan_id' - - def test_create(self): - installment_plan = payplug.InstallmentPlan.create() - - assert isinstance(installment_plan, resources.InstallmentPlan) - assert installment_plan.some == 'installment_plan' \ No newline at end of file + assert installment_plan.id == 'installment_plan_id' \ No newline at end of file From 5dadbf4026dc1ae833400f500ec523e4c23515cb Mon Sep 17 00:00:00 2001 From: Florian Richard Date: Wed, 9 Jun 2021 18:46:03 +0200 Subject: [PATCH 10/13] add abort and create methods and tests --- payplug/__init__.py | 31 ++++++++++++++++++- .../test_init/test_dao_installment_plans.py | 21 ++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/payplug/__init__.py b/payplug/__init__.py index bd27d59..1ee40dd 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -420,7 +420,7 @@ def get_simulation(**data): class InstallmentPlan: """ - A DAO for resources.InstallmentPlans which provides a way to query installment plans. + A DAO for resources.InstallmentPlan which provides a way to query installment plans. """ @staticmethod @@ -437,3 +437,32 @@ def retrieve(installment_plan_id): http_client = HttpClient() response, __ = http_client.get(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan_id)) return resources.InstallmentPlan(**response) + + + @staticmethod + def create(**data): + """ + Create an installment plan. + :param data: data required to create an installment plan + :return: The installment plan + :rtype resources.InstallmentPlan + """ + http_client = HttpClient() + response, _ = http_client.post(routes.url(routes.INSTALLMENT_PLANS), data) + return resources.InstallmentPlan(**response) + + + @staticmethod + def abort(installment_plan_id): + """ + Abort an installment plan. + :param installment_plan_id: The installment plan id + :type installment_plan_id: string + + :return: The installment plan + :rtype resources.InstallmentPlan + """ + abort = {"aborted": True} + http_client = HttpClient() + response, _ = http_client.patch(routes.url(routes.INSTALLMENT_PLANS, resource_id=installment_plan_id), abort) + return resources.InstallmentPlan(**response) diff --git a/payplug/test/test_init/test_dao_installment_plans.py b/payplug/test/test_init/test_dao_installment_plans.py index 9715d12..f38742b 100644 --- a/payplug/test/test_init/test_dao_installment_plans.py +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -6,9 +6,28 @@ @patch('payplug.config.secret_key', 'a_secret_key') @patch.object(payplug.HttpClient, 'get', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 200)) +@patch.object(payplug.HttpClient, 'patch', lambda *args, **kwargs: ({'id': 'installment_plan_id', 'failure': {'code': 'aborted'}}, 204)) +@patch.object(payplug.HttpClient, 'post', lambda *args, **kwargs: ({'id': 'installment_plan_id'}, 201)) class TestInstallmentPlanRetrieve(TestBase): def test_retrieve(self): installment_plan = payplug.InstallmentPlan.retrieve('installment_plan_id') assert isinstance(installment_plan, resources.InstallmentPlan) - assert installment_plan.id == 'installment_plan_id' \ No newline at end of file + assert installment_plan.id == 'installment_plan_id' + + + def test_update(self): + installment_plan = payplug.InstallmentPlan.retrieve('installment_plan_id') + installment_plan = payplug.InstallmentPlan.abort(installment_plan) + + assert isinstance(installment_plan, resources.InstallmentPlan) + assert installment_plan.id == 'installment_plan_id' + assert installment_plan.failure['code'] == 'aborted' + + + def test_create(self): + installment_plan = payplug.InstallmentPlan.create() + + assert isinstance(installment_plan, resources.InstallmentPlan) + assert installment_plan.id == 'installment_plan_id' + \ No newline at end of file From c4b440774d649c916c98df7fc17032b094341a03 Mon Sep 17 00:00:00 2001 From: Philippe L'ATTENTION Date: Tue, 31 Aug 2021 09:22:06 +0400 Subject: [PATCH 11/13] [IMP] InstallmentPlans api resource - Fix object name "intallment_plan" - Refactor mapper InstallmentPlan data to have APIResource object like Payment - Adding Schedule APIResource and initializer to map list to list of Schedule(s) - Add tests notifications treat - Fix tests to use new APIResource objects - Bump version --- payplug/__version__.py | 2 +- payplug/resources.py | 39 ++++++- .../test_init/test_dao_installment_plans.py | 3 +- payplug/test/test_notifications.py | 108 ++++++++++++++++++ .../test_resources/test_installment_plans.py | 81 +++++++------ 5 files changed, 194 insertions(+), 39 deletions(-) diff --git a/payplug/__version__.py b/payplug/__version__.py index cf0bccd..b795e2c 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.4.0' +__version__ = '1.4.1' diff --git a/payplug/resources.py b/payplug/resources.py index 050c39e..98842fc 100644 --- a/payplug/resources.py +++ b/payplug/resources.py @@ -397,7 +397,36 @@ class InstallmentPlan(APIResource, VerifiableAPIResource, ReconstituableAPIResou """ An InstallmentPlans Resource """ - object_type = 'intallment_plan' + object_type = 'installment_plan' + + @property + def _mapper(self): + """ + Maps payment attributes to their specific types. + + :see :func:`~APIResource._mapper` + """ + return { + 'hosted_payment': Payment.HostedPayment, + 'notification': Payment.Notification, + 'failure': Payment.Failure, + 'billing': Payment.Billing, + 'shipping': Payment.Shipping, + } + + def _initialize(self, **resource_attributes): + """ + Initialize a resource. + Default behavior is just to set all the attributes. You may want to override this. + + :param resource_attributes: The resource attributes + """ + + schedules = [] + for schedule in resource_attributes.get('schedule', []): + schedules.append(InstallmentPlan.Schedule(**schedule)) + resource_attributes["schedule"] = schedules + super(InstallmentPlan, self)._initialize(**resource_attributes) def get_consistent_resource(self): """ @@ -408,4 +437,10 @@ def get_consistent_resource(self): response, _ = http_client.get( routes.url(routes.INSTALLMENT_PLANS, resource_id=self.id) ) - return InstallmentPlan(**response) \ No newline at end of file + return InstallmentPlan(**response) + + class Schedule(APIResource): + """ + Schedule information + """ + pass diff --git a/payplug/test/test_init/test_dao_installment_plans.py b/payplug/test/test_init/test_dao_installment_plans.py index f38742b..7846d8c 100644 --- a/payplug/test/test_init/test_dao_installment_plans.py +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -22,7 +22,7 @@ def test_update(self): assert isinstance(installment_plan, resources.InstallmentPlan) assert installment_plan.id == 'installment_plan_id' - assert installment_plan.failure['code'] == 'aborted' + assert installment_plan.failure.code == 'aborted' def test_create(self): @@ -30,4 +30,3 @@ def test_create(self): assert isinstance(installment_plan, resources.InstallmentPlan) assert installment_plan.id == 'installment_plan_id' - \ No newline at end of file diff --git a/payplug/test/test_notifications.py b/payplug/test/test_notifications.py index 475e0c8..0227e3c 100644 --- a/payplug/test/test_notifications.py +++ b/payplug/test/test_notifications.py @@ -56,3 +56,111 @@ def test_treat_payment(self): def test_treat_binary_string(self): safe_payment = notifications.treat(b'{"id": "pay_test_unsafe", "object": "payment"}') assert safe_payment.id == 'pay_test' + + +@patch('payplug.config.secret_key', 'a_secret_key') +class TestTreatNotificationsInstallmentPlanSuccess(TestBase): + @classmethod + def setup_class(cls): + api_response = { + "hosted_payment": { + "cancel_url": "https://example.com/payment/payplug/cancel", + "return_url": "https://example.com/shop/payment/validate", + "payment_url": "https://secure.payplug.com/pay/test/59TXrYROSdmt0Y7W9Ubpg8" + }, + "customer": { + "phone_number": "None", + "city": "PARIS", + "first_name": "JOHN", + "last_name": "DOE", + "language": "fr", + "address1": "21 Elm Street", + "address2": "None", + "postcode": "75018", + "country": "France", + "email": "johndoe@example.com", + }, + "schedule": [ + {"date": "2021-08-04", "amount": 2600, "payment_ids": ["pay_59TXrYROSdmt0Y7W9Ubpg8"]}, + {"date": "2021-09-04", "amount": 2600, "payment_ids": []}, + {"date": "2021-10-04", "amount": 2600, "payment_ids": []}, + ], + "notification": {"url": "https://example.com/payment/payplug/ipn"}, + "created_at": 1628079236, + "object": "installment_plan", + "is_active": True, + "currency": "EUR", + "is_live": False, + "is_fully_paid": False, + "id": "inst_2aSCmLRZFtAA7arHJfGrE1", + "failure": "None", + "metadata": {"customer_id": "23", "acquirer_id": 5, "reference": "CMD0000123"}, + } + cls.patcher_get = patch.object(payplug.resources.HttpClient, 'get', return_value=(api_response, 200)) + cls.patcher_get.start() + + @classmethod + def teardown_class(cls): + cls.patcher_get.stop() + + def test_treat_installment_plan(self): + json_data = '''{ + "hosted_payment":{ + "cancel_url":"https://example.com/payment/payplug/cancel", + "return_url":"https://example.com/shop/payment/validate", + "payment_url":"https://secure.payplug.com/pay/test/59TXrYROSdmt0Y7W9Ubpg8" + }, + "customer":{ + "phone_number":"None", + "city":"PARIS", + "first_name":"JOHN", + "last_name":"DOE", + "language":"fr", + "address1":"21 Elm Street", + "address2":"None", + "postcode":"75018", + "country":"France", + "email":"johndoe@example.com" + }, + "schedule":[ + { + "date":"2021-08-04", + "amount":2600, + "payment_ids":[ + "pay_59TXrYROSdmt0Y7W9Ubpg8" + ] + }, + { + "date":"2021-09-04", + "amount":2600, + "payment_ids":[ + + ] + }, + { + "date":"2021-10-04", + "amount":2600, + "payment_ids":[ + + ] + } + ], + "notification":{ + "url":"https://example.com/payment/payplug/ipn" + }, + "created_at":1628079236, + "object":"installment_plan", + "is_active":true, + "currency":"EUR", + "is_live":false, + "is_fully_paid":false, + "id":"inst_2aSCmLRZFtAA7arHJfGrE1", + "failure":"None", + "metadata":{ + "customer_id":"23", + "acquirer_id":5, + "reference":"CMD0000123" + } + }''' + safe_installment_plan = notifications.treat(json_data) + assert safe_installment_plan.id == 'inst_2aSCmLRZFtAA7arHJfGrE1' diff --git a/payplug/test/test_resources/test_installment_plans.py b/payplug/test/test_resources/test_installment_plans.py index 38b9dfb..e13b080 100644 --- a/payplug/test/test_resources/test_installment_plans.py +++ b/payplug/test/test_resources/test_installment_plans.py @@ -1,4 +1,5 @@ -from payplug.resources import InstallmentPlan +# -*- coding: utf-8 -*- +from payplug.resources import InstallmentPlan, Payment from payplug.test import TestBase @@ -67,43 +68,55 @@ def test_initializer_installment_plans(self): }, {"date": "2019-03-24", "amount": 10000, "payment_ids": []}, ], - "failure": null, + "failure": { + "code": "a_failure_code", + "message": 'A weird failure message ®±' + }, "metadata": {"customer_id": 42}, } installment_plan_object = InstallmentPlan(**installment_plan_attributes) - + assert isinstance(installment_plan_object, InstallmentPlan) assert installment_plan_object.created_at == 1548326773 assert installment_plan_object.currency == "EUR" - assert installment_plan_object.billing == { - "title": "mr", - "first_name": "John", - "last_name": "Watson", - "email": "john.watson@example.net", - "mobile_phone_number": null, - "landline_phone_number": null, - "address1": "221B Baker Street", - "address2": null, - "postcode": "NW16XE", - "city": "London", - "state": null, - "country": "GB", - "language": "en", - } - assert installment_plan_object.shipping == { - "title": "mr", - "first_name": "John", - "last_name": "Watson", - "email": "john.watson@example.net", - "mobile_phone_number": null, - "landline_phone_number": null, - "address1": "221B Baker Street", - "address2": null, - "postcode": "NW16XE", - "city": "London", - "state": null, - "country": "GB", - "language": "en", - "delivery_type": "BILLING", - } + assert installment_plan_object.is_fully_paid == False + + assert type(installment_plan_object.billing) == Payment.Billing + assert installment_plan_object.billing.title == 'mr' + assert installment_plan_object.billing.first_name == "John" + assert installment_plan_object.billing.last_name == "Watson" + assert installment_plan_object.billing.email == "john.watson@example.net" + assert installment_plan_object.billing.address1 == '221B Baker Street' + assert installment_plan_object.billing.postcode == 'NW16XE' + assert installment_plan_object.billing.city == 'London' + assert installment_plan_object.billing.country == 'GB' + assert installment_plan_object.billing.language == 'en' + + assert type(installment_plan_object.shipping) == Payment.Shipping + assert installment_plan_object.shipping.title == 'mr' + assert installment_plan_object.shipping.first_name == "John" + assert installment_plan_object.shipping.last_name == "Watson" + assert installment_plan_object.shipping.email == "john.watson@example.net" + assert installment_plan_object.shipping.address1 == '221B Baker Street' + assert installment_plan_object.shipping.postcode == 'NW16XE' + assert installment_plan_object.shipping.city == 'London' + assert installment_plan_object.shipping.country == 'GB' + assert installment_plan_object.shipping.language == 'en' + assert installment_plan_object.shipping.delivery_type == 'BILLING' + + assert type(installment_plan_object.hosted_payment) == Payment.HostedPayment + assert installment_plan_object.hosted_payment.payment_url == "https://secure.payplug.com/pay/1FTGSRWYHla7eDkfTo2Usd" + assert installment_plan_object.hosted_payment.return_url == "https://example.net/success?id=42" + assert installment_plan_object.hosted_payment.cancel_url == "https://example.net/cancel?id=42" + + + assert installment_plan_object.notification.url == "https://example.net/notifications?id=42" + assert type(installment_plan_object.schedule) == list + for schedule in installment_plan_object.schedule: + assert type(schedule) == InstallmentPlan.Schedule + assert type(schedule.date) == str + assert type(schedule.amount) == int + assert type(schedule.payment_ids) == list + for payment_id in schedule.payment_ids: + assert type(payment_id) == str \ No newline at end of file From 6d293bf32bd0009ea8df79d16aac1ae285204357 Mon Sep 17 00:00:00 2001 From: Florian Richard Date: Wed, 24 Nov 2021 15:51:28 +0100 Subject: [PATCH 12/13] typo --- payplug/test/test_init/test_dao_installment_plans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payplug/test/test_init/test_dao_installment_plans.py b/payplug/test/test_init/test_dao_installment_plans.py index 7846d8c..c2038f0 100644 --- a/payplug/test/test_init/test_dao_installment_plans.py +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -18,7 +18,7 @@ def test_retrieve(self): def test_update(self): installment_plan = payplug.InstallmentPlan.retrieve('installment_plan_id') - installment_plan = payplug.InstallmentPlan.abort(installment_plan) + installment_plan = payplug.InstallmentPlan.abort(installment_plan.id) assert isinstance(installment_plan, resources.InstallmentPlan) assert installment_plan.id == 'installment_plan_id' From 9dab701dea165dbacfbd393091446495a52a0113 Mon Sep 17 00:00:00 2001 From: alopez-pp <63304175+alopez-pp@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:20:51 +0100 Subject: [PATCH 13/13] Update README.rst --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 8d9aef8..960eb47 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -Python library for the PayPlug API +Python library for the Payplug API ================================== .. image:: https://github.com/payplug/payplug-python/workflows/CI/badge.svg @@ -9,15 +9,15 @@ Python library for the PayPlug API :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 +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. -You can create a PayPlug account at https://www.payplug.com. +You can create a Payplug account at https://www.payplug.com. Prerequisites ------------- -PayPlug's library relies on **python-requests>=1.0.1** to perform HTTP requests and requires **OpenSSL** to secure +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.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 @@ -61,7 +61,7 @@ To get started, add the following piece of code to the header of your Python pro import payplug -If everything runs without errors, congratulations. You installed PayPlug python library! You're ready to create your +If everything runs without errors, congratulations. You installed Payplug python library! You're ready to create your first payment. Usage