diff --git a/Adyen/client.py b/Adyen/client.py index bcdb266b..28a74ce9 100644 --- a/Adyen/client.py +++ b/Adyen/client.py @@ -145,13 +145,14 @@ def _determine_hpp_url(platform, action): result = '/'.join([base_uri, service]) return result - def _determine_checkout_url(self, platform, action): + def _determine_checkout_url(self, platform, action, path_param=None): """This returns the Adyen API endpoint based on the provided platform, service and action. Args: platform (str): Adyen platform, ie 'live' or 'test'. action (str): the API action to perform. + path_param Optional[(str)]: a generic id that can be used to modify a payment e.g. paymentPspReference. """ api_version = settings.API_CHECKOUT_VERSION if platform == "test": @@ -171,6 +172,14 @@ def _determine_checkout_url(self, platform, action): action = "payments/details" if action == "paymentsResult": action = "payments/result" + if action == "paymentsCancelsWithoutReference": + action = "payments/cancels" + if action == "paymentsCancelsWithReference": + action = f"payments/{path_param}/cancels" + if action == "paymentsReversals": + action = f"payments/{path_param}/reversals" + if action == "payments/Refunds": + action = f"payments/{path_param}/refunds" if action == "originKeys": api_version = settings.API_CHECKOUT_UTILITY_VERSION if action == "paymentMethodsBalance": @@ -437,7 +446,7 @@ class instance. status_code, headers, message) return adyen_result - def call_checkout_api(self, request_data, action, idempotency_key=None, + def call_checkout_api(self, request_data, action, idempotency_key=None, path_param=None, **kwargs): """This will call the checkout adyen api. xapi key merchant_account, and platform are pulled from root module level and or self object. @@ -452,6 +461,7 @@ def call_checkout_api(self, request_data, action, idempotency_key=None, https://docs.adyen.com/developers/checkout/api-integration service (str): This is the API service to be called. action (str): The specific action of the API service to be called + path_param (str): This is used to pass the id or referenceID to the API sercie """ if not self.http_init: self.http_client = HTTPClient(self.USER_AGENT_SUFFIX, @@ -520,7 +530,7 @@ def call_checkout_api(self, request_data, action, idempotency_key=None, headers = {} if idempotency_key: headers[self.IDEMPOTENCY_HEADER_NAME] = idempotency_key - url = self._determine_checkout_url(platform, action) + url = self._determine_checkout_url(platform, action, path_param) raw_response, raw_request, status_code, headers = \ self.http_client.request(url, json=request_data, diff --git a/Adyen/services.py b/Adyen/services.py index 3c44273a..9bbb45c1 100644 --- a/Adyen/services.py +++ b/Adyen/services.py @@ -280,6 +280,13 @@ class AdyenCheckoutApi(AdyenServiceBase): payments/details originKeys + Modifications: + capture + refunds + cancels + reversals + + Please refer to the checkout documentation for specifics around the API. https://docs.adyen.com/developers/checkout @@ -322,6 +329,42 @@ def payment_result(self, request=None, **kwargs): action = "paymentsResult" return self.client.call_checkout_api(request, action, **kwargs) + def payment_captures(self, path_param, request=None, idempotency_key=None, **kwargs): + if path_param == "": + raise ValueError( + 'must contain a pspReference in the path_param, path_param cannot be empty' + ) + action = "paymentsCapture" + return self.client.call_checkout_api(request, action, path_param, idempotency_key, **kwargs) + + def payments_cancels_without_reference(self, request=None, idempotency_key=None, **kwargs): + action = "paymentsCancelsWithoutReference" + return self.client.call_checkout_api(request, action, idempotency_key, **kwargs) + + def payments_cancels_with_reference(self, path_param, request=None, idempotency_key=None, **kwargs): + if path_param == "": + raise ValueError( + 'must contain a pspReference in the path_param, path_param cannot be empty' + ) + action = "paymentsCancelsWithReference" + return self.client.call_checkout_api(request, action, path_param, idempotency_key, **kwargs) + + def payments_reversals(self, path_param, request=None, idempotency_key=None, **kwargs): + if path_param == "": + raise ValueError( + 'must contain a pspReference in the path_param, path_param cannot be empty' + ) + action = "paymentsReversals" + return self.client.call_checkout_api(request, action, path_param, idempotency_key, **kwargs) + + def payments_refunds(self, path_param, request=None, idempotency_key=None, **kwargs): + if path_param == "": + raise ValueError( + 'must contain a pspReference in the path_param, path_param cannot be empty' + ) + action = "paymentsRefunds" + return self.client.call_checkout_api(request, action, path_param, idempotency_key, **kwargs) + def origin_keys(self, request=None, **kwargs): action = "originKeys" return self.client.call_checkout_api(request, action, **kwargs) diff --git a/README.md b/README.md index 61973c89..d878a202 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,85 @@ [![Build Status](https://travis-ci.org/Adyen/adyen-python-api-library.svg?branch=master)](https://travis-ci.org/Adyen/adyen-python-api-library) [![Coverage Status](https://coveralls.io/repos/github/Adyen/adyen-python-api-library/badge.svg?branch=master)](https://coveralls.io/github/Adyen/adyen-python-api-library?branch=master) -# Adyen APIs Library for Python - -This library simplifies working with Adyen APIs and allows you to integrate Adyen -payments within any Python application. - -## Integration -The Library supports all APIs under the following services: - -* checkout -* checkout utility -* payments -* modifications -* payouts -* recurring - -## Requirements - +This is the officially supported Python library for using Adyen's APIs. +## Integration +The library supports all APIs under the following services: + +* [Checkout API](https://docs.adyen.com/api-explorer/#/CheckoutService/v67/overview): Our latest integration for accepting online payments. Current supported version: **v67** +* [Payments API](https://docs.adyen.com/api-explorer/#/Payment/v64/overview): Our classic integration for online payments. Current supported version: **v64** +* [Recurring API](https://docs.adyen.com/api-explorer/#/Recurring/v49/overview): Endpoints for managing saved payment details. Current supported version: **v49** +* [Payouts API](https://docs.adyen.com/api-explorer/#/Payout/v64/overview): Endpoints for sending funds to your customers. Current supported version: **v64** +* [Orders API](https://docs.adyen.com/api-explorer/#/CheckoutService/v67/post/orders): Endpoints for creating and canceling orders. Current supported version: **v67** +* [Utility API](https://docs.adyen.com/api-explorer/#/CheckoutService/v67/post/originKeys): This operation takes the origin domains and returns a JSON object containing the corresponding origin keys for the domains. Current supported version: **v67** + +For more information, refer to our [documentation](https://docs.adyen.com/) or the [API Explorer](https://docs.adyen.com/api-explorer/). + + +## Prerequisites + +- [Adyen test account](https://docs.adyen.com/get-started-with-adyen) +- [API key](https://docs.adyen.com/development-resources/api-credentials#generate-api-key). For testing, your API credential needs to have the [API PCI Payments role](https://docs.adyen.com/development-resources/api-credentials#roles). - Python 2.7 or 3.6 - Packages: requests or pycurl ( optional ) -- Adyen account. If you don't have this you can request it here: https://www.adyen.com/home/discover/test-account-signup#form + -## Installation + ## Installation ### For development propose -Clone this repository and run ```make install``` +Clone this repository and run +~~~~ bash +make install +~~~~ ### For usage propose -Use pip command: ```pip install Adyen``` - -## Usage - -Create a class instance of the 'Adyen' class. - -```python +Use pip command: +~~~~ bash +pip install Adyen +~~~~ + +## Using the library + + +### General use with API key + +~~~~ python import Adyen ady = Adyen.Adyen() -ady.payment.client.username = "webservice user name" +ady.payment.client.xapikey = "YourXapikey" ady.payment.client.skin_code = "skin code for Hosted Payment pages" ady.payment.client.hmac = "HMAC key for skin code" ady.payment.client.platform = "test" # Environment to use the library in. ady.payment.client.merchant_account = "merchant account name from CA" -ady.payment.client.password = "webservice user password" -``` - -## Documentation -* https://docs.adyen.com/developers/development-resources/libraries -* https://docs.adyen.com/developers/checkout +~~~~ + +### Example integration + +For a closer look at how our Python library works, clone our [example integration](https://github.com/adyen-examples/adyen-python-online-payments). This includes commented code, highlighting key features and concepts, and examples of API calls that can be made using the library. -## Support -If you have a feature request, or spotted a bug or a technical problem, create a GitHub issue. For other questions, contact our [support team](https://support.adyen.com/hc/en-us/requests/new?ticket_form_id=360000705420). ## Contributing -We strongly encourage you to join us in contributing to this repository so everyone can benefit from: -* New features and functionality -* Resolved bug fixes and issues -* Any general improvements - -Read our [**contribution guidelines**](CONTRIBUTING.md) to find out how. - + +We encourage you to contribute to this repository, so everyone can benefit from new features, bug fixes, and any other improvements. + + +Have a look at our [contributing guidelines](https://github.com/Adyen/adyen-python-api-library/blob/develop/CONTRIBUTING.md) to find out how to raise a pull request. + + +## Support +If you have a feature request, or spotted a bug or a technical problem, [create an issue here](https://github.com/Adyen/adyen-web/issues/new/choose). + +For other questions, [contact our Support Team](https://www.adyen.help/hc/en-us/requests/new?ticket_form_id=360000705420). + + ## Licence -MIT license see LICENSE +This repository is available under the [MIT license](https://github.com/Adyen/adyen-python-api-library/blob/master/LICENSE.md). + + +## See also +* [Example integration](https://github.com/adyen-examples/adyen-python-online-payments) +* [Adyen docs](https://docs.adyen.com/) +* [API Explorer](https://docs.adyen.com/api-explorer/) diff --git a/test/CheckoutTest.py b/test/CheckoutTest.py index 14bec1ab..bffc5526 100644 --- a/test/CheckoutTest.py +++ b/test/CheckoutTest.py @@ -236,6 +236,201 @@ def test_payments_result_error_mocked(self): self.assertEqual("Invalid payload provided", result.message['message']) self.assertEqual("validation", result.message['errorType']) + def test_payments_cancels_without_reference(self): + requests = { + "paymentReference": "Payment123", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "reference": "YourCancelReference", + } + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentscancel-" + "withoutreference-succes.json") + results = self.adyen.checkout.payments_cancels_without_reference(request=requests) + self.assertIsNotNone(results.message['paymentReference']) + self.assertEqual("8412534564722331", results.message['pspReference']) + self.assertEqual("received", results.message['status']) + + def test_payments_cancels_without_reference_error_mocked(self): + requests = { + "paymentReference": "Payment123", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "reference": "YourCancelReference", + } + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentsresult" + "-error-invalid-" + "data-payload-" + "422.json") + + result = self.adyen.checkout.payments_cancels_without_reference(requests) + self.assertEqual(422, result.message['status']) + self.assertEqual("14_018", result.message['errorCode']) + self.assertEqual("Invalid payload provided", result.message['message']) + self.assertEqual("validation", result.message['errorType']) + + def test_payments_cancels_success_mocked(self): + requests = {"reference": "Your wro order number", "merchantAccount": "YOUR_MERCHANT_ACCOUNT"} + reference_id = "8836183819713023" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentscancels" + "-success.json") + result = self.adyen.checkout.payments_cancels_with_reference(request=requests, path_param=reference_id) + self.assertEqual(reference_id, result.message["paymentPspReference"]) + self.assertEqual("received", result.message['status']) + + def test_payments_cancels_error_mocked(self): + requests = {"reference": "Your wro order number"} + psp_reference = "8836183819713023" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentsresult-error-invalid-" + "data-payload-422.json") + result = self.adyen.checkout.payments_cancels_with_reference(request=requests, path_param=psp_reference) + self.assertEqual(422, result.message['status']) + self.assertEqual("14_018", result.message['errorCode']) + self.assertEqual("Invalid payload provided", result.message['message']) + self.assertEqual("validation", result.message['errorType']) + + def test_payments_refunds_success_mocked(self): + requests = { + "paymentReference": "Payment123", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "reference": "YourCancelReference", + } + psp_reference = "Payment123" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentscancel-" + "withoutreference-succes.json") + + result = self.adyen.checkout.payments_cancels_without_reference(request=requests, path_param=psp_reference) + self.assertEqual(psp_reference, result.message["paymentReference"]) + self.assertIsNotNone(result.message["pspReference"]) + self.assertEqual("received", result.message['status']) + + def test_payments_refunds_error_mocked(self): + requests = { + "paymentReference": "Payment123", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "reference": "YourCancelReference", + } + reference_id = "Payment123" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentsresult-error-invalid-" + "data-payload-422.json") + + result = self.adyen.checkout.payments_cancels_without_reference(request=requests, path_param=reference_id) + self.assertEqual(422, result.message['status']) + self.assertEqual("14_018", result.message['errorCode']) + self.assertEqual("Invalid payload provided", result.message['message']) + self.assertEqual("validation", result.message['errorType']) + + def test_payments_refunds_raises_vaulue_error(self): + requests = { + "paymentReference": "Payment123", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "reference": "YourCancelReference", + } + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentscancel-" + "withoutreference-succes.json") + with self.assertRaises(ValueError) as exc: + self.adyen.checkout.payments_cancels_with_reference(request=requests, path_param="") + self.assertEqual(exc.exception.__class__, ValueError) + self.assertEqual(exc.exception.__str__(), 'must contain a pspReference in the path_param, path_param cannot ' + 'be empty') + + def test_reversals_success_mocked(self): + requests = { + "reference": "YourReversalReference", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT" + } + psp_reference = "8836183819713023" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentsreversals-" + "success.json") + + result = self.adyen.checkout.payments_reversals(request=requests, path_param=psp_reference) + self.assertEqual(psp_reference, result.message["paymentPspReference"]) + self.assertIsNotNone(result.message["pspReference"]) + self.assertEqual("received", result.message['status']) + + def test_payments_reversals_failure_mocked(self): + requests = { + "reference": "YourReversalReference", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT" + } + psp_reference = "8836183819713023" + self.adyen.client = self.test.create_client_from_file(200, requests, + "test/mocks/" + "checkout/" + "paymentsresult-error-invalid-" + "data-payload-422.json") + + result = self.adyen.checkout.payments_reversals(request=requests, path_param=psp_reference) + self.assertEqual(422, result.message['status']) + self.assertEqual("14_018", result.message['errorCode']) + self.assertEqual("Invalid payload provided", result.message['message']) + self.assertEqual("validation", result.message['errorType']) + + def test_payments_capture_success_mocked(self): + request = { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "amount": { + "value": 2500, + "currency": "EUR" + }, + "reference": "YOUR_UNIQUE_REFERENCE" + } + psp_reference = "8536214160615591" + self.adyen.client = self.test.create_client_from_file(200, request, + "test/mocks/" + "checkout/" + "paymentcapture-" + "success.json") + + result = self.adyen.checkout.payment_captures(request=request, path_param=psp_reference) + self.assertEqual(psp_reference, result.message["paymentPspReference"]) + self.assertIsNotNone(result.message["pspReference"]) + self.assertEqual("received", result.message['status']) + self.assertEqual(2500, result.message['amount']['value']) + + def test_payments_capture_error_mocked(self): + request = { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "amount": { + "value": 2500, + "currency": "EUR" + }, + "reference": "YOUR_UNIQUE_REFERENCE" + } + psp_reference = "8536214160615591" + self.adyen.client = self.test.create_client_from_file(200, request, + "test/mocks/" + "checkout/" + "paymentsresult-error-invalid-" + "data-payload-422.json") + + result = self.adyen.checkout.payment_captures(request=request, path_param=psp_reference) + self.assertEqual(422, result.message['status']) + self.assertEqual("14_018", result.message['errorCode']) + self.assertEqual("Invalid payload provided", result.message['message']) + self.assertEqual("validation", result.message['errorType']) + def test_orders_success(self): request = {'merchantAccount': "YourMerchantAccount"} self.adyen.client = self.test.create_client_from_file(200, request, diff --git a/test/mocks/checkout/paymentcapture-success.json b/test/mocks/checkout/paymentcapture-success.json new file mode 100644 index 00000000..00b019d2 --- /dev/null +++ b/test/mocks/checkout/paymentcapture-success.json @@ -0,0 +1,11 @@ +{ + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "paymentPspReference": "8536214160615591", + "pspReference": "8836226171638872", + "reference": "YOUR_UNIQUE_REFERENCE", + "status": "received", + "amount": { + "currency": "EUR", + "value": 2500 + } +} \ No newline at end of file diff --git a/test/mocks/checkout/paymentscancel-withoutreference-succes.json b/test/mocks/checkout/paymentscancel-withoutreference-succes.json new file mode 100644 index 00000000..744700ce --- /dev/null +++ b/test/mocks/checkout/paymentscancel-withoutreference-succes.json @@ -0,0 +1,7 @@ +{ + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "paymentReference": "Payment123", + "pspReference" : "8412534564722331", + "reference": "YourCancelReference", + "status" : "received" +} \ No newline at end of file diff --git a/test/mocks/checkout/paymentscancels-success.json b/test/mocks/checkout/paymentscancels-success.json new file mode 100644 index 00000000..f9883a90 --- /dev/null +++ b/test/mocks/checkout/paymentscancels-success.json @@ -0,0 +1,7 @@ +{ + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "paymentPspReference": "8836183819713023", + "pspReference" : "8412534564722331", + "reference": "Cancel123", + "status" : "received" +} \ No newline at end of file diff --git a/test/mocks/checkout/paymentsreversals-success.json b/test/mocks/checkout/paymentsreversals-success.json new file mode 100644 index 00000000..2503aed0 --- /dev/null +++ b/test/mocks/checkout/paymentsreversals-success.json @@ -0,0 +1,7 @@ +{ + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "paymentPspReference": "8836183819713023", + "pspReference" : "8863534564726784", + "reference": "YourReversalReference", + "status" : "received" +} \ No newline at end of file