Skip to content

[API-7661] Adds support for warnings in Events API response #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ except sift.client.ApiException:
# request failed
pass

# To include `warnings` field to Events API response via calling `track()` method, set it by the `include_warnings` param:
try:
response = client.track("$transaction", properties, include_warnings=True)
# ...
except sift.client.ApiException:
# request failed
pass

# Request a score for the user with user_id 23056
try:
response = client.score(user_id)
Expand Down
25 changes: 22 additions & 3 deletions sift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def track(
abuse_types=None,
timeout=None,
version=None,
include_score_percentiles=False):
include_score_percentiles=False,
include_warnings=False):
"""Track an event and associated properties to the Sift Science client.
This call is blocking. Check out https://siftscience.com/resources/references/events-api
for more information on what types of events you can send and fields you can add to the
Expand Down Expand Up @@ -137,6 +138,10 @@ def track(
include_score_percentiles(optional) : Whether to add new parameter in the query parameter.
if include_score_percentiles is true then add a new parameter called fields in the query parameter

include_warnings(optional) : Whether the API response should include `warnings` field.
if include_warnings is True `warnings` field returns the amount of validation warnings
along with their descriptions. They are not critical enough to reject the whole request,
but important enough to be fixed.
Returns:
A sift.client.Response object if the track call succeeded, otherwise
raises an ApiException.
Expand Down Expand Up @@ -179,8 +184,12 @@ def track(
if force_workflow_run:
params['force_workflow_run'] = 'true'

if include_score_percentiles:
params['fields'] = 'SCORE_PERCENTILES'
include_fields = Client._get_fields_param(include_score_percentiles,
include_warnings)
if include_fields:
params['fields'] = ",".join(include_fields)


try:
response = self.session.post(
path,
Expand Down Expand Up @@ -1120,6 +1129,16 @@ def _verification_resend_url(self):
def _verification_check_url(self):
return (API_URL_VERIFICATION + 'check')

@staticmethod
def _get_fields_param(include_score_percentiles, include_warnings):
return [
field for include, field in [
(include_score_percentiles, 'SCORE_PERCENTILES'),
(include_warnings, 'WARNINGS')
] if include
]


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

Expand Down
185 changes: 97 additions & 88 deletions test_integration_app/events_api/test_events_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,122 +445,131 @@ def create_content_review(self):

def create_order(self):
# Sample $create_order event
order_properties = self.build_create_order_event()
return self.client.track("$create_order", order_properties)

def create_order_with_warnings(self):
# Sample $create_order event
order_properties = self.build_create_order_event()
return self.client.track("$create_order", order_properties, include_warnings=True)

def build_create_order_event(self):
order_properties = {
# Required Fields
"$user_id" : self.user_id,
"$user_id": self.user_id,
# Supported Fields
"$session_id" : "gigtleqddo84l8cm15qe4il",
"$order_id" : "ORDER-28168441",
"$user_email" : self.user_email,
"$verification_phone_number" : "+123456789012",
"$amount" : 115940000, # $115.94
"$currency_code" : "USD",
"$billing_address" : {
"$name" : "Bill Jones",
"$phone" : "1-415-555-6041",
"$address_1" : "2100 Main Street",
"$address_2" : "Apt 3B",
"$city" : "New London",
"$region" : "New Hampshire",
"$country" : "US",
"$zipcode" : "03257"
"$session_id": "gigtleqddo84l8cm15qe4il",
"$order_id": "ORDER-28168441",
"$user_email": self.user_email,
"$verification_phone_number": "+123456789012",
"$amount": 115940000, # $115.94
"$currency_code": "USD",
"$billing_address": {
"$name": "Bill Jones",
"$phone": "1-415-555-6041",
"$address_1": "2100 Main Street",
"$address_2": "Apt 3B",
"$city": "New London",
"$region": "New Hampshire",
"$country": "US",
"$zipcode": "03257"
},
"$payment_methods" : [
"$payment_methods": [
{
"$payment_type" : "$credit_card",
"$payment_gateway" : "$braintree",
"$card_bin" : "542486",
"$card_last4" : "4444"
"$payment_type": "$credit_card",
"$payment_gateway": "$braintree",
"$card_bin": "542486",
"$card_last4": "4444"
}
],
"$ordered_from" : {
"$store_id" : "123",
"$store_address" : {
"$name" : "Bill Jones",
"$phone" : "1-415-555-6040",
"$address_1" : "2100 Main Street",
"$address_2" : "Apt 3B",
"$city" : "New London",
"$region" : "New Hampshire",
"$country" : "US",
"$zipcode" : "03257"
"$ordered_from": {
"$store_id": "123",
"$store_address": {
"$name": "Bill Jones",
"$phone": "1-415-555-6040",
"$address_1": "2100 Main Street",
"$address_2": "Apt 3B",
"$city": "New London",
"$region": "New Hampshire",
"$country": "US",
"$zipcode": "03257"
}
},
"$brand_name" : "sift",
"$site_domain" : "sift.com",
"$site_country" : "US",
"$shipping_address" : {
"$name" : "Bill Jones",
"$phone" : "1-415-555-6041",
"$address_1" : "2100 Main Street",
"$address_2" : "Apt 3B",
"$city" : "New London",
"$region" : "New Hampshire",
"$country" : "US",
"$zipcode" : "03257"
"$brand_name": "sift",
"$site_domain": "sift.com",
"$site_country": "US",
"$shipping_address": {
"$name": "Bill Jones",
"$phone": "1-415-555-6041",
"$address_1": "2100 Main Street",
"$address_2": "Apt 3B",
"$city": "New London",
"$region": "New Hampshire",
"$country": "US",
"$zipcode": "03257"
},
"$expedited_shipping" : True,
"$shipping_method" : "$physical",
"$shipping_carrier" : "UPS",
"$expedited_shipping": True,
"$shipping_method": "$physical",
"$shipping_carrier": "UPS",
"$shipping_tracking_numbers": ["1Z204E380338943508", "1Z204E380338943509"],
"$items" : [
"$items": [
{
"$item_id" : "12344321",
"$product_title" : "Microwavable Kettle Corn: Original Flavor",
"$price" : 4990000, # $4.99
"$upc" : "097564307560",
"$sku" : "03586005",
"$brand" : "Peters Kettle Corn",
"$manufacturer" : "Peters Kettle Corn",
"$category" : "Food and Grocery",
"$tags" : ["Popcorn", "Snacks", "On Sale"],
"$quantity" : 4
"$item_id": "12344321",
"$product_title": "Microwavable Kettle Corn: Original Flavor",
"$price": 4990000, # $4.99
"$upc": "097564307560",
"$sku": "03586005",
"$brand": "Peters Kettle Corn",
"$manufacturer": "Peters Kettle Corn",
"$category": "Food and Grocery",
"$tags": ["Popcorn", "Snacks", "On Sale"],
"$quantity": 4
},
{
"$item_id" : "B004834GQO",
"$product_title" : "The Slanket Blanket-Texas Tea",
"$price" : 39990000, # $39.99
"$upc" : "6786211451001",
"$sku" : "004834GQ",
"$brand" : "Slanket",
"$manufacturer" : "Slanket",
"$category" : "Blankets & Throws",
"$tags" : ["Awesome", "Wintertime specials"],
"$color" : "Texas Tea",
"$quantity" : 2
"$item_id": "B004834GQO",
"$product_title": "The Slanket Blanket-Texas Tea",
"$price": 39990000, # $39.99
"$upc": "6786211451001",
"$sku": "004834GQ",
"$brand": "Slanket",
"$manufacturer": "Slanket",
"$category": "Blankets & Throws",
"$tags": ["Awesome", "Wintertime specials"],
"$color": "Texas Tea",
"$quantity": 2
}
],
# For marketplaces, use $seller_user_id to identify the seller
"$seller_user_id" : "slinkys_emporium",
"$seller_user_id": "slinkys_emporium",

"$promotions" : [
"$promotions": [
{
"$promotion_id" : "FirstTimeBuyer",
"$status" : "$success",
"$description" : "$5 off",
"$discount" : {
"$amount" : 5000000, # $5.00
"$currency_code" : "USD",
"$minimum_purchase_amount" : 25000000 # $25.00
"$promotion_id": "FirstTimeBuyer",
"$status": "$success",
"$description": "$5 off",
"$discount": {
"$amount": 5000000, # $5.00
"$currency_code": "USD",
"$minimum_purchase_amount": 25000000 # $25.00
}
}
],

# Sample Custom Fields
"digital_wallet" : "apple_pay", # "google_wallet", etc.
"coupon_code" : "dollarMadness",
"shipping_choice" : "FedEx Ground Courier",
"is_first_time_buyer" : False,
"digital_wallet": "apple_pay", # "google_wallet", etc.
"coupon_code": "dollarMadness",
"shipping_choice": "FedEx Ground Courier",
"is_first_time_buyer": False,

# Send this information from a BROWSER client.
"$browser" : {
"$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",
"$accept_language" : "en-US",
"$content_language" : "en-GB"
"$browser": {
"$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",
"$accept_language": "en-US",
"$content_language": "en-GB"
}
}
return self.client.track("$create_order", order_properties)
return order_properties

def flag_content(self):
# Sample $flag_content event
flag_content_properties = {
Expand Down
19 changes: 17 additions & 2 deletions test_integration_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ def isOK(self, response):
return ((response.status == 0) and ((response.http_status_code == 200) or (response.http_status_code == 201)))
else:
return ((response.http_status_code == 200) or (response.http_status_code == 201))


def is_ok_with_warnings(self, response):
return self.isOK(response) and \
hasattr(response, 'body') and \
len(response.body['warnings']) > 0

def is_ok_without_warnings(self, response):
return self.isOK(response) and \
hasattr(response, 'body') and \
'warnings' not in response.body

def runAllMethods():
objUtils = Utils()
objEvents = test_events_api.EventsAPI()
Expand All @@ -24,7 +34,7 @@ def runAllMethods():
objVerification = test_verification_api.VerificationAPI()
objPSPMerchant = test_psp_merchant_api.PSPMerchantAPI()

#Events APIs
# Events APIs
assert (objUtils.isOK(objEvents.add_item_to_cart()) == True)
assert (objUtils.isOK(objEvents.add_promotion()) == True)
assert (objUtils.isOK(objEvents.chargeback()) == True)
Expand Down Expand Up @@ -55,6 +65,11 @@ def runAllMethods():
assert (objUtils.isOK(objEvents.update_order()) == True)
assert (objUtils.isOK(objEvents.update_password()) == True)
assert (objUtils.isOK(objEvents.verification()) == True)

# Testing include warnings query param
assert (objUtils.is_ok_without_warnings(objEvents.create_order()) == True)
assert (objUtils.is_ok_with_warnings(objEvents.create_order_with_warnings()) == True)

print("Events API Tested")

# Decision APIs
Expand Down
38 changes: 38 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,44 @@ def test_get_user_score_include_score_percentiles_ok(self):
assert (response.body['scores']['payment_abuse']['score'] == 0.97)
assert ('latest_decisions' in response.body)

def test_warnings_added_as_fields_param(self):
event = '$transaction'
mock_response = mock.Mock()
mock_response.content = '{"status": 0, "error_message": "OK"}'
mock_response.json.return_value = json.loads(mock_response.content)
mock_response.status_code = 200
with mock.patch.object(self.sift_client.session, 'post') as mock_post:
mock_post.return_value = mock_response
response = self.sift_client.track(event, valid_transaction_properties(),
include_warnings=True)
mock_post.assert_called_with(
'https://api.siftscience.com/v205/events',
data=mock.ANY,
headers=mock.ANY,
timeout=mock.ANY,
params={'fields': 'WARNINGS'})
self.assertIsInstance(response, sift.client.Response)

def test_warnings_and_score_percentiles_added_as_fields_param(self):
event = '$transaction'
mock_response = mock.Mock()
mock_response.content = '{"status": 0, "error_message": "OK"}'
mock_response.json.return_value = json.loads(mock_response.content)
mock_response.status_code = 200
with mock.patch.object(self.sift_client.session, 'post') as mock_post:
mock_post.return_value = mock_response
response = self.sift_client.track(event, valid_transaction_properties(),
include_score_percentiles=True,
include_warnings=True)
mock_post.assert_called_with(
'https://api.siftscience.com/v205/events',
data=mock.ANY,
headers=mock.ANY,
timeout=mock.ANY,
params={'fields': 'SCORE_PERCENTILES,WARNINGS'})
self.assertIsInstance(response, sift.client.Response)


def main():
unittest.main()

Expand Down