Before this commit: If a payulatam payment is received from the confirmation page (so, with the webhook). If the value have some decimals it might be rounded in the wrong way. As such, the generated signature to compare with is wrong and the payment validation cancelled After this commit If we cross compare the signature generation documentation: https://developers.payulatam.com/latam/en/docs/integrations/webcheckout-integration/response-page.html#signature-validation https://developers.payulatam.com/latam/en/docs/integrations/webcheckout-integration/confirmation-page.html#signature-validation We notice that the `new_value` computation is slightly different depending on the return. The one we currently use for both method is the "return" one which is computed differently from the "confirm" one. With this change of code a "confirmation" page payment will be validated as intended. I also changed the log level from warning to exception so that the traceback and exception message is logged. Before this commit there was just a generic warning message opw-3018628 closes odoo/odoo#114552 X-original-commit: ee30c21eb5189b2cfcfd6d249985c947f759eaa2 Signed-off-by: Loan (LSE) <lse@odoo.com> Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com> Signed-off-by: Sens Loan (lse) <lse@odoo.com>
118 lines
5.0 KiB
Python
118 lines
5.0 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
import pprint
|
|
|
|
from werkzeug.exceptions import Forbidden
|
|
|
|
from odoo import http
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.http import request
|
|
from odoo.tools import consteq
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PayuLatamController(http.Controller):
|
|
_return_url = '/payment/payulatam/return'
|
|
_webhook_url = '/payment/payulatam/webhook'
|
|
|
|
@http.route(_return_url, type='http', auth='public', methods=['GET'])
|
|
def payulatam_return_from_checkout(self, **data):
|
|
""" Process the notification data sent by PayU Latam after redirection from checkout.
|
|
|
|
See http://developers.payulatam.com/latam/en/docs/integrations/webcheckout-integration/response-page.html.
|
|
|
|
:param dict data: The notification data
|
|
"""
|
|
_logger.info("handling redirection from PayU Latam with data:\n%s", pprint.pformat(data))
|
|
|
|
# Check the integrity of the notification
|
|
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_notification_data(
|
|
'payulatam', data
|
|
)
|
|
self._verify_notification_signature(data, tx_sudo)
|
|
|
|
# Handle the notification data
|
|
tx_sudo._handle_notification_data('payulatam', data)
|
|
return request.redirect('/payment/status')
|
|
|
|
@http.route(_webhook_url, type='http', auth='public', methods=['POST'], csrf=False)
|
|
def payulatam_webhook(self, **raw_data):
|
|
""" Process the notification data sent by PayU Latam to the webhook.
|
|
|
|
See http://developers.payulatam.com/latam/en/docs/integrations/webcheckout-integration/confirmation-page.html.
|
|
|
|
:param dict raw_data: The un-formatted notification data
|
|
:return: An empty string to acknowledge the notification
|
|
:rtype: str
|
|
"""
|
|
_logger.info(
|
|
"notification received from PayU Latam with data:\n%s", pprint.pformat(raw_data)
|
|
)
|
|
data = self._normalize_data_keys(raw_data)
|
|
|
|
try:
|
|
# Check the origin and integrity of the notification
|
|
tx_sudo = request.env['payment.transaction'].sudo().with_context(
|
|
payulatam_is_confirmation_page=True
|
|
)._get_tx_from_notification_data('payulatam', data)
|
|
self._verify_notification_signature(data, tx_sudo) # Use the normalized data.
|
|
|
|
# Handle the notification data
|
|
tx_sudo._handle_notification_data('payulatam', data)
|
|
except ValidationError: # Acknowledge the notification to avoid getting spammed
|
|
_logger.exception("unable to handle the notification data; skipping to acknowledge")
|
|
|
|
return ''
|
|
|
|
@staticmethod
|
|
def _normalize_data_keys(webhook_notification_data):
|
|
""" Reshape the webhook notification data to process them as redirect notification data.
|
|
|
|
:param dict webhook_notification_data: The webhook notification data
|
|
:return: The normalized notification data
|
|
:rtype: dict
|
|
"""
|
|
state_pol = webhook_notification_data.get('state_pol')
|
|
if state_pol == '4':
|
|
lap_transaction_state = 'APPROVED'
|
|
elif state_pol == '6':
|
|
lap_transaction_state = 'DECLINED'
|
|
elif state_pol == '5':
|
|
lap_transaction_state = 'EXPIRED'
|
|
else:
|
|
lap_transaction_state = f'INVALID state_pol {state_pol}'
|
|
return {
|
|
'lapTransactionState': lap_transaction_state,
|
|
'transactionState': webhook_notification_data.get('state_pol'),
|
|
'TX_VALUE': webhook_notification_data.get('value'),
|
|
'currency': webhook_notification_data.get('currency'),
|
|
'referenceCode': webhook_notification_data.get('reference_sale'),
|
|
'transactionId': webhook_notification_data.get('transaction_id'),
|
|
'message': webhook_notification_data.get('response_message_pol'),
|
|
'signature': webhook_notification_data.get('sign'),
|
|
}
|
|
|
|
@staticmethod
|
|
def _verify_notification_signature(notification_data, tx_sudo):
|
|
""" Check that the received signature matches the expected one.
|
|
|
|
:param dict notification_data: The notification data
|
|
:param recordset tx_sudo: The sudoed transaction referenced by the notification data, as a
|
|
`payment.transaction` record
|
|
:return: None
|
|
:raise: :class:`werkzeug.exceptions.Forbidden` if the signatures don't match
|
|
"""
|
|
# Retrieve the received signature from the data
|
|
received_signature = notification_data.get('signature')
|
|
if not received_signature:
|
|
_logger.warning("received notification with missing signature")
|
|
raise Forbidden()
|
|
|
|
# Compare the received signature with the expected signature computed from the data
|
|
expected_signature = tx_sudo.provider_id._payulatam_generate_sign(notification_data)
|
|
if not consteq(received_signature, expected_signature):
|
|
_logger.warning("received notification with invalid signature")
|
|
raise Forbidden()
|