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',
|
||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||
'version': '15.0.1.0.0',
|
||||
'version': '15.0.1.1.0',
|
||||
'category': 'Sale',
|
||||
'description': """
|
||||
Automate Order Fraud Detection with the Signifyd API.
|
||||
@@ -12,10 +12,12 @@ Automate Order Fraud Detection with the Signifyd API.
|
||||
'hibou_professional',
|
||||
'stock',
|
||||
'website_sale',
|
||||
'website_payment',
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/partner_views.xml',
|
||||
'views/payment_views.xml',
|
||||
'views/sale_views.xml',
|
||||
'views/signifyd_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.
|
||||
|
||||
from . import partner
|
||||
from . import payment
|
||||
from . import sale_order
|
||||
from . import signifyd
|
||||
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)
|
||||
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):
|
||||
self.ensure_one()
|
||||
@@ -35,7 +35,13 @@ class SaleOrder(models.Model):
|
||||
return res
|
||||
|
||||
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):
|
||||
if not self.website_id.signifyd_connector_id:
|
||||
@@ -66,6 +72,18 @@ class SaleOrder(models.Model):
|
||||
|
||||
@api.model
|
||||
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 = {
|
||||
'draft': 'FAILURE',
|
||||
'pending': 'PENDING',
|
||||
@@ -77,7 +95,7 @@ class SaleOrder(models.Model):
|
||||
recipients = self.partner_invoice_id + self.partner_shipping_id
|
||||
new_case_vals = {
|
||||
'decisionRequest': {
|
||||
'paymentFraud': 'GUARANTEE',
|
||||
'paymentFraud': decision_request,
|
||||
},
|
||||
'purchase': {
|
||||
"orderSessionId": order_session_id,
|
||||
|
||||
@@ -47,6 +47,11 @@ class SignifydCase(models.Model):
|
||||
score = fields.Float(string='Transaction Score')
|
||||
adjusted_score = fields.Float(string='Adjusted Score')
|
||||
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):
|
||||
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)
|
||||
adjusted_score = case.get('adjustedScore', self.adjusted_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 = {
|
||||
'case_id': case_id,
|
||||
@@ -128,13 +141,15 @@ class SignifydCase(models.Model):
|
||||
'guarantee_disposition': guarantee_disposition,
|
||||
'score': score,
|
||||
'last_update': dt.now(), # why not just use
|
||||
'checkpoint_action': checkpoint_action,
|
||||
}
|
||||
|
||||
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()
|
||||
for user in connector.notify_user_ids:
|
||||
self.create_notification(user, outcome)
|
||||
self.create_notification(user, outcome or checkpoint_action)
|
||||
|
||||
self.write(vals)
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ class SignifydConnector(models.Model):
|
||||
webhooks_registered = fields.Boolean(string='Successfully Registered Webhooks')
|
||||
notify_user_ids = fields.Many2many('res.users', string='Receive decline notifications')
|
||||
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
|
||||
# however other entities currently use this by reference
|
||||
@@ -73,6 +80,10 @@ class SignifydConnector(models.Model):
|
||||
"event": "GUARANTEE_COMPLETION",
|
||||
"url": base_url + "/signifyd/cases/update"
|
||||
},
|
||||
{
|
||||
"event": "DECISION_MADE",
|
||||
"url": base_url + "/signifyd/cases/update"
|
||||
},
|
||||
]
|
||||
}
|
||||
data = json.dumps(values, indent=4)
|
||||
@@ -127,6 +138,14 @@ class SignifydConnector(models.Model):
|
||||
score = post.get('score')
|
||||
disposition_reason = post.get('dispositionReason')
|
||||
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())
|
||||
|
||||
values = {}
|
||||
@@ -144,5 +163,6 @@ class SignifydConnector(models.Model):
|
||||
values.update({'disposition_reason': disposition_reason}) if disposition_reason else ''
|
||||
values.update({'disposition': disposition}) if disposition else ''
|
||||
values.update({'last_update': last_update})
|
||||
values.update({'checkpoint_action': checkpoint_action})
|
||||
|
||||
return values
|
||||
|
||||
@@ -7,7 +7,7 @@ class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
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):
|
||||
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 expr="//field[@name='payment_term_id']" position="after">
|
||||
<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)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -40,7 +40,7 @@
|
||||
<field name="inherit_id" ref="sale.view_order_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_status']" position="after">
|
||||
<field name="signifyd_disposition_status"/>
|
||||
<field name="signifyd_checkpoint_action"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -50,7 +50,7 @@
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="default" eval="True"/>
|
||||
<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>
|
||||
|
||||
</odoo>
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="checkpoint_action"/>
|
||||
<field name="last_update"/>
|
||||
<field name="uuid"/>
|
||||
<field name="case_id"/>
|
||||
@@ -105,6 +106,7 @@
|
||||
<group>
|
||||
<field name="secret_key" attrs="{'invisible': [('test_mode', '=', True)]}"/>
|
||||
<field name="secret_key_test" attrs="{'invisible': [('test_mode', '!=', True)]}"/>
|
||||
<field name="signifyd_case_type" />
|
||||
<p class="text-muted">
|
||||
Optional: Add users to be notified if a sale order is declined by Signifyd.
|
||||
</p>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<field name="signifyd_hold" 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"
|
||||
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"
|
||||
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>
|
||||
</field>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user