Skip to content

Commit 06cd95e

Browse files
authored
Verification endpoints implemented (#96)
* Verification API (v1)
1 parent 0a7b830 commit 06cd95e

File tree

3 files changed

+415
-5
lines changed

3 files changed

+415
-5
lines changed

README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,58 @@ try:
183183
except sift.client.ApiException:
184184
# request failed
185185
pass
186-
```
187186

187+
# The send call triggers the generation of a OTP code that is stored by Sift and email/sms the code to the user.
188+
send_properties = {
189+
"$user_id": "billy_jones_301",
190+
"$send_to": "[email protected]",
191+
"$verification_type": "$email",
192+
"$brand_name": "MyTopBrand",
193+
"$language": "en",
194+
"$site_country": "IN",
195+
"$event": {
196+
"$session_id": "SOME_SESSION_ID",
197+
"$verified_event": "$login",
198+
"$verified_entity_id": "SOME_SESSION_ID",
199+
"$reason": "$automated_rule",
200+
"$ip": "192.168.1.1",
201+
"$browser": {
202+
"$user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
203+
}
204+
}
205+
}
206+
try:
207+
response = client.verification_send(send_properties)
208+
except sift.client.ApiException:
209+
# request failed
210+
pass
211+
212+
# The resend call generates a new OTP and sends it to the original recipient with the same settings.
213+
resend_properties = {
214+
"$user_id": "billy_jones_301",
215+
"$verified_event": "$login",
216+
"$verified_entity_id": "SOME_SESSION_ID"
217+
}
218+
try:
219+
response = client.verification_resend(resend_properties)
220+
except sift.client.ApiException:
221+
# request failed
222+
pass
223+
224+
# The check call is used for verifying the OTP provided by the end user to Sift.
225+
check_properties = {
226+
"$user_id": "billy_jones_301",
227+
"$code": 123456,
228+
"$verified_event": "$login",
229+
"$verified_entity_id": "SOME_SESSION_ID"
230+
}
231+
try:
232+
response = client.verification_check(check_properties)
233+
except sift.client.ApiException:
234+
# request failed
235+
pass
236+
237+
```
188238

189239
## Testing
190240

sift/client.py

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
API_URL = 'https://api.siftscience.com'
2424
API3_URL = 'https://api3.siftscience.com'
25+
API_URL_VERIFICATION = 'https://api.sift.com/v1/verification/'
26+
2527
DECISION_SOURCES = ['MANUAL_REVIEW', 'AUTOMATED_RULE', 'CHARGEBACK']
2628

2729

@@ -180,7 +182,6 @@ def track(
180182
if include_score_percentiles:
181183
field_types = ['SCORE_PERCENTILES']
182184
params['fields'] = ','.join(field_types)
183-
184185
try:
185186
response = self.session.post(
186187
path,
@@ -485,7 +486,6 @@ def apply_user_decision(self, user_id, properties, timeout=None):
485486

486487
self._validate_apply_decision_request(properties, user_id)
487488
url = self._user_decisions_url(self.account_id, user_id)
488-
489489
try:
490490
return Response(self.session.post(
491491
url,
@@ -524,7 +524,6 @@ def apply_order_decision(self, user_id, order_id, properties, timeout=None):
524524
self._validate_apply_decision_request(properties, user_id)
525525

526526
url = self._order_apply_decisions_url(self.account_id, user_id, order_id)
527-
528527
try:
529528
return Response(self.session.post(
530529
url,
@@ -788,7 +787,6 @@ def update_psp_merchant_profile(self, merchant_id, properties, timeout=None):
788787
timeout = self.timeout
789788

790789
url = self._psp_merchant_id_url(self.account_id, merchant_id)
791-
792790
try:
793791
return Response(self.session.put(
794792
url,
@@ -854,6 +852,196 @@ def get_a_psp_merchant_profile(self, merchant_id, timeout=None):
854852
except requests.exceptions.RequestException as e:
855853
raise ApiException(str(e), url)
856854

855+
def verification_send(self, properties, timeout=None, version=None):
856+
"""The send call triggers the generation of a OTP code that is stored by Sift and email/sms the code to the user.
857+
This call is blocking. Check out https://sift.com/developers/docs/python/verification-api/send
858+
for more information on our send response structure.
859+
860+
Args:
861+
862+
properties:
863+
864+
$user_id: User ID of user being verified, e.g. johndoe123.
865+
$send_to: The phone / email to send the OTP to.
866+
$verification_type: The channel used for verification. Should be either $email or $sms.
867+
$brand_name(optional): Name of the brand of product or service the user interacts with.
868+
$language(optional): Language of the content of the web site.
869+
$site_country(optional): Country of the content of the site.
870+
$event:
871+
$session_id: The session being verified. See $verification in the Sift Events API documentation.
872+
$verified_event: The type of the reserved event being verified.
873+
$reason(optional): The trigger for the verification. See $verification in the Sift Events API documentation.
874+
$ip(optional): The user's IP address.
875+
$browser:
876+
$user_agent: The user agent of the browser that is verifying. Represented by the $browser object.
877+
Use this field if the client is a browser.
878+
879+
880+
timeout(optional): Use a custom timeout (in seconds) for this call.
881+
882+
version(optional): Use a different version of the Sift Science API for this call.
883+
884+
Returns:
885+
A sift.client.Response object if the send call succeeded, or raises an ApiException.
886+
"""
887+
888+
if timeout is None:
889+
timeout = self.timeout
890+
891+
self._validate_send_request(properties)
892+
893+
url = self._verification_send_url()
894+
895+
try:
896+
return Response(self.session.post(
897+
url,
898+
data=json.dumps(properties),
899+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
900+
headers={'Content-type': 'application/json',
901+
'Accept': '*/*',
902+
'User-Agent': self._user_agent()},
903+
timeout=timeout))
904+
905+
except requests.exceptions.RequestException as e:
906+
raise ApiException(str(e), url)
907+
908+
def _validate_send_request(self, properties):
909+
""" This method is used to validate arguments passed to the send method. """
910+
911+
if not isinstance(properties, dict):
912+
raise TypeError("properties must be a dict")
913+
elif not properties:
914+
raise ValueError("properties dictionary may not be empty")
915+
916+
user_id = properties.get('$user_id')
917+
_assert_non_empty_unicode(user_id, 'user_id', error_cls=ValueError)
918+
919+
send_to = properties.get('$send_to')
920+
_assert_non_empty_unicode(send_to, 'send_to', error_cls=ValueError)
921+
922+
verification_type = properties.get('$verification_type')
923+
_assert_non_empty_unicode(
924+
verification_type, 'verification_type', error_cls=ValueError)
925+
926+
event = properties.get('$event')
927+
if not isinstance(event, dict):
928+
raise TypeError("$event must be a dict")
929+
elif not event:
930+
raise ValueError("$event dictionary may not be empty")
931+
932+
session_id = event.get('$session_id')
933+
_assert_non_empty_unicode(
934+
session_id, 'session_id', error_cls=ValueError)
935+
936+
def verification_resend(self, properties, timeout=None, version=None):
937+
"""A user can ask for a new OTP (one-time password) if they haven’t received the previous one,
938+
or in case the previous OTP expired.
939+
This call is blocking. Check out https://sift.com/developers/docs/python/verification-api/resend
940+
for more information on our send response structure.
941+
942+
Args:
943+
944+
properties:
945+
946+
$user_id: User ID of user being verified, e.g. johndoe123.
947+
$verified_event(optional): This will be the event type that triggered the verification.
948+
$verified_entity_id(optional): The ID of the entity impacted by the event being verified.
949+
950+
timeout(optional): Use a custom timeout (in seconds) for this call.
951+
952+
version(optional): Use a different version of the Sift Science API for this call.
953+
954+
Returns:
955+
A sift.client.Response object if the send call succeeded, or raises an ApiException.
956+
"""
957+
958+
if timeout is None:
959+
timeout = self.timeout
960+
961+
self._validate_resend_request(properties)
962+
963+
url = self._verification_resend_url()
964+
965+
try:
966+
return Response(self.session.post(
967+
url,
968+
data=json.dumps(properties),
969+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
970+
headers={'Content-type': 'application/json',
971+
'Accept': '*/*',
972+
'User-Agent': self._user_agent()},
973+
timeout=timeout))
974+
975+
except requests.exceptions.RequestException as e:
976+
raise ApiException(str(e), url)
977+
978+
def _validate_resend_request(self, properties):
979+
""" This method is used to validate arguments passed to the send method. """
980+
981+
if not isinstance(properties, dict):
982+
raise TypeError("properties must be a dict")
983+
elif not properties:
984+
raise ValueError("properties dictionary may not be empty")
985+
986+
user_id = properties.get('$user_id')
987+
_assert_non_empty_unicode(user_id, 'user_id', error_cls=ValueError)
988+
989+
def verification_check(self, properties, timeout=None, version=None):
990+
"""The verification_check call is used for checking the OTP provided by the end user to Sift.
991+
Sift then compares the OTP, checks rate limits and responds with a decision whether the user should be able to proceed or not.
992+
This call is blocking. Check out https://sift.com/developers/docs/python/verification-api/check
993+
for more information on our check response structure.
994+
995+
Args:
996+
997+
properties:
998+
$user_id: User ID of user being verified, e.g. johndoe123.
999+
$code: The code the user sent to the customer for validation..
1000+
$verified_event(optional): This will be the event type that triggered the verification.
1001+
$verified_entity_id(optional): The ID of the entity impacted by the event being verified.
1002+
1003+
timeout(optional): Use a custom timeout (in seconds) for this call.
1004+
version(optional): Use a different version of the Sift Science API for this call.
1005+
1006+
Returns:
1007+
A sift.client.Response object if the check call succeeded, or raises
1008+
an ApiException.
1009+
"""
1010+
if timeout is None:
1011+
timeout = self.timeout
1012+
1013+
self._validate_check_request(properties)
1014+
1015+
url = self._verification_check_url()
1016+
1017+
try:
1018+
return Response(self.session.post(
1019+
url,
1020+
data=json.dumps(properties),
1021+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
1022+
headers={'Content-type': 'application/json',
1023+
'Accept': '*/*',
1024+
'User-Agent': self._user_agent()},
1025+
timeout=timeout))
1026+
1027+
except requests.exceptions.RequestException as e:
1028+
raise ApiException(str(e), url)
1029+
1030+
def _validate_check_request(self, properties):
1031+
""" This method is used to validate arguments passed to the check method. """
1032+
1033+
if not isinstance(properties, dict):
1034+
raise TypeError("properties must be a dict")
1035+
elif not properties:
1036+
raise ValueError("properties dictionary may not be empty")
1037+
1038+
user_id = properties.get('$user_id')
1039+
_assert_non_empty_unicode(user_id, 'user_id', error_cls=ValueError)
1040+
1041+
otp_code = properties.get('$code')
1042+
if otp_code is None:
1043+
raise ValueError("code is required")
1044+
8571045
def _user_agent(self):
8581046
return 'SiftScience/v%s sift-python/%s' % (sift.version.API_VERSION, sift.version.VERSION)
8591047

@@ -912,6 +1100,14 @@ def _psp_merchant_id_url(self, account_id, merchant_id):
9121100
return (self.url + '/v3/accounts/%s/psp_management/merchants/%s' %
9131101
(_quote_path(account_id), _quote_path(merchant_id)))
9141102

1103+
def _verification_send_url(self):
1104+
return (API_URL_VERIFICATION + 'send')
1105+
1106+
def _verification_resend_url(self):
1107+
return (API_URL_VERIFICATION + 'resend')
1108+
1109+
def _verification_check_url(self):
1110+
return (API_URL_VERIFICATION + 'check')
9151111

9161112
class Response(object):
9171113
HTTP_CODES_WITHOUT_BODY = [204, 304]

0 commit comments

Comments
 (0)