diff --git a/website_sale_signifyd/data/signifyd_coverage.xml b/website_sale_signifyd/data/signifyd_coverage.xml new file mode 100644 index 00000000..eaedcccb --- /dev/null +++ b/website_sale_signifyd/data/signifyd_coverage.xml @@ -0,0 +1,27 @@ + + + Fraud + Use when you need a financial guarantee for Payment Fraud. + FRAUD + + + Item not received + Use when you need a financial guarantee for Item Not Received. + INR + + + Significantly Not As Described + Use when you need a financial guarantee for fraud alleging items are Significantly Not As Described. + SNAD + + + All + Use when you need a financial guarantee on all chargebacks. + ALL + + + None + Use when you do not need a financial guarantee. + NONE + + \ No newline at end of file diff --git a/website_sale_signifyd/models/__init__.py b/website_sale_signifyd/models/__init__.py index fe7d814f..96cb7f80 100644 --- a/website_sale_signifyd/models/__init__.py +++ b/website_sale_signifyd/models/__init__.py @@ -1,9 +1,12 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. +from . import delivery_carrier from . import partner from . import payment +from . import product_template from . import sale_order -from . import signifyd +from . import signifyd_case +from . import signifyd_coverage from . import signifyd_connector from . import stock from . import website diff --git a/website_sale_signifyd/models/delivery_carrier.py b/website_sale_signifyd/models/delivery_carrier.py new file mode 100644 index 00000000..2fc8e760 --- /dev/null +++ b/website_sale_signifyd/models/delivery_carrier.py @@ -0,0 +1,21 @@ +from odoo import fields, models + + +class DeliveryCarrier(models.Model): + _inherit = 'delivery.carrier' + + signifyd_fulfillment_method = fields.Selection( + [ + ("DELIVERY", "Delivery"), + ("COUNTER_PICKUP", "Counter Pickup"), + ("CURBSIDE_PICKUP", "Curbside Pickup"), + ("LOCKER_PICKUP", "Locker Pickup"), + ("STANDARD_SHIPPING", "Standard Shipping"), + ("EXPEDITED_SHIPPING", "Expedited Shipping"), + ("GAS_PICKUP", "Gas Pickup"), + ("SCHEDULED_DELIVERY", "Scheduled Delivery") + ], + string='Signifyd integration Fullfillment Method', + default='STANDARD_SHIPPING' + ) + # TODO add to view \ No newline at end of file diff --git a/website_sale_signifyd/models/payment.py b/website_sale_signifyd/models/payment.py index 4e31f7ef..e9f875bd 100644 --- a/website_sale_signifyd/models/payment.py +++ b/website_sale_signifyd/models/payment.py @@ -6,9 +6,12 @@ from odoo import api, fields, models class PaymentAcquirer(models.Model): _inherit = 'payment.acquirer' + # TODO: remove options no longer available in api v3 signifyd_case_type = fields.Selection([ ('', 'No Case'), ('SCORE', 'Score'), ('DECISION', 'Decision'), ('GUARANTEE', 'Guarantee'), ], string='Signifyd Case Creation', default='') + + signify_coverage_types = fields.Many2many('signifyd.coverage', string='Signifyd Coverage Types') diff --git a/website_sale_signifyd/models/product_template.py b/website_sale_signifyd/models/product_template.py new file mode 100644 index 00000000..a1a0ee1f --- /dev/null +++ b/website_sale_signifyd/models/product_template.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + is_digital = fields.Boolean(help='Used in Signifyd case creation.') + # TODO add to view \ No newline at end of file diff --git a/website_sale_signifyd/models/sale_order.py b/website_sale_signifyd/models/sale_order.py index 890c94e1..bb76a8ac 100644 --- a/website_sale_signifyd/models/sale_order.py +++ b/website_sale_signifyd/models/sale_order.py @@ -37,11 +37,19 @@ class SaleOrder(models.Model): def _should_post_signifyd(self): # 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 + 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 + case_required = self.website_id.signifyd_connector_id.signifyd_case_type not in [ + self.env.ref('website_sale_signifyd.signifyd_coverage_none').id, + False + ] + coverage_types = self.transaction_ids.signifyd_coverage_ids + def post_signifyd_case(self): if not self.website_id.signifyd_connector_id: @@ -93,6 +101,47 @@ class SaleOrder(models.Model): 'error': 'ERROR', } recipients = self.partner_invoice_id + self.partner_shipping_id + + new_case_vals = { + # FIXME: UUID? + 'orderId': self.id, + 'purchase': { + 'createdAt': self.date_order.isoformat(timespec='seconds'), + 'orderChannel': 'WEB', + 'totalPrice': self.amount_total, + 'totalShippingCost': self.amount_delivery, + # TODO: check - previously used partner_id.currency_id, but then wouldn't the amount_total be in the wrong currency? + 'currency': self.currency_id.name, + 'confirmationEmail': self.partner_id.email, + 'confirmationPhone': self.partner_id.phone, + 'products': [ + 'itemName': line.product_id.name, + 'itemPrice': line.price_unit, + 'itemQuantity': line.product_uom_qty, + 'itemIsDigital': line.product_id.is_digital, + 'itemCategory': line.product_id.categ_id.name, + # 'itemSubCategory'? + 'itemId': line.product_id.id, + 'itemUrl': line.product_id.website_url, + 'itemWeight': line.product_id.weight, + for line in self.order_line if line.product_id + ], + 'shipments': [ + { + 'carrier': carrier.name, + 'fulfillmentMethod': carrier.signifyd_fulfillment_method, + } for carrier in self.carrier_id + ], + 'coverageRequests' + + } + + for line in new_case_vals['purchase']['products']: + optional_keys = ['itemUrl', 'itemWeight'] + for key in optional_keys: + if not line[key]: + line.pop(key) + new_case_vals = { 'decisionRequest': { 'paymentFraud': decision_request, diff --git a/website_sale_signifyd/models/signifyd.py b/website_sale_signifyd/models/signifyd_case.py similarity index 96% rename from website_sale_signifyd/models/signifyd.py rename to website_sale_signifyd/models/signifyd_case.py index 0bce4b96..d5b5133f 100644 --- a/website_sale_signifyd/models/signifyd.py +++ b/website_sale_signifyd/models/signifyd_case.py @@ -11,6 +11,8 @@ class SignifydCase(models.Model): _name = 'signifyd.case' _description = 'Stores Signifyd case information on orders.' + # flow_type = fields.Selection([('pre', 'PreAuth'), ('post', 'PostAuth')], default='post', required=True) + order_id = fields.Many2one('sale.order', required=True) partner_id = fields.Many2one('res.partner') case_id = fields.Char(string='Case ID') @@ -53,6 +55,9 @@ class SignifydCase(models.Model): ('REJECT', 'Reject'), ], string='Checkpoint Action') + coverage_ids = fields.Many2many('signifyd.coverage', string='Requested Coverage Types') + # TODO add to view + def _get_connector(self): return self.order_id.website_id.signifyd_connector_id diff --git a/website_sale_signifyd/models/signifyd_connector.py b/website_sale_signifyd/models/signifyd_connector.py index 6065d8dc..c642fcc5 100644 --- a/website_sale_signifyd/models/signifyd_connector.py +++ b/website_sale_signifyd/models/signifyd_connector.py @@ -19,6 +19,7 @@ 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') + # TODO: remove options no longer available in api v3 signifyd_case_type = fields.Selection([ ('', 'No Case'), ('SCORE', 'Score'), @@ -26,10 +27,11 @@ class SignifydConnector(models.Model): ('GUARANTEE', 'Guarantee'), ], string='Default Case Creation', help='Used for internal/admin orders, overridden by payment acquirer.', required=True, default='') + signifyd_coverage_ids = fields.Many2many('signifyd.coverage', string='Available Coverage Types') # TODO ideally this would be a regular constant # however other entities currently use this by reference - API_URL = 'https://api.signifyd.com/v2' + API_URL = 'https://api.signifyd.com/v3' def get_headers(self): self.ensure_one() diff --git a/website_sale_signifyd/models/signifyd_coverage.py b/website_sale_signifyd/models/signifyd_coverage.py new file mode 100644 index 00000000..05c0e3c8 --- /dev/null +++ b/website_sale_signifyd/models/signifyd_coverage.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class SignifydCoverage(models.Model): + _name = 'signifyd.coverage' + _description = 'Signifyd Coverage type' + + name = fields.Char(required=True) + description = fields.Char() + code = fields.Char(required=True) diff --git a/website_sale_signifyd/security/ir.model.access.csv b/website_sale_signifyd/security/ir.model.access.csv index dbebf821..b8723341 100644 --- a/website_sale_signifyd/security/ir.model.access.csv +++ b/website_sale_signifyd/security/ir.model.access.csv @@ -6,4 +6,5 @@ portal_signifyd_connector,portal_signifyd_connector,model_signifyd_connector,bas manage_signifyd_case,manage_signifyd_case,model_signifyd_case,base.group_erp_manager,1,1,1,1 access_signifyd_case,access_signifyd_case,model_signifyd_case,base.group_user,1,0,0,0 public_signifyd_case,public_signifyd_case,model_signifyd_case,base.group_public,1,0,0,0 -portal_signifyd_case,portal_signifyd_case,model_signifyd_case,base.group_portal,1,0,0,0 \ No newline at end of file +portal_signifyd_case,portal_signifyd_case,model_signifyd_case,base.group_portal,1,0,0,0 +access_signifyd_coverage,access_signifyd_coverage,model_signifyd_coverage,base.group_user,1,0,0,0 diff --git a/website_sale_signifyd/views/payment_views.xml b/website_sale_signifyd/views/payment_views.xml index 5be6afd5..4ee468ed 100644 --- a/website_sale_signifyd/views/payment_views.xml +++ b/website_sale_signifyd/views/payment_views.xml @@ -7,7 +7,8 @@ - + + diff --git a/website_sale_signifyd/views/signifyd_views.xml b/website_sale_signifyd/views/signifyd_views.xml index 34d05dc0..9bc5585d 100644 --- a/website_sale_signifyd/views/signifyd_views.xml +++ b/website_sale_signifyd/views/signifyd_views.xml @@ -106,7 +106,8 @@ - + +

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