Fixes a large number of cases where strings are translated then
formatted, instead of letting `_()` do the formatting internally,
which allows it to recover from incorrect translations (missing,
broken, or extra placeholders).
Also
- removes translation markers entirely when there's nothing to
translate e.g. `_("%s - %s")` is not useful
- fixes a few messes which lead to only partial translatability
(DRY is generally a bad idea when translations are involved, even
more so when you don't make the variable part translatable)
- fixes a few nearby issues noticed at the same time
- replaces a few `"%s"` by `%r`, which should automatically quote
strings relatively appropriately
- fixes translated strings which use `\` to escape a newline (in order
to fill-paragraph): `\` escapes only the newline, if the
continuation string is indented this results in a bunch of spaces
ending in the string to translate, which is pretty garbage for the
translator, using implicit concatenation works much better
Note: some of the updates revert f-string parameters to %, because
babel (2.9) apparently has trouble with f-strings and blows up trying
to extract them.
Not in scope:
Helping translators fix translatable strings e.g. any translation
string with more than one placeholder probably should use keyword
placeholders
- Provides more context / data to the translator to make sense of the
sentence.
- Allows reordering the translated terms, which can be necessary
depending on the sentence and language.
closes odoo/odoo#139314
Related: odoo/enterprise#49311
Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
119 lines
5.1 KiB
Python
119 lines
5.1 KiB
Python
# coding: utf-8
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import json
|
|
import logging
|
|
import pprint
|
|
import random
|
|
import requests
|
|
import string
|
|
from werkzeug.exceptions import Forbidden
|
|
|
|
from odoo import fields, models, api, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class PosPaymentMethod(models.Model):
|
|
_inherit = 'pos.payment.method'
|
|
|
|
def _get_payment_terminal_selection(self):
|
|
return super(PosPaymentMethod, self)._get_payment_terminal_selection() + [('adyen', 'Adyen')]
|
|
|
|
# Adyen
|
|
adyen_api_key = fields.Char(string="Adyen API key", help='Used when connecting to Adyen: https://docs.adyen.com/user-management/how-to-get-the-api-key/#description', copy=False)
|
|
adyen_terminal_identifier = fields.Char(help='[Terminal model]-[Serial number], for example: P400Plus-123456789', copy=False)
|
|
adyen_test_mode = fields.Boolean(help='Run transactions in the test environment.')
|
|
|
|
adyen_latest_response = fields.Char(copy=False, groups='base.group_erp_manager') # used to buffer the latest asynchronous notification from Adyen.
|
|
adyen_latest_diagnosis = fields.Char(copy=False, groups='base.group_erp_manager') # used to determine if the terminal is still connected.
|
|
|
|
@api.constrains('adyen_terminal_identifier')
|
|
def _check_adyen_terminal_identifier(self):
|
|
for payment_method in self:
|
|
if not payment_method.adyen_terminal_identifier:
|
|
continue
|
|
# sudo() to search all companies
|
|
existing_payment_method = self.sudo().search([('id', '!=', payment_method.id),
|
|
('adyen_terminal_identifier', '=', payment_method.adyen_terminal_identifier)],
|
|
limit=1)
|
|
if existing_payment_method:
|
|
if existing_payment_method.company_id == payment_method.company_id:
|
|
raise ValidationError(_('Terminal %s is already used on payment method %s.',
|
|
payment_method.adyen_terminal_identifier, existing_payment_method.display_name))
|
|
else:
|
|
raise ValidationError(_('Terminal %s is already used in company %s on payment method %s.',
|
|
payment_method.adyen_terminal_identifier,
|
|
existing_payment_method.company_id.name,
|
|
existing_payment_method.display_name))
|
|
|
|
def _get_adyen_endpoints(self):
|
|
return {
|
|
'terminal_request': 'https://terminal-api-%s.adyen.com/async',
|
|
}
|
|
|
|
def _is_write_forbidden(self, fields):
|
|
whitelisted_fields = {'adyen_latest_response', 'adyen_latest_diagnosis'}
|
|
return super(PosPaymentMethod, self)._is_write_forbidden(fields - whitelisted_fields)
|
|
|
|
def _adyen_diagnosis_request_data(self, pos_config_name):
|
|
service_id = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
|
|
return {
|
|
"SaleToPOIRequest": {
|
|
"MessageHeader": {
|
|
"ProtocolVersion": "3.0",
|
|
"MessageClass": "Service",
|
|
"MessageCategory": "Diagnosis",
|
|
"MessageType": "Request",
|
|
"ServiceID": service_id,
|
|
"SaleID": pos_config_name,
|
|
"POIID": self.adyen_terminal_identifier,
|
|
},
|
|
"DiagnosisRequest": {
|
|
"HostDiagnosisFlag": False
|
|
}
|
|
}
|
|
}
|
|
|
|
def get_latest_adyen_status(self):
|
|
self.ensure_one()
|
|
latest_response = self.sudo().adyen_latest_response
|
|
latest_response = json.loads(latest_response) if latest_response else False
|
|
return latest_response
|
|
|
|
def proxy_adyen_request(self, data, operation=False):
|
|
''' Necessary because Adyen's endpoints don't have CORS enabled '''
|
|
if data['SaleToPOIRequest']['MessageHeader']['MessageCategory'] == 'Payment': # Clear only if it is a payment request
|
|
self.sudo().adyen_latest_response = '' # avoid handling old responses multiple times
|
|
|
|
if not operation:
|
|
operation = 'terminal_request'
|
|
|
|
return self._proxy_adyen_request_direct(data, operation)
|
|
|
|
def _proxy_adyen_request_direct(self, data, operation):
|
|
self.ensure_one()
|
|
TIMEOUT = 10
|
|
|
|
_logger.info('request to adyen\n%s', pprint.pformat(data))
|
|
|
|
environment = 'test' if self.adyen_test_mode else 'live'
|
|
endpoint = self._get_adyen_endpoints()[operation] % environment
|
|
headers = {
|
|
'x-api-key': self.adyen_api_key,
|
|
}
|
|
req = requests.post(endpoint, json=data, headers=headers, timeout=TIMEOUT)
|
|
|
|
# Authentication error doesn't return JSON
|
|
if req.status_code == 401:
|
|
return {
|
|
'error': {
|
|
'status_code': req.status_code,
|
|
'message': req.text
|
|
}
|
|
}
|
|
|
|
if req.text == 'ok':
|
|
return True
|
|
|
|
return req.json()
|