Skip to content

Commit 1d94500

Browse files
Merge pull request #56 from Adyen/feature/checkoutv40
Feature/checkoutv40
2 parents 72c24d8 + a66d3cc commit 1d94500

23 files changed

+1712
-84
lines changed

Adyen/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
AdyenRecurring,
1919
AdyenPayment,
2020
AdyenThirdPartyPayout,
21-
AdyenHPP)
21+
AdyenHPP,
22+
AdyenCheckoutApi)
2223

2324
from .httpclient import HTTPClient
2425

@@ -30,10 +31,12 @@ def __init__(self, **kwargs):
3031
self.payout = AdyenThirdPartyPayout(client=self.client)
3132
self.hpp = AdyenHPP(client=self.client)
3233
self.recurring = AdyenRecurring(client=self.client)
34+
self.checkout = AdyenCheckoutApi(client=self.client)
3335

3436

3537
_base_adyen_obj = Adyen()
3638
recurring = _base_adyen_obj.recurring
3739
hpp = _base_adyen_obj.hpp
3840
payment = _base_adyen_obj.payment
3941
payout = _base_adyen_obj.payout
42+
checkout = _base_adyen_obj.checkout

Adyen/client.py

Lines changed: 127 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
AdyenInvalidRequestError,
1616
AdyenAPIInvalidFormat,
1717
AdyenAPIInvalidAmount,
18-
)
18+
AdyenEndpointInvalidFormat)
1919
from . import settings
2020

2121

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

