diff --git a/website_sale_signifyd/data/signifyd_coverage.xml b/website_sale_signifyd/data/signifyd_coverage.xml index eaedcccb..652ddf37 100644 --- a/website_sale_signifyd/data/signifyd_coverage.xml +++ b/website_sale_signifyd/data/signifyd_coverage.xml @@ -18,10 +18,12 @@ All Use when you need a financial guarantee on all chargebacks. ALL + True None Use when you do not need a financial guarantee. NONE + True \ No newline at end of file diff --git a/website_sale_signifyd/models/__init__.py b/website_sale_signifyd/models/__init__.py index 96cb7f80..d59fa371 100644 --- a/website_sale_signifyd/models/__init__.py +++ b/website_sale_signifyd/models/__init__.py @@ -1,12 +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 res_partner +from . import payment_acquirer from . import product_template from . import sale_order from . import signifyd_case from . import signifyd_coverage from . import signifyd_connector -from . import stock +from . import stock_picking from . import website diff --git a/website_sale_signifyd/models/payment.py b/website_sale_signifyd/models/payment.py deleted file mode 100644 index e9f875bd..00000000 --- a/website_sale_signifyd/models/payment.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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' - - # 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/payment_acquirer.py b/website_sale_signifyd/models/payment_acquirer.py new file mode 100644 index 00000000..3888aee7 --- /dev/null +++ b/website_sale_signifyd/models/payment_acquirer.py @@ -0,0 +1,27 @@ +# 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' + + # 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='') + signifyd_case_required = fields.Boolean(string='Create Signifyd Case', default=True) + signifyd_coverage_ids = fields.Many2many('signifyd.coverage', string='Available Coverage Types', + help='Note that exclusive coverage types will only allow one to be selected.') + + @api.onchange('signifyd_coverage_ids') + def _onchange_signifyd_coverage_ids(self): + self.signifyd_coverage_ids = self.signifyd_coverage_ids._apply_exclusivity() + + @api.onchange('signifyd_case_required') + def _onchange_signifyd_case_required(self): + if not self.signifyd_case_required: + self.signifyd_coverage_ids = False \ No newline at end of file diff --git a/website_sale_signifyd/models/partner.py b/website_sale_signifyd/models/res_partner.py similarity index 100% rename from website_sale_signifyd/models/partner.py rename to website_sale_signifyd/models/res_partner.py diff --git a/website_sale_signifyd/models/sale_order.py b/website_sale_signifyd/models/sale_order.py index bb76a8ac..ce9da46a 100644 --- a/website_sale_signifyd/models/sale_order.py +++ b/website_sale_signifyd/models/sale_order.py @@ -37,19 +37,10 @@ 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) - 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 - + acquirers = self.transaction_ids.acquirer_id + if acquirers and not any(acquirers.mapped('signifyd_case_required')): + return False + return True def post_signifyd_case(self): if not self.website_id.signifyd_connector_id: @@ -78,8 +69,26 @@ class SaleOrder(models.Model): # TODO do we need to raise an exception? return None + def _get_coverage_types(self): + coverage_none = self.env.ref('website_sale_signifyd.signifyd_coverage_none') + coverage_all = self.env.ref('website_sale_signifyd.signifyd_coverage_all') + acquirer_coverage_types = self.transaction_ids.acquirer_id.signifyd_coverage_ids + # 'ALL' if specified by any acquirer + if coverage_all in acquirer_coverage_types: + return coverage_all + # 'NONE' if specified by all acquirers + if all(self.transaction_ids.acquirer_id.mapped(lambda a: a.signifyd_coverage_ids) == coverage_none): + return coverage_none + # Specific acquirer-level coverage types + if acquirer_coverage_types - coverage_none: + return acquirer_coverage_types - coverage_none + # Default: connector-level + return self.website_id.signifyd_connector_id.signifyd_coverage_ids or coverage_none + @api.model def _prepare_signifyd_case_values(self, order_session_id, checkout_token, browser_ip_address): + coverage_codes = self._get_coverage_types().mapped('code') + decision_request = self.website_id.signifyd_connector_id.signifyd_case_type or 'DECISION' # find the highest 'acquirer override' @@ -102,6 +111,7 @@ class SaleOrder(models.Model): } recipients = self.partner_invoice_id + self.partner_shipping_id + # API v3 WIP new_case_vals = { # FIXME: UUID? 'orderId': self.id, @@ -132,7 +142,7 @@ class SaleOrder(models.Model): 'fulfillmentMethod': carrier.signifyd_fulfillment_method, } for carrier in self.carrier_id ], - 'coverageRequests' + 'coverageRequests': coverage_codes, } @@ -142,82 +152,83 @@ class SaleOrder(models.Model): if not line[key]: line.pop(key) - new_case_vals = { - 'decisionRequest': { - 'paymentFraud': decision_request, - }, - 'purchase': { - "orderSessionId": order_session_id, - "orderId": self.id, - "checkoutToken": checkout_token, - "browserIpAddress": browser_ip_address, - "currency": self.partner_id.currency_id.name, - "orderChannel": "WEB", - "totalPrice": self.amount_total, - 'products': [ - { - "itemId": line.product_id.id, - "itemName": line.product_id.name, - "itemIsDigital": False, - "itemCategory": line.product_id.categ_id.name, - "itemUrl": line.product_id.website_url or '', - "itemQuantity": line.product_uom_qty, - "itemPrice": line.price_unit, - "itemWeight": line.product_id.weight or 0.1, - } - for line in self.order_line if line.product_id - ], - 'shipments': [{ - "shipper": carrier.name, - "shippingMethod": "ground", - "shippingPrice": self.amount_delivery, - } - for carrier in self.carrier_id - ], - }, - 'recipients': [ - { - "fullName": partner.name, - "confirmationEmail": partner.email, - "confirmationPhone": partner.phone, - "organization": partner.company_id.name, - "deliveryAddress": { - "streetAddress": partner.street, - "unit": partner.street2, - "city": partner.city, - "provinceCode": partner.state_id.code, - "postalCode": partner.zip, - "countryCode": partner.country_id.code, - } - } - for partner in recipients - ], - 'transactions': [ - { - "parentTransactionId": None, - "transactionId": tx.id, - "gateway": tx.acquirer_id.name, - "paymentMethod": "CREDIT_CARD", - "gatewayStatusCode": tx_status_type.get(tx.state, 'PENDING'), - "type": "AUTHORIZATION", - "currency": self.partner_id.currency_id.name, - "amount": tx.amount, - # "avsResponseCode": "Y", - # "cvvResponseCode": "N", - "checkoutPaymentDetails": { - "holderName": tx.partner_id.name, - "billingAddress": { - "streetAddress": tx.partner_id.street, - "unit": tx.partner_id.street2, - "city": tx.partner_id.city, - "provinceCode": tx.partner_id.state_id.code, - "postalCode": tx.partner_id.zip, - "countryCode": tx.partner_id.country_id.code, - } - } - } - for tx in self.transaction_ids - ], - } + # API v2 + # new_case_vals = { + # 'decisionRequest': { + # 'paymentFraud': decision_request, + # }, + # 'purchase': { + # "orderSessionId": order_session_id, + # "orderId": self.id, + # "checkoutToken": checkout_token, + # "browserIpAddress": browser_ip_address, + # "currency": self.partner_id.currency_id.name, + # "orderChannel": "WEB", + # "totalPrice": self.amount_total, + # 'products': [ + # { + # "itemId": line.product_id.id, + # "itemName": line.product_id.name, + # "itemIsDigital": False, + # "itemCategory": line.product_id.categ_id.name, + # "itemUrl": line.product_id.website_url or '', + # "itemQuantity": line.product_uom_qty, + # "itemPrice": line.price_unit, + # "itemWeight": line.product_id.weight or 0.1, + # } + # for line in self.order_line if line.product_id + # ], + # 'shipments': [{ + # "shipper": carrier.name, + # "shippingMethod": "ground", + # "shippingPrice": self.amount_delivery, + # } + # for carrier in self.carrier_id + # ], + # }, + # 'recipients': [ + # { + # "fullName": partner.name, + # "confirmationEmail": partner.email, + # "confirmationPhone": partner.phone, + # "organization": partner.company_id.name, + # "deliveryAddress": { + # "streetAddress": partner.street, + # "unit": partner.street2, + # "city": partner.city, + # "provinceCode": partner.state_id.code, + # "postalCode": partner.zip, + # "countryCode": partner.country_id.code, + # } + # } + # for partner in recipients + # ], + # 'transactions': [ + # { + # "parentTransactionId": None, + # "transactionId": tx.id, + # "gateway": tx.acquirer_id.name, + # "paymentMethod": "CREDIT_CARD", + # "gatewayStatusCode": tx_status_type.get(tx.state, 'PENDING'), + # "type": "AUTHORIZATION", + # "currency": self.partner_id.currency_id.name, + # "amount": tx.amount, + # # "avsResponseCode": "Y", + # # "cvvResponseCode": "N", + # "checkoutPaymentDetails": { + # "holderName": tx.partner_id.name, + # "billingAddress": { + # "streetAddress": tx.partner_id.street, + # "unit": tx.partner_id.street2, + # "city": tx.partner_id.city, + # "provinceCode": tx.partner_id.state_id.code, + # "postalCode": tx.partner_id.zip, + # "countryCode": tx.partner_id.country_id.code, + # } + # } + # } + # for tx in self.transaction_ids + # ], + # } return new_case_vals diff --git a/website_sale_signifyd/models/signifyd_connector.py b/website_sale_signifyd/models/signifyd_connector.py index c642fcc5..392a4e91 100644 --- a/website_sale_signifyd/models/signifyd_connector.py +++ b/website_sale_signifyd/models/signifyd_connector.py @@ -27,7 +27,12 @@ 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') + signifyd_coverage_ids = fields.Many2many('signifyd.coverage', string='Available Coverage Types', + help='Note that exclusive coverage types will only allow one to be selected.') + + @api.onchange('signifyd_coverage_ids') + def _onchange_signifyd_coverage_ids(self): + 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 diff --git a/website_sale_signifyd/models/signifyd_coverage.py b/website_sale_signifyd/models/signifyd_coverage.py index 05c0e3c8..82a4739f 100644 --- a/website_sale_signifyd/models/signifyd_coverage.py +++ b/website_sale_signifyd/models/signifyd_coverage.py @@ -8,3 +8,7 @@ class SignifydCoverage(models.Model): name = fields.Char(required=True) description = fields.Char() code = fields.Char(required=True) + exclusive = fields.Boolean()) + + def _apply_exclusivity(self): + return self.filtered('exclusive')[:1] or self diff --git a/website_sale_signifyd/models/stock.py b/website_sale_signifyd/models/stock_picking.py similarity index 100% rename from website_sale_signifyd/models/stock.py rename to website_sale_signifyd/models/stock_picking.py