Before this commit the neutralize system introduced in v16 was using ORM
methods in order to change appropriate records. Although flexible, this approach
could lead to call some methods with side effects while neutralizing
(eg: overloads of write).
This patch converts the neutralize system to a safer "inert" SQL based approach
by migrating the generic method _neutralize to SQL files exposed in the
data folder.
Task id: 2961687
closes odoo/odoo#102792
X-original-commit: e5dbded9bb363351feff7ca8a56c7f8a6860f492
Related: odoo/enterprise#32580
Signed-off-by: Fabien Meghazi <fme@odoo.com>
124 lines
4.8 KiB
Python
124 lines
4.8 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import hashlib
|
|
import hmac
|
|
import logging
|
|
import pprint
|
|
|
|
import requests
|
|
from werkzeug.urls import url_join
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
|
|
from odoo.addons.payment_razorpay.const import SUPPORTED_CURRENCIES
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PaymentProvider(models.Model):
|
|
_inherit = 'payment.provider'
|
|
|
|
code = fields.Selection(
|
|
selection_add=[('razorpay', "Razorpay")], ondelete={'razorpay': 'set default'}
|
|
)
|
|
razorpay_key_id = fields.Char(
|
|
string="Razorpay Key Id",
|
|
help="The key solely used to identify the account with Razorpay.",
|
|
required_if_provider='razorpay',
|
|
)
|
|
razorpay_key_secret = fields.Char(
|
|
string="Razorpay Key Secret",
|
|
required_if_provider='razorpay',
|
|
groups='base.group_system',
|
|
)
|
|
razorpay_webhook_secret = fields.Char(
|
|
string="Razorpay Webhook Secret",
|
|
required_if_provider='razorpay',
|
|
groups='base.group_system',
|
|
)
|
|
|
|
#=== COMPUTE METHODS ===#
|
|
|
|
def _compute_feature_support_fields(self):
|
|
""" Override of `payment` to enable additional features. """
|
|
super()._compute_feature_support_fields()
|
|
self.filtered(lambda p: p.code == 'razorpay').update({
|
|
'support_manual_capture': True,
|
|
'support_refund': 'partial',
|
|
})
|
|
|
|
# === BUSINESS METHODS ===#
|
|
|
|
@api.model
|
|
def _get_compatible_providers(self, *args, currency_id=None, **kwargs):
|
|
""" Override of `payment` to filter out Razorpay providers for unsupported currencies. """
|
|
providers = super()._get_compatible_providers(*args, currency_id=currency_id, **kwargs)
|
|
|
|
currency = self.env['res.currency'].browse(currency_id).exists()
|
|
if currency and currency.name not in SUPPORTED_CURRENCIES:
|
|
providers = providers.filtered(lambda p: p.code != 'razorpay')
|
|
|
|
return providers
|
|
|
|
def _razorpay_make_request(self, endpoint, payload=None, method='POST'):
|
|
""" Make a request to Razorpay API at the specified endpoint.
|
|
|
|
Note: self.ensure_one()
|
|
|
|
:param str endpoint: The endpoint to be reached by the request.
|
|
:param dict payload: The payload of the request.
|
|
:param str method: The HTTP method of the request.
|
|
:return The JSON-formatted content of the response.
|
|
:rtype: dict
|
|
:raise ValidationError: If an HTTP error occurs.
|
|
"""
|
|
self.ensure_one()
|
|
|
|
url = url_join('https://api.razorpay.com/v1/', endpoint)
|
|
auth = (self.razorpay_key_id, self.razorpay_key_secret)
|
|
try:
|
|
if method == 'GET':
|
|
response = requests.get(url, params=payload, auth=auth, timeout=10)
|
|
else:
|
|
response = requests.post(url, json=payload, auth=auth, timeout=10)
|
|
try:
|
|
response.raise_for_status()
|
|
except requests.exceptions.HTTPError:
|
|
_logger.exception(
|
|
"Invalid API request at %s with data:\n%s", url, pprint.pformat(payload),
|
|
)
|
|
raise ValidationError("Razorpay: " + _(
|
|
"The communication with the API failed. Razorpay gave us the following "
|
|
"information: '%s'", response.json().get('error', {}).get('description')
|
|
))
|
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
_logger.exception("Unable to reach endpoint at %s", url)
|
|
raise ValidationError(
|
|
"Razorpay: " + _("Could not establish the connection to the API.")
|
|
)
|
|
return response.json()
|
|
|
|
def _razorpay_calculate_signature(self, data, is_redirect=True):
|
|
""" Compute the signature for the request's data according to the Razorpay documentation.
|
|
|
|
See https://razorpay.com/docs/webhooks/validate-test#validate-webhooks and
|
|
https://razorpay.com/docs/payments/payment-gateway/web-integration/hosted/build-integration.
|
|
|
|
:param dict|bytes data: The data to sign.
|
|
:param bool is_redirect: Whether the data should be treated as redirect data or as coming
|
|
from a webhook notification.
|
|
:return: The calculated signature.
|
|
:rtype: str
|
|
"""
|
|
if is_redirect:
|
|
secret = self.razorpay_key_secret
|
|
signing_string = f'{data["razorpay_order_id"]}|{data["razorpay_payment_id"]}'
|
|
return hmac.new(
|
|
secret.encode(), msg=signing_string.encode(), digestmod=hashlib.sha256
|
|
).hexdigest()
|
|
else: # Notification data.
|
|
secret = self.razorpay_webhook_secret
|
|
return hmac.new(secret.encode(), msg=data, digestmod=hashlib.sha256).hexdigest()
|