mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[MIG] website_sale_signifyd: 1.1.0 (from 14)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
'name': 'Signifyd Connector',
|
'name': 'Signifyd Connector',
|
||||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||||
'version': '15.0.1.0.0',
|
'version': '15.0.1.1.0',
|
||||||
'category': 'Sale',
|
'category': 'Sale',
|
||||||
'description': """
|
'description': """
|
||||||
Automate Order Fraud Detection with the Signifyd API.
|
Automate Order Fraud Detection with the Signifyd API.
|
||||||
@@ -12,10 +12,12 @@ Automate Order Fraud Detection with the Signifyd API.
|
|||||||
'hibou_professional',
|
'hibou_professional',
|
||||||
'stock',
|
'stock',
|
||||||
'website_sale',
|
'website_sale',
|
||||||
|
'website_payment',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/partner_views.xml',
|
'views/partner_views.xml',
|
||||||
|
'views/payment_views.xml',
|
||||||
'views/sale_views.xml',
|
'views/sale_views.xml',
|
||||||
'views/signifyd_views.xml',
|
'views/signifyd_views.xml',
|
||||||
'views/stock_views.xml',
|
'views/stock_views.xml',
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(cr, version):
|
||||||
|
cr.execute('''
|
||||||
|
UPDATE signifyd_case
|
||||||
|
SET checkpoint_action = 'ACCEPT'
|
||||||
|
WHERE guarantee_disposition = 'APPROVED'
|
||||||
|
''')
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
from . import partner
|
from . import partner
|
||||||
|
from . import payment
|
||||||
from . import sale_order
|
from . import sale_order
|
||||||
from . import signifyd
|
from . import signifyd
|
||||||
from . import signifyd_connector
|
from . import signifyd_connector
|
||||||
|
|||||||
14
website_sale_signifyd/models/payment.py
Normal file
14
website_sale_signifyd/models/payment.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentAcquirer(models.Model):
|
||||||
|
_inherit = 'payment.acquirer'
|
||||||
|
|
||||||
|
signifyd_case_type = fields.Selection([
|
||||||
|
('', 'No Case'),
|
||||||
|
('SCORE', 'Score'),
|
||||||
|
('DECISION', 'Decision'),
|
||||||
|
('GUARANTEE', 'Guarantee'),
|
||||||
|
], string='Signifyd Case Creation', default='')
|
||||||
@@ -10,7 +10,7 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
signifyd_case_id = fields.Many2one('signifyd.case', readonly=1, copy=False)
|
signifyd_case_id = fields.Many2one('signifyd.case', readonly=1, copy=False)
|
||||||
singifyd_score = fields.Float(related='signifyd_case_id.score')
|
singifyd_score = fields.Float(related='signifyd_case_id.score')
|
||||||
signifyd_disposition_status = fields.Selection(related='signifyd_case_id.guarantee_disposition')
|
signifyd_checkpoint_action = fields.Selection(string='Signifyd Action', related='signifyd_case_id.checkpoint_action')
|
||||||
|
|
||||||
def action_view_signifyd_case(self):
|
def action_view_signifyd_case(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -35,7 +35,13 @@ class SaleOrder(models.Model):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _should_post_signifyd(self):
|
def _should_post_signifyd(self):
|
||||||
return self.state in ('sale', 'done') and not self.signifyd_case_id
|
# If we have no transaction/acquirer we will still send!
|
||||||
|
# this case is useful for admin or free orders but could be customized here.
|
||||||
|
case_required = bool(self.website_id.signifyd_connector_id.signifyd_case_type)
|
||||||
|
a_case_types = self.transaction_ids.mapped('acquirer_id.signifyd_case_type')
|
||||||
|
if a_case_types:
|
||||||
|
case_required = any(a_case_types)
|
||||||
|
return self.state in ('sale', 'done') and not self.signifyd_case_id and case_required
|
||||||
|
|
||||||
def post_signifyd_case(self):
|
def post_signifyd_case(self):
|
||||||
if not self.website_id.signifyd_connector_id:
|
if not self.website_id.signifyd_connector_id:
|
||||||
@@ -66,6 +72,18 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _prepare_signifyd_case_values(self, order_session_id, checkout_token, browser_ip_address):
|
def _prepare_signifyd_case_values(self, order_session_id, checkout_token, browser_ip_address):
|
||||||
|
decision_request = self.website_id.signifyd_connector_id.signifyd_case_type or 'DECISION'
|
||||||
|
|
||||||
|
# find the highest 'acquirer override'
|
||||||
|
# note that we shouldn't be here if the override would prevent sending
|
||||||
|
a_case_types = self.transaction_ids.mapped('acquirer_id.signifyd_case_type')
|
||||||
|
if a_case_types and 'GUARANTEE' in a_case_types:
|
||||||
|
decision_request = 'GUARANTEE'
|
||||||
|
elif a_case_types and 'SCORE' in a_case_types:
|
||||||
|
decision_request = 'SCORE'
|
||||||
|
elif a_case_types and 'DECISION' in a_case_types:
|
||||||
|
decision_request = 'DECISION'
|
||||||
|
|
||||||
tx_status_type = {
|
tx_status_type = {
|
||||||
'draft': 'FAILURE',
|
'draft': 'FAILURE',
|
||||||
'pending': 'PENDING',
|
'pending': 'PENDING',
|
||||||
@@ -77,7 +95,7 @@ class SaleOrder(models.Model):
|
|||||||
recipients = self.partner_invoice_id + self.partner_shipping_id
|
recipients = self.partner_invoice_id + self.partner_shipping_id
|
||||||
new_case_vals = {
|
new_case_vals = {
|
||||||
'decisionRequest': {
|
'decisionRequest': {
|
||||||
'paymentFraud': 'GUARANTEE',
|
'paymentFraud': decision_request,
|
||||||
},
|
},
|
||||||
'purchase': {
|
'purchase': {
|
||||||
"orderSessionId": order_session_id,
|
"orderSessionId": order_session_id,
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ class SignifydCase(models.Model):
|
|||||||
score = fields.Float(string='Transaction Score')
|
score = fields.Float(string='Transaction Score')
|
||||||
adjusted_score = fields.Float(string='Adjusted Score')
|
adjusted_score = fields.Float(string='Adjusted Score')
|
||||||
signifyd_url = fields.Char('Signifyd.com', compute='_compute_signifyd_url')
|
signifyd_url = fields.Char('Signifyd.com', compute='_compute_signifyd_url')
|
||||||
|
checkpoint_action = fields.Selection([
|
||||||
|
('ACCEPT', 'Accept'),
|
||||||
|
('HOLD', 'Hold'),
|
||||||
|
('REJECT', 'Reject'),
|
||||||
|
], string='Checkpoint Action')
|
||||||
|
|
||||||
def _get_connector(self):
|
def _get_connector(self):
|
||||||
return self.order_id.website_id.signifyd_connector_id
|
return self.order_id.website_id.signifyd_connector_id
|
||||||
@@ -115,6 +120,14 @@ class SignifydCase(models.Model):
|
|||||||
guarantee_disposition = case.get('guaranteeDisposition', self.guarantee_disposition)
|
guarantee_disposition = case.get('guaranteeDisposition', self.guarantee_disposition)
|
||||||
adjusted_score = case.get('adjustedScore', self.adjusted_score)
|
adjusted_score = case.get('adjustedScore', self.adjusted_score)
|
||||||
score = case.get('score', self.score)
|
score = case.get('score', self.score)
|
||||||
|
checkpoint_action = case.get('checkpointAction', self.checkpoint_action)
|
||||||
|
if not checkpoint_action and guarantee_disposition:
|
||||||
|
if guarantee_disposition == 'APPROVED':
|
||||||
|
checkpoint_action = 'ACCEPT'
|
||||||
|
elif guarantee_disposition == 'DECLINED':
|
||||||
|
checkpoint_action = 'REJECT'
|
||||||
|
else:
|
||||||
|
checkpoint_action = 'HOLD'
|
||||||
|
|
||||||
vals = {
|
vals = {
|
||||||
'case_id': case_id,
|
'case_id': case_id,
|
||||||
@@ -128,13 +141,15 @@ class SignifydCase(models.Model):
|
|||||||
'guarantee_disposition': guarantee_disposition,
|
'guarantee_disposition': guarantee_disposition,
|
||||||
'score': score,
|
'score': score,
|
||||||
'last_update': dt.now(), # why not just use
|
'last_update': dt.now(), # why not just use
|
||||||
|
'checkpoint_action': checkpoint_action,
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome = vals.get('guarantee_disposition')
|
outcome = vals.get('guarantee_disposition')
|
||||||
if outcome == 'DECLINED':
|
checkpoint_action = vals.get('checkpoint_action')
|
||||||
|
if outcome == 'DECLINED' or checkpoint_action == 'REJECT':
|
||||||
connector = self._get_connector()
|
connector = self._get_connector()
|
||||||
for user in connector.notify_user_ids:
|
for user in connector.notify_user_ids:
|
||||||
self.create_notification(user, outcome)
|
self.create_notification(user, outcome or checkpoint_action)
|
||||||
|
|
||||||
self.write(vals)
|
self.write(vals)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ class SignifydConnector(models.Model):
|
|||||||
webhooks_registered = fields.Boolean(string='Successfully Registered Webhooks')
|
webhooks_registered = fields.Boolean(string='Successfully Registered Webhooks')
|
||||||
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')
|
||||||
|
signifyd_case_type = fields.Selection([
|
||||||
|
('', 'No Case'),
|
||||||
|
('SCORE', 'Score'),
|
||||||
|
('DECISION', 'Decision'),
|
||||||
|
('GUARANTEE', 'Guarantee'),
|
||||||
|
], string='Default Case Creation', help='Used for internal/admin orders, overridden by payment acquirer.',
|
||||||
|
required=True, default='')
|
||||||
|
|
||||||
# TODO ideally this would be a regular constant
|
# TODO ideally this would be a regular constant
|
||||||
# however other entities currently use this by reference
|
# however other entities currently use this by reference
|
||||||
@@ -73,6 +80,10 @@ class SignifydConnector(models.Model):
|
|||||||
"event": "GUARANTEE_COMPLETION",
|
"event": "GUARANTEE_COMPLETION",
|
||||||
"url": base_url + "/signifyd/cases/update"
|
"url": base_url + "/signifyd/cases/update"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"event": "DECISION_MADE",
|
||||||
|
"url": base_url + "/signifyd/cases/update"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
data = json.dumps(values, indent=4)
|
data = json.dumps(values, indent=4)
|
||||||
@@ -127,6 +138,14 @@ class SignifydConnector(models.Model):
|
|||||||
score = post.get('score')
|
score = post.get('score')
|
||||||
disposition_reason = post.get('dispositionReason')
|
disposition_reason = post.get('dispositionReason')
|
||||||
disposition = post.get('disposition')
|
disposition = post.get('disposition')
|
||||||
|
checkpoint_action = post.get('checkpointAction', '')
|
||||||
|
if not checkpoint_action and guarantee_disposition:
|
||||||
|
if guarantee_disposition == 'APPROVED':
|
||||||
|
checkpoint_action = 'ACCEPT'
|
||||||
|
elif guarantee_disposition == 'DECLINED':
|
||||||
|
checkpoint_action = 'REJECT'
|
||||||
|
else:
|
||||||
|
checkpoint_action = 'HOLD'
|
||||||
last_update = str(dt.now())
|
last_update = str(dt.now())
|
||||||
|
|
||||||
values = {}
|
values = {}
|
||||||
@@ -144,5 +163,6 @@ class SignifydConnector(models.Model):
|
|||||||
values.update({'disposition_reason': disposition_reason}) if disposition_reason else ''
|
values.update({'disposition_reason': disposition_reason}) if disposition_reason else ''
|
||||||
values.update({'disposition': disposition}) if disposition else ''
|
values.update({'disposition': disposition}) if disposition else ''
|
||||||
values.update({'last_update': last_update})
|
values.update({'last_update': last_update})
|
||||||
|
values.update({'checkpoint_action': checkpoint_action})
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class StockPicking(models.Model):
|
|||||||
_inherit = 'stock.picking'
|
_inherit = 'stock.picking'
|
||||||
|
|
||||||
singifyd_case_id = fields.Many2one(related='sale_id.signifyd_case_id')
|
singifyd_case_id = fields.Many2one(related='sale_id.signifyd_case_id')
|
||||||
signifyd_hold = fields.Selection(related='sale_id.signifyd_disposition_status')
|
signifyd_hold = fields.Selection(related='sale_id.signifyd_checkpoint_action')
|
||||||
|
|
||||||
def action_view_signifyd_case(self):
|
def action_view_signifyd_case(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
15
website_sale_signifyd/views/payment_views.xml
Normal file
15
website_sale_signifyd/views/payment_views.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="acquirer_form_website_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">acquirer.form.inherit.website.inherit</field>
|
||||||
|
<field name="model">payment.acquirer</field>
|
||||||
|
<field name="inherit_id" ref="website_payment.acquirer_form_website"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='website_id']" position="after">
|
||||||
|
<field name="signifyd_case_type"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='payment_term_id']" position="after">
|
<xpath expr="//field[@name='payment_term_id']" position="after">
|
||||||
<field name="signifyd_case_id" attrs="{'invisible': [('signifyd_case_id', '=', False)]}"/>
|
<field name="signifyd_case_id" attrs="{'invisible': [('signifyd_case_id', '=', False)]}"/>
|
||||||
<field name="signifyd_disposition_status" string="Signifyd Status"
|
<field name="signifyd_checkpoint_action" string="Signifyd Status"
|
||||||
attrs="{'invisible': [('signifyd_case_id', '=', False)]}"/>
|
attrs="{'invisible': [('signifyd_case_id', '=', False)]}"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<field name="inherit_id" ref="sale.view_order_tree"/>
|
<field name="inherit_id" ref="sale.view_order_tree"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='invoice_status']" position="after">
|
<xpath expr="//field[@name='invoice_status']" position="after">
|
||||||
<field name="signifyd_disposition_status"/>
|
<field name="signifyd_checkpoint_action"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<field name="res_model">sale.order</field>
|
<field name="res_model">sale.order</field>
|
||||||
<field name="default" eval="True"/>
|
<field name="default" eval="True"/>
|
||||||
<field name="description">Signifyd Status Updated</field>
|
<field name="description">Signifyd Status Updated</field>
|
||||||
<field name="relation_field">signifyd_case_id.guarantee_disposition</field>
|
<field name="relation_field">signifyd_case_id.checkpoint_action</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
|
<field name="checkpoint_action"/>
|
||||||
<field name="last_update"/>
|
<field name="last_update"/>
|
||||||
<field name="uuid"/>
|
<field name="uuid"/>
|
||||||
<field name="case_id"/>
|
<field name="case_id"/>
|
||||||
@@ -105,6 +106,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<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" />
|
||||||
<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>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
<field name="signifyd_hold" invisible="1"/>
|
<field name="signifyd_hold" invisible="1"/>
|
||||||
<field name="singifyd_case_id" invisible="1"/>
|
<field name="singifyd_case_id" invisible="1"/>
|
||||||
<button name="action_view_signifyd_case" string="On Hold" type="object" class="oe_stat_button text-danger"
|
<button name="action_view_signifyd_case" string="On Hold" type="object" class="oe_stat_button text-danger"
|
||||||
icon="fa-hand-stop-o" attrs="{'invisible': [ '|', ('signifyd_hold', '=', 'APPROVED'), ('singifyd_case_id', '=', False)]}"/>
|
icon="fa-hand-stop-o" attrs="{'invisible': [ '|', ('signifyd_hold', '=', 'ACCEPT'), ('singifyd_case_id', '=', False)]}"/>
|
||||||
|
|
||||||
<button name="action_view_signifyd_case" string="Approved" type="object" class="oe_stat_button text-success"
|
<button name="action_view_signifyd_case" string="Approved" type="object" class="oe_stat_button text-success"
|
||||||
icon="fa-thumbs-o-up" attrs="{'invisible': [ '|', ('signifyd_hold', '!=', 'APPROVED'), ('singifyd_case_id', '=', False)]}"/>
|
icon="fa-thumbs-o-up" attrs="{'invisible': [ '|', ('signifyd_hold', '!=', 'ACCEPT'), ('singifyd_case_id', '=', False)]}"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user