diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..eff7b5f7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..be483d89 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,51 @@ +name: "Code scanning - action" + +on: + push: + pull_request: + schedule: + - cron: '0 15 * * 4' + +jobs: + CodeQL-Build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/Adyen/client.py b/Adyen/client.py index e5a0d54c..22bc404e 100644 --- a/Adyen/client.py +++ b/Adyen/client.py @@ -64,15 +64,25 @@ class AdyenClient(object): skin_code (str, optional): skin_code to place directory_lookup requests and generate hpp signatures with. hmac (str, optional): Hmac key that is used for signature calculation. + http_timeout (int, optional): The timeout in seconds for HTTP calls, + default 30. """ - def __init__(self, username=None, password=None, xapikey=None, - review_payout_username=None, review_payout_password=None, - store_payout_username=None, store_payout_password=None, - platform="test", merchant_account=None, - merchant_specific_url=None, skin_code=None, - hmac=None, - http_force=None, live_endpoint_prefix=None): + def __init__( + self, + username=None, + password=None, + xapikey=None, + review_payout_username=None, + review_payout_password=None, + store_payout_username=None, store_payout_password=None, + platform="test", merchant_account=None, + merchant_specific_url=None, skin_code=None, + hmac=None, + http_force=None, + live_endpoint_prefix=None, + http_timeout=30, + ): self.username = username self.password = password self.xapikey = xapikey @@ -91,9 +101,9 @@ def __init__(self, username=None, password=None, xapikey=None, self.http_init = False self.http_force = http_force self.live_endpoint_prefix = live_endpoint_prefix + self.http_timeout = http_timeout - @staticmethod - def _determine_api_url(platform, service, action): + def _determine_api_url(self, platform, service, action): """This returns the Adyen API endpoint based on the provided platform, service and action. @@ -102,7 +112,13 @@ def _determine_api_url(platform, service, action): service (str): API service to place request through. action (str): the API action to perform. """ - base_uri = settings.BASE_PAL_URL.format(platform) + if platform == "live" and self.live_endpoint_prefix: + base_uri = settings.PAL_LIVE_ENDPOINT_URL_TEMPLATE.format( + self.live_endpoint_prefix + ) + else: + base_uri = settings.BASE_PAL_URL.format(platform) + if service == "Recurring": api_version = settings.API_RECURRING_VERSION elif service == "Payout": @@ -199,8 +215,14 @@ def _store_payout_pass(self, **kwargs): 'Adyen.store_payout_password = 'Your payout password'""" raise AdyenInvalidRequestError(errorstring) - def call_api(self, request_data, service, action, idempotency=False, - **kwargs): + def call_api( + self, + request_data, + service, + action, + idempotency=False, + **kwargs + ): """This will call the adyen api. username, password, merchant_account, and platform are pulled from root module level and or self object. AdyenResult will be returned on 200 response. Otherwise, an exception @@ -217,12 +239,15 @@ def call_api(self, request_data, service, action, idempotency=False, https://docs.adyen.com/manuals/api-manual#apiidempotency Returns: AdyenResult: The AdyenResult is returned when a request was - succesful. + successful. """ if not self.http_init: - self.http_client = HTTPClient(self.USER_AGENT_SUFFIX, - self.LIB_VERSION, - self.http_force) + self.http_client = HTTPClient( + user_agent_suffix=self.USER_AGENT_SUFFIX, + lib_version=self.LIB_VERSION, + force_request=self.http_force, + timeout=self.http_timeout, + ) self.http_init = True # username at self object has highest priority. fallback to root module diff --git a/Adyen/httpclient.py b/Adyen/httpclient.py index 38e5a419..e948e163 100644 --- a/Adyen/httpclient.py +++ b/Adyen/httpclient.py @@ -35,7 +35,13 @@ class HTTPClient(object): - def __init__(self, user_agent_suffix, lib_version, force_request=None): + def __init__( + self, + user_agent_suffix, + lib_version, + force_request=None, + timeout=None, + ): # Check if requests already available, default to urllib self.user_agent = user_agent_suffix + lib_version if not force_request: @@ -53,17 +59,20 @@ def __init__(self, user_agent_suffix, lib_version, force_request=None): else: self.request = self._urllib_post - def _pycurl_post(self, - url, - json=None, - data=None, - username="", - password="", - xapikey="", - headers=None, - timeout=30): + self.timeout = timeout + + def _pycurl_post( + self, + url, + json=None, + data=None, + username="", + password="", + xapikey="", + headers=None + ): """This function will POST to the url endpoint using pycurl. returning - an AdyenResult object on 200 HTTP responce. Either json or data has to + an AdyenResult object on 200 HTTP response. Either json or data has to be provided. If username and password are provided, basic auth will be used. @@ -129,7 +138,7 @@ def _pycurl_post(self, raw_request = json_lib.dumps(json) if json else urlencode(data) curl.setopt(curl.POSTFIELDS, raw_request) - curl.setopt(curl.TIMEOUT, timeout) + curl.setopt(curl.TIMEOUT, self.timeout) curl.perform() # Grab the response content @@ -143,14 +152,16 @@ def _pycurl_post(self, return result, raw_request, status_code, response_headers - def _requests_post(self, url, - json=None, - data=None, - username="", - password="", - xapikey="", - headers=None, - timeout=30): + def _requests_post( + self, + url, + json=None, + data=None, + username="", + password="", + xapikey="", + headers=None + ): """This function will POST to the url endpoint using requests. Returning an AdyenResult object on 200 HTTP response. Either json or data has to be provided. @@ -191,8 +202,14 @@ def _requests_post(self, url, # can be identified as coming from the Adyen Python library. headers['User-Agent'] = self.user_agent - request = requests.post(url, auth=auth, data=data, json=json, - headers=headers, timeout=timeout) + request = requests.post( + url=url, + auth=auth, + data=data, + json=json, + headers=headers, + timeout=self.timeout + ) # Ensure either json or data is returned for raw request # Updated: Only return regular dict, @@ -201,14 +218,16 @@ def _requests_post(self, url, return request.text, message, request.status_code, request.headers - def _urllib_post(self, url, - json=None, - data=None, - username="", - password="", - xapikey="", - headers=None, - timeout=30): + def _urllib_post( + self, + url, + json=None, + data=None, + username="", + password="", + xapikey="", + headers=None, + ): """This function will POST to the url endpoint using urllib2. returning an AdyenResult object on 200 HTTP responce. Either json or data has to @@ -228,7 +247,6 @@ def _urllib_post(self, url, xapikey (str, optional): Adyen API key. Will be used for auth if username and password are absent. headers (dict, optional): Key/Value pairs of headers to include - timeout (int, optional): Default 30. Timeout for the request. Returns: str: Raw response received @@ -279,7 +297,7 @@ def _urllib_post(self, url, # URLlib raises all non 200 responses as en error. try: - response = urlopen(url_request, timeout=timeout) + response = urlopen(url_request, timeout=self.timeout) except HTTPError as e: raw_response = e.read() @@ -293,13 +311,15 @@ def _urllib_post(self, url, return (raw_response, raw_request, response.getcode(), dict(response.info())) - def request(self, url, - json="", - data="", - username="", - password="", - headers=None, - timout=30): + def request( + self, + url, + json="", + data="", + username="", + password="", + headers=None, + ): """This is overridden on module initialization. This function will make an HTTP POST to a given url. Either json/data will be what is posted to the end point. he HTTP request needs to be basicAuth when username and @@ -313,7 +333,7 @@ def request(self, url, key/value of request to place as www-form username (str, optional): Username for basic auth. Must be - uncluded as part of password. + included as part of password. password (str, optional): Password for basic auth. Must be included as part of username. xapikey (str, optional): Adyen API key. Will be used for auth @@ -324,7 +344,6 @@ def request(self, url, str: Raw response received int: HTTP status code, eg 200,404,401 dict: Key/Value pairs of the headers received. - :param timout: """ raise NotImplementedError('request of HTTPClient should have been ' 'overridden on initialization. ' diff --git a/Adyen/settings.py b/Adyen/settings.py index 83ddcfff..21d30ee6 100644 --- a/Adyen/settings.py +++ b/Adyen/settings.py @@ -1,14 +1,16 @@ # Those constants are used from the library only BASE_PAL_URL = "https://pal-{}.adyen.com/pal/servlet" +PAL_LIVE_ENDPOINT_URL_TEMPLATE = "https://{}-pal-live" \ + ".adyenpayments.com/pal/servlet" BASE_HPP_URL = "https://{}.adyen.com/hpp" ENDPOINT_CHECKOUT_TEST = "https://checkout-test.adyen.com" ENDPOINT_CHECKOUT_LIVE_SUFFIX = "https://{}-checkout-live" \ ".adyenpayments.com/checkout" API_BIN_LOOKUP_VERSION = "v50" -API_CHECKOUT_VERSION = "v49" +API_CHECKOUT_VERSION = "v64" API_CHECKOUT_UTILITY_VERSION = "v1" -API_RECURRING_VERSION = "v25" -API_PAYMENT_VERSION = "v49" -API_PAYOUT_VERSION = "v30" -LIB_VERSION = "3.0.0" +API_RECURRING_VERSION = "v49" +API_PAYMENT_VERSION = "v64" +API_PAYOUT_VERSION = "v64" +LIB_VERSION = "3.1.0" LIB_NAME = "adyen-python-api-library" diff --git a/setup.py b/setup.py index 03268046..6489c9f9 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='Adyen', packages=['Adyen'], - version='3.0.0', + version='3.1.0', maintainer='Adyen', maintainer_email='support@adyen.com', description='Adyen Python Api', diff --git a/test/DetermineEndpointTest.py b/test/DetermineEndpointTest.py index 48345f0a..69d46ee7 100644 --- a/test/DetermineEndpointTest.py +++ b/test/DetermineEndpointTest.py @@ -21,14 +21,14 @@ def test_checkout_api_url_custom(self): url = self.adyen.client._determine_checkout_url("live", "payments") self.client.live_endpoint_prefix = "1797a841fbb37ca7-AdyenDemo" self.assertEqual(url, "https://1797a841fbb37ca7-AdyenDemo-checkout-" - "live.adyenpayments.com/checkout/v49/payments") + "live.adyenpayments.com/checkout/v64/payments") def test_checkout_api_url(self): self.client.live_endpoint_prefix = None url = self.adyen.client._determine_checkout_url("test", "paymentsDetails") self.assertEqual(url, "https://checkout-test.adyen.com" - "/v49/payments/details") + "/v64/payments/details") def test_payments_invalid_platform(self): @@ -54,3 +54,55 @@ def test_payments_invalid_platform(self): self.adyen.checkout.payments(request) except AdyenEndpointInvalidFormat as error: self.assertIsNotNone(error) + + def test_pal_url_live_endpoint_prefix_live_platform(self): + self.client.live_endpoint_prefix = "1797a841fbb37ca7-AdyenDemo" + url = self.adyen.client._determine_api_url( + "live", "Payment", "payments" + ) + self.assertEqual( + url, + ("https://1797a841fbb37ca7-AdyenDemo-pal-" + "live.adyenpayments.com/pal/servlet/Payment/v64/payments") + ) + + def test_pal_url_live_endpoint_prefix_test_platform(self): + self.client.live_endpoint_prefix = "1797a841fbb37ca7-AdyenDemo" + url = self.adyen.client._determine_api_url( + "test", "Payment", "payments" + ) + self.assertEqual( + url, + "https://pal-test.adyen.com/pal/servlet/Payment/v64/payments" + ) + + def test_pal_url_no_live_endpoint_prefix_live_platform(self): + self.client.live_endpoint_prefix = None + url = self.adyen.client._determine_api_url( + "live", "Payment", "payments" + ) + self.assertEqual( + url, + "https://pal-live.adyen.com/pal/servlet/Payment/v64/payments" + ) + + def test_pal_url_no_live_endpoint_prefix_test_platform(self): + self.client.live_endpoint_prefix = None + url = self.adyen.client._determine_api_url( + "test", "Payment", "payments" + ) + self.assertEqual( + url, + "https://pal-test.adyen.com/pal/servlet/Payment/v64/payments" + ) + + def test_binlookup_url_no_live_endpoint_prefix_test_platform(self): + self.client.live_endpoint_prefix = None + url = self.adyen.client._determine_api_url( + "test", "BinLookup", "get3dsAvailability" + ) + self.assertEqual( + url, + ("https://pal-test.adyen.com/pal/servlet/" + "BinLookup/v50/get3dsAvailability") + )