diff --git a/README.md b/README.md index 7f48711..288a8df 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/sift/client.py b/sift/client.py index 37c9d6e..1d0f487 100644 --- a/sift/client.py +++ b/sift/client.py @@ -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 @@ -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. @@ -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, @@ -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] diff --git a/test_integration_app/events_api/test_events_api.py b/test_integration_app/events_api/test_events_api.py index f123a73..04607e0 100644 --- a/test_integration_app/events_api/test_events_api.py +++ b/test_integration_app/events_api/test_events_api.py @@ -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 = { diff --git a/test_integration_app/main.py b/test_integration_app/main.py index eca8726..63fd928 100644 --- a/test_integration_app/main.py +++ b/test_integration_app/main.py @@ -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() @@ -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) @@ -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 diff --git a/tests/test_client.py b/tests/test_client.py index 8438829..2a5e094 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -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()