Files
odoo_source/addons/payment_paypal/controllers/main.py
T
Antoine Vandevenne (anv) c902e02317 [FIX] payment(_*), account_payment: fix post-refactoring issues
account_payment:
  - Processing fees computation was done based on the wrong country.
payment:
  - The acquirer's cancel message was missing from the
    /payment/confirmation page.
  - `redirect_form_view_id` field was declared with attribute 'name'
    instead of 'string'.
  - Uninstalling a payment acquirer would fail with a traceback.
  - The first acquirer was not automatically selected if it was the only
    selectable payment option of a 'manage' payment form.
  - Specifying a preferred acquirer to the /payment/pay page would show
    not acquirer at all if the preferred option was incompatible with
    the constraints, rather than falling back on showing all acquirers.
payment_adyen:
  - When the value of the API URL fields is malformed (e.g., missing the
    "https://"), clicking on the confirm button raised a traceback.
payment_ogone:
  - There was a typo in the return route.
payment_paypal:
  - PayPal acquirers were not filtered out if the currency was not not
    supported.
  - Returning to the webshop without paying would raise a traceback.

task-2494916

closes odoo/odoo#69996

X-original-commit: 4f7e463fb8b13506caa8aff0beeef5eb0720bf00
Related: odoo/enterprise#17997
Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com>
2021-04-28 11:39:26 +00:00

99 lines
4.6 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import pprint
import requests
import werkzeug
from odoo import _, http
from odoo.exceptions import ValidationError
from odoo.http import request
_logger = logging.getLogger(__name__)
class PaypalController(http.Controller):
_return_url = '/payment/paypal/dpn/'
_notify_url = '/payment/paypal/ipn/'
@http.route(_return_url, type='http', auth='public', methods=['GET', 'POST'], csrf=False)
def paypal_dpn(self, **data):
""" Route used by the PDT notification.
The "PDT notification" is actually POST data sent along the user redirection.
The route also allows the GET method in case the user clicks on "go back to merchant site".
"""
_logger.info("beginning DPN with post data:\n%s", pprint.pformat(data))
self._validate_data_authenticity(**data)
if data:
request.env['payment.transaction']._handle_feedback_data('paypal', data)
else:
pass # The customer has cancelled the payment, don't do anything
return werkzeug.utils.redirect('/payment/status')
@http.route(_notify_url, type='http', auth='public', methods=['GET', 'POST'], csrf=False)
def paypal_ipn(self, **data):
""" Route used by the IPN. """
_logger.info("beginning IPN with post data:\n%s", pprint.pformat(data))
try:
self._validate_data_authenticity(**data)
request.env['payment.transaction']._handle_feedback_data('paypal', data)
except ValidationError: # Acknowledge the notification to avoid getting spammed
_logger.exception("unable to handle the IPN data; skipping to acknowledge the notif")
return ''
def _validate_data_authenticity(self, **data):
""" Validate the authenticity of data received through DPN or IPN
The verification is done in three steps:
- 1: POST the complete, unaltered, message back to Paypal (preceded by
`cmd=_notify-validate`), in the same encoding.
- 2: PayPal sends back either 'VERIFIED' or 'INVALID'.
- 3: Return an empty HTTP 200 response (done at the end of the route method).
See https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro
As per https://developer.paypal.com/docs/api-basics/notifications/payment-data-transfer/,
PDT notifications should be verified in a similar but different manner:
- The transaction ID should be retrieved from the GET param `tx`.
- The POST should use `_notify-synch` (as per previous versions of this method) as `cmd`,
and only have as params the transaction ID and the PDT Identity Token (under the key
`at`, as per previous versions of this method).
- The payment data should be parsed from the response of the check request.
In practice, however, the transaction ID is never given by PayPal and the documentation
has no mention of `_notify_synch` nor `at`. Because of this, PDT cannot be verified as
prescribed by the documentation.
Nevertheless, previous versions of this method used a bad heuristic (assessing the presence
of the optional, PDT-specific, param `amt`) to determine whether the notification was a PDT.
Since PDT notifications have in practice always been successfully authenticated by using the
IPN protocol, this method does explicitly that for both PDT and IPN.
:param dict data: The data whose authenticity to check
:return: None
:raise: ValidationError if the authenticity could not be verified
"""
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
'paypal', data
)
acquirer_sudo = tx_sudo.acquirer_id
# Request PayPal for an authenticity check
data['cmd'] = '_notify-validate'
response = requests.post(acquirer_sudo._paypal_get_api_url(), data, timeout=60)
response.raise_for_status()
# Inspect the response code and raise if not 'VERIFIED'.
response_code = response.text
if response_code == 'VERIFIED':
_logger.info("authenticity of notification data verified")
elif response_code == 'INVALID':
raise ValidationError("PayPal: " + _("Notification data were not acknowledged."))
else:
raise ValidationError(
"PayPal: " + _(
"Received unrecognized authentication check response code: received %s, "
"expected VERIFIED or INVALID.",
response_code
)
)