diff --git a/website_sale_signifyd/__manifest__.py b/website_sale_signifyd/__manifest__.py index 710defae..3532faca 100644 --- a/website_sale_signifyd/__manifest__.py +++ b/website_sale_signifyd/__manifest__.py @@ -1,7 +1,7 @@ { 'name': 'Signifyd Connector', 'author': 'Hibou Corp. ', - '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', diff --git a/website_sale_signifyd/migrations/15.0.1.1.0/post-migration.py b/website_sale_signifyd/migrations/15.0.1.1.0/post-migration.py new file mode 100644 index 00000000..c9404a5a --- /dev/null +++ b/website_sale_signifyd/migrations/15.0.1.1.0/post-migration.py @@ -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' + ''') diff --git a/website_sale_signifyd/models/__init__.py b/website_sale_signifyd/models/__init__.py index 378ba4e5..fe7d814f 100644 --- a/website_sale_signifyd/models/__init__.py +++ b/website_sale_signifyd/models/__init__.py @@ -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 diff --git a/website_sale_signifyd/models/payment.py b/website_sale_signifyd/models/payment.py new file mode 100644 index 00000000..4e31f7ef --- /dev/null +++ b/website_sale_signifyd/models/payment.py @@ -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='') diff --git a/website_sale_signifyd/models/sale_order.py b/website_sale_signifyd/models/sale_order.py index 27d8defd..890c94e1 100644 --- a/website_sale_signifyd/models/sale_order.py +++ b/website_sale_signifyd/models/sale_order.py @@ -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, diff --git a/website_sale_signifyd/models/signifyd.py b/website_sale_signifyd/models/signifyd.py index c2d1a6c5..0bce4b96 100644 --- a/website_sale_signifyd/models/signifyd.py +++ b/website_sale_signifyd/models/signifyd.py @@ -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) diff --git a/website_sale_signifyd/models/signifyd_connector.py b/website_sale_signifyd/models/signifyd_connector.py index beec610f..6065d8dc 100644 --- a/website_sale_signifyd/models/signifyd_connector.py +++ b/website_sale_signifyd/models/signifyd_connector.py @@ -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 diff --git a/website_sale_signifyd/models/stock.py b/website_sale_signifyd/models/stock.py index 5429cf17..41479df4 100644 --- a/website_sale_signifyd/models/stock.py +++ b/website_sale_signifyd/models/stock.py @@ -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() diff --git a/website_sale_signifyd/views/payment_views.xml b/website_sale_signifyd/views/payment_views.xml new file mode 100644 index 00000000..5be6afd5 --- /dev/null +++ b/website_sale_signifyd/views/payment_views.xml @@ -0,0 +1,15 @@ + + + + + acquirer.form.inherit.website.inherit + payment.acquirer + + + + + + + + + \ No newline at end of file diff --git a/website_sale_signifyd/views/sale_views.xml b/website_sale_signifyd/views/sale_views.xml index 555a8c64..368195c0 100644 --- a/website_sale_signifyd/views/sale_views.xml +++ b/website_sale_signifyd/views/sale_views.xml @@ -28,7 +28,7 @@ - @@ -40,7 +40,7 @@ - + @@ -50,7 +50,7 @@ sale.order Signifyd Status Updated - signifyd_case_id.guarantee_disposition + signifyd_case_id.checkpoint_action \ No newline at end of file diff --git a/website_sale_signifyd/views/signifyd_views.xml b/website_sale_signifyd/views/signifyd_views.xml index e08b97df..34d05dc0 100644 --- a/website_sale_signifyd/views/signifyd_views.xml +++ b/website_sale_signifyd/views/signifyd_views.xml @@ -37,6 +37,7 @@ + @@ -105,6 +106,7 @@ +

Optional: Add users to be notified if a sale order is declined by Signifyd.

diff --git a/website_sale_signifyd/views/stock_views.xml b/website_sale_signifyd/views/stock_views.xml index 822d2443..4134fe2d 100644 --- a/website_sale_signifyd/views/stock_views.xml +++ b/website_sale_signifyd/views/stock_views.xml @@ -10,10 +10,10 @@