Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
26cccd9
Add payment methods with mock test
AlexandrosMor Jan 3, 2019
e6986a7
Format lines
AlexandrosMor Jan 4, 2019
40b60b0
Format lines for client
AlexandrosMor Jan 4, 2019
2e834fb
Format lines for client long lines
AlexandrosMor Jan 4, 2019
25ca3f2
Format lines for checkout
AlexandrosMor Jan 4, 2019
bee6945
Format lines for checkout again
AlexandrosMor Jan 4, 2019
1b50cad
Add all checkout components
AlexandrosMor Jan 4, 2019
b515f7c
format test lines
AlexandrosMor Jan 4, 2019
a2bfc2c
Add checkout test cases for errors
AlexandrosMor Jan 4, 2019
22fb285
Fixes with e2e test
AlexandrosMor Jan 8, 2019
247cbaa
Fixes comment length
AlexandrosMor Jan 8, 2019
1ca76fb
remove white space
AlexandrosMor Jan 8, 2019
8c257eb
correct checkout utility
AlexandrosMor Jan 8, 2019
236cd10
add custom endpoint
AlexandrosMor Jan 9, 2019
4782cb6
Remove white space in unit test
AlexandrosMor Jan 9, 2019
0a134fa
Add checkout utility url
AlexandrosMor Jan 9, 2019
2421b54
rename to payments details
AlexandrosMor Jan 9, 2019
5e7d9c3
remove unused code
AlexandrosMor Jan 9, 2019
f3627f7
add utility api version
AlexandrosMor Jan 9, 2019
c397486
Improve settings and client
AlexandrosMor Jan 10, 2019
4467940
Correct checkout url payments result
AlexandrosMor Jan 10, 2019
0aa55af
imporove determine checkout url
AlexandrosMor Jan 15, 2019
a13dc7e
fix determine checkout url
AlexandrosMor Jan 15, 2019
25afd0c
remove ENDPOINT_LIVE_SUFFIX for pal
AlexandrosMor Jan 15, 2019
768c84b
Deprecate API_VERSION
AlexandrosMor Jan 15, 2019
42cb7e4
Intoduce API_PAYMENT_VERSION and API_PAYOUT_VERSION deprecate API_VER…
AlexandrosMor Jan 15, 2019
ec4f563
Remove API_VERSION
AlexandrosMor Jan 16, 2019
a66d3cc
UpdateAPI_PAYMENT_VERSION = v40
AlexandrosMor Jan 16, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Adyen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
AdyenRecurring,
AdyenPayment,
AdyenThirdPartyPayout,
AdyenHPP)
AdyenHPP,
AdyenCheckoutApi)

from .httpclient import HTTPClient

Expand All @@ -30,10 +31,12 @@ def __init__(self, **kwargs):
self.payout = AdyenThirdPartyPayout(client=self.client)
self.hpp = AdyenHPP(client=self.client)
self.recurring = AdyenRecurring(client=self.client)
self.checkout = AdyenCheckoutApi(client=self.client)


_base_adyen_obj = Adyen()
recurring = _base_adyen_obj.recurring
hpp = _base_adyen_obj.hpp
payment = _base_adyen_obj.payment
payout = _base_adyen_obj.payout
checkout = _base_adyen_obj.checkout
143 changes: 127 additions & 16 deletions Adyen/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
AdyenInvalidRequestError,
AdyenAPIInvalidFormat,
AdyenAPIInvalidAmount,
)
AdyenEndpointInvalidFormat)
from . import settings


Expand Down Expand Up @@ -66,15 +66,16 @@ class AdyenClient(object):
hmac (str, optional): Hmac key that is used for signature calculation.
"""

def __init__(self, username=None, password=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, app_name=None,
http_force=None):
http_force=None, live_endpoint_prefix=None):
self.username = username
self.password = password
self.xapikey = xapikey
self.review_payout_username = review_payout_username
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we not add live_endpoint_prefix as property ?

self.review_payout_password = review_payout_password
self.store_payout_username = store_payout_username
Expand All @@ -86,10 +87,11 @@ def __init__(self, username=None, password=None,
self.skin_code = skin_code
self.psp_list = []
self.app_name = app_name
self.LIB_VERSION = "1.3.0"
self.LIB_VERSION = "1.4.0"
self.USER_AGENT_SUFFIX = "adyen-python-api-library/"
self.http_init = False
self.http_force = http_force
self.live_endpoint_prefix = live_endpoint_prefix

def _determine_api_url(self, platform, service, action):
"""This returns the Adyen API endpoint based on the provided platform,
Expand All @@ -103,8 +105,10 @@ def _determine_api_url(self, platform, service, action):
base_uri = settings.BASE_PAL_URL.format(platform)
if service == "Recurring":
api_version = settings.API_RECURRING_VERSION
elif service == "Payout":
api_version = settings.API_PAYOUT_VERSION
else:
api_version = settings.API_VERSION
api_version = settings.API_PAYMENT_VERSION
return '/'.join([base_uri, service, api_version, action])

