mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP][H4547] website_sale_signifyd: API v3 + cleanup WIP
This commit is contained in:
@@ -8,10 +8,9 @@ Automate Order Fraud Detection with the Signifyd API.
|
|||||||
""",
|
""",
|
||||||
'website': 'https://hibou.io/',
|
'website': 'https://hibou.io/',
|
||||||
'depends': [
|
'depends': [
|
||||||
'delivery',
|
|
||||||
'hibou_professional',
|
'hibou_professional',
|
||||||
'stock',
|
'stock',
|
||||||
'website_sale',
|
'website_sale_delivery',
|
||||||
'website_payment',
|
'website_payment',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
|
|||||||
@@ -43,20 +43,23 @@ class SaleOrder(models.Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def post_signifyd_case(self):
|
def post_signifyd_case(self):
|
||||||
if not self.website_id.signifyd_connector_id:
|
signifyd_api = self.website_id.signifyd_connector_id.get_connection()
|
||||||
|
if not signifyd_api:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
browser_ip_address = request.httprequest.environ['REMOTE_ADDR']
|
browser_ip_address = request.httprequest.environ['REMOTE_ADDR']
|
||||||
if request.session:
|
if request.session:
|
||||||
checkout_token = request.session.session_token
|
checkout_token = request.session.session_token
|
||||||
order_session_id = checkout_token
|
order_session_id = checkout_token
|
||||||
else:
|
else:
|
||||||
checkout_token = ''
|
checkout_token = ''
|
||||||
|
|
||||||
# Session values for Signifyd post
|
# Session values for Signifyd post
|
||||||
sig_vals = self._prepare_signifyd_case_values(order_session_id, checkout_token, browser_ip_address)
|
sig_vals = self._prepare_signifyd_case_values(order_session_id, checkout_token, browser_ip_address)
|
||||||
|
|
||||||
case = self.env['signifyd.case'].post_case(self.website_id.signifyd_connector_id, sig_vals)
|
response = signifyd_api.post_case(sig_vals)
|
||||||
|
success_response = response.get('signifydId')
|
||||||
success_response = case.get('investigationId')
|
|
||||||
if success_response:
|
if success_response:
|
||||||
new_case = self.env['signifyd.case'].create({
|
new_case = self.env['signifyd.case'].create({
|
||||||
'order_id': self.id,
|
'order_id': self.id,
|
||||||
@@ -77,7 +80,7 @@ class SaleOrder(models.Model):
|
|||||||
if coverage_all in acquirer_coverage_types:
|
if coverage_all in acquirer_coverage_types:
|
||||||
return coverage_all
|
return coverage_all
|
||||||
# 'NONE' if specified by all acquirers
|
# 'NONE' if specified by all acquirers
|
||||||
if all(self.transaction_ids.acquirer_id.mapped(lambda a: a.signifyd_coverage_ids) == coverage_none):
|
if all(self.transaction_ids.acquirer_id.mapped(lambda a: a.signifyd_coverage_ids == coverage_none)):
|
||||||
return coverage_none
|
return coverage_none
|
||||||
# Specific acquirer-level coverage types
|
# Specific acquirer-level coverage types
|
||||||
if acquirer_coverage_types - coverage_none:
|
if acquirer_coverage_types - coverage_none:
|
||||||
@@ -116,7 +119,7 @@ class SaleOrder(models.Model):
|
|||||||
# FIXME: UUID?
|
# FIXME: UUID?
|
||||||
'orderId': self.id,
|
'orderId': self.id,
|
||||||
'purchase': {
|
'purchase': {
|
||||||
'createdAt': self.date_order.isoformat(timespec='seconds'),
|
'createdAt': self.date_order.isoformat(timespec='seconds') + '+00:00',
|
||||||
'orderChannel': 'WEB',
|
'orderChannel': 'WEB',
|
||||||
'totalPrice': self.amount_total,
|
'totalPrice': self.amount_total,
|
||||||
'totalShippingCost': self.amount_delivery,
|
'totalShippingCost': self.amount_delivery,
|
||||||
@@ -143,8 +146,8 @@ class SaleOrder(models.Model):
|
|||||||
'fulfillmentMethod': carrier.signifyd_fulfillment_method,
|
'fulfillmentMethod': carrier.signifyd_fulfillment_method,
|
||||||
} for carrier in self.carrier_id
|
} for carrier in self.carrier_id
|
||||||
],
|
],
|
||||||
'coverageRequests': coverage_codes,
|
|
||||||
},
|
},
|
||||||
|
'coverageRequests': coverage_codes,
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
'parentTransactionId': None,
|
'parentTransactionId': None,
|
||||||
|
|||||||
36
website_sale_signifyd/models/signifyd_api.py
Normal file
36
website_sale_signifyd/models/signifyd_api.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_URL = 'https://api.signifyd.com/v3'
|
||||||
|
|
||||||
|
|
||||||
|
class SignifydAPI:
|
||||||
|
_teamid = None
|
||||||
|
_key = None
|
||||||
|
|
||||||
|
def __init__(self, key, teamid):
|
||||||
|
self._key = b64encode(key.encode()).decode().strip('=')
|
||||||
|
self._teamid = teamid
|
||||||
|
|
||||||
|
def _get_headers(self):
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Basic ' + self._key,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _request(self, method, path, headers=None, json=None):
|
||||||
|
headers = headers or {}
|
||||||
|
headers.update(self._get_headers())
|
||||||
|
request = requests.request(method, API_URL + path, headers=headers, json=json)
|
||||||
|
return request
|
||||||
|
|
||||||
|
def get(self, path, headers=None):
|
||||||
|
return self._request('GET', path, headers=headers)
|
||||||
|
|
||||||
|
def post(self, path, headers=None, json=None):
|
||||||
|
return self._request('POST', path, headers=headers, json=json)
|
||||||
|
|
||||||
|
def post_case(self, connector, values):
|
||||||
|
# data = json.dumps(values, indent=4, sort_keys=True, default=str)
|
||||||
|
r = self._post('/orders/events/sales', json=values)
|
||||||
|
return r.json()
|
||||||
@@ -78,18 +78,14 @@ class SignifydCase(models.Model):
|
|||||||
subtype_xmlid='website_sale_signifyd.disposition_change')
|
subtype_xmlid='website_sale_signifyd.disposition_change')
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.model
|
# @api.model
|
||||||
def post_case(self, connector, values):
|
# def post_case(self, connector, values):
|
||||||
headers = connector.get_headers()
|
# headers = connector.get_headers()
|
||||||
data = json.dumps(values, indent=4, sort_keys=True, default=str)
|
# # data = json.dumps(values, indent=4, sort_keys=True, default=str)
|
||||||
|
# url = connector.API_URL + '/orders/events/sales'
|
||||||
# TODO this should be in `signifyd.connector`
|
# # TODO this should be in `signifyd.connector`
|
||||||
r = requests.post(
|
# r = requests.post(url=url, headers=headers, json=values)
|
||||||
connector.API_URL + '/cases',
|
# return r.json()
|
||||||
headers=headers,
|
|
||||||
data=data,
|
|
||||||
)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_case(self):
|
def get_case(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from base64 import b64encode
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
from .signifyd_api import SignifydAPI
|
||||||
|
|
||||||
|
|
||||||
class SignifydConnector(models.Model):
|
class SignifydConnector(models.Model):
|
||||||
@@ -20,40 +21,26 @@ class SignifydConnector(models.Model):
|
|||||||
notify_user_ids = fields.Many2many('res.users', string='Receive decline notifications')
|
notify_user_ids = fields.Many2many('res.users', string='Receive decline notifications')
|
||||||
website_ids = fields.One2many('website', 'signifyd_connector_id', string='Used on Websites')
|
website_ids = fields.One2many('website', 'signifyd_connector_id', string='Used on Websites')
|
||||||
# TODO: remove options no longer available in api v3
|
# TODO: remove options no longer available in api v3
|
||||||
signifyd_case_type = fields.Selection([
|
# signifyd_case_type = fields.Selection([
|
||||||
('', 'No Case'),
|
# ('', 'No Case'),
|
||||||
('SCORE', 'Score'),
|
# ('SCORE', 'Score'),
|
||||||
('DECISION', 'Decision'),
|
# ('DECISION', 'Decision'),
|
||||||
('GUARANTEE', 'Guarantee'),
|
# ('GUARANTEE', 'Guarantee'),
|
||||||
], string='Default Case Creation', help='Used for internal/admin orders, overridden by payment acquirer.',
|
# ], string='Default Case Creation', help='Used for internal/admin orders, overridden by payment acquirer.',
|
||||||
required=True, default='')
|
# required=True, default='')
|
||||||
signifyd_coverage_ids = fields.Many2many('signifyd.coverage', string='Available Coverage Types',
|
signifyd_coverage_ids = fields.Many2many('signifyd.coverage', string='Available Coverage Types',
|
||||||
help='Note that exclusive coverage types will only allow one to be selected.')
|
help='Note that exclusive coverage types will only allow one to be selected.')
|
||||||
|
teamid = fields.Char(string='Signifyd Team ID')
|
||||||
|
|
||||||
@api.onchange('signifyd_coverage_ids')
|
@api.onchange('signifyd_coverage_ids')
|
||||||
def _onchange_signifyd_coverage_ids(self):
|
def _onchange_signifyd_coverage_ids(self):
|
||||||
self.signifyd_coverage_ids = self.signifyd_coverage_ids._apply_exclusivity()
|
self.signifyd_coverage_ids = self.signifyd_coverage_ids._apply_exclusivity()
|
||||||
|
|
||||||
# TODO ideally this would be a regular constant
|
|
||||||
# however other entities currently use this by reference
|
|
||||||
API_URL = 'https://api.signifyd.com/v3'
|
|
||||||
|
|
||||||
def get_headers(self):
|
def get_connection(self):
|
||||||
self.ensure_one()
|
if not self:
|
||||||
# Check for prod or test mode
|
return
|
||||||
if self.test_mode:
|
return SignifydAPI(self.name, self.secret_key, self.teamid)
|
||||||
api_key = self.secret_key_test
|
|
||||||
else:
|
|
||||||
api_key = self.secret_key
|
|
||||||
|
|
||||||
b64_auth_key = b64encode(api_key.encode()).decode().replace('=', '')
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Authorization': 'Basic ' + b64_auth_key,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def register_webhooks(self):
|
def register_webhooks(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -69,27 +56,27 @@ class SignifydConnector(models.Model):
|
|||||||
if not base_url:
|
if not base_url:
|
||||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||||
values = {
|
values = {
|
||||||
"webhooks": [
|
'webhooks': [
|
||||||
# Given we are creating the cases, we do not need to know about it
|
# Given we are creating the cases, we do not need to know about it
|
||||||
# {
|
# {
|
||||||
# "event": "CASE_CREATION",
|
# "event": "CASE_CREATION",
|
||||||
# "url": base_url + "/signifyd/cases/update"
|
# "url": base_url + "/signifyd/cases/update"
|
||||||
# },
|
# },
|
||||||
{
|
{
|
||||||
"event": "CASE_RESCORE",
|
'event': 'CASE_RESCORE',
|
||||||
"url": base_url + "/signifyd/cases/update"
|
'url': base_url + '/signifyd/cases/update'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event": "CASE_REVIEW",
|
'event': 'CASE_REVIEW',
|
||||||
"url": base_url + "/signifyd/cases/update"
|
'url': base_url + '/signifyd/cases/update'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event": "GUARANTEE_COMPLETION",
|
'event': 'GUARANTEE_COMPLETION',
|
||||||
"url": base_url + "/signifyd/cases/update"
|
'url': base_url + '/signifyd/cases/update'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event": "DECISION_MADE",
|
'event': 'DECISION_MADE',
|
||||||
"url": base_url + "/signifyd/cases/update"
|
'url': base_url + '/signifyd/cases/update'
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
<field name="secret_key" attrs="{'invisible': [('test_mode', '=', True)]}"/>
|
<field name="secret_key" attrs="{'invisible': [('test_mode', '=', True)]}"/>
|
||||||
<field name="secret_key_test" attrs="{'invisible': [('test_mode', '!=', True)]}"/>
|
<field name="secret_key_test" attrs="{'invisible': [('test_mode', '!=', True)]}"/>
|
||||||
<!-- <field name="signifyd_case_type" /> -->
|
<!-- <field name="signifyd_case_type" /> -->
|
||||||
<field name="signifyd_coverage_ids"/>
|
<field name="signifyd_coverage_ids" widget="many2many_tags"/>
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
Optional: Add users to be notified if a sale order is declined by Signifyd.
|
Optional: Add users to be notified if a sale order is declined by Signifyd.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user