Skip to content

Commit 57dd5a3

Browse files
woochicaAleffio
authored andcommitted
Add support for Third Party Payout service (#42)
* Add support for third party payouts * Fix PEP-8 fixes * Remove bank details from payout validation If card details are provided, bank details are no longer mandatory. * Remove trailing comma (style fix) * Handle validation error with status code 500 * Billing address is not mandatory for storeDetailAndSubmitThirdParty * Add test cases for all payout actions * Fix wrong indentation * Use assertRaisesRegexp to be backward compat with Python 2.7 * check required fields recursively
1 parent 2773e68 commit 57dd5a3

16 files changed

+470
-10
lines changed

Adyen/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
AdyenBase,
1818
AdyenRecurring,
1919
AdyenPayment,
20+
AdyenThirdPartyPayout,
2021
AdyenHPP)
2122

2223
from .httpclient import HTTPClient
@@ -33,6 +34,7 @@ class Adyen(AdyenBase):
3334
def __init__(self, **kwargs):
3435
self.client = AdyenClient(**kwargs)
3536
self.payment = AdyenPayment(client=self.client)
37+
self.payout = AdyenThirdPartyPayout(client=self.client)
3638
self.hpp = AdyenHPP(client=self.client)
3739
self.recurring = AdyenRecurring(client=self.client)
3840

@@ -41,3 +43,4 @@ def __init__(self, **kwargs):
4143
recurring = _base_adyen_obj.recurring
4244
hpp = _base_adyen_obj.hpp
4345
payment = _base_adyen_obj.payment
46+
payout = _base_adyen_obj.payout

Adyen/client.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _review_payout_username(self, **kwargs):
136136
review_payout_username = kwargs['username']
137137
elif self.review_payout_username:
138138
review_payout_username = self.review_payout_username
139-
if not review_payout_username:
139+
else:
140140
errorstring = """AdyenInvalidRequestError: Please set your review payout
141141
webservice username. You can do this by running
142142
'Adyen.review_payout_username = 'Your payout username' """
@@ -149,7 +149,7 @@ def _review_payout_pass(self, **kwargs):
149149
review_payout_password = kwargs["password"]
150150
elif self.review_payout_password:
151151
review_payout_password = self.review_payout_password
152-
if not review_payout_password:
152+
else:
153153
errorstring = """AdyenInvalidRequestError: Please set your review payout
154154
webservice password. You can do this by running
155155
'Adyen.review_payout_password = 'Your payout password'"""
@@ -162,7 +162,7 @@ def _store_payout_username(self, **kwargs):
162162
store_payout_username = kwargs['username']
163163
elif self.store_payout_username:
164164
store_payout_username = self.store_payout_username
165-
if not store_payout_username:
165+
else:
166166
errorstring = """AdyenInvalidRequestError: Please set your store payout
167167
webservice username. You can do this by running
168168
'Adyen.store_payout_username = 'Your payout username'"""
@@ -175,7 +175,7 @@ def _store_payout_pass(self, **kwargs):
175175
store_payout_password = kwargs["password"]
176176
elif self.store_payout_password:
177177
store_payout_password = self.store_payout_password
178-
if not store_payout_password:
178+
else:
179179
errorstring = """AdyenInvalidRequestError: Please set your store payout
180180
webservice password. You can do this by running
181181
'Adyen.store_payout_password = 'Your payout password'"""
@@ -552,6 +552,15 @@ def _handle_http_error(self, url, response_obj, status_code, psp_ref,
552552
)
553553

554554
elif status_code == 500:
555+
if response_obj.get("errorType") == "validation":
556+
err_args = (response_obj.get("errorCode"),
557+
response_obj.get("message"),
558+
status_code)
559+
erstr = "Received validation error with errorCode: %s," \
560+
" message: %s, HTTP Code: %s. Please verify" \
561+
" the values provided." % err_args
562+
raise AdyenAPIValidationError(erstr)
563+
555564
if response_obj.get("message") == "Failed to serialize node " \
556565
"Failed to parse [123.34]" \
557566
" as a Long":

Adyen/services.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,58 @@ def cancel_or_refund(self, request="", **kwargs):
222222
action = "cancelOrRefund"
223223