def _determine_hpp_url(self, platform, action):
Expand All @@ -121,6 +125,34 @@ def _determine_hpp_url(self, platform, action):
result = '/'.join([base_uri, service])
return result

def _determine_checkout_url(self, platform, action):
"""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.
"""
api_version = settings.API_CHECKOUT_VERSION
if platform == "test":
base_uri = settings.ENDPOINT_CHECKOUT_TEST
elif self.live_endpoint_prefix is not None and platform == "live":
base_uri = settings.ENDPOINT_CHECKOUT_LIVE_SUFFIX.format(
self.live_endpoint_prefix)
elif self.live_endpoint_prefix is None and platform == "live":
errorstring = """Please set your live suffix. You can set it
by running 'settings.
ENDPOINT_CHECKOUT_LIVE_SUFFIX = 'Your live suffix'"""
raise AdyenEndpointInvalidFormat(errorstring)
if action == "paymentsDetails":
action = "payments/details"
if action == "paymentsResult":
action = "payments/result"
if action == "originKeys":
api_version = settings.API_CHECKOUT_UTILITY_VERSION

return '/'.join([base_uri, api_version, action])

def _review_payout_username(self, **kwargs):
if 'username' in kwargs:
return kwargs['username']
Expand Down Expand Up @@ -190,35 +222,47 @@ def call_api(self, request_data, service, action, idempotency=False,

# username at self object has highest priority. fallback to root module
# and ensure that it is set.
if self.xapikey:
xapikey = self.xapikey
elif 'xapikey' in kwargs:
xapikey = kwargs.pop("xapikey")

if self.username:
username = self.username
elif 'username' in kwargs:
username = kwargs.pop("username")
elif service == "Payout":
if any(substring in action for substring in ["store", "submit"]):
if any(substring in action for substring in
["store", "submit"]):
username = self._store_payout_username(**kwargs)
else:
username = self._review_payout_username(**kwargs)
if not username:
errorstring = """Please set your webservice username.
You can do this by running 'Adyen.username = 'Your username'"""
You can do this by running
'Adyen.username = 'Your username'"""
raise AdyenInvalidRequestError(errorstring)

# password at self object has highest priority. fallback to root module
# and ensure that it is set.
# password at self object has highest priority.
# fallback to root module
# and ensure that it is set.
if self.password:
password = self.password
elif 'password' in kwargs:
password = kwargs.pop("password")
elif service == "Payout":
if any(substring in action for substring in ["store", "submit"]):
if any(substring in action for substring in
["store", "submit"]):
password = self._store_payout_pass(**kwargs)
else:
password = self._review_payout_pass(**kwargs)
if not password:
errorstring = """Please set your webservice password.
You can do this by running 'Adyen.password = 'Your password'"""
You can do this by running
'Adyen.password = 'Your password'"""
raise AdyenInvalidRequestError(errorstring)
# xapikey at self object has highest priority.
# fallback to root module
# and ensure that it is set.

# platform at self object has highest priority. fallback to root module
# and ensure that it is set to either 'live' or 'test'.
Expand Down Expand Up @@ -331,6 +375,73 @@ class instance.
status_code, headers, message)
return adyen_result

def call_checkout_api(self, request_data, action, **kwargs):
"""This will call the checkout adyen api. xapi key merchant_account,
and platform are pulled from root module level and or self object.
AdyenResult will be returned on 200 response. Otherwise, an exception
is raised.

Args:
request_data (dict): The dictionary of the request to place. This
should be in the structure of the Adyen API.
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
"""
if not self.http_init:
self.http_client = HTTPClient(self.app_name,
self.USER_AGENT_SUFFIX,
self.LIB_VERSION,
self.http_force)
self.http_init = True

# xapi at self object has highest priority. fallback to root module
# and ensure that it is set.
if self.xapikey:
xapikey = self.xapikey
elif 'xapikey' in kwargs:
xapikey = kwargs.pop("xapikey")

