Skip to content

Commit 61e3726

Browse files
authored
Merge pull request #171 from Adyen/develop
Release 7.1.0
2 parents c868734 + 3292133 commit 61e3726

29 files changed

+805
-66
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* @AlexandrosMor @martinsrenato @rikterbeek @acampos1916 @candemiralp
1+
* @AlexandrosMor @rikterbeek @acampos1916 @candemiralp @michaelpaul @jillingk

.github/workflows/unittest.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Python package
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
build:
8+
9+
runs-on: ubuntu-latest
10+
strategy:
11+
matrix:
12+
python-version: ['3.6','3.7', '3.8']
13+
14+
steps:
15+
- uses: actions/checkout@v3
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install tox
24+
sudo apt install libcurl4-openssl-dev
25+
- name: Test with tox
26+
run: tox

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

Adyen/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
AdyenPayment,
2121
AdyenThirdPartyPayout,
2222
AdyenHPP,
23-
AdyenCheckoutApi
23+
AdyenCheckoutApi,
24+
AdyenTerminal
2425
)
2526

2627
from .httpclient import HTTPClient
@@ -35,6 +36,7 @@ def __init__(self, **kwargs):
3536
self.hpp = AdyenHPP(client=self.client)
3637
self.recurring = AdyenRecurring(client=self.client)
3738
self.checkout = AdyenCheckoutApi(client=self.client)
39+
self.terminal = AdyenTerminal(client=self.client)
3840

3941

4042
_base_adyen_obj = Adyen()
@@ -44,3 +46,4 @@ def __init__(self, **kwargs):
4446
payout = _base_adyen_obj.payout
4547
checkout = _base_adyen_obj.checkout
4648
binlookup = _base_adyen_obj.binlookup
49+
terminal = _base_adyen_obj.terminal

