Odoo18-Base/addons/payment_worldline/controllers/main.py
2025-01-06 10:57:38 +07:00

108 lines
4.6 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from base64 import b64encode
import hashlib
import hmac
import logging
import pprint
from werkzeug.exceptions import Forbidden
from odoo import http
from odoo.exceptions import ValidationError
from odoo.http import request
_logger = logging.getLogger(__name__)
class WorldlineController(http.Controller):
_return_url = '/payment/worldline/return'
_webhook_url = '/payment/worldline/webhook'
@http.route(_return_url, type='http', auth='public', methods=['GET'])
def worldline_return_from_checkout(self, **data):
""" Process the notification data sent by Worldline after redirection.
:param dict data: The notification data, including the provider id appended to the URL in
`_get_specific_rendering_values`.
"""
_logger.info("Handling redirection from Worldline with data:\n%s", pprint.pformat(data))
provider_id = int(data['provider_id'])
provider_sudo = request.env['payment.provider'].sudo().browse(provider_id).exists()
if not provider_sudo or provider_sudo.code != 'worldline':
_logger.warning("Received payment data with invalid provider id.")
raise Forbidden()
# Fetch the checkout session data from Worldline.
checkout_session_data = provider_sudo._worldline_make_request(
f'hostedcheckouts/{data["hostedCheckoutId"]}', method='GET'
)
_logger.info(
"Response of '/hostedcheckouts/<hostedCheckoutId>' request:\n%s",
pprint.pformat(checkout_session_data)
)
notification_data = checkout_session_data.get('createdPaymentOutput', {})
# Handle the notification data.
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data(
'worldline', notification_data
)
tx_sudo._handle_notification_data('worldline', notification_data)
return request.redirect('/payment/status')
@http.route(_webhook_url, type='http', auth='public', methods=['POST'], csrf=False)
def worldline_webhook(self):
""" Process the notification data sent by Worldline to the webhook.
See https://docs.direct.worldline-solutions.com/en/integration/api-developer-guide/webhooks.
:return: An empty string to acknowledge the notification.
:rtype: str
"""
notification_data = request.get_json_data()
_logger.info(
"Notification received from Worldline with data:\n%s", pprint.pformat(notification_data)
)
try:
# Check the integrity of the notification.
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data(
'worldline', notification_data
)
received_signature = request.httprequest.headers.get('X-GCS-Signature')
request_data = request.httprequest.data
self._verify_notification_signature(request_data, received_signature, tx_sudo)
# Handle the notification data.
tx_sudo._handle_notification_data('worldline', notification_data)
except ValidationError: # Acknowledge the notification to avoid getting spammed.
_logger.exception("Unable to handle the notification data; skipping to acknowledge.")
return request.make_json_response('') # Acknowledge the notification.
@staticmethod
def _verify_notification_signature(request_data, received_signature, tx_sudo):
""" Check that the received signature matches the expected one.
:param dict|bytes request_data: The request data.
:param str received_signature: The signature to compare with the expected signature.
:param payment.transaction tx_sudo: The sudoed transaction referenced by the notification
data.
:return: None
:raise Forbidden: If the signatures don't match.
"""
# Retrieve the received signature from the payload.
if not received_signature:
_logger.warning("Received notification with missing signature.")
raise Forbidden()
# Compare the received signature with the expected signature computed from the payload.
webhook_secret = tx_sudo.provider_id.worldline_webhook_secret
expected_signature = b64encode(
hmac.new(webhook_secret.encode(), request_data, hashlib.sha256).digest()
)
if not hmac.compare_digest(received_signature.encode(), expected_signature):
_logger.warning("Received notification with invalid signature.")
raise Forbidden()