diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a09d6d..5723baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +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 + 1.3.0 ----- - Add AccountingReport class to handle the new /accounting\_reports API endpoint diff --git a/README.rst b/README.rst index 18e1d35..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 @@ -71,14 +71,23 @@ 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', + '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', @@ -88,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: diff --git a/payplug/__init__.py b/payplug/__init__.py index 143abd7..1ee40dd 100644 --- a/payplug/__init__.py +++ b/payplug/__init__.py @@ -397,3 +397,72 @@ 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) + + +class InstallmentPlan: + """ + A DAO for resources.InstallmentPlan which provides a way to query installment plans. + """ + + @staticmethod + def retrieve(installment_plan_id): + """ + Retrieve an installment plan from its id. + + :param installment_plan_id: The installment plan id + :type installment_plan_id: string + + :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)) + 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/__version__.py b/payplug/__version__.py index ea1a019..b795e2c 100644 --- a/payplug/__version__.py +++ b/payplug/__version__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = '1.3.0' +__version__ = '1.4.1' diff --git a/payplug/resources.py b/payplug/resources.py index 871f965..98842fc 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): """ @@ -354,3 +368,79 @@ 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 + + +class InstallmentPlan(APIResource, VerifiableAPIResource, ReconstituableAPIResource): + """ + An InstallmentPlans Resource + """ + 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): + """ + :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) + + class Schedule(APIResource): + """ + Schedule information + """ + pass diff --git a/payplug/routes.py b/payplug/routes.py index 8f3b522..562c27b 100644 --- a/payplug/routes.py +++ b/payplug/routes.py @@ -7,6 +7,8 @@ CUSTOMER_RESOURCE = '/customers' 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..c2038f0 --- /dev/null +++ b/payplug/test/test_init/test_dao_installment_plans.py @@ -0,0 +1,32 @@ +# -*- 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, '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' + + + def test_update(self): + installment_plan = payplug.InstallmentPlan.retrieve('installment_plan_id') + installment_plan = payplug.InstallmentPlan.abort(installment_plan.id) + + 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' 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_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 new file mode 100644 index 0000000..e13b080 --- /dev/null +++ b/payplug/test/test_resources/test_installment_plans.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +from payplug.resources import InstallmentPlan, Payment +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": { + "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.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 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 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')