diff --git a/Adyen/__init__.py b/Adyen/__init__.py index 413bb8d2..888066a6 100644 --- a/Adyen/__init__.py +++ b/Adyen/__init__.py @@ -20,7 +20,8 @@ AdyenPayment, AdyenThirdPartyPayout, AdyenHPP, - AdyenCheckoutApi + AdyenCheckoutApi, + AdyenTerminal ) from .httpclient import HTTPClient @@ -35,6 +36,7 @@ def __init__(self, **kwargs): self.hpp = AdyenHPP(client=self.client) self.recurring = AdyenRecurring(client=self.client) self.checkout = AdyenCheckoutApi(client=self.client) + self.terminal = AdyenTerminal(client=self.client) _base_adyen_obj = Adyen() @@ -44,3 +46,4 @@ def __init__(self, **kwargs): payout = _base_adyen_obj.payout checkout = _base_adyen_obj.checkout binlookup = _base_adyen_obj.binlookup +terminal = _base_adyen_obj.terminal diff --git a/Adyen/client.py b/Adyen/client.py index e2a2190e..8915f078 100644 --- a/Adyen/client.py +++ b/Adyen/client.py @@ -89,6 +89,7 @@ def __init__( api_payment_version=None, api_payout_version=None, api_recurring_version=None, + api_terminal_version=None, ): self.username = username self.password = password @@ -115,6 +116,7 @@ def __init__( self.api_payment_version = api_payment_version or settings.API_PAYMENT_VERSION self.api_payout_version = api_payout_version or settings.API_PAYOUT_VERSION self.api_recurring_version = api_recurring_version or settings.API_RECURRING_VERSION + self.api_terminal_version = api_terminal_version or settings.API_TERMINAL_VERSION def _determine_api_url(self, platform, service, action): """This returns the Adyen API endpoint based on the provided platform, @@ -138,6 +140,9 @@ def _determine_api_url(self, platform, service, action): api_version = self.api_payout_version elif service == "BinLookup": api_version = self.api_bin_lookup_version + elif service == "terminal": + base_uri = settings.BASE_TERMINAL_URL.format(platform) + api_version = self.api_terminal_version else: api_version = self.api_payment_version return '/'.join([base_uri, service, api_version, action]) diff --git a/Adyen/services.py b/Adyen/services.py index ecbb423e..a8a3f90c 100644 --- a/Adyen/services.py +++ b/Adyen/services.py @@ -410,3 +410,40 @@ def get_cost_estimate(self, request="", **kwargs): action = "getCostEstimate" return self.client.call_api(request, self.service, action, **kwargs) + + +class AdyenTerminal(AdyenServiceBase): + """This represents the Adyen API Terminal service. + + API call currently implemented: + - assignTerminals + - findTerminal + - getStoreUnderAccount + - getTerminalDetails + - getTerminalsUnderAccount + Please refer to the Terminal Manual for specifics around the API. + https://docs.adyen.com/api-explorer/#/postfmapi/ + + Args: + client (AdyenAPIClient, optional): An API client for the service to + use. If not provided, a new API client will be created. + """ + + def __init__(self, client=None): + super(AdyenTerminal, self).__init__(client=client) + self.service = "terminal" + + def assign_terminals(self, request="", **kwargs): + return self.client.call_api(request, self.service, "assignTerminals", **kwargs) + + def find_terminal(self, request="", **kwargs): + return self.client.call_api(request, self.service, "findTerminal", **kwargs) + + def get_stores_under_account(self, request="", **kwargs): + return self.client.call_api(request, self.service, "getStoresUnderAccount", **kwargs) + + def get_terminal_details(self, request="", **kwargs): + return self.client.call_api(request, self.service, "getTerminalDetails", **kwargs) + + def get_terminals_under_account(self, request="", **kwargs): + return self.client.call_api(request, self.service, "getTerminalsUnderAccount", **kwargs) \ No newline at end of file diff --git a/Adyen/settings.py b/Adyen/settings.py index ece12553..8d8defac 100644 --- a/Adyen/settings.py +++ b/Adyen/settings.py @@ -1,5 +1,6 @@ # Those constants are used from the library only BASE_PAL_URL = "https://pal-{}.adyen.com/pal/servlet" +BASE_TERMINAL_URL = "https://postfmapi-{}.adyen.com/postfmapi" PAL_LIVE_ENDPOINT_URL_TEMPLATE = "https://{}-pal-live" \ ".adyenpayments.com/pal/servlet" BASE_HPP_URL = "https://{}.adyen.com/hpp" @@ -12,5 +13,6 @@ API_RECURRING_VERSION = "v49" API_PAYMENT_VERSION = "v64" API_PAYOUT_VERSION = "v64" +API_TERMINAL_VERSION = "v1" LIB_VERSION = "7.0.0" LIB_NAME = "adyen-python-api-library" diff --git a/README.md b/README.md index 26f943ec..95fe0e68 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The library supports all APIs under the following services: * [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** +* [Terminal API](https://docs.adyen.com/api-explorer/#/postfmapi/v1/overview): Endpoints for interacting with POS terminals. **v1** For more information, refer to our [documentation](https://docs.adyen.com/) or the [API Explorer](https://docs.adyen.com/api-explorer/). diff --git a/test/TerminalTest.py b/test/TerminalTest.py new file mode 100644 index 00000000..3fa23695 --- /dev/null +++ b/test/TerminalTest.py @@ -0,0 +1,298 @@ +import pkg_resources +import unittest + +import Adyen + +try: + from BaseTest import BaseTest +except ImportError: + from .BaseTest import BaseTest + +VERSION = pkg_resources.get_distribution("Adyen").version + + +class TestTerminal(unittest.TestCase): + adyen = Adyen.Adyen(username="YourWSUser", + password="YourWSPassword", + platform="test", + xapikey="YourXapikey") + test = BaseTest(adyen) + client = adyen.client + + def test_assign_terminals(self): + request = { + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "store": "YOUR_STORE", + "terminals": [ + "P400Plus-275479597" + ] + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/assignTerminals.json" + ) + result = self.adyen.terminal.assign_terminals(request=request) + self.assertIn("P400Plus-275479597", result.message["results"]) + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/assignTerminals", + headers={}, + json={ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "store": "YOUR_STORE", + "terminals": [ + "P400Plus-275479597" + ], + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + def test_assign_terminals_422(self): + request = { + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "store": "YOUR_STORE", + "terminals": [ + "P400Plus-123456789" + ] + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/assignTerminals-422.json" + ) + result = self.adyen.terminal.assign_terminals(request=request) + self.assertEqual(422, result.message["status"]) + self.assertEqual("000", result.message["errorCode"]) + self.assertEqual("Terminals not found: P400Plus-123456789", result.message["message"]) + self.assertEqual("validation", result.message["errorType"]) + + def test_find_terminal(self): + request = { + "terminal": "P400Plus-275479597", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT" + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/findTerminal.json" + ) + result = self.adyen.terminal.find_terminal(request=request) + self.assertIn("P400Plus-275479597", result.message["terminal"]) + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/findTerminal", + headers={}, + json={ + "terminal": "P400Plus-275479597", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + def test_find_terminal_422(self): + request = { + "terminal": "P400Plus-123456789" + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/findTerminal-422.json" + ) + result = self.adyen.terminal.find_terminal(request=request) + self.assertEqual(422, result.message["status"]) + self.assertEqual("000", result.message["errorCode"]) + self.assertEqual("Terminal not found", result.message["message"]) + self.assertEqual("validation", result.message["errorType"]) + + def test_get_stores_under_account(self): + request = { + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT" + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/getStoresUnderAccount.json" + ) + result = self.adyen.terminal.get_stores_under_account(request=request) + self.assertEqual(result.message["stores"], [ + { + "store": "YOUR_STORE", + "description": "YOUR_STORE", + "address": { + "city": "The City", + "countryCode": "NL", + "postalCode": "1234", + "streetAddress": "The Street" + }, + "status": "Active", + "merchantAccountCode": "YOUR_MERCHANT_ACCOUNT" + } + ]) + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/getStoresUnderAccount", + headers={}, + json={ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + def test_get_terminal_details(self): + request = { + "terminal": "P400Plus-275479597", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + } + + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/getTerminalDetails.json" + ) + result = self.adyen.terminal.get_terminal_details(request=request) + + self.assertEqual(result.message["deviceModel"], "P400Plus") + self.assertEqual(result.message["terminal"], "P400Plus-275479597") + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/getTerminalDetails", + headers={}, + json={ + "terminal": "P400Plus-275479597", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + def test_get_terminal_details_422(self): + request = { + "terminal": "P400Plus-123456789" + } + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/getTerminalDetails-422.json" + ) + result = self.adyen.terminal.get_terminal_details(request=request) + self.assertEqual(422, result.message["status"]) + self.assertEqual("000", result.message["errorCode"]) + self.assertEqual("Terminal not found", result.message["message"]) + self.assertEqual("validation", result.message["errorType"]) + + def test_get_terminals_under_account(self): + request = { + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT" + } + + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/getTerminalsUnderAccount.json" + ) + result = self.adyen.terminal.get_terminals_under_account(request=request) + + self.assertEqual(result.message["merchantAccounts"], [ + { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "inStoreTerminals": [ + "P400Plus-275479597" + ], + "stores": [ + { + "store": "YOUR_STORE", + "inStoreTerminals": [ + "M400-401972715" + ] + } + ] + } + ]) + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/getTerminalsUnderAccount", + headers={}, + json={ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + def test_get_terminals_under_account_store(self): + request = { + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "store": "YOUR_STORE" + } + + self.test.create_client_from_file( + 200, request, "test/mocks/terminal/getTerminalsUnderAccount-store.json" + ) + result = self.adyen.terminal.get_terminals_under_account(request=request) + + self.assertEqual(result.message["merchantAccounts"], [ + { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "stores": [ + { + "store": "YOUR_STORE", + "inStoreTerminals": [ + "M400-401972715" + ] + } + ] + } + ]) + + self.client.http_client.request.assert_called_once_with( + "https://postfmapi-test.adyen.com/postfmapi/terminal/v1/getTerminalsUnderAccount", + headers={}, + json={ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "store": "YOUR_STORE", + "applicationInfo": { + "adyenLibrary": { + "version": VERSION, + "name": "adyen-python-api-library" + } + }, + }, + xapikey="YourXapikey" + ) + + +TestTerminal.client.http_force = "requests" +suite = unittest.TestLoader().loadTestsFromTestCase(TestTerminal) +unittest.TextTestRunner(verbosity=2).run(suite) + +TestTerminal.client.http_force = "pycurl" +TestTerminal.client.http_init = False +suite = unittest.TestLoader().loadTestsFromTestCase(TestTerminal) +unittest.TextTestRunner(verbosity=2).run(suite) + +TestTerminal.client.http_force = "other" +TestTerminal.client.http_init = False +suite = unittest.TestLoader().loadTestsFromTestCase(TestTerminal) +unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/mocks/terminal/assignTerminals-422.json b/test/mocks/terminal/assignTerminals-422.json new file mode 100644 index 00000000..313d8f4a --- /dev/null +++ b/test/mocks/terminal/assignTerminals-422.json @@ -0,0 +1,6 @@ +{ + "status": 422, + "errorCode": "000", + "message": "Terminals not found: P400Plus-123456789", + "errorType": "validation" +} \ No newline at end of file diff --git a/test/mocks/terminal/assignTerminals.json b/test/mocks/terminal/assignTerminals.json new file mode 100644 index 00000000..e53c2818 --- /dev/null +++ b/test/mocks/terminal/assignTerminals.json @@ -0,0 +1,5 @@ +{ + "results": { + "P400Plus-275479597": "RemoveConfigScheduled" + } +} \ No newline at end of file diff --git a/test/mocks/terminal/findTerminal-422.json b/test/mocks/terminal/findTerminal-422.json new file mode 100644 index 00000000..31a38a82 --- /dev/null +++ b/test/mocks/terminal/findTerminal-422.json @@ -0,0 +1,6 @@ +{ + "status": 422, + "errorCode": "000", + "message": "Terminal not found", + "errorType": "validation" +} \ No newline at end of file diff --git a/test/mocks/terminal/findTerminal.json b/test/mocks/terminal/findTerminal.json new file mode 100644 index 00000000..de3d766d --- /dev/null +++ b/test/mocks/terminal/findTerminal.json @@ -0,0 +1,6 @@ +{ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "merchantInventory": false, + "terminal": "P400Plus-275479597" +} \ No newline at end of file diff --git a/test/mocks/terminal/getStoresUnderAccount.json b/test/mocks/terminal/getStoresUnderAccount.json new file mode 100644 index 00000000..6eb1ff3b --- /dev/null +++ b/test/mocks/terminal/getStoresUnderAccount.json @@ -0,0 +1,16 @@ +{ + "stores": [ + { + "store": "YOUR_STORE", + "description": "YOUR_STORE", + "address": { + "city": "The City", + "countryCode": "NL", + "postalCode": "1234", + "streetAddress": "The Street" + }, + "status": "Active", + "merchantAccountCode": "YOUR_MERCHANT_ACCOUNT" + } + ] +} \ No newline at end of file diff --git a/test/mocks/terminal/getTerminalDetails-422.json b/test/mocks/terminal/getTerminalDetails-422.json new file mode 100644 index 00000000..31a38a82 --- /dev/null +++ b/test/mocks/terminal/getTerminalDetails-422.json @@ -0,0 +1,6 @@ +{ + "status": 422, + "errorCode": "000", + "message": "Terminal not found", + "errorType": "validation" +} \ No newline at end of file diff --git a/test/mocks/terminal/getTerminalDetails.json b/test/mocks/terminal/getTerminalDetails.json new file mode 100644 index 00000000..74e1a16d --- /dev/null +++ b/test/mocks/terminal/getTerminalDetails.json @@ -0,0 +1,13 @@ +{ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "merchantInventory": false, + "terminal": "P400Plus-275479597", + "deviceModel": "P400Plus", + "serialNumber": "275-479-597", + "permanentTerminalId": "75479597", + "terminalStatus": "ReAssignToStorePending", + "firmwareVersion": "Verifone_VOS 1.57.6", + "country": "NETHERLANDS", + "dhcpEnabled": false +} \ No newline at end of file diff --git a/test/mocks/terminal/getTerminalsUnderAccount-store.json b/test/mocks/terminal/getTerminalsUnderAccount-store.json new file mode 100644 index 00000000..9b85d712 --- /dev/null +++ b/test/mocks/terminal/getTerminalsUnderAccount-store.json @@ -0,0 +1,16 @@ +{ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccounts": [ + { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "stores": [ + { + "store": "YOUR_STORE", + "inStoreTerminals": [ + "M400-401972715" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/test/mocks/terminal/getTerminalsUnderAccount.json b/test/mocks/terminal/getTerminalsUnderAccount.json new file mode 100644 index 00000000..0728da5d --- /dev/null +++ b/test/mocks/terminal/getTerminalsUnderAccount.json @@ -0,0 +1,19 @@ +{ + "companyAccount": "YOUR_COMPANY_ACCOUNT", + "merchantAccounts": [ + { + "merchantAccount": "YOUR_MERCHANT_ACCOUNT", + "inStoreTerminals": [ + "P400Plus-275479597" + ], + "stores": [ + { + "store": "YOUR_STORE", + "inStoreTerminals": [ + "M400-401972715" + ] + } + ] + } + ] +} \ No newline at end of file