If a payment request is made from a cron (e.g., Subscriptions' cron for recurring payments), the worker is not bound to an HTTP request and therefore the value of `request.httprequest.remote_addr` is an instance of the 'LocalProxy' object, which is not serializable. If we're in this scenario, don't send the customer IP address. task-2494916 closes odoo/odoo#71717 X-original-commit: e85464ad2281f25cbaf7ded0c1e5d7659a322696 Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com> Co-authored-by: Toufik Ben Jaa <tbe@odoo.com>
159 lines
6.4 KiB
Python
159 lines
6.4 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import fields
|
|
from odoo.http import request
|
|
from odoo.tools import consteq, float_round, ustr
|
|
from odoo.tools.misc import hmac as hmac_tool
|
|
|
|
|
|
# Access token management
|
|
|
|
def generate_access_token(*values):
|
|
""" Generate an access token based on the provided values.
|
|
|
|
The token allows to later verify the validity of a request, based on a given set of values.
|
|
These will generally include the partner id, amount, currency id, transaction id or transaction
|
|
reference.
|
|
All values must be convertible to a string.
|
|
|
|
:param list values: The values to use for the generation of the token
|
|
:return: The generated access token
|
|
:rtype: str
|
|
"""
|
|
token_str = '|'.join(str(val) for val in values)
|
|
access_token = hmac_tool(request.env(su=True), 'generate_access_token', token_str)
|
|
return access_token
|
|
|
|
|
|
def check_access_token(access_token, *values):
|
|
""" Check the validity of the access token for the provided values.
|
|
|
|
The values must be provided in the exact same order as they were to `generate_access_token`.
|
|
All values must be convertible to a string.
|
|
|
|
:param str access_token: The access token used to verify the provided values
|
|
:param list values: The values to verify against the token
|
|
:return: True if the check is successful
|
|
:rtype: bool
|
|
"""
|
|
authentic_token = generate_access_token(*values)
|
|
return access_token and consteq(ustr(access_token), authentic_token)
|
|
|
|
|
|
# Transaction values formatting
|
|
|
|
def singularize_reference_prefix(prefix='tx', separator='-', max_length=None):
|
|
""" Make the prefix more unique by suffixing it with the current datetime.
|
|
|
|
When the prefix is a placeholder that would be part of a large sequence of references sharing
|
|
the same prefix, such as "tx" or "validation", singularizing it allows to make it part of a
|
|
single-element sequence of transactions. The computation of the full reference will then execute
|
|
faster by failing to find existing references with a matching prefix.
|
|
|
|
If the `max_length` argument is passed, the end of the prefix can be stripped before
|
|
singularizing to ensure that the result accounts for no more than `max_length` characters.
|
|
|
|
:param str prefix: The custom prefix to singularize
|
|
:param str separator: The custom separator used to separate the prefix from the suffix
|
|
:param int max_length: The maximum length of the singularized prefix
|
|
:return: The singularized prefix
|
|
:rtype: str
|
|
"""
|
|
if prefix is None:
|
|
prefix = 'tx'
|
|
if max_length:
|
|
DATETIME_LENGTH = 14
|
|
assert max_length >= 1 + len(separator) + DATETIME_LENGTH # 1 char + separator + datetime
|
|
prefix = prefix[:max_length-len(separator)-DATETIME_LENGTH]
|
|
return f'{prefix}{separator}{fields.Datetime.now().strftime("%Y%m%d%H%M%S")}'
|
|
|
|
|
|
def to_major_currency_units(minor_amount, currency, arbitrary_decimal_number=None):
|
|
""" Return the amount converted to the major units of its currency.
|
|
|
|
The conversion is done by dividing the amount by 10^k where k is the number of decimals of the
|
|
currency as per the ISO 4217 norm.
|
|
To force a different number of decimals, set it as the value of the `decimal_number` argument.
|
|
|
|
:param float minor_amount: The amount in minor units, to convert in major units
|
|
:param recordset currency: The currency of the amount, as a `res.currency` record
|
|
:param int arbitrary_decimal_number: The number of decimals to use instead of that of ISO 4217
|
|
:return: The amount in major units of its currency
|
|
:rtype: int
|
|
"""
|
|
currency.ensure_one()
|
|
|
|
if arbitrary_decimal_number is None:
|
|
decimal_number = currency.decimal_places
|
|
else:
|
|
decimal_number = arbitrary_decimal_number
|
|
return float_round(minor_amount, 0) / (10**decimal_number)
|
|
|
|
|
|
def to_minor_currency_units(major_amount, currency, arbitrary_decimal_number=None):
|
|
""" Return the amount converted to the minor units of its currency.
|
|
|
|
The conversion is done by multiplying the amount by 10^k where k is the number of decimals of
|
|
the currency as per the ISO 4217 norm.
|
|
To force a different number of decimals, set it as the value of the `decimal_number` argument.
|
|
|
|
Note: currency.ensure_one() if arbitrary_decimal_number is not provided
|
|
|
|
:param float major_amount: The amount in major units, to convert in minor units
|
|
:param recordset currency: The currency of the amount, as a `res.currency` record
|
|
:param int arbitrary_decimal_number: The number of decimals to use instead of that of ISO 4217
|
|
:return: The amount in minor units of its currency
|
|
:rtype: int
|
|
"""
|
|
if arbitrary_decimal_number is not None:
|
|
decimal_number = arbitrary_decimal_number
|
|
else:
|
|
currency.ensure_one()
|
|
decimal_number = currency.decimal_places
|
|
return int(float_round(major_amount, decimal_number) * (10**decimal_number))
|
|
|
|
|
|
# Token values formatting
|
|
|
|
def build_token_name(payment_details_short=None, final_length=16):
|
|
""" Pad plain payment details with leading X's to build a token name of the desired length.
|
|
|
|
:param str payment_details_short: The plain part of the payment details (usually last 4 digits)
|
|
:param int final_length: The desired final length of the token name (16 for a bank card)
|
|
:return: The padded token name
|
|
:rtype: str
|
|
"""
|
|
payment_details_short = payment_details_short or '????'
|
|
return f"{'X' * (final_length - len(payment_details_short))}{payment_details_short}"
|
|
|
|
|
|
# Partner values formatting
|
|
|
|
def format_partner_address(address1="", address2=""):
|
|
""" Format a two-parts partner address into a one-line address string.
|
|
|
|
:param str address1: The first part of the address, usually the `street1` field
|
|
:param str address2: The second part of the address, usually the `street2` field
|
|
:return: The formatted one-line address
|
|
:rtype: str
|
|
"""
|
|
address1 = address1 or "" # Avoid casting as "False"
|
|
address2 = address2 or "" # Avoid casting as "False"
|
|
return f"{address1} {address2}".strip()
|
|
|
|
|
|
def split_partner_name(partner_name):
|
|
""" Split a single-line partner name in a tuple of first name, last name.
|
|
|
|
:param str partner_name: The partner name
|
|
:return: The splitted first name and last name
|
|
:rtype: tuple
|
|
"""
|
|
return " ".join(partner_name.split()[:-1]), partner_name.split()[-1]
|
|
|
|
|
|
# Security
|
|
|
|
def get_customer_ip_address():
|
|
return request and request.httprequest.remote_addr or ''
|