[WIP] signifyd v3: working webhooks

This commit is contained in:
Milan
2024-12-16 16:19:05 +00:00
parent 35f29af2ac
commit 17c22ed9db
3 changed files with 36 additions and 85 deletions

View File

@@ -13,16 +13,20 @@ class SignifydWebhooks(Controller):
def _case_update(self):
data = json.loads(request.httprequest.data)
vals = request.env['signifyd.connector'].process_post_values(data)
case_id = vals.get('case_id')
case_id = data.get('signifydId')
if not case_id:
# Testing webhook
return {'response': 'success'}
case = self._get_case(case_id)
if case:
case.update_case_info(vals)
return {'response': 'success'}
if case_id == 1:
# Special case when verifying webhook.
return {'response': 'success'}
raise NotFound('CaseId: %s Cannot be found.' % (case_id,))
if not case:
raise NotFound('CaseId: %s Cannot be found.' % (case_id,))
case.connector_id._check_webhook_signature(request)
case.update_case_info(data)
return {'response': 'success'}
def _get_case(self, case_id):
return request.env['signifyd.case'].sudo().search([('case_id', '=', case_id)], limit=1)

View File

@@ -73,32 +73,14 @@ class SignifydCase(models.Model):
subtype_xmlid='website_sale_signifyd.disposition_change')
return res
# @api.model
# def post_case(self, connector, values):
# headers = connector.get_headers()
# # data = json.dumps(values, indent=4, sort_keys=True, default=str)
# url = connector.API_URL + '/orders/events/sales'
# # TODO this should be in `signifyd.connector`
# r = requests.post(url=url, headers=headers, json=values)
# return r.json()
# def get_case(self):
# self.ensure_one()
# if not self.case_id:
# raise UserError(_('Case not represented in Signifyd.'))
# connector = self._get_connector()
# headers = connector.get_headers()
# r = requests.get(
# connector.API_URL + '/cases/' + str(self.case_id),
# headers=headers
# )
# return r.json()
def get_decision_vals(self):
def get_decision_vals(self, data=None):
self.ensure_one()
api = self.connector_id.get_connection()
response = api.get_decision(self.ref)
response.raise_for_status()
data = response.json()
if not data:
api = self.connector_id.get_connection()
response = api.get_decision(self.ref)
response.raise_for_status()
data = response.json()
decision = data['decision']
case_vals = {
@@ -116,21 +98,10 @@ class SignifydCase(models.Model):
'snadChargebacks': self.env.ref('website_sale_signifyd.signifyd_coverage_snad').id,
'allChargebacks': self.env.ref('website_sale_signifyd.signifyd_coverage_all').id
}
# coverage_ids = []
# if coverage.get('inrChargebacks'):
# coverage_ids += self.env.ref('website_sale_signifyd.signifyd_coverage_inr').ids
# if coverage.get('fraudChargebacks'):
# coverage_ids += self.env.ref('website_sale_signifyd.signifyd_coverage_fraud').ids
# if coverage.get('snadChargebacks'):
# coverage_ids += self.env.ref('website_sale_signifyd.signifyd_coverage_snad').ids
# if coverage.get('allChargebacks'):
# coverage_ids = self.env.ref('website_sale_signifyd.signifyd_coverage_all').ids
# vals['coverage_ids'] = coverage_ids
for name, vals in coverage.items():
if not vals:
continue
case_vals['coverage_line_ids'] += [(0, 0, {
# 'case_id': self.id,
'coverage_type_id': coverage_map[name],
'amount': vals.get('amount'),
'currency_id': self.env['res.currency'].search(
@@ -143,47 +114,12 @@ class SignifydCase(models.Model):
for record in self:
record.update_case_info()
def update_case_info(self, vals=None):
def update_case_info(self, data=None):
self.ensure_one()
if not self.case_id:
raise UserError(_('Case not represented in Signifyd.'))
if not vals:
vals = self.get_decision_vals()
# case_id = case.get('caseId')
# if not case_id:
# raise ValueError(_('Signifyd Case has no ID?'))
# team_id = case.get('teamId', self.team_id)
# team_name = case.get('teamName', self.team_name)
# ref = case.get('ref', self.ref)
# status = case.get('status', self.status)
# review_disposition = case.get('reviewDisposition', self.review_disposition)
# order_outcome = case.get('orderOutcome', self.order_outcome)
# 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,
# 'team_id': team_id,
# 'team_name': team_name,
# 'ref': ref,
# 'status': status,
# 'review_disposition': review_disposition,
# 'order_outcome': order_outcome,
# 'adjusted_score': adjusted_score,
# 'guarantee_disposition': guarantee_disposition,
# 'score': score,
# 'last_update': dt.now(), # why not just use
# 'checkpoint_action': checkpoint_action,
# }
vals = self.get_decision_vals(data)
outcome = vals.get('guarantee_disposition')
checkpoint_action = vals.get('checkpoint_action')

View File

@@ -2,9 +2,12 @@
import requests
from datetime import datetime as dt
from base64 import b64encode
from base64 import b64encode, b64decode
import json
import hmac
import hashlib
from odoo import api, fields, models
from .signifyd_api import SignifydAPI
@@ -14,9 +17,7 @@ class SignifydConnector(models.Model):
_description = 'Interact with Signifyd API'
name = fields.Char(string='Connector Name', required=True)
test_mode = fields.Boolean(string='Test Mode')
secret_key = fields.Char(string='API Key', required=True)
secret_key_test = fields.Char(string='TEST API Key')
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')
@@ -88,6 +89,16 @@ class SignifydConnector(models.Model):
self.webhooks_registered = False
return notification
def _check_webhook_signature(self, request):
signature = b64decode(request.httprequest.headers.get('SIGNIFYD-SEC-HMAC-SHA256'))
if not signature:
raise Exception('Signifyd webhook missing signature')
key = self.secret_key.encode()
digest = hmac.new(key, request.httprequest.data, hashlib.sha256).digest()
if signature != digest:
raise Exception('Signifyd webhook signature does not match')
def process_post_values(self, post):
# Construct dict from request data for endpoints
uuid = post.get('uuid')