- Improved onboarding / KYC flow
- Display KYC stage for each "document" (identity, bank account,
shareholder, etc.)
- Only update to the API what's been updated by the user to prevent
undergoing another full round of data verification
- Show new pricing
- Support refund of adyen.transaction
- Simplify payouts (let Adyen automatically handle them)
- They are now automatically handled by Adyen and not manually
triggered by a cron
- "Balance" dashboard
- Show the balance per currency
- List of payouts and their status
- Handle account/transaction notifications
- Show split of fees
- Details on payment method (card country, card type, etc.) used
- Display details from the linked payment.transaction
- Verify proxy signature for notifications
- Notifications are now signed and authenticity verified
TaskID: 2129218
Closes odoo/odoo#68952
Co-authored-by: Antoine Prieels <anp@odoo.com>
94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
import logging
|
|
import pprint
|
|
|
|
from odoo import http
|
|
from odoo.http import request
|
|
|
|
from odoo.addons.payment import utils as payment_utils
|
|
from odoo.addons.payment_odoo.const import CURRENCY_DECIMALS
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class OdooController(http.Controller):
|
|
_notification_url = '/payment/odoo/notification'
|
|
|
|
@http.route(_notification_url, type='json', auth='public')
|
|
def odoo_notification(self):
|
|
""" Process the data sent by Adyen to the webhook based on the event code.
|
|
|
|
See https://docs.adyen.com/development-resources/webhooks/understand-notifications for the
|
|
exhaustive list of event codes.
|
|
|
|
:return: None
|
|
"""
|
|
# Payload data represent a single notification's data. Because two notifications of a same
|
|
# batch can be related to different sub-merchants, the proxy splits the batches and send
|
|
# individual notifications one by one to this endpoint.
|
|
notification_data = json.loads(request.httprequest.data)
|
|
|
|
# Check the source and integrity of the notification
|
|
received_signature = notification_data.get('additionalData', {}).get(
|
|
'metadata.merchant_signature'
|
|
)
|
|
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
|
|
'odoo', notification_data
|
|
)
|
|
|
|
# Ignore signature for REFUND, the signature will be the one of the original transaction
|
|
event_code = notification_data['eventCode']
|
|
if event_code != 'REFUND' and not self._verify_notification_signature(
|
|
received_signature, tx_sudo
|
|
):
|
|
return
|
|
|
|
_logger.info("notification received:\n%s", pprint.pformat(notification_data))
|
|
if notification_data['success'] != 'true' and event_code != 'REFUND':
|
|
return # Don't handle failed events
|
|
|
|
request.env['adyen.transaction'].sudo()._handle_payment_notification(
|
|
notification_data, tx_sudo
|
|
)
|
|
|
|
# Reshape the notification data for parsing
|
|
if event_code == 'AUTHORISATION':
|
|
notification_data['resultCode'] = 'Authorised'
|
|
elif event_code == 'CANCELLATION':
|
|
notification_data['resultCode'] = 'Cancelled'
|
|
elif event_code in ['NOTIFICATION_OF_CHARGEBACK', 'CHARGEBACK']:
|
|
notification_data['resultCode'] = 'Chargeback'
|
|
elif event_code == 'REFUND':
|
|
notification_data['resultCode'] = 'Refund'
|
|
else:
|
|
return # Don't handle unsupported event codes
|
|
|
|
# Handle the notification data as a regular feedback
|
|
request.env['payment.transaction'].sudo()._handle_feedback_data('odoo', notification_data)
|
|
|
|
def _verify_notification_signature(self, received_signature, tx):
|
|
""" Check that the signature computed from the transaction values matches the received one.
|
|
|
|
:param str received_signature: The signature sent with the notification
|
|
:param recordset tx: The transaction of the notification, as a `payment.transaction` record
|
|
:return: Whether the signatures match
|
|
:rtype: str
|
|
"""
|
|
|
|
if not received_signature:
|
|
_logger.warning("ignored notification with missing signature")
|
|
return False
|
|
|
|
converted_amount = payment_utils.to_minor_currency_units(
|
|
tx.amount, tx.currency_id, CURRENCY_DECIMALS.get(tx.currency_id.name)
|
|
)
|
|
if not payment_utils.check_access_token(
|
|
received_signature, converted_amount, tx.currency_id.name, tx.reference
|
|
):
|
|
_logger.warning("ignored notification with invalid signature")
|
|
return False
|
|
|
|
return True
|