Files
odoo_source/addons/payment_adyen/models/payment_provider.py
T
Laurent Desausoi 7593c073d2 [IMP] core: use inert SQL based neutralization
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>
2022-10-09 22:04:00 +02:00

142 lines
5.9 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import re
import requests
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.addons.payment_adyen.const import API_ENDPOINT_VERSIONS
_logger = logging.getLogger(__name__)
class PaymentProvider(models.Model):
_inherit = 'payment.provider'
code = fields.Selection(
selection_add=[('adyen', "Adyen")], ondelete={'adyen': 'set default'})
adyen_merchant_account = fields.Char(
string="Merchant Account",
help="The code of the merchant account to use with this provider",
required_if_provider='adyen', groups='base.group_system')
adyen_api_key = fields.Char(
string="API Key", help="The API key of the webservice user", required_if_provider='adyen',
groups='base.group_system')
adyen_client_key = fields.Char(
string="Client Key", help="The client key of the webservice user",
required_if_provider='adyen')
adyen_hmac_key = fields.Char(
string="HMAC Key", help="The HMAC key of the webhook", required_if_provider='adyen',
groups='base.group_system')
adyen_checkout_api_url = fields.Char(
string="Checkout API URL", help="The base URL for the Checkout API endpoints",
required_if_provider='adyen')
adyen_recurring_api_url = fields.Char(
string="Recurring API URL", help="The base URL for the Recurring API endpoints",
required_if_provider='adyen')
#=== CRUD METHODS ===#
@api.model_create_multi
def create(self, values_list):
for values in values_list:
self._adyen_trim_api_urls(values)
return super().create(values_list)
def write(self, values):
self._adyen_trim_api_urls(values)
return super().write(values)
@api.model
def _adyen_trim_api_urls(self, values):
""" Remove the version and the endpoint from the url of Adyen API fields.
:param dict values: The create or write values
:return: None
"""
for field_name in ('adyen_checkout_api_url', 'adyen_recurring_api_url'):
if values.get(field_name): # Test the value in case we're duplicating a provider
values[field_name] = re.sub(r'[vV]\d+(/.*)?', '', values[field_name])
#=== 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 == 'adyen').update({
'support_manual_capture': True,
'support_refund': 'partial',
'support_tokenization': True,
})
#=== BUSINESS METHODS ===#
def _adyen_make_request(
self, url_field_name, endpoint, endpoint_param=None, payload=None, method='POST'
):
""" Make a request to Adyen API at the specified endpoint.
Note: self.ensure_one()
:param str url_field_name: The name of the field holding the base URL for the request
:param str endpoint: The endpoint to be reached by the request
:param str endpoint_param: A variable required by some endpoints which are interpolated with
it if provided. For example, the provider reference of the source
transaction for the '/payments/{}/refunds' endpoint.
: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
"""
def _build_url(_base_url, _version, _endpoint):
""" Build an API URL by appending the version and endpoint to a base URL.
The final URL follows this pattern: `<_base>/V<_version>/<_endpoint>`.
:param str _base_url: The base of the url prefixed with `https://`
:param int _version: The version of the endpoint
:param str _endpoint: The endpoint of the URL.
:return: The final URL
:rtype: str
"""
_base = _base_url.rstrip('/') # Remove potential trailing slash
_endpoint = _endpoint.lstrip('/') # Remove potential leading slash
return f'{_base}/V{_version}/{_endpoint}'
self.ensure_one()
base_url = self[url_field_name] # Restrict request URL to the stored API URL fields
version = API_ENDPOINT_VERSIONS[endpoint]
endpoint = endpoint if not endpoint_param else endpoint.format(endpoint_param)
url = _build_url(base_url, version, endpoint)
headers = {'X-API-Key': self.adyen_api_key}
try:
response = requests.request(method, url, json=payload, headers=headers, timeout=60)
response.raise_for_status()
except requests.exceptions.ConnectionError:
_logger.exception("unable to reach endpoint at %s", url)
raise ValidationError("Adyen: " + _("Could not establish the connection to the API."))
except requests.exceptions.HTTPError as error:
_logger.exception(
"invalid API request at %s with data %s: %s", url, payload, error.response.text
)
raise ValidationError("Adyen: " + _("The communication with the API failed."))
return response.json()
def _adyen_compute_shopper_reference(self, partner_id):
""" Compute a unique reference of the partner for Adyen.
This is used for the `shopperReference` field in communications with Adyen and stored in the
`adyen_shopper_reference` field on `payment.token` if the payment method is tokenized.
:param recordset partner_id: The partner making the transaction, as a `res.partner` id
:return: The unique reference for the partner
:rtype: str
"""
return f'ODOO_PARTNER_{partner_id}'