69-
def __init__(self, username=None, password=None,
69+
def __init__(self, username=None, password=None, xapikey=None,
7070
review_payout_username=None, review_payout_password=None,
7171
store_payout_username=None, store_payout_password=None,
7272
platform="test", merchant_account=None,
7373
merchant_specific_url=None, skin_code=None,
7474
hmac=None, app_name=None,
75-
http_force=None):
75+
http_force=None, live_endpoint_prefix=None):
7676
self.username = username
7777
self.password = password
78+
self.xapikey = xapikey
7879
self.review_payout_username = review_payout_username
7980
self.review_payout_password = review_payout_password
8081
self.store_payout_username = store_payout_username
@@ -86,10 +87,11 @@ def __init__(self, username=None, password=None,
8687
self.skin_code = skin_code
8788
self.psp_list = []
8889
self.app_name = app_name
89-
self.LIB_VERSION = "1.3.0"
90+
self.LIB_VERSION = "1.4.0"
9091
self.USER_AGENT_SUFFIX = "adyen-python-api-library/"
9192
self.http_init = False
9293
self.http_force = http_force
94+
self.live_endpoint_prefix = live_endpoint_prefix
9395

9496
def _determine_api_url(self, platform, service, action):
9597
"""This returns the Adyen API endpoint based on the provided platform,
@@ -103,8 +105,10 @@ def _determine_api_url(self, platform, service, action):
103105
base_uri = settings.BASE_PAL_URL.format(platform)
104106
if service == "Recurring":
105107
api_version = settings.API_RECURRING_VERSION
108+
elif service == "Payout":
109+
api_version = settings.API_PAYOUT_VERSION
106110
else:
107-
api_version = settings.API_VERSION
111+
api_version = settings.API_PAYMENT_VERSION
108112
return '/'.join([base_uri, service, api_version, action])
109113

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

128+
def _determine_checkout_url(self, platform, action):
129+
"""This returns the Adyen API endpoint based on the provided platform,
130+
service and action.
131+
132+
Args:
133+
platform (str): Adyen platform, ie 'live' or 'test'.
134+
action (str): the API action to perform.
135+
"""
136+
api_version = settings.API_CHECKOUT_VERSION
137+
if platform == "test":
138+
base_uri = settings.ENDPOINT_CHECKOUT_TEST
139+
elif self.live_endpoint_prefix is not None and platform == "live":
140+
base_uri = settings.ENDPOINT_CHECKOUT_LIVE_SUFFIX.format(
141+
self.live_endpoint_prefix)
142+
elif self.live_endpoint_prefix is None and platform == "live":
143+
errorstring = """Please set your live suffix. You can set it
144+
by running 'settings.
145+
ENDPOINT_CHECKOUT_LIVE_SUFFIX = 'Your live suffix'"""
146+
raise AdyenEndpointInvalidFormat(errorstring)
147+
if action == "paymentsDetails":
148+
action = "payments/details"
149+
if action == "paymentsResult":
150+
action = "payments/result"
151+
if action == "originKeys":
152+
api_version = settings.API_CHECKOUT_UTILITY_VERSION
153+
154+
return '/'.join([base_uri, api_version, action])
155+
124156
def _review_payout_username(self, **kwargs):
125157
if 'username' in kwargs:
126158
return kwargs['username']
@@ -190,35 +222,47 @@ def call_api(self, request_data, service, action, idempotency=False,
190222

191223
# username at self object has highest priority. fallback to root module
192224
# and ensure that it is set.
225+
if self.xapikey:
226+
xapikey = self.xapikey
227+
elif 'xapikey' in kwargs:
228+
xapikey = kwargs.pop("xapikey")
229+
193230
if self.username:
194231
username = self.username
195232
elif 'username' in kwargs:
196233
username = kwargs.pop("username")
197234
elif service == "Payout":
198-
if any(substring in action for substring in ["store", "submit"]):
235+
if any(substring in action for substring in
236+
["store", "submit"]):
199237
username = self._store_payout_username(**kwargs)
200238
else:
201239
username = self._review_payout_username(**kwargs)
202240
if not username:
203241
errorstring = """Please set your webservice username.
204-
You can do this by running 'Adyen.username = 'Your username'"""
242+
You can do this by running
243+
'Adyen.username = 'Your username'"""
205244
raise AdyenInvalidRequestError(errorstring)
206-
207-
# password at self object has highest priority. fallback to root module
208-
# and ensure that it is set.
245+
# password at self object has highest priority.
246+
# fallback to root module
247+
# and ensure that it is set.
209248
if self.password:
210249
password = self.password
211250
elif 'password' in kwargs:
212251
password = kwargs.pop("password")
213252
elif service == "Payout":
214-
if any(substring in action for substring in ["store", "submit"]):
253+
if any(substring in action for substring in
254+
["store", "submit"]):
215255
password = self._store_payout_pass(**kwargs)
216256
else:
217257
password = self._review_payout_pass(**kwargs)
218258
if not password:
219259
errorstring = """Please set your webservice password.
220-
You can do this by running 'Adyen.password = 'Your password'"""
260+
You can do this by running
261+
'Adyen.password = 'Your password'"""
221262
raise AdyenInvalidRequestError(errorstring)
263+
# xapikey at self object has highest priority.
264+
# fallback to root module
265+
# and ensure that it is set.
222266

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

378+
def call_checkout_api(self, request_data, action, **kwargs):
379+
"""This will call the checkout adyen api. xapi key merchant_account,
380+
and platform are pulled from root module level and or self object.
381+
AdyenResult will be returned on 200 response. Otherwise, an exception
382+
is raised.
383+
384+
Args:
385+
request_data (dict): The dictionary of the request to place. This
386+
should be in the structure of the Adyen API.
387+
https://docs.adyen.com/developers/checkout/api-integration
388+
service (str): This is the API service to be called.
389+
action (str): The specific action of the API service to be called
390+
"""
391+
if not self.http_init:
392+
self.http_client = HTTPClient(self.app_name,
393+
self.USER_AGENT_SUFFIX,
394+
self.LIB_VERSION,
395+
self.http_force)
396+
self.http_init = True
397+
398+
# xapi at self object has highest priority. fallback to root module
399+
# and ensure that it is set.
400+
if self.xapikey:
401+
xapikey = self.xapikey
402+
elif 'xapikey' in kwargs:
403+
xapikey = kwargs.pop("xapikey")
404+
405+
if not xapikey:
406+
errorstring = """Please set your webservice xapikey.
407+
You can do this by running 'Adyen.xapikey = 'Your xapikey'"""
408+
raise AdyenInvalidRequestError(errorstring)
409+
410+
# platform at self object has highest priority. fallback to root module
411+
# and ensure that it is set to either 'live' or 'test'.
412+
if self.platform:
413+
platform = self.platform
414+
elif 'platform' in kwargs:
415+
platform = kwargs.pop('platform')
416+
417+
if not isinstance(platform, str):
418+
errorstring = "'platform' value must be type of string"
419+
raise TypeError(errorstring)
420+
elif platform.lower() not in ['live', 'test']:
421+
errorstring = "'platform' must be the value of 'live' or 'test'"
422+
raise ValueError(errorstring)
423+
424+
if not request_data.get('merchantAccount'):
425+
request_data['merchantAccount'] = self.merchant_account
426+
427+
# Adyen requires this header to be set and uses the combination of
428+
# merchant account and merchant reference to determine uniqueness.
429+
headers = {}
430+
431+
url = self._determine_checkout_url(platform, action)
432+
433+
raw_response, raw_request, status_code, headers = \
434+
self.http_client.request(url, json=request_data,
435+
xapikey=xapikey, headers=headers,
436+
**kwargs)
437+
438+
# Creates AdyenResponse if request was successful, raises error if not.
439+
adyen_result = self._handle_response(url, raw_response, raw_request,
440+
status_code, headers,
441+
request_data)
442+
443+
return adyen_result
444+
334445
def hpp_payment(self, request_data, action, hmac_key="", **kwargs):
335446

336447
if not self.http_init:
@@ -386,7 +497,6 @@ def _handle_response(self, url, raw_response, raw_request,
386497
Returns:
387498
AdyenResult: Result object if successful.
388499
"""
389-
390500
if status_code != 200:
391501
response = {}
392502
# If the result can't be parsed into json, most likely is raw html.
@@ -405,9 +515,10 @@ def _handle_response(self, url, raw_response, raw_request,
405515
"Unexpected error while communicating with Adyen."
406516
" Received the response data:'{}', HTTP Code:'{}'. "
407517
"Please reach out to support@adyen.com if the "
408-
"problem persists with the psp:{}"
409-
.format(raw_response, status_code,
410-
headers.get('pspReference')),
518+
"problem persists with the psp:{}".format(
519+
raw_response,
520+
status_code,
521+
headers.get('pspReference')),
411522
status_code=status_code,
412523
raw_request=raw_request,
413524
raw_response=raw_response,

Adyen/exceptions.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,10 @@ def __str__(self):
2525

2626
def debug(self):
2727
return ("class: {}\nmessage: {}\nHTTP status_code:{}\nurl: {}"
28-
"request: {}\nresponse: {}\nheaders: {}".format(
29-
self.__class__.__name__,
30-
self.message,
31-
self.status_code,
32-
self.url,
33-
self.raw_request,
34-
self.raw_response,
35-
self.headers
36-
)
37-
)
28+
"request: {}\nresponse: {}\nheaders: {}"
29+
.format(self.__class__.__name__, self.message,
30+
self.status_code, self.url, self.raw_request,
31+
self.raw_response, self.headers))
3832

3933

4034
class AdyenInvalidRequestError(AdyenError):
@@ -71,3 +65,7 @@ class AdyenAPIInvalidAmount(AdyenAPIResponseError):
7165

7266
class AdyenAPIInvalidFormat(AdyenAPIResponseError):
7367
pass
68+
69+
70+
class AdyenEndpointInvalidFormat(AdyenError):
71+
pass

Adyen/httpclient.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def _requests_post(self, url,
142142
data=None,
143143
username="",
144144
password="",
145+
xapikey="",
145146
headers=None,
146147
timeout=30):
147148
"""This function will POST to the url endpoint using requests.
@@ -175,6 +176,8 @@ def _requests_post(self, url,
175176
auth = None
176177
if username and password:
177178
auth = requests.auth.HTTPBasicAuth(username, password)
179+
elif xapikey:
180+
headers['x-api-key'] = xapikey
178181

179182
# Add User-Agent header to request so that the request
180183
# can be identified as coming from the Adyen Python library.
@@ -246,12 +249,12 @@ def _urllib_post(self, url,
246249
if username and password:
247250
if sys.version_info[0] >= 3:
248251
basic_authstring = base64.encodebytes(('%s:%s' %
249-
(username, password))
250-
.encode()).decode().\
252+
(username, password))
253+
.encode()).decode(). \
251254
replace('\n', '')
252255
else:
253256
basic_authstring = base64.encodestring('%s:%s' % (username,
254-
password)).\
257+
password)). \
255258
replace('\n', '')
256259
url_request.add_header("Authorization",
257260
"Basic %s" % basic_authstring)

0 commit comments

Comments
 (0)