Adyen/client.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def __init__(
8989
api_payment_version=None,
9090
api_payout_version=None,
9191
api_recurring_version=None,
92+
api_terminal_version=None,
9293
):
9394
self.username = username
9495
self.password = password
@@ -115,6 +116,7 @@ def __init__(
115116
self.api_payment_version = api_payment_version or settings.API_PAYMENT_VERSION
116117
self.api_payout_version = api_payout_version or settings.API_PAYOUT_VERSION
117118
self.api_recurring_version = api_recurring_version or settings.API_RECURRING_VERSION
119+
self.api_terminal_version = api_terminal_version or settings.API_TERMINAL_VERSION
118120

119121
def _determine_api_url(self, platform, service, action):
120122
"""This returns the Adyen API endpoint based on the provided platform,
@@ -138,6 +140,9 @@ def _determine_api_url(self, platform, service, action):
138140
api_version = self.api_payout_version
139141
elif service == "BinLookup":
140142
api_version = self.api_bin_lookup_version
143+
elif service == "terminal":
144+
base_uri = settings.BASE_TERMINAL_URL.format(platform)
145+
api_version = self.api_terminal_version
141146
else:
142147
api_version = self.api_payment_version
143148
return '/'.join([base_uri, service, api_version, action])
@@ -157,13 +162,14 @@ def _determine_hpp_url(platform, action):
157162
result = '/'.join([base_uri, service])
158163
return result
159164

160-
def _determine_checkout_url(self, platform, action):
165+
def _determine_checkout_url(self, platform, action, path_param=None):
161166
"""This returns the Adyen API endpoint based on the provided platform,
162167
service and action.
163168
164169
Args:
165170
platform (str): Adyen platform, ie 'live' or 'test'.
166171
action (str): the API action to perform.
172+
path_param Optional[(str)]: a generic id that can be used to modify a payment e.g. paymentPspReference.
167173
"""
168174
api_version = self.api_checkout_version
169175
if platform == "test":
@@ -183,6 +189,16 @@ def _determine_checkout_url(self, platform, action):
183189
action = "payments/details"
184190
if action == "paymentsResult":
185191
action = "payments/result"
192+
if action == "cancels":
193+
action = "/cancels"
194+
if action == "paymentsCancelsWithReference":
195+
action = f"payments/{path_param}/cancels"
196+
if action == "paymentsCapture":
197+
action = f"/payments/{path_param}/captures"
198+
if action == "paymentsReversals":
199+
action = f"payments/{path_param}/reversals"
200+
if action == "payments/Refunds":
201+
action = f"payments/{path_param}/refunds"
186202
if action == "originKeys":
187203
api_version = self.api_checkout_utility_version
188204
if action == "paymentMethodsBalance":
@@ -449,7 +465,7 @@ class instance.
449465
status_code, headers, message)
450466
return adyen_result
451467

452-
def call_checkout_api(self, request_data, action, idempotency_key=None,
468+
def call_checkout_api(self, request_data, action, idempotency_key=None, path_param=None,
453469
**kwargs):
454470
"""This will call the checkout adyen api. xapi key merchant_account,
455471
and platform are pulled from root module level and or self object.
@@ -464,6 +480,7 @@ def call_checkout_api(self, request_data, action, idempotency_key=None,
464480
https://docs.adyen.com/api-explorer/#/CheckoutService
465481
service (str): This is the API service to be called.
466482
action (str): The specific action of the API service to be called
483+
path_param (str): This is used to pass the id or referenceID to the API sercie
467484
"""
468485
if not self.http_init:
469486
self._init_http_client()
@@ -530,7 +547,7 @@ def call_checkout_api(self, request_data, action, idempotency_key=None,
530547
headers = {}
531548
if idempotency_key:
532549
headers[self.IDEMPOTENCY_HEADER_NAME] = idempotency_key
533-
url = self._determine_checkout_url(platform, action)
550+
url = self._determine_checkout_url(platform, action, path_param)
534551

535552
raw_response, raw_request, status_code, headers = \
536553
self.http_client.request(url, json=request_data,
@@ -594,7 +611,7 @@ def _handle_response(self, url, raw_response, raw_request,
594611
Returns:
595612
AdyenResult: Result object if successful.
596613
"""
597-
if (status_code != 200 and status_code != 201):
614+
if (status_code != 200 and status_code != 201):
598615
response = {}
599616
# If the result can't be parsed into json, most likely is raw html.
600617
# Some response are neither json or raw html, handle them here:
@@ -629,7 +646,7 @@ def _handle_response(self, url, raw_response, raw_request,
629646
else:
630647
try:
631648
response = json_lib.loads(raw_response)
632-
psp = headers.get('pspReference', response.get('pspReference'))
649+
psp = self._get_psp(response, headers)
633650
return AdyenResult(message=response, status_code=status_code,
634651
psp=psp, raw_request=raw_request,
635652
raw_response=raw_response)
@@ -777,3 +794,11 @@ def _error_from_hpp(html):
777794
match_obj = re.search(r'>Error:\s*(.*?)<br', html)
778795
if match_obj:
779796
return match_obj.group(1)
797+
798+
@staticmethod
799+
def _get_psp(response, header):
800+
psp_ref = response.get('pspReference')
801+
if psp_ref == "":
802+
return header.get('pspReference')
803+
else:
804+
return psp_ref

Adyen/services.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ class AdyenCheckoutApi(AdyenServiceBase):
279279
payments/details
280280
originKeys
281281
282+
Modifications:
283+
capture
284+
refunds
285+
cancels
286+
reversals
287+
288+
282289
Please refer to the checkout documentation for specifics around the API.
283290
https://docs.adyen.com/online-payments
284291
@@ -321,6 +328,42 @@ def payment_result(self, request=None, **kwargs):
321328
action = "paymentsResult"
322329
return self.client.call_checkout_api(request, action, **kwargs)
323330

331+
def payments_captures(self, request, idempotency_key=None, path_param=None, **kwargs):
332+
if path_param == "":
333+
raise ValueError(
334+
'must contain a pspReference in the path_param, path_param cannot be empty'
335+
)
336+
action = "paymentsCapture"
337+
return self.client.call_checkout_api(request, action, idempotency_key, path_param, **kwargs)
338+
339+
def payments_cancels_without_reference(self, request, idempotency_key=None, **kwargs):
340+
action = "cancels"
341+
return self.client.call_checkout_api(request, action, idempotency_key, **kwargs)
342+
343+
def payments_cancels_with_reference(self, request, idempotency_key=None, path_param=None, **kwargs):
344+
if path_param == "":
345+
raise ValueError(
346+
'must contain a pspReference in the path_param, path_param cannot be empty'
347+
)
348+
action = "paymentsCancelsWithReference"
349+
return self.client.call_checkout_api(request, action, idempotency_key, path_param, **kwargs)
350+
351+
def payments_reversals(self, request, idempotency_key=None, path_param=None, **kwargs):
352+
if path_param == "":
353+
raise ValueError(
354+
'must contain a pspReference in the path_param, path_param cannot be empty'
355+
)
356+
action = "paymentsReversals"
357+
return self.client.call_checkout_api(request, action, idempotency_key, path_param, **kwargs)
358+
359+
def payments_refunds(self, request, idempotency_key=None, path_param=None, **kwargs):
360+
if path_param == "":
361+
raise ValueError(
362+
'must contain a pspReference in the path_param, path_param cannot be empty'
363+
)
364+
action = "paymentsRefunds"
365+
return self.client.call_checkout_api(request, action, idempotency_key, path_param, **kwargs)
366+
324367
def origin_keys(self, request=None, **kwargs):
325368
action = "originKeys"
326369
return self.client.call_checkout_api(request, action, **kwargs)
@@ -367,3 +410,40 @@ def get_cost_estimate(self, request="", **kwargs):
367410
action = "getCostEstimate"
368411

369412
return self.client.call_api(request, self.service, action, **kwargs)
413+
414+
415+
class AdyenTerminal(AdyenServiceBase):
416+
"""This represents the Adyen API Terminal service.
417+
418+
API call currently implemented:
419+
- assignTerminals
420+
- findTerminal
421+
- getStoreUnderAccount
422+
- getTerminalDetails
423+
- getTerminalsUnderAccount
424+
Please refer to the Terminal Manual for specifics around the API.
425+
https://docs.adyen.com/api-explorer/#/postfmapi/
426+
427+
Args:
428+
client (AdyenAPIClient, optional): An API client for the service to
429+
use. If not provided, a new API client will be created.
430+
"""
431+
432+
def __init__(self, client=None):
433+
super(AdyenTerminal, self).__init__(client=client)
434+
self.service = "terminal"
435+
436+
def assign_terminals(self, request="", **kwargs):
437+
return self.client.call_api(request, self.service, "assignTerminals", **kwargs)
438+
439+
def find_terminal(self, request="", **kwargs):
440+
return self.client.call_api(request, self.service, "findTerminal", **kwargs)
441+
442+
def get_stores_under_account(self, request="", **kwargs):
443+
return self.client.call_api(request, self.service, "getStoresUnderAccount", **kwargs)
444+
445+
def get_terminal_details(self, request="", **kwargs):
446+
return self.client.call_api(request, self.service, "getTerminalDetails", **kwargs)
447+
448+
def get_terminals_under_account(self, request="", **kwargs):
449+
return self.client.call_api(request, self.service, "getTerminalsUnderAccount", **kwargs)

Adyen/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Those constants are used from the library only
22
BASE_PAL_URL = "https://pal-{}.adyen.com/pal/servlet"
3+
BASE_TERMINAL_URL = "https://postfmapi-{}.adyen.com/postfmapi"
34
PAL_LIVE_ENDPOINT_URL_TEMPLATE = "https://{}-pal-live" \
45
".adyenpayments.com/pal/servlet"
56
BASE_HPP_URL = "https://{}.adyen.com/hpp"
@@ -12,5 +13,6 @@
1213
API_RECURRING_VERSION = "v49"
1314
API_PAYMENT_VERSION = "v64"
1415
API_PAYOUT_VERSION = "v64"
15-
LIB_VERSION = "7.0.0"
16+
API_TERMINAL_VERSION = "v1"
17+
LIB_VERSION = "7.1.0"
1618
LIB_NAME = "adyen-python-api-library"

Adyen/util.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def escape_val(val):
3535

3636

3737
def is_valid_hmac(dict_object, hmac_key):
38+
dict_object = dict_object.copy()
39+
3840
if 'additionalData' in dict_object:
3941
if dict_object['additionalData']['hmacSignature'] == "":
4042
raise ValueError("Must Provide hmacSignature in additionalData")
@@ -43,7 +45,7 @@ def is_valid_hmac(dict_object, hmac_key):
4345
del dict_object['additionalData']
4446
merchant_sign = generate_hpp_sig(dict_object, hmac_key)
4547
merchant_sign_str = merchant_sign.decode("utf-8")
46-
return merchant_sign_str == expected_sign
48+
return hmac.compare_digest(merchant_sign_str, expected_sign)
4749

4850

4951
def generate_notification_sig(dict_object, hmac_key):
@@ -85,6 +87,8 @@ def escape_val(val):
8587

8688

8789
def is_valid_hmac_notification(dict_object, hmac_key):
90+
dict_object = dict_object.copy()
91+
8892
if 'additionalData' in dict_object:
8993
if dict_object['additionalData']['hmacSignature'] == "":
9094
raise ValueError("Must Provide hmacSignature in additionalData")
@@ -93,4 +97,4 @@ def is_valid_hmac_notification(dict_object, hmac_key):
9397
del dict_object['additionalData']
9498
merchant_sign = generate_notification_sig(dict_object, hmac_key)
9599
merchant_sign_str = merchant_sign.decode("utf-8")
96-
return merchant_sign_str == expected_sign
100+
return hmac.compare_digest(merchant_sign_str, expected_sign)

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Adyen APIs Library for Python
22

3-
[![version](https://img.shields.io/badge/version-7.0.0-blue.svg)](https://docs.adyen.com/development-resources/libraries)
3+
[![version](https://img.shields.io/badge/version-7.1.0-blue.svg)](https://docs.adyen.com/development-resources/libraries)
44

55
This is the officially supported Python library for using Adyen's APIs.
66

@@ -13,6 +13,7 @@ The library supports all APIs under the following services:
1313
* [Payouts API](https://docs.adyen.com/api-explorer/#/Payout/v64/overview): Endpoints for sending funds to your customers. Current supported version: **v64**
1414
* [Orders API](https://docs.adyen.com/api-explorer/#/CheckoutService/v67/post/orders): Endpoints for creating and canceling orders. Current supported version: **v67**
1515
* [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**
16+
* [Terminal API](https://docs.adyen.com/api-explorer/#/postfmapi/v1/overview): Endpoints for interacting with POS terminals. **v1**
1617

1718
For more information, refer to our [documentation](https://docs.adyen.com/) or the [API Explorer](https://docs.adyen.com/api-explorer/).
1819

@@ -21,7 +22,7 @@ For more information, refer to our [documentation](https://docs.adyen.com/) or t
2122

2223
- [Adyen test account](https://docs.adyen.com/get-started-with-adyen)
2324
- [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).
24-
- Python 2.7 or 3.6
25+
- Python 2.7 **(Deprecated)** or 3.6
2526
- Packages: requests or pycurl ( optional )
2627

2728

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
setup(
44
name='Adyen',
55
packages=['Adyen'],
6-
version='7.0.0',
6+
version='7.1.0',
77
maintainer='Adyen',
88
maintainer_email='[email protected]',
99
description='Adyen Python Api',

0 commit comments

Comments
 (0)