224224
if validation.check_in(request, action):
225-
return self.client.call_api(request, self.service,
226-
action, **kwargs)
225+
return self.client.call_api(
226+
request, self.service, action, **kwargs
227+
)
228+
229+
230+
class AdyenThirdPartyPayout(AdyenServiceBase):
231+
"""This represents the Adyen API Third Party Payouts Service.
232+
233+
https://docs.adyen.com/developers/api-reference/third-party-payouts-api
234+
235+
The AdyenThirdPartyPayout class is accessible as adyen.payout.method(args)
236+
237+
Args:
238+
client (AdyenAPIClient, optional): An API client for the service to
239+
use. If not provided, a new API client will be created.
240+
"""
241+
242+
def __init__(self, client=None):
243+
super(AdyenThirdPartyPayout, self).__init__(client=client)
244+
self.service = "Payout"
245+
246+
def confirm(self, request=None, **kwargs):
247+
action = "confirmThirdParty"
248+
if validation.check_in(request, action):
249+
return self.client.call_api(
250+
request, self.service, action, **kwargs
251+
)
252+
253+
def decline(self, request=None, **kwargs):
254+
action = "declineThirdParty"
255+
if validation.check_in(request, action):
256+
return self.client.call_api(
257+
request, self.service, action, **kwargs
258+
)
259+
260+
def store_detail(self, request=None, **kwargs):
261+
action = "storeDetail"
262+
if validation.check_in(request, action):
263+
return self.client.call_api(
264+
request, self.service, action, **kwargs
265+
)
266+
267+
def submit(self, request=None, **kwargs):
268+
action = "submitThirdParty"
269+
if validation.check_in(request, action):
270+
return self.client.call_api(
271+
request, self.service, action, **kwargs
272+
)
273+
274+
def store_detail_and_submit(self, request=None, **kwargs):
275+
action = "storeDetailAndSubmitThirdParty"
276+
if validation.check_in(request, action):
277+
return self.client.call_api(
278+
request, self.service, action, **kwargs
279+
)

Adyen/validation.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import absolute_import, division, unicode_literals
2+
import re
23

34
actions = {}
45
actions['listRecurringDetails'] = ["shopperReference"]
@@ -16,17 +17,53 @@
1617
actions['refund'] = ["modificationAmount", "originalReference"]
1718
actions['cancelOrRefund'] = ["originalReference"]
1819

20+
payout_required_fields = {
21+
'confirmThirdParty': (
22+
'merchantAccount',
23+
'originalReference'
24+
),
25+
'declineThirdParty': (
26+
'merchantAccount',
27+
'originalReference'
28+
),
29+
'storeDetail': (
30+
'merchantAccount',
31+
'recurring.contract',
32+
),
33+
'submitThirdParty': (
34+
'amount.currency',
35+
'amount.value',
36+
'merchantAccount',
37+
'recurring.contract',
38+
'reference',
39+
'shopperEmail',
40+
'shopperReference',
41+
'selectedRecurringDetailReference'
42+
),
43+
'storeDetailAndSubmitThirdParty': (
44+
'amount.currency',
45+
'amount.value',
46+
'merchantAccount',
47+
'recurring.contract',
48+
'reference',
49+
'shopperEmail',
50+
'shopperReference',
51+
)
52+
}
53+
54+
actions.update(payout_required_fields)
55+
1956

2057
def check_in(request, action):
2158
# This function checks for missing properties in the request dict
2259
# for the corresponding action.
2360

2461
if request:
25-
action = actions[action]
62+
required_fields = actions[action]
2663
missing = []
27-
for x in action:
28-
if x not in request:
29-
missing.append(x)
64+
for field in required_fields:
65+
if not is_key_present(request, field):
66+
missing.append(field)
3067
if len(missing) > 0:
3168
missing_string = ""
3269
for idx, val in enumerate(missing):
@@ -43,3 +80,15 @@ def check_in(request, action):
4380
erstr = "Provide a request dict with the following properties:" \
4481
" %s" % req_str
4582
raise ValueError(erstr)
83+
84+
85+
def is_key_present(request, key):
86+
m = re.search('([^\.]+)\.(.+)', key)
87+
if m:
88+
parent_key = m.group(1)
89+
child_key = m.group(2)
90+
if parent_key in request:
91+
return is_key_present(request[parent_key], child_key)
92+
elif key in request:
93+
return True
94+
return False

0 commit comments

Comments
 (0)