if not xapikey:
errorstring = """Please set your webservice xapikey.
You can do this by running 'Adyen.xapikey = 'Your xapikey'"""
raise AdyenInvalidRequestError(errorstring)

# platform at self object has highest priority. fallback to root module
# and ensure that it is set to either 'live' or 'test'.
if self.platform:
platform = self.platform
elif 'platform' in kwargs:
platform = kwargs.pop('platform')

if not isinstance(platform, str):
errorstring = "'platform' value must be type of string"
raise TypeError(errorstring)
elif platform.lower() not in ['live', 'test']:
errorstring = "'platform' must be the value of 'live' or 'test'"
raise ValueError(errorstring)

if not request_data.get('merchantAccount'):
request_data['merchantAccount'] = self.merchant_account

# Adyen requires this header to be set and uses the combination of
# merchant account and merchant reference to determine uniqueness.
headers = {}

url = self._determine_checkout_url(platform, action)

raw_response, raw_request, status_code, headers = \
self.http_client.request(url, json=request_data,
xapikey=xapikey, headers=headers,
**kwargs)

# Creates AdyenResponse if request was successful, raises error if not.
adyen_result = self._handle_response(url, raw_response, raw_request,
status_code, headers,
request_data)

return adyen_result

def hpp_payment(self, request_data, action, hmac_key="", **kwargs):

if not self.http_init:
Expand Down Expand Up @@ -386,7 +497,6 @@ def _handle_response(self, url, raw_response, raw_request,
Returns:
AdyenResult: Result object if successful.
"""

if status_code != 200:
response = {}
# If the result can't be parsed into json, most likely is raw html.
Expand All @@ -405,9 +515,10 @@ def _handle_response(self, url, raw_response, raw_request,
"Unexpected error while communicating with Adyen."
" Received the response data:'{}', HTTP Code:'{}'. "
"Please reach out to support@adyen.com if the "
"problem persists with the psp:{}"
.format(raw_response, status_code,
headers.get('pspReference')),
"problem persists with the psp:{}".format(
raw_response,
status_code,
headers.get('pspReference')),
status_code=status_code,
raw_request=raw_request,
raw_response=raw_response,
Expand Down
18 changes: 8 additions & 10 deletions Adyen/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,10 @@ def __str__(self):

def debug(self):
return ("class: {}\nmessage: {}\nHTTP status_code:{}\nurl: {}"
"request: {}\nresponse: {}\nheaders: {}".format(
self.__class__.__name__,
self.message,
self.status_code,
self.url,
self.raw_request,
self.raw_response,
self.headers
)
)
"request: {}\nresponse: {}\nheaders: {}"
.format(self.__class__.__name__, self.message,
self.status_code, self.url, self.raw_request,
self.raw_response, self.headers))


class AdyenInvalidRequestError(AdyenError):
Expand Down Expand Up @@ -71,3 +65,7 @@ class AdyenAPIInvalidAmount(AdyenAPIResponseError):

class AdyenAPIInvalidFormat(AdyenAPIResponseError):
pass


class AdyenEndpointInvalidFormat(AdyenError):
pass
9 changes: 6 additions & 3 deletions Adyen/httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def _requests_post(self, url,
data=None,
username="",
password="",
xapikey="",
headers=None,
timeout=30):
"""This function will POST to the url endpoint using requests.
Expand Down Expand Up @@ -175,6 +176,8 @@ def _requests_post(self, url,
auth = None
if username and password:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here the api key can be the default and not the fallback plan

Copy link
Contributor Author

@AlexandrosMor AlexandrosMor Jan 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean default. Do you suggest to remove basic authentication? If the xapi become first then it is default, correct?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Attilla ApiKey has preference over username and password.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is fixed by adding the xapikey in high priority

auth = requests.auth.HTTPBasicAuth(username, password)
elif xapikey:
headers['x-api-key'] = xapikey

# Add User-Agent header to request so that the request
# can be identified as coming from the Adyen Python library.
Expand Down Expand Up @@ -246,12 +249,12 @@ def _urllib_post(self, url,
if username and password:
if sys.version_info[0] >= 3:
basic_authstring = base64.encodebytes(('%s:%s' %
(username, password))
.encode()).decode().\
(username, password))
.encode()).decode(). \
replace('\n', '')
else:
basic_authstring = base64.encodestring('%s:%s' % (username,
password)).\
password)). \
replace('\n', '')
url_request.add_header("Authorization",
"Basic %s" % basic_authstring)
Expand Down
Loading