19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -8,41 +8,17 @@ Odoo addon: payment
pip install odoo-bringout-oca-ocb-payment
```
## Patches Applied
- **Payment Provider Dependencies Removal**: External payment provider dependencies have been removed to prevent installation issues. See [doc/PATCH_REMOVE_PAYMENT_PROVIDERS.md](doc/PATCH_REMOVE_PAYMENT_PROVIDERS.md) for details.
## Dependencies
This addon depends on:
- onboarding
- portal
## Manifest Information
- **Name**: Payment Engine
- **Version**: 2.0
- **Category**: Hidden
- **License**: LGPL-3
- **Installable**: False
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `payment`.
- Repository: https://github.com/OCA/OCB
- Branch: 19.0
- Path: addons/payment
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md
This package preserves the original LGPL-3 license.

View file

@ -5,14 +5,10 @@ from . import models
from . import utils
from . import wizards
from odoo import api, SUPERUSER_ID
def setup_provider(env, code, **kwargs):
env['payment.provider']._setup_provider(code, **kwargs)
def setup_provider(cr, registry, code):
env = api.Environment(cr, SUPERUSER_ID, {})
env['payment.provider']._setup_provider(code)
def reset_payment_provider(cr, registry, code):
env = api.Environment(cr, SUPERUSER_ID, {})
env['payment.provider']._remove_provider(code)
def reset_payment_provider(env, code, **kwargs):
env['payment.provider']._remove_provider(code, **kwargs)

View file

@ -5,46 +5,51 @@
'version': '2.0',
'category': 'Hidden',
'summary': "The payment engine used by payment provider modules.",
'depends': ['portal'],
'depends': ['onboarding', 'portal'],
'data': [
'data/payment_icon_data.xml',
# Record data.
'data/ir_actions_server_data.xml',
'data/payment_method_data.xml',
'data/payment_provider_data.xml',
'data/payment_cron.xml',
'views/payment_portal_templates.xml',
'views/payment_templates.xml',
# QWeb templates.
'views/express_checkout_templates.xml',
'views/payment_form_templates.xml',
'views/portal_templates.xml',
# Model views.
'views/payment_provider_views.xml',
'views/payment_icon_views.xml',
'views/payment_method_views.xml', # Depends on `action_payment_provider`.
'views/payment_transaction_views.xml',
'views/payment_token_views.xml', # Depends on `action_payment_transaction_linked_to_token`
'views/payment_token_views.xml', # Depends on `action_payment_transaction_linked_to_token`.
'views/res_partner_views.xml',
# Security.
'security/ir.model.access.csv',
'security/payment_security.xml',
# Wizard views.
'wizards/payment_capture_wizard_views.xml',
'wizards/payment_link_wizard_views.xml',
'wizards/payment_onboarding_views.xml',
],
'demo': [
'data/payment_demo.xml',
],
'assets': {
'web.assets_frontend': [
'payment/static/src/scss/portal_payment.scss',
'payment/static/src/scss/payment_templates.scss',
'payment/static/src/scss/payment_form.scss',
'payment/static/lib/jquery.payment/jquery.payment.js',
'payment/static/src/js/checkout_form.js',
'payment/static/src/js/express_checkout_form.js',
'payment/static/src/js/manage_form.js',
'payment/static/src/js/payment_form_mixin.js',
'payment/static/src/js/post_processing.js',
'payment/static/src/xml/payment_post_processing.xml',
'payment/static/src/**/*',
('remove', 'payment/static/src/js/payment_wizard_copy_clipboard_field.js'),
],
'web.assets_backend': [
'payment/static/src/scss/payment_provider.scss',
'payment/static/src/js/payment_wizard_copy_clipboard_field.js',
],
'web.assets_unit_tests_setup': [
'payment/static/src/interactions/payment_button.js',
'payment/static/src/interactions/payment_form.js',
],
'web.assets_unit_tests': [
'payment/static/tests/**/*.test.js',
],
},
'author': 'Odoo S.A.',
'license': 'LGPL-3',
}

View file

@ -1,6 +1,265 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# According to https://en.wikipedia.org/wiki/ISO_4217#Minor_unit_fractions
from odoo.tools.translate import LazyTranslate
_lt = LazyTranslate(__name__, default_lang='en_US')
# The keys that are sensitive and should not be logged.
SENSITIVE_KEYS = set()
# Mapping of ISO 3166-1 country codes in alpha-2 format to the numeric format.
COUNTRY_NUMERIC_CODES = {
'AF': '004',
'AL': '008',
'DZ': '012',
'AS': '016',
'AD': '020',
'AO': '024',
'AG': '028',
'AZ': '031',
'AR': '032',
'AU': '036',
'AT': '040',
'BS': '044',
'BH': '048',
'BD': '050',
'AM': '051',
'BB': '052',
'BE': '056',
'BM': '060',
'BT': '064',
'BO': '068',
'BA': '070',
'BW': '072',
'BV': '074',
'BR': '076',
'BZ': '084',
'IO': '086',
'SB': '090',
'VG': '092',
'BN': '096',
'BG': '100',
'MM': '104',
'BI': '108',
'BY': '112',
'KH': '116',
'CM': '120',
'CA': '124',
'CV': '132',
'KY': '136',
'CF': '140',
'LK': '144',
'TD': '148',
'CL': '152',
'CN': '156',
'TW': '158',
'CX': '162',
'CC': '166',
'CO': '170',
'KM': '174',
'YT': '175',
'CG': '178',
'CD': '180',
'CK': '184',
'CR': '188',
'HR': '191',
'CU': '192',
'CY': '196',
'CZ': '203',
'DK': '208',
'DM': '212',
'DO': '214',
'EC': '218',
'SV': '222',
'GQ': '226',
'ET': '231',
'ER': '232',
'EE': '233',
'FO': '234',
'FK': '238',
'GS': '239',
'FJ': '242',
'FI': '246',
'AX': '248',
'FR': '250',
'GF': '254',
'PF': '258',
'TF': '260',
'DJ': '262',
'GA': '266',
'GE': '268',
'GM': '270',
'PS': '275',
'DE': '276',
'GH': '288',
'GI': '292',
'KI': '296',
'GR': '300',
'GL': '304',
'GD': '308',
'GP': '312',
'GU': '316',
'GT': '320',
'GN': '324',
'GY': '328',
'HT': '332',
'HM': '334',
'VA': '336',
'HN': '340',
'HK': '344',
'HU': '348',
'IS': '352',
'IN': '356',
'ID': '360',
'IR': '364',
'IQ': '368',
'IE': '372',
'IL': '376',
'IT': '380',
'CI': '384',
'JM': '388',
'JP': '392',
'KZ': '398',
'JO': '400',
'KE': '404',
'KP': '408',
'KR': '410',
'KW': '414',
'KG': '417',
'LA': '418',
'LB': '422',
'LS': '426',
'LV': '428',
'LR': '430',
'LY': '434',
'LI': '438',
'LT': '440',
'LU': '442',
'MO': '446',
'MG': '450',
'MW': '454',
'MY': '458',
'MV': '462',
'ML': '466',
'MT': '470',
'MQ': '474',
'MR': '478',
'MU': '480',
'MX': '484',
'MC': '492',
'MN': '496',
'MD': '498',
'ME': '499',
'MS': '500',
'MA': '504',
'MZ': '508',
'OM': '512',
'NA': '516',
'NR': '520',
'NP': '524',
'NL': '528',
'CW': '531',
'AW': '533',
'SX': '534',
'BQ': '535',
'NC': '540',
'VU': '548',
'NZ': '554',
'NI': '558',
'NE': '562',
'NG': '566',
'NU': '570',
'NF': '574',
'NO': '578',
'MP': '580',
'UM': '581',
'FM': '583',
'MH': '584',
'PW': '585',
'PK': '586',
'PA': '591',
'PG': '598',
'PY': '600',
'PE': '604',
'PH': '608',
'PN': '612',
'PL': '616',
'PT': '620',
'GW': '624',
'TL': '626',
'PR': '630',
'QA': '634',
'RE': '638',
'RO': '642',
'RU': '643',
'RW': '646',
'BL': '652',
'SH': '654',
'KN': '659',
'AI': '660',
'LC': '662',
'MF': '663',
'PM': '666',
'VC': '670',
'SM': '674',
'ST': '678',
'SA': '682',
'SN': '686',
'RS': '688',
'SC': '690',
'SL': '694',
'SG': '702',
'SK': '703',
'VN': '704',
'SI': '705',
'SO': '706',
'ZA': '710',
'ZW': '716',
'ES': '724',
'SS': '728',
'SD': '729',
'EH': '732',
'SR': '740',
'SZ': '748',
'SE': '752',
'CH': '756',
'SY': '760',
'TJ': '762',
'TH': '764',
'TG': '768',
'TK': '772',
'TO': '776',
'TT': '780',
'AE': '784',
'TN': '788',
'TR': '792',
'TM': '795',
'TC': '796',
'TV': '798',
'UG': '800',
'UA': '804',
'MK': '807',
'EG': '818',
'GB': '826',
'GG': '831',
'JE': '832',
'IM': '833',
'TZ': '834',
'US': '840',
'VI': '850',
'BF': '854',
'UY': '858',
'UZ': '860',
'VE': '862',
'WF': '876',
'WS': '882',
'YE': '887',
'ZM': '894'
}
# Mapping of ISO 4217 currency codes to their number of decimals.
# See https://en.wikipedia.org/wiki/ISO_4217#Minor_unit_fractions.
CURRENCY_MINOR_UNITS = {
'ADF': 2,
'ADP': 0,
@ -279,3 +538,15 @@ CURRENCY_MINOR_UNITS = {
'ZWN': 2,
'ZWR': 2
}
REPORT_REASONS_MAPPING = {
'exceed_max_amount': _lt("maximum amount exceeded"),
'express_checkout_not_supported': _lt("express checkout not supported"),
'incompatible_country': _lt("incompatible country"),
'incompatible_currency': _lt("incompatible currency"),
'incompatible_website': _lt("incompatible website"),
'manual_capture_not_supported': _lt("manual capture not supported"),
'provider_not_available': _lt("no supported provider available"),
'tokenization_not_supported': _lt("tokenization not supported"),
'validation_not_supported': _lt("tokenization without payment no supported"),
}

View file

@ -1,10 +1,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import urllib.parse
import werkzeug
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from odoo import _, http
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.addons.payment import utils as payment_utils
@ -38,7 +39,7 @@ class PaymentPortal(portal.CustomerPortal):
)
def payment_pay(
self, reference=None, amount=None, currency_id=None, partner_id=None, company_id=None,
provider_id=None, access_token=None, **kwargs
access_token=None, **kwargs
):
""" Display the payment form with optional filtering of payment options.
@ -47,33 +48,34 @@ class PaymentPortal(portal.CustomerPortal):
In addition to the desired filtering, a second one ensures that none of the following
rules is broken:
- Public users are not allowed to save their payment method as a token.
- Payments made by public users should either *not* be made on behalf of a specific
partner or have an access token validating the partner, amount and currency.
We let access rights and security rules do their job for logged in users.
:param str reference: The custom prefix to compute the full reference
:param str amount: The amount to pay
:param str currency_id: The desired currency, as a `res.currency` id
:param str partner_id: The partner making the payment, as a `res.partner` id
:param str company_id: The related company, as a `res.company` id
:param str provider_id: The desired provider, as a `payment.provider` id
:param str access_token: The access token used to authenticate the partner
- Public users are not allowed to save their payment method as a token.
- Payments made by public users should either *not* be made on behalf of a specific partner
or have an access token validating the partner, amount and currency.
We let access rights and security rules do their job for logged users.
:param str reference: The custom prefix to compute the full reference.
:param str amount: The amount to pay.
:param str currency_id: The desired currency, as a `res.currency` id.
:param str partner_id: The partner making the payment, as a `res.partner` id.
:param str company_id: The related company, as a `res.company` id.
:param str access_token: The access token used to authenticate the partner.
:param dict kwargs: Optional data passed to helper methods.
:return: The rendered checkout form
:return: The rendered payment form.
:rtype: str
:raise: werkzeug.exceptions.NotFound if the access token is invalid
:raise NotFound: If the access token is invalid.
"""
# Cast numeric parameters as int or float and void them if their str value is malformed
currency_id, provider_id, partner_id, company_id = tuple(map(
self._cast_as_int, (currency_id, provider_id, partner_id, company_id)
currency_id, partner_id, company_id = tuple(map(
self._cast_as_int, (currency_id, partner_id, company_id)
))
amount = self._cast_as_float(amount)
# Raise an HTTP 404 if a partner is provided with an invalid access token
if partner_id:
if not payment_utils.check_access_token(access_token, partner_id, amount, currency_id):
raise werkzeug.exceptions.NotFound() # Don't leak information about ids.
raise NotFound() # Don't leak information about ids.
user_sudo = request.env.user
logged_in = not user_sudo._is_public()
@ -104,60 +106,76 @@ class PaymentPortal(portal.CustomerPortal):
# Make sure that the currency exists and is active
currency = request.env['res.currency'].browse(currency_id).exists()
if not currency or not currency.active:
raise werkzeug.exceptions.NotFound() # The currency must exist and be active.
raise NotFound() # The currency must exist and be active.
# Select all providers and tokens that match the constraints
availability_report = {}
# Select all the payment methods and tokens that match the payment context.
providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers(
company_id, partner_sudo.id, amount, currency_id=currency.id, **kwargs
) # In sudo mode to read the fields of providers and partner (if not logged in)
if provider_id in providers_sudo.ids: # Only keep the desired provider if it's suitable
providers_sudo = providers_sudo.browse(provider_id)
payment_tokens = request.env['payment.token'].search(
[('provider_id', 'in', providers_sudo.ids), ('partner_id', '=', partner_sudo.id)]
) if logged_in else request.env['payment.token']
company_id,
partner_sudo.id,
amount,
currency_id=currency.id,
report=availability_report,
**kwargs,
) # In sudo mode to read the fields of providers and partner (if logged out).
payment_methods_sudo = request.env['payment.method'].sudo()._get_compatible_payment_methods(
providers_sudo.ids,
partner_sudo.id,
currency_id=currency.id,
report=availability_report,
**kwargs,
) # In sudo mode to read the fields of providers.
tokens_sudo = request.env['payment.token'].sudo()._get_available_tokens(
providers_sudo.ids, partner_sudo.id
) # In sudo mode to be able to read tokens of other partners and the fields of providers.
# Make sure that the partner's company matches the company passed as parameter.
if not PaymentPortal._can_partner_pay_in_company(partner_sudo, company):
providers_sudo = request.env['payment.provider'].sudo()
payment_tokens = request.env['payment.token']
# Compute the fees taken by providers supporting the feature
fees_by_provider = {
provider_sudo: provider_sudo._compute_fees(amount, currency, partner_sudo.country_id)
for provider_sudo in providers_sudo.filtered('fees_active')
}
company_mismatch = not PaymentPortal._can_partner_pay_in_company(partner_sudo, company)
# Generate a new access token in case the partner id or the currency id was updated
access_token = payment_utils.generate_access_token(partner_sudo.id, amount, currency.id)
rendering_context = {
'providers': providers_sudo,
'tokens': payment_tokens,
'fees_by_provider': fees_by_provider,
'show_tokenize_input': self._compute_show_tokenize_input_mapping(
providers_sudo, logged_in=logged_in, **kwargs
portal_page_values = {
'res_company': company, # Display the correct logo in a multi-company environment.
'company_mismatch': company_mismatch,
'expected_company': company,
'partner_is_different': partner_is_different,
}
payment_form_values = {
'show_tokenize_input_mapping': self._compute_show_tokenize_input_mapping(
providers_sudo, **kwargs
),
}
payment_context = {
'reference_prefix': reference,
'amount': amount,
'currency': currency,
'partner_id': partner_sudo.id,
'access_token': access_token,
'providers_sudo': providers_sudo,
'payment_methods_sudo': payment_methods_sudo,
'tokens_sudo': tokens_sudo,
'availability_report': availability_report,
'transaction_route': '/payment/transaction',
'landing_route': '/payment/confirmation',
'res_company': company, # Display the correct logo in a multi-company environment
'partner_is_different': partner_is_different,
**self._get_custom_rendering_context_values(**kwargs),
'access_token': access_token,
}
rendering_context = {
**portal_page_values,
**payment_form_values,
**payment_context,
**self._get_extra_payment_form_values(
**payment_context, currency_id=currency.id, **kwargs
), # Pass the payment context to allow overriding modules to check document access.
}
return request.render(self._get_payment_page_template_xmlid(**kwargs), rendering_context)
@staticmethod
def _compute_show_tokenize_input_mapping(providers_sudo, logged_in=False, **kwargs):
def _compute_show_tokenize_input_mapping(providers_sudo, **kwargs):
""" Determine for each provider whether the tokenization input should be shown or not.
:param recordset providers_sudo: The providers for which to determine whether the
tokenization input should be shown or not, as a sudoed
`payment.provider` recordset.
:param bool logged_in: Whether the user is logged in or not.
:param dict kwargs: The optional data passed to the helper methods.
:return: The mapping of the computed value for each provider id.
:rtype: dict
@ -165,8 +183,7 @@ class PaymentPortal(portal.CustomerPortal):
show_tokenize_input_mapping = {}
for provider_sudo in providers_sudo:
show_tokenize_input = provider_sudo.allow_tokenization \
and not provider_sudo._is_tokenization_required(**kwargs) \
and logged_in
and not provider_sudo._is_tokenization_required(**kwargs)
show_tokenize_input_mapping[provider_sudo.id] = show_tokenize_input
return show_tokenize_input_mapping
@ -182,43 +199,63 @@ class PaymentPortal(portal.CustomerPortal):
:rtype: str
"""
partner_sudo = request.env.user.partner_id # env.user is always sudoed
availability_report = {}
# Select all the payment methods and tokens that match the payment context.
providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers(
request.env.company.id,
partner_sudo.id,
0., # There is no amount to pay with validation transactions.
force_tokenization=True,
is_validation=True,
)
# Get all partner's tokens for which providers are not disabled.
tokens_sudo = request.env['payment.token'].sudo().search([
('partner_id', 'in', [partner_sudo.id, partner_sudo.commercial_partner_id.id]),
('provider_id.state', 'in', ['enabled', 'test']),
])
report=availability_report,
**kwargs,
) # In sudo mode to read the fields of providers and partner (if logged out).
payment_methods_sudo = request.env['payment.method'].sudo()._get_compatible_payment_methods(
providers_sudo.ids,
partner_sudo.id,
force_tokenization=True,
report=availability_report,
) # In sudo mode to read the fields of providers.
tokens_sudo = request.env['payment.token'].sudo()._get_available_tokens(
None, partner_sudo.id, is_validation=True
) # In sudo mode to read the commercial partner's and providers' fields.
access_token = payment_utils.generate_access_token(partner_sudo.id, None, None)
rendering_context = {
'providers': providers_sudo,
'tokens': tokens_sudo,
payment_form_values = {
'mode': 'validation',
'allow_token_selection': False,
'allow_token_deletion': True,
}
payment_context = {
'reference_prefix': payment_utils.singularize_reference_prefix(prefix='V'),
'partner_id': partner_sudo.id,
'access_token': access_token,
'providers_sudo': providers_sudo,
'payment_methods_sudo': payment_methods_sudo,
'tokens_sudo': tokens_sudo,
'availability_report': availability_report,
'transaction_route': '/payment/transaction',
'landing_route': '/my/payment_method',
**self._get_custom_rendering_context_values(**kwargs),
'access_token': access_token,
}
rendering_context = {
**payment_form_values,
**payment_context,
**self._get_extra_payment_form_values(**kwargs),
}
return request.render('payment.payment_methods', rendering_context)
def _get_custom_rendering_context_values(self, **kwargs):
""" Return a dict of additional rendering context values.
def _get_extra_payment_form_values(self, **kwargs):
""" Return a dict of extra payment form values to include in the rendering context.
:param dict kwargs: Optional data. This parameter is not used here
:return: The dict of additional rendering context values
:param dict kwargs: Optional data. This parameter is not used here.
:return: The dict of extra payment form values.
:rtype: dict
"""
return {}
@http.route('/payment/transaction', type='json', auth='public')
@http.route('/payment/transaction', type='jsonrpc', auth='public')
def payment_transaction(self, amount, currency_id, partner_id, access_token, **kwargs):
""" Create a draft transaction and return its processing values.
@ -231,58 +268,61 @@ class PaymentPortal(portal.CustomerPortal):
:param dict kwargs: Locally unused data passed to `_create_transaction`
:return: The mandatory values for the processing of the transaction
:rtype: dict
:raise: ValidationError if the access token is invalid
:raise Forbidden: If the access token is invalid.
"""
# Check the access token against the transaction values
amount = amount and float(amount) # Cast as float in case the JS stripped the '.0'
if not payment_utils.check_access_token(access_token, partner_id, amount, currency_id):
raise ValidationError(_("The access token is invalid."))
raise Forbidden()
kwargs.pop('custom_create_values', None) # Don't allow passing arbitrary create values
self._validate_transaction_kwargs(kwargs, additional_allowed_keys=('reference_prefix',))
tx_sudo = self._create_transaction(
amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs
)
self._update_landing_route(tx_sudo, access_token) # Add the required parameters to the route
self._update_landing_route(tx_sudo, access_token) # Add the required params to the route.
return tx_sudo._get_processing_values()
def _create_transaction(
self, payment_option_id, reference_prefix, amount, currency_id, partner_id, flow,
tokenization_requested, landing_route, is_validation=False,
self, provider_id, payment_method_id, token_id, amount, currency_id, partner_id, flow,
tokenization_requested, landing_route, reference_prefix=None, is_validation=False,
custom_create_values=None, **kwargs
):
""" Create a draft transaction based on the payment context and return it.
:param int payment_option_id: The payment option handling the transaction, as a
`payment.provider` id or a `payment.token` id
:param str reference_prefix: The custom prefix to compute the full reference
:param float|None amount: The amount to pay in the given currency.
None if in a payment method validation operation
:param int|None currency_id: The currency of the transaction, as a `res.currency` id.
None if in a payment method validation operation
:param int partner_id: The partner making the payment, as a `res.partner` id
:param str flow: The online payment flow of the transaction: 'redirect', 'direct' or 'token'
:param bool tokenization_requested: Whether the user requested that a token is created
:param str landing_route: The route the user is redirected to after the transaction
:param bool is_validation: Whether the operation is a validation
:param dict custom_create_values: Additional create values overwriting the default ones
:param int provider_id: The provider of the provider payment method or token, as a
`payment.provider` id.
:param int|None payment_method_id: The payment method, if any, as a `payment.method` id.
:param int|None token_id: The token, if any, as a `payment.token` id.
:param float|None amount: The amount to pay, or `None` if in a validation operation.
:param int|None currency_id: The currency of the amount, as a `res.currency` id, or `None`
if in a validation operation.
:param int partner_id: The partner making the payment, as a `res.partner` id.
:param str flow: The online payment flow of the transaction: 'redirect', 'direct' or 'token'.
:param bool tokenization_requested: Whether the user requested that a token is created.
:param str landing_route: The route the user is redirected to after the transaction.
:param str reference_prefix: The custom prefix to compute the full reference.
:param bool is_validation: Whether the operation is a validation.
:param dict custom_create_values: Additional create values overwriting the default ones.
:param dict kwargs: Locally unused data passed to `_is_tokenization_required` and
`_compute_reference`
:return: The sudoed transaction that was created
:rtype: recordset of `payment.transaction`
:raise: UserError if the flow is invalid
`_compute_reference`.
:return: The sudoed transaction that was created.
:rtype: payment.transaction
"""
# Prepare create values
# Prepare the create values.
provider_sudo = request.env['payment.provider'].sudo().browse(provider_id)
tokenize = False
if flow in ['redirect', 'direct']: # Direct payment or payment with redirection
provider_sudo = request.env['payment.provider'].sudo().browse(payment_option_id)
payment_method_sudo = request.env['payment.method'].sudo().browse(payment_method_id)
token_id = None
tokenize = bool(
# Don't tokenize if the user tried to force it through the browser's developer tools
provider_sudo.allow_tokenization
and payment_method_sudo.support_tokenization
# Token is only created if required by the flow or requested by the user
and (provider_sudo._is_tokenization_required(**kwargs) or tokenization_requested)
)
elif flow == 'token': # Payment by token
token_sudo = request.env['payment.token'].sudo().browse(payment_option_id)
token_sudo = request.env['payment.token'].sudo().browse(token_id)
# Prevent from paying with a token that doesn't belong to the current partner (either
# the current user's partner if logged in, or the partner on behalf of whom the payment
@ -291,13 +331,7 @@ class PaymentPortal(portal.CustomerPortal):
if partner_sudo.commercial_partner_id != token_sudo.partner_id.commercial_partner_id:
raise AccessError(_("You do not have access to this payment token."))
provider_sudo = token_sudo.provider_id
token_id = payment_option_id
tokenize = False
else:
raise UserError(
_("The payment should either be direct, with redirection, or made by a token.")
)
payment_method_id = token_sudo.payment_method_id.id
reference = request.env['payment.transaction']._compute_reference(
provider_sudo.code,
@ -307,11 +341,15 @@ class PaymentPortal(portal.CustomerPortal):
)
if is_validation: # Providers determine the amount and currency in validation operations
amount = provider_sudo._get_validation_amount()
currency_id = provider_sudo._get_validation_currency().id
payment_method = request.env['payment.method'].browse(payment_method_id)
currency_id = provider_sudo.with_context(
validation_pm=payment_method # Will be converted to a kwarg in master.
)._get_validation_currency().id
# Create the transaction
tx_sudo = request.env['payment.transaction'].sudo().create({
'provider_id': provider_sudo.id,
'payment_method_id': payment_method_id,
'reference': reference,
'amount': amount,
'currency_id': currency_id,
@ -323,13 +361,13 @@ class PaymentPortal(portal.CustomerPortal):
**(custom_create_values or {}),
}) # In sudo mode to allow writing on callback fields
if flow == 'token':
tx_sudo._send_payment_request() # Payments by token process transactions immediately
else:
tx_sudo._log_sent_message()
if flow != 'token':
tx_sudo._log_sent_message() # Direct/Redirect payments go through the payment form.
elif not request.env.context.get('delay_token_charge'):
tx_sudo._charge_with_token() # Token payments are charged immediately.
# Monitor the transaction to make it available in the portal
PaymentPostProcessing.monitor_transactions(tx_sudo)
# Monitor the transaction to make it available in the portal.
PaymentPostProcessing.monitor_transaction(tx_sudo)
return tx_sudo
@ -360,7 +398,7 @@ class PaymentPortal(portal.CustomerPortal):
:param str tx_id: The transaction to confirm, as a `payment.transaction` id
:param str access_token: The access token used to verify the user
:param dict kwargs: Optional data. This parameter is not used here
:raise: werkzeug.exceptions.NotFound if the access token is invalid
:raise NotFound: If the access token is invalid.
"""
tx_id = self._cast_as_int(tx_id)
if tx_id:
@ -370,10 +408,7 @@ class PaymentPortal(portal.CustomerPortal):
if not payment_utils.check_access_token(
access_token, tx_sudo.partner_id.id, tx_sudo.amount, tx_sudo.currency_id.id
):
raise werkzeug.exceptions.NotFound() # Don't leak information about ids.
# Stop monitoring the transaction now that it reached a final state.
PaymentPostProcessing.remove_transactions(tx_sudo)
raise NotFound() # Don't leak information about ids.
# Display the payment confirmation page to the user
return request.render('payment.confirm', qcontext={'tx': tx_sudo})
@ -381,7 +416,7 @@ class PaymentPortal(portal.CustomerPortal):
# Display the portal homepage to the user
return request.redirect('/my/home')
@http.route('/payment/archive_token', type='json', auth='user')
@http.route('/payment/archive_token', type='jsonrpc', auth='user')
def archive_token(self, token_id):
""" Check that a user has write access on a token and archive the token if so.
@ -441,3 +476,36 @@ class PaymentPortal(portal.CustomerPortal):
:rtype: str
"""
return not partner.company_id or partner.company_id == document_company
@staticmethod
def _validate_transaction_kwargs(kwargs, additional_allowed_keys=()):
""" Verify that the keys of a transaction route's kwargs are all whitelisted.
The whitelist consists of all the keys that are expected to be passed to a transaction
route, plus optional contextually allowed keys.
This method must be called in all transaction routes to ensure that no undesired kwarg can
be passed as param and then injected in the create values of the transaction.
:param dict kwargs: The transaction route's kwargs to verify.
:param tuple additional_allowed_keys: The keys of kwargs that are contextually allowed.
:return: None
:raise BadRequest: If some kwargs keys are rejected.
"""
whitelist = {
'provider_id',
'payment_method_id',
'token_id',
'amount',
'flow',
'tokenization_requested',
'landing_route',
'is_validation',
'csrf_token',
}
whitelist.update(additional_allowed_keys)
rejected_keys = set(kwargs.keys()) - whitelist
if rejected_keys:
raise BadRequest(
_("The following kwargs are not whitelisted: %s", ', '.join(rejected_keys))
)

View file

@ -1,13 +1,14 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from datetime import timedelta
import psycopg2
from odoo import fields, http
from odoo import http
from odoo.http import request
from odoo.tools.translate import LazyTranslate
_lt = LazyTranslate(__name__)
_logger = logging.getLogger(__name__)
@ -22,118 +23,70 @@ class PaymentPostProcessing(http.Controller):
their post-processing.
"""
MONITORED_TX_IDS_KEY = '__payment_monitored_tx_ids__'
MONITORED_TX_ID_KEY = '__payment_monitored_tx_id__'
@http.route('/payment/status', type='http', auth='public', website=True, sitemap=False)
@http.route('/payment/status', type='http', auth='public', website=True, sitemap=False, list_as_website_content=_lt("Payment Status"))
def display_status(self, **kwargs):
""" Display the payment status page.
""" Fetch the transaction and display it on the payment status page.
:param dict kwargs: Optional data. This parameter is not used here
:return: The rendered status page
:rtype: str
"""
return request.render('payment.payment_status')
monitored_tx = self._get_monitored_transaction()
# The session might have expired, or the transaction never existed.
values = {'tx': monitored_tx} if monitored_tx else {'payment_not_found': True}
return request.render('payment.payment_status', values)
@http.route('/payment/status/poll', type='json', auth='public')
@http.route('/payment/status/poll', type='jsonrpc', auth='public')
def poll_status(self, **_kwargs):
""" Fetch the transactions to display on the status page and finalize their post-processing.
""" Fetch the transaction and trigger its post-processing.
:return: The post-processing values of the transactions
:return: The post-processing values of the transaction.
:rtype: dict
"""
# Retrieve recent user's transactions from the session
limit_date = fields.Datetime.now() - timedelta(days=1)
monitored_txs = request.env['payment.transaction'].sudo().search([
('id', 'in', self.get_monitored_transaction_ids()),
('last_state_change', '>=', limit_date)
])
if not monitored_txs: # The transaction was not correctly created
return {
'success': False,
'error': 'no_tx_found',
}
# We only poll the payment status if a payment was found, so the transaction should exist.
monitored_tx = self._get_monitored_transaction()
# Build the list of display values with the display message and post-processing values
display_values_list = []
for tx in monitored_txs:
display_message = None
if tx.state == 'pending':
display_message = tx.provider_id.pending_msg
elif tx.state == 'done':
display_message = tx.provider_id.done_msg
elif tx.state == 'cancel':
display_message = tx.provider_id.cancel_msg
display_values_list.append({
'display_message': display_message,
**tx._get_post_processing_values(),
})
# Stop monitoring already post-processed transactions
post_processed_txs = monitored_txs.filtered('is_post_processed')
self.remove_transactions(post_processed_txs)
# Finalize post-processing of transactions before displaying them to the user
txs_to_post_process = (monitored_txs - post_processed_txs).filtered(
lambda t: t.state == 'done'
)
success, error = True, None
try:
txs_to_post_process._finalize_post_processing()
except psycopg2.OperationalError: # A collision of accounting sequences occurred
request.env.cr.rollback() # Rollback and try later
success = False
error = 'tx_process_retry'
except Exception as e:
request.env.cr.rollback()
success = False
error = str(e)
_logger.exception(
"encountered an error while post-processing transactions with ids %s:\n%s",
', '.join([str(tx_id) for tx_id in txs_to_post_process.ids]), e
)
# Post-process the transaction before redirecting the user to the landing route and its
# document.
if not monitored_tx.is_post_processed:
try:
monitored_tx._post_process()
except (
psycopg2.OperationalError, psycopg2.IntegrityError
): # The database cursor could not be committed.
request.env.cr.rollback() # Rollback and try later.
raise Exception('retry')
except Exception as e:
request.env.cr.rollback()
_logger.exception(
"Encountered an error while post-processing transaction with id %s:\n%s",
monitored_tx.id, e
)
raise
return {
'success': success,
'error': error,
'display_values_list': display_values_list,
'provider_code': monitored_tx.provider_code,
'state': monitored_tx.state,
'landing_route': monitored_tx.landing_route,
}
@classmethod
def monitor_transactions(cls, transactions):
""" Add the ids of the provided transactions to the list of monitored transaction ids.
def monitor_transaction(cls, transaction):
""" Make the provided transaction id monitored.
:param recordset transactions: The transactions to monitor, as a `payment.transaction`
recordset
:param payment.transaction transaction: The transaction to monitor.
:return: None
"""
if transactions:
monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
request.session[cls.MONITORED_TX_IDS_KEY] = list(
set(monitored_tx_ids).union(transactions.ids)
)
request.session[cls.MONITORED_TX_ID_KEY] = transaction.id
@classmethod
def get_monitored_transaction_ids(cls):
""" Return the ids of transactions being monitored.
def _get_monitored_transaction(self):
""" Retrieve the user's last transaction from the session (the transaction being monitored).
Only the ids and not the recordset itself is returned to allow the caller browsing the
recordset with sudo privileges, and using the ids in a custom query.
:return: The ids of transactions being monitored
:rtype: list
:return: the user's last transaction
:rtype: payment.transaction
"""
return request.session.get(cls.MONITORED_TX_IDS_KEY, [])
@classmethod
def remove_transactions(cls, transactions):
""" Remove the ids of the provided transactions from the list of monitored transaction ids.
:param recordset transactions: The transactions to remove, as a `payment.transaction`
recordset
:return: None
"""
if transactions:
monitored_tx_ids = request.session.get(cls.MONITORED_TX_IDS_KEY, [])
request.session[cls.MONITORED_TX_IDS_KEY] = [
tx_id for tx_id in monitored_tx_ids if tx_id not in transactions.ids
]
return request.env['payment.transaction'].sudo().browse(
request.session.get(self.MONITORED_TX_ID_KEY)
).exists()

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_start_payment_onboarding" model="ir.actions.server">
<field name="name"/>
<field name="model_id" ref="payment.model_payment_provider"/>
<field name="state">code</field>
<field name="code">
action = env['res.config.settings'].sudo().create({})._start_payment_onboarding()
</field>
</record>
</odoo>

View file

@ -2,14 +2,16 @@
<odoo>
<record model="ir.cron" id="cron_post_process_payment_tx">
<field name="name">payment: post-process transactions</field>
<field name="name">Payment: Post-process transactions</field>
<field name="model_id" ref="payment.model_payment_transaction" />
<field name="state">code</field>
<field name="code">model._cron_finalize_post_processing()</field>
<field name="code">model._cron_post_process()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">10</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="active" eval="False"/>
</record>
<function model="payment.provider" name="_toggle_post_processing_cron"/>
</odoo>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Enable the EUR currency since it's the currency of the company. -->
<function model="res.currency" name="action_unarchive" eval="[[ref('base.EUR')]]"/>
</odoo>

View file

@ -1,184 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_icon_cc_visa" model="payment.icon">
<field name="sequence">10</field>
<field name="name">VISA</field>
<field name="image" type="base64" file="payment/static/img/visa.png"/>
</record>
<record id="payment_icon_cc_mastercard" model="payment.icon">
<field name="sequence">20</field>
<field name="name">MasterCard</field>
<field name="image" type="base64" file="payment/static/img/mastercard.png"/>
</record>
<record id="payment_icon_cc_american_express" model="payment.icon">
<field name="sequence">30</field>
<field name="name">American Express</field>
<field name="image" type="base64" file="payment/static/img/american_express.png"/>
</record>
<record id="payment_icon_cc_discover" model="payment.icon">
<field name="sequence">40</field>
<field name="name">Discover</field>
<field name="image" type="base64" file="payment/static/img/discover.png"/>
</record>
<record id="payment_icon_cc_diners_club_intl" model="payment.icon">
<field name="sequence">50</field>
<field name="name">Diners Club International</field>
<field name="image" type="base64" file="payment/static/img/diners_club_intl.png"/>
</record>
<record id="payment_icon_paypal" model="payment.icon">
<field name="sequence">60</field>
<field name="name">Paypal</field>
<field name="image" type="base64" file="payment/static/img/paypal.png"/>
</record>
<record id="payment_icon_cc_rupay" model="payment.icon">
<field name="sequence">65</field>
<field name="name">Rupay</field>
<field name="image" type="base64" file="payment/static/img/rupay.png"/>
</record>
<record id="payment_icon_apple_pay" model="payment.icon">
<field name="sequence">70</field>
<field name="name">Apple Pay</field>
<field name="image" type="base64" file="payment/static/img/applepay.png"/>
</record>
<record id="payment_icon_cc_jcb" model="payment.icon">
<field name="sequence">80</field>
<field name="name">JCB</field>
<field name="image" type="base64" file="payment/static/img/jcb.png"/>
</record>
<record id="payment_icon_cc_maestro" model="payment.icon">
<field name="sequence">90</field>
<field name="name">Maestro</field>
<field name="image" type="base64" file="payment/static/img/maestro.png"/>
</record>
<record id="payment_icon_cc_cirrus" model="payment.icon">
<field name="sequence">100</field>
<field name="name">Cirrus</field>
<field name="image" type="base64" file="payment/static/img/cirrus.png"/>
</record>
<record id="payment_icon_cc_unionpay" model="payment.icon">
<field name="sequence">110</field>
<field name="name">UnionPay</field>
<field name="image" type="base64" file="payment/static/img/unionpay.png"/>
</record>
<record id="payment_icon_cc_bancontact" model="payment.icon">
<field name="sequence">120</field>
<field name="name">Bancontact</field>
<field name="image" type="base64" file="payment/static/img/bancontact.png"/>
</record>
<record id="payment_icon_cc_western_union" model="payment.icon">
<field name="sequence">130</field>
<field name="name">Western Union</field>
<field name="image" type="base64" file="payment/static/img/western_union.png"/>
</record>
<record id="payment_icon_sepa" model="payment.icon">
<field name="sequence">140</field>
<field name="name">SEPA Direct Debit</field>
<field name="image" type="base64" file="payment/static/img/sepa.png"/>
</record>
<record id="payment_icon_cc_ideal" model="payment.icon">
<field name="sequence">150</field>
<field name="name">iDEAL</field>
<field name="image" type="base64" file="payment/static/img/ideal.png"/>
</record>
<record id="payment_icon_cc_webmoney" model="payment.icon">
<field name="sequence">160</field>
<field name="name">WebMoney</field>
<field name="image" type="base64" file="payment/static/img/webmoney.png"/>
</record>
<record id="payment_icon_cc_giropay" model="payment.icon">
<field name="sequence">170</field>
<field name="name">Giropay</field>
<field name="image" type="base64" file="payment/static/img/giropay.png"/>
</record>
<record id="payment_icon_cc_eps" model="payment.icon">
<field name="sequence">180</field>
<field name="name">EPS</field>
<field name="image" type="base64" file="payment/static/img/eps.png"/>
</record>
<record id="payment_icon_cc_p24" model="payment.icon">
<field name="sequence">190</field>
<field name="name">P24</field>
<field name="image" type="base64" file="payment/static/img/p24.png"/>
</record>
<record id="payment_icon_cc_codensa_easy_credit" model="payment.icon">
<field name="sequence">200</field>
<field name="name">Codensa Easy Credit</field>
<field name="image" type="base64" file="payment/static/img/codensa_easy_credit.png"/>
</record>
<record id="payment_icon_kbc" model="payment.icon">
<field name="sequence">210</field>
<field name="name">KBC</field>
<field name="image" type="base64" file="payment/static/img/kbc.png"/>
</record>
<record id="payment_icon_mpesa" model="payment.icon">
<field name="sequence">220</field>
<field name="name">M-Pesa</field>
<field name="image" type="base64" file="payment/static/img/m-pesa.png"/>
</record>
<record id="payment_icon_airtel_money" model="payment.icon">
<field name="sequence">230</field>
<field name="name">Airtel Money</field>
<field name="image" type="base64" file="payment/static/img/airtel-money.png"/>
</record>
<record id="payment_icon_mtn_mobile_money" model="payment.icon">
<field name="sequence">240</field>
<field name="name">MTN Mobile Money</field>
<field name="image" type="base64" file="payment/static/img/mtn-mobile-money.png"/>
</record>
<record id="payment_icon_barter_by_flutterwave" model="payment.icon">
<field name="sequence">250</field>
<field name="name">Barter by Flutterwave</field>
<field name="image" type="base64" file="payment/static/img/barter-by-flutterwave.png"/>
</record>
<record id="payment_icon_sadad" model="payment.icon">
<field name="sequence">260</field>
<field name="name">Sadad</field>
<field name="image" type="base64" file="payment/static/img/sadad.png"/>
</record>
<record id="payment_icon_mada" model="payment.icon">
<field name="sequence">270</field>
<field name="name">Mada</field>
<field name="image" type="base64" file="payment/static/img/mada.png"/>
</record>
<record id="payment_icon_bbva_bancomer" model="payment.icon">
<field name="sequence">280</field>
<field name="name">BBVA Bancomer</field>
<field name="image" type="base64" file="payment/static/img/bbva-bancomer.png"/>
</record>
<record id="payment_icon_citibanamex" model="payment.icon">
<field name="sequence">280</field>
<field name="name">CitiBanamex</field>
<field name="image" type="base64" file="payment/static/img/citibanamex.png"/>
</record>
</odoo>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,557 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Simplified payment provider data - removed external payment modules dependencies -->
<record id="payment_provider_adyen" model="payment.provider">
<field name="name">Adyen</field>
<field name="image_128" type="base64" file="payment_adyen/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_adyen"/>
<!-- https://www.adyen.com/payment-methods -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_ach_direct_debit'),
ref('payment.payment_method_affirm'),
ref('payment.payment_method_afterpay'),
ref('payment.payment_method_alipay'),
ref('payment.payment_method_alipay_hk'),
ref('payment.payment_method_alma'),
ref('payment.payment_method_bacs_direct_debit'),
ref('payment.payment_method_bancontact'),
ref('payment.payment_method_benefit'),
ref('payment.payment_method_bizum'),
ref('payment.payment_method_blik'),
ref('payment.payment_method_card'),
ref('payment.payment_method_cash_app_pay'),
ref('payment.payment_method_clearpay'),
ref('payment.payment_method_dana'),
ref('payment.payment_method_duitnow'),
ref('payment.payment_method_elo'),
ref('payment.payment_method_eps'),
ref('payment.payment_method_fpx'),
ref('payment.payment_method_gcash'),
ref('payment.payment_method_gopay'),
ref('payment.payment_method_hipercard'),
ref('payment.payment_method_ideal'),
ref('payment.payment_method_kakaopay'),
ref('payment.payment_method_klarna'),
ref('payment.payment_method_klarna_paynow'),
ref('payment.payment_method_klarna_pay_over_time'),
ref('payment.payment_method_knet'),
ref('payment.payment_method_mbway'),
ref('payment.payment_method_mobile_pay'),
ref('payment.payment_method_momo'),
ref('payment.payment_method_multibanco'),
ref('payment.payment_method_napas_card'),
ref('payment.payment_method_online_banking_czech_republic'),
ref('payment.payment_method_online_banking_india'),
ref('payment.payment_method_online_banking_slovakia'),
ref('payment.payment_method_online_banking_thailand'),
ref('payment.payment_method_open_banking'),
ref('payment.payment_method_p24'),
ref('payment.payment_method_paybright'),
ref('payment.payment_method_paysafecard'),
ref('payment.payment_method_paynow'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_paytm'),
ref('payment.payment_method_paytrail'),
ref('payment.payment_method_pix'),
ref('payment.payment_method_promptpay'),
ref('payment.payment_method_ratepay'),
ref('payment.payment_method_samsung_pay'),
ref('payment.payment_method_sepa_direct_debit'),
ref('payment.payment_method_sofort'),
ref('payment.payment_method_swish'),
ref('payment.payment_method_touch_n_go'),
ref('payment.payment_method_trustly'),
ref('payment.payment_method_twint'),
ref('payment.payment_method_upi'),
ref('payment.payment_method_vipps'),
ref('payment.payment_method_wallets_india'),
ref('payment.payment_method_walley'),
ref('payment.payment_method_wechat_pay'),
ref('payment.payment_method_zip'),
])]"
/>
</record>
<record id="payment_provider_aps" model="payment.provider">
<field name="name">Amazon Payment Services</field>
<field name="image_128" type="base64" file="payment_aps/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_aps"/>
<!-- https://paymentservices.amazon.com/docs/EN/24.html -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_mada'),
ref('payment.payment_method_knet'),
ref('payment.payment_method_meeza'),
ref('payment.payment_method_naps'),
ref('payment.payment_method_omannet'),
ref('payment.payment_method_benefit'),
])]"
/>
</record>
<record id="payment_provider_asiapay" model="payment.provider">
<field name="name">Asiapay</field>
<field name="image_128" type="base64" file="payment_asiapay/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_asiapay"/>
<!-- See https://www.asiapay.com/payment.html#option -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_alipay'),
ref('payment.payment_method_wechat_pay'),
ref('payment.payment_method_poli'),
ref('payment.payment_method_afterpay'),
ref('payment.payment_method_clearpay'),
ref('payment.payment_method_humm'),
ref('payment.payment_method_zip'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_atome'),
ref('payment.payment_method_pace'),
ref('payment.payment_method_shopback'),
ref('payment.payment_method_grabpay'),
ref('payment.payment_method_samsung_pay'),
ref('payment.payment_method_hoolah'),
ref('payment.payment_method_boost'),
ref('payment.payment_method_duitnow'),
ref('payment.payment_method_touch_n_go'),
ref('payment.payment_method_bancnet'),
ref('payment.payment_method_gcash'),
ref('payment.payment_method_paynow'),
ref('payment.payment_method_linepay'),
ref('payment.payment_method_bangkok_bank'),
ref('payment.payment_method_krungthai_bank'),
ref('payment.payment_method_uob'),
ref('payment.payment_method_scb'),
ref('payment.payment_method_bank_of_ayudhya'),
ref('payment.payment_method_kasikorn_bank'),
ref('payment.payment_method_rabbit_line_pay'),
ref('payment.payment_method_truemoney'),
ref('payment.payment_method_fpx'),
ref('payment.payment_method_fps'),
ref('payment.payment_method_hd'),
ref('payment.payment_method_maybank'),
ref('payment.payment_method_pay_id'),
ref('payment.payment_method_promptpay'),
ref('payment.payment_method_techcom'),
ref('payment.payment_method_tienphong'),
ref('payment.payment_method_ttb'),
ref('payment.payment_method_upi'),
ref('payment.payment_method_vietcom'),
ref('payment.payment_method_tendopay'),
ref('payment.payment_method_alipay_hk'),
ref('payment.payment_method_bharatqr'),
ref('payment.payment_method_momo'),
ref('payment.payment_method_octopus'),
ref('payment.payment_method_maya'),
ref('payment.payment_method_uatp'),
ref('payment.payment_method_tenpay'),
ref('payment.payment_method_enets'),
ref('payment.payment_method_jkopay'),
ref('payment.payment_method_payme'),
ref('payment.payment_method_tmb'),
])]"
/>
</record>
<record id="payment_provider_authorize" model="payment.provider">
<field name="name">Authorize.net</field>
<field name="image_128"
type="base64"
file="payment_authorize/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_authorize"/>
<!-- https://www.authorize.net/solutions/merchantsolutions/onlinemerchantaccount/ -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_ach_direct_debit'),
ref('payment.payment_method_card'),
])]"
/>
</record>
<record id="payment_provider_buckaroo" model="payment.provider">
<field name="name">Buckaroo</field>
<field name="image_128"
type="base64"
file="payment_buckaroo/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_buckaroo"/>
<!-- https://www.buckaroo-payments.com/products/payment-methods/ -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_bancontact'),
ref('payment.payment_method_bank_reference'),
ref('payment.payment_method_card'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_ideal'),
ref('payment.payment_method_afterpay_riverty'),
ref('payment.payment_method_sepa_direct_debit'),
ref('payment.payment_method_alipay'),
ref('payment.payment_method_wechat_pay'),
ref('payment.payment_method_klarna'),
ref('payment.payment_method_trustly'),
ref('payment.payment_method_sofort'),
ref('payment.payment_method_in3'),
ref('payment.payment_method_tinka'),
ref('payment.payment_method_billink'),
ref('payment.payment_method_kbc_cbc'),
ref('payment.payment_method_belfius'),
ref('payment.payment_method_p24'),
ref('payment.payment_method_poste_pay'),
ref('payment.payment_method_eps'),
ref('payment.payment_method_cartes_bancaires'),
])]"
/>
</record>
<record id="payment_provider_demo" model="payment.provider">
<field name="name">Demo</field>
<field name="sequence">40</field>
<field name="image_128" type="base64" file="payment_demo/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_demo"/>
</record>
<record id="payment_provider_dpo" model="payment.provider">
<field name="name">DPO Pay</field>
<field name="image_128" type="base64" file="payment_dpo/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_dpo"/>
<!-- https://dpogroup.com/Payment-methods/ -->
<field
name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_dpo'),
])]"
/>
</record>
<record id="payment_provider_flutterwave" model="payment.provider">
<field name="name">Flutterwave</field>
<field name="image_128"
type="base64"
file="payment_flutterwave/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_flutterwave"/>
<!-- https://developer.flutterwave.com/v3.0.0/docs/payment-methods -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_mpesa'),
ref('payment.payment_method_mobile_money'),
ref('payment.payment_method_bank_transfer'),
ref('payment.payment_method_bank_account'),
ref('payment.payment_method_credit'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_ussd'),
])]"
/>
</record>
<record id="payment_provider_iyzico" model="payment.provider">
<field name="name">Iyzico</field>
<field name="image_128" type="base64" file="payment_iyzico/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_iyzico"/>
<field
name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_bank_transfer'),
])]"
/>
</record>
<record id="payment_provider_mercado_pago" model="payment.provider">
<field name="name">Mercado Pago</field>
<field name="image_128"
type="base64"
file="payment_mercado_pago/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_mercado_pago"/>
<!-- Payment methods must be fetched from the API. See
https://www.mercadopago.com.ar/developers/en/reference/payment_methods/_payment_methods/
-->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_bank_transfer'),
ref('payment.payment_method_card'),
ref('payment.payment_method_mercado_pago_wallet'),
])]"
/>
</record>
<record id="payment_provider_mollie" model="payment.provider">
<field name="name">Mollie</field>
<field name="image_128" type="base64" file="payment_mollie/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_mollie"/>
<!-- https://www.mollie.com/en/payments -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_bancontact'),
ref('payment.payment_method_bank_transfer'),
ref('payment.payment_method_belfius'),
ref('payment.payment_method_card'),
ref('payment.payment_method_eps'),
ref('payment.payment_method_ideal'),
ref('payment.payment_method_kbc_cbc'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_paysafecard'),
ref('payment.payment_method_p24'),
ref('payment.payment_method_trustly'),
ref('payment.payment_method_twint'),
])]"
/>
</record>
<record id="payment_provider_nuvei" model="payment.provider">
<field name="name">Nuvei</field>
<field name="image_128" type="base64" file="payment_nuvei/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_nuvei"/>
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_astropay'),
ref('payment.payment_method_boleto'),
ref('payment.payment_method_card'),
ref('payment.payment_method_nuvei_local'),
ref('payment.payment_method_oxxopay'),
ref('payment.payment_method_pix'),
ref('payment.payment_method_pse'),
ref('payment.payment_method_spei'),
ref('payment.payment_method_webpay'),
])]"
/>
</record>
<record id="payment_provider_paymob" model="payment.provider">
<field name="name">Paymob</field>
<field name="image_128" type="base64" file="payment_paymob/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_paymob"/>
<!-- For each country available payment methods are different but card is common for all-->
<!-- https://developers.paymob.com/egypt/payment-methods -->
<field
name="payment_method_ids"
eval="[Command.set([
ref('payment_method_aman'),
ref('payment.payment_method_card'),
ref('payment_method_contact'),
ref('payment_method_easypaisa'),
ref('payment_method_forsa'),
ref('payment_method_halan'),
ref('payment_method_installments_eg'),
ref('payment_method_jazzcash'),
ref('payment_method_kiosk'),
ref('payment_method_mobile_wallet_eg'),
ref('payment_method_omannet'),
ref('payment_method_premium_card'),
ref('payment_method_souhoola'),
ref('payment_method_stcpay'),
ref('payment_method_sympl'),
ref('payment_method_tabby'),
ref('payment_method_tamara'),
ref('payment_method_valu'),
])]"
/>
</record>
<record id="payment_provider_paypal" model="payment.provider">
<field name="name">PayPal</field>
<field name="image_128" type="base64" file="payment_paypal/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_paypal"/>
<!-- https://www.paypal.com/us/selfhelp/article/Which-credit-cards-can-I-accept-with-PayPal-Merchant-Services-FAQ1525#business -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_paypal'),
])]"
/>
</record>
<record id="payment_provider_razorpay" model="payment.provider">
<field name="name">Razorpay</field>
<field name="image_128" type="base64" file="payment_razorpay/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_razorpay"/>
<!-- https://razorpay.com/docs/payments/payment-methods/#supported-payment-methods -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_emi_india'),
ref('payment.payment_method_fpx'),
ref('payment.payment_method_netbanking'),
ref('payment.payment_method_paylater_india'),
ref('payment.payment_method_paynow'),
ref('payment.payment_method_upi'),
ref('payment.payment_method_wallets_india'),
])]"
/>
</record>
<record id="payment_provider_redsys" model="payment.provider">
<field name="name">Redsys</field>
<field name="image_128" type="base64" file="payment_redsys/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_redsys"/>
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_bizum'),
ref('payment.payment_method_card'),
])]"
/>
</record>
<record id="payment_provider_sepa_direct_debit" model="payment.provider">
<field name="name">SEPA Direct Debit</field>
<field name="sequence">20</field>
<field name="image_128"
type="base64"
file="base/static/img/icons/payment_sepa_direct_debit.png"/>
<field name="module_id" ref="base.module_payment_sepa_direct_debit"/>
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_sepa_direct_debit'),
])]"
/>
</record>
<record id="payment_provider_stripe" model="payment.provider">
<field name="name">Stripe</field>
<field name="image_128" type="base64" file="payment_stripe/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_stripe"/>
<!--
See https://stripe.com/payments/payment-methods-guide
See https://support.goteamup.com/hc/en-us/articles/115002089349-Which-cards-and-payment-types-can-I-accept-with-Stripe-
-->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_ach_direct_debit'),
ref('payment.payment_method_affirm'),
ref('payment.payment_method_afterpay'),
ref('payment.payment_method_alipay'),
ref('payment.payment_method_amazon_pay'),
ref('payment.payment_method_bacs_direct_debit'),
ref('payment.payment_method_bancontact'),
ref('payment.payment_method_becs_direct_debit'),
ref('payment.payment_method_boleto'),
ref('payment.payment_method_card'),
ref('payment.payment_method_cash_app_pay'),
ref('payment.payment_method_clearpay'),
ref('payment.payment_method_eps'),
ref('payment.payment_method_fpx'),
ref('payment.payment_method_grabpay'),
ref('payment.payment_method_ideal'),
ref('payment.payment_method_klarna'),
ref('payment.payment_method_mobile_pay'),
ref('payment.payment_method_multibanco'),
ref('payment.payment_method_p24'),
ref('payment.payment_method_paynow'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_pix'),
ref('payment.payment_method_promptpay'),
ref('payment.payment_method_revolut_pay'),
ref('payment.payment_method_sepa_direct_debit'),
ref('payment.payment_method_sofort'),
ref('payment.payment_method_twint'),
ref('payment.payment_method_upi'),
ref('payment.payment_method_wechat_pay'),
ref('payment.payment_method_zip'),
])]"
/>
</record>
<record id="payment_provider_toss_payments" model="payment.provider">
<field name="name">Toss Payments</field>
<field name="image_128" type="base64" file="payment_toss_payments/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_toss_payments"/>
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_card'),
ref('payment.payment_method_mobile'),
ref('payment.payment_method_bank_transfer'),
])]"
/>
</record>
<record id="payment_provider_transfer" model="payment.provider">
<field name="name">Wire Transfer</field>
<field name="sequence">30</field>
<field name="image_128" type="base64" file="payment_custom/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_custom"/>
</record>
<record id="payment_provider_worldline" model="payment.provider">
<field name="name">Worldline</field>
<field name="image_128" type="base64" file="payment_worldline/static/description/icon.png"/>
<field name="module_id" ref="base.module_payment_worldline"/>
<!-- https://docs.direct.worldline-solutions.com/en/payment-methods-and-features/index -->
<field name="payment_method_ids"
eval="[Command.set([
ref('payment.payment_method_alipay_plus'),
ref('payment.payment_method_bancontact'),
ref('payment.payment_method_bizum'),
ref('payment.payment_method_card'),
ref('payment.payment_method_cofidis'),
ref('payment.payment_method_eps'),
ref('payment.payment_method_floa_bank'),
ref('payment.payment_method_ideal'),
ref('payment.payment_method_klarna'),
ref('payment.payment_method_mbway'),
ref('payment.payment_method_multibanco'),
ref('payment.payment_method_p24'),
ref('payment.payment_method_paypal'),
ref('payment.payment_method_post_finance'),
ref('payment.payment_method_twint'),
ref('payment.payment_method_wechat_pay'),
])]"
/>
</record>
<record id="payment_provider_xendit" model="payment.provider">
<field name="name">Xendit</field>
<field name="image_128"
type="base64"
file="payment_xendit/static/description/icon.png"
/>
<field name="module_id" ref="base.module_payment_xendit"/>
<!-- See https://docs.xendit.co/payment-link/payment-channels for payment methods. -->
<field name="payment_method_ids"
eval="[(6, 0, [
ref('payment.payment_method_7eleven'),
ref('payment.payment_method_akulaku'),
ref('payment.payment_method_appota'),
ref('payment.payment_method_bangkok_bank'),
ref('payment.payment_method_bank_bca'),
ref('payment.payment_method_bank_permata'),
ref('payment.payment_method_billease'),
ref('payment.payment_method_bni'),
ref('payment.payment_method_bri'),
ref('payment.payment_method_bsi'),
ref('payment.payment_method_card'),
ref('payment.payment_method_cashalo'),
ref('payment.payment_method_cebuana'),
ref('payment.payment_method_cimb_niaga'),
ref('payment.payment_method_dana'),
ref('payment.payment_method_fpx'),
ref('payment.payment_method_gcash'),
ref('payment.payment_method_grabpay'),
ref('payment.payment_method_jeniuspay'),
ref('payment.payment_method_kfh'),
ref('payment.payment_method_kredivo'),
ref('payment.payment_method_krungthai_bank'),
ref('payment.payment_method_linepay'),
ref('payment.payment_method_linkaja'),
ref('payment.payment_method_mandiri'),
ref('payment.payment_method_maya'),
ref('payment.payment_method_ovo'),
ref('payment.payment_method_promptpay'),
ref('payment.payment_method_qris'),
ref('payment.payment_method_scb'),
ref('payment.payment_method_shopeepay'),
ref('payment.payment_method_truemoney'),
ref('payment.payment_method_uob'),
ref('payment.payment_method_wechat_pay'),
ref('payment.payment_method_touch_n_go'),
ref('payment.payment_method_viettelpay'),
ref('payment.payment_method_vietcapital'),
ref('payment.payment_method_vnptwallet'),
ref('payment.payment_method_vpbank'),
ref('payment.payment_method_woori'),
ref('payment.payment_method_zalopay'),
])]"/>
</record>
</odoo>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,758 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * payment
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Odoo 9.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-08-18 14:07+0000\n"
"PO-Revision-Date: 2015-09-08 06:27+0000\n"
"Last-Translator: Martin Trigaux\n"
"Language-Team: Spanish (Panama) (http://www.transifex.com/odoo/odoo-9/"
"language/es_PA/)\n"
"Language: es_PA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_transaction_callback_eval
msgid ""
" Will be safe_eval with `self` being the current transaction. i."
"e.:\n"
" self.env['my.model'].payment_validated(self)"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_adyen
msgid "-This installs the module payment_adyen."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_authorize
msgid "-This installs the module payment_authorize."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_buckaroo
msgid "-This installs the module payment_buckaroo."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_ogone
msgid "-This installs the module payment_ogone."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_paypal
msgid "-This installs the module payment_paypal."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_sips
msgid "-This installs the module payment_sips."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_account_config_settings_module_payment_transfer
msgid "-This installs the module payment_transfer."
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_html_3ds
msgid "3D Secure HTML"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid ""
"<span class=\"text-danger\">Test</span>\n"
" <span class=\"o_stat_text\">Environment</"
"span>"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid ""
"<span class=\"text-success\">Production</span>\n"
" <span class=\"o_stat_text\">Environment</"
"span>"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_acquirer_id
msgid "Acquirer"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_method_acquirer_id
msgid "Acquirer Account"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_method_acquirer_ref
msgid "Acquirer Ref."
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_acquirer_reference
msgid "Acquirer Reference"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_method_active
msgid "Active"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_fees_active
msgid "Add Extra Fees"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_address
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "Address"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_adyen
msgid "Adyen"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_amount
#: model:ir.model.fields,help:payment.field_payment_transaction_amount
msgid "Amount"
msgstr ""
#. module: payment
#: selection:payment.acquirer,auto_confirm:0
msgid "At payment no acquirer confirmation needed"
msgstr ""
#. module: payment
#: selection:payment.acquirer,auto_confirm:0
msgid "At payment with acquirer confirmation"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_authorize
msgid "Authorize.Net"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_buckaroo
msgid "Buckaroo"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_cancel_msg
msgid "Cancel Message"
msgstr ""
#. module: payment
#: selection:payment.transaction,state:0
msgid "Canceled"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_city
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "City"
msgstr "Ciudad"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_company_id
msgid "Company"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "Configuration"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.payment_acquirer_installation
msgid "Configure payment acquiring methods"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_res_partner_payment_method_count
msgid "Count Payment Method"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_country_id
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "Country"
msgstr "País"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_create_uid
#: model:ir.model.fields,field_description:payment.field_payment_method_create_uid
#: model:ir.model.fields,field_description:payment.field_payment_transaction_create_uid
msgid "Created by"
msgstr "Creado por"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_create_date
#: model:ir.model.fields,field_description:payment.field_payment_method_create_date
msgid "Created on"
msgstr "Creado en"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_create_date
msgid "Creation Date"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "Credentials"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.view_partners_form_payment_defaultcreditcard
msgid "Credit card(s)"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_currency_id
msgid "Currency"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "Customer Details"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_sequence
msgid "Determine the display order"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_display_name
#: model:ir.model.fields,field_description:payment.field_payment_method_display_name
#: model:ir.model.fields,field_description:payment.field_payment_transaction_display_name
msgid "Display Name"
msgstr ""
#. module: payment
#: selection:payment.transaction,state:0
msgid "Done"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_done_msg
msgid "Done Message"
msgstr ""
#. module: payment
#: selection:payment.transaction,state:0
msgid "Draft"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "E-mail"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_email
msgid "Email"
msgstr "Correo electrónico"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_environment
msgid "Environment"
msgstr ""
#. module: payment
#: selection:payment.transaction,state:0
msgid "Error"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_error_msg
msgid "Error Message"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_fees
msgid "Fees"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_transaction_fees
msgid "Fees amount; set by the system because depends on the acquirer"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_transaction_state_message
msgid "Field used to store error and/or validation messages for information"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_fees_dom_fixed
msgid "Fixed domestic fees"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_fees_int_fixed
msgid "Fixed international fees"
msgstr ""
#. module: payment
#: selection:payment.transaction,type:0
msgid "Form"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_view_template_id
msgid "Form Button Template"
msgstr ""
#. module: payment
#: selection:payment.transaction,type:0
msgid "Form with credentials storage"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_search
msgid "Group By"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_pre_msg
msgid "Help Message"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_id
#: model:ir.model.fields,field_description:payment.field_payment_method_id
#: model:ir.model.fields,field_description:payment.field_payment_transaction_id
msgid "ID"
msgstr "ID"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_image
msgid "Image"
msgstr "Imagen"
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_transaction_reference
msgid "Internal reference of the TX"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_lang
msgid "Language"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer___last_update
#: model:ir.model.fields,field_description:payment.field_payment_method___last_update
#: model:ir.model.fields,field_description:payment.field_payment_transaction___last_update
msgid "Last Modified on"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_write_uid
#: model:ir.model.fields,field_description:payment.field_payment_method_write_uid
#: model:ir.model.fields,field_description:payment.field_payment_transaction_write_uid
msgid "Last Updated by"
msgstr "Última actualización de"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_write_date
#: model:ir.model.fields,field_description:payment.field_payment_method_write_date
#: model:ir.model.fields,field_description:payment.field_payment_transaction_write_date
msgid "Last Updated on"
msgstr "Última actualización en"
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_website_published
msgid "Make this payment acquirer available (Customer invoices, etc.)"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_image_medium
msgid "Medium-sized image"
msgstr "Imagen mediana"
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_image_medium
msgid ""
"Medium-sized image of this provider. It is automatically resized as a "
"128x128px image, with aspect ratio preserved. Use this field in form views "
"or some kanban views."
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_state_message
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "Message"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_post_msg
msgid "Message displayed after having done the payment process."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_pre_msg
msgid "Message displayed to explain and help the payment process."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_error_msg
msgid "Message displayed, if error is occur during the payment process."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_cancel_msg
msgid "Message displayed, if order is cancel during the payment process."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_done_msg
msgid ""
"Message displayed, if order is done successfully after having done the "
"payment process."
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_pending_msg
msgid ""
"Message displayed, if order is in pending state after having done the "
"payment process."
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "Messages"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_name
#: model:ir.model.fields,field_description:payment.field_payment_method_name
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "Name"
msgstr "Nombre"
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_method_name
msgid "Name of the payment method"
msgstr ""
#. module: payment
#: selection:payment.acquirer,auto_confirm:0
msgid "No automatic confirmation"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_ogone
msgid "Ogone"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_auto_confirm
msgid "Order Confirmation"
msgstr ""
#. module: payment
#: model:ir.model,name:payment.model_res_partner
#: model:ir.model.fields,field_description:payment.field_payment_method_partner_id
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_id
msgid "Partner"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_name
msgid "Partner Name"
msgstr ""
#. module: payment
#: model:ir.model,name:payment.model_payment_acquirer
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "Payment Acquirer"
msgstr "Método de pago"
#. module: payment
#: model:ir.actions.act_window,name:payment.action_payment_acquirer
#: model:ir.ui.menu,name:payment.payment_acquirer_menu
#: model_terms:ir.ui.view,arch_db:payment.acquirer_list
msgid "Payment Acquirers"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_payment_method_id
msgid "Payment Method"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_res_partner_payment_method_ids
#: model_terms:ir.ui.view,arch_db:payment.payment_method_form_view
#: model_terms:ir.ui.view,arch_db:payment.payment_method_tree_view
#: model_terms:ir.ui.view,arch_db:payment.payment_method_view_search
msgid "Payment Methods"
msgstr ""
#. module: payment
#: model:ir.model,name:payment.model_payment_transaction
msgid "Payment Transaction"
msgstr "Transacción de pago"
#. module: payment
#: model:ir.actions.act_window,name:payment.action_payment_transaction
#: model:ir.actions.act_window,name:payment.action_payment_tx_ids
#: model:ir.actions.act_window,name:payment.payment_transaction_action_child
#: model:ir.model.fields,field_description:payment.field_payment_method_payment_ids
#: model:ir.ui.menu,name:payment.payment_transaction_menu
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
#: model_terms:ir.ui.view,arch_db:payment.transaction_list
msgid "Payment Transactions"
msgstr ""
#. module: payment
#: model:ir.ui.menu,name:payment.root_payment_menu
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
#: model_terms:ir.ui.view,arch_db:payment.payment_method_form_view
msgid "Payments"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_paypal
msgid "Paypal"
msgstr ""
#. module: payment
#: selection:payment.transaction,state:0
msgid "Pending"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_pending_msg
msgid "Pending Message"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_phone
msgid "Phone"
msgstr "Teléfono"
#. module: payment
#: selection:payment.acquirer,environment:0
msgid "Production"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_provider
#: model_terms:ir.ui.view,arch_db:payment.acquirer_search
msgid "Provider"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_reference
msgid "Reference"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_transaction_acquirer_reference
msgid "Reference of the TX as stored in the acquirer database"
msgstr ""
#. module: payment
#: constraint:payment.acquirer:0
msgid "Required fields not filled"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_callback_eval
msgid "S2S Callback"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_registration_view_template_id
msgid "S2S Form Template"
msgstr ""
#. module: payment
#: model:ir.actions.act_window,name:payment.payment_method_action
#: model:ir.ui.menu,name:payment.payment_method_menu
msgid "Saved Payment Data"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_sequence
msgid "Sequence"
msgstr "Secuencia"
#. module: payment
#: selection:payment.transaction,type:0
msgid "Server To Server"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_sips
msgid "Sips"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_image_small
msgid "Small-sized image"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_image_small
msgid ""
"Small-sized image of this provider. It is automatically resized as a 64x64px "
"image, with aspect ratio preserved. Use this field anywhere a small image is "
"required."
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_state
msgid "Status"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_registration_view_template_id
msgid "Template for method registration"
msgstr ""
#. module: payment
#: selection:payment.acquirer,environment:0
msgid "Test"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_post_msg
msgid "Thanks Message"
msgstr ""
#. module: payment
#: constraint:payment.transaction:0
msgid "The payment transaction reference must be unique!"
msgstr ""
#. module: payment
#: model:ir.model.fields,help:payment.field_payment_acquirer_image
msgid ""
"This field holds the image used for this provider, limited to 1024x1024px"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid ""
"This template renders the acquirer button with all necessary values.\n"
" It is be rendered with qWeb with "
"the following evaluation context:"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_type
msgid "Type"
msgstr "Tipo"
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_date_validate
msgid "Validation Date"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_fees_dom_var
msgid "Variable domestic fees (in percents)"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_fees_int_var
msgid "Variable international fees (in percents)"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_acquirer_website_published
msgid "Visible in Portal / Website"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_account_config_settings_module_payment_transfer
msgid "Wire Transfer"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.transaction_form
msgid "ZIP"
msgstr ""
#. module: payment
#: model:ir.model.fields,field_description:payment.field_payment_transaction_partner_zip
msgid "Zip"
msgstr ""
#. module: payment
#: model:ir.model,name:payment.model_account_config_settings
msgid "account.config.settings"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "acquirer: payment.acquirer browse record"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "amount: the transaction amount, a float"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "context: the current context dictionary"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "currency: the transaction currency browse record"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "partner: the buyer partner browse record, not necessarily set"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid ""
"partner_values: specific values about the buyer, for example coming from a "
"shipping form"
msgstr ""
#. module: payment
#: model:ir.model,name:payment.model_payment_method
msgid "payment.method"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "reference: the transaction reference number"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "tx_url: transaction URL to post the form"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "tx_values: transaction values"
msgstr ""
#. module: payment
#: model_terms:ir.ui.view,arch_db:payment.acquirer_form
msgid "user: current user browse record"
msgstr ""

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import re
class SensitiveDataFilter(logging.Filter):
def __init__(self, sensitive_keys):
super().__init__()
if sensitive_keys is None:
self._sensitive_keys = set()
else:
self._sensitive_keys = sensitive_keys
self._compile_patterns()
def _compile_patterns(self):
"""Precompile regex patterns for all sensitive keys, matching double/single-quoted JSON
entries.
:return: None
"""
self._patterns = []
for key in self._sensitive_keys:
# 1st group: "<key>" or '<key>'
# 2nd group: The quote char for the value
pattern = re.compile(rf'([\'"]{key}[\'"])\s*:\s*([\'"])([^\'"]+)\2')
self._patterns.append(pattern)
def filter(self, record):
"""Override of `logging` to mask any sensitive data in record.args before the record is
emitted. Always returns True to allow the record through.
:return: True
:rtype: bool
"""
if len(self._patterns) != len(self._sensitive_keys): # If keys changed.
self._compile_patterns() # Recompile the patterns.
record.args = self._mask(record.args)
return True
def _mask(self, data):
"""Recursively mask dicts, iterables, and strings.
:param data: The data to mask.
:return: The masked data.
"""
if isinstance(data, dict):
masked_dict = {}
for k, v in data.items():
masked_dict[k] = "[REDACTED]" if k in self._sensitive_keys else self._mask(v)
return masked_dict
if isinstance(data, (list, tuple, set)):
cls = type(data)
return cls(self._mask(v) for v in data)
if isinstance(data, str):
return self._mask_string(data)
return data
def _mask_string(self, text):
"""Apply each pattern, replacing the value with [REDACTED].
:param str text: The string to mask.
:return: The masked string.
:rtype: str
"""
def replace(m):
# 1st group: "<key>" or '<key>'
# 2nd group: The quote char for the value
quote = m.group(2)
return f"{m.group(1)}: {quote}[REDACTED]{quote}"
for pattern in self._patterns:
text = re.sub(pattern, replace, text)
return text
def get_payment_logger(name, sensitive_keys=None):
"""Return a logger with a SensitiveDataFilter added if sensitive keys are provided.
:param str name: The name of the logger.
:param set sensitive_keys: The keys of the payment data that should not be logged.
:return: The logger.
:rtype: logging.Logger
"""
logger = logging.getLogger(name)
if sensitive_keys is not None:
logger.addFilter(SensitiveDataFilter(sensitive_keys))
return logger

View file

@ -1,9 +1,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import ir_http
from . import payment_method
from . import payment_provider
from . import payment_icon
from . import payment_token
from . import payment_transaction
from . import res_company
from . import res_country
from . import res_partner

View file

@ -8,5 +8,5 @@ class IrHttp(models.AbstractModel):
@classmethod
def _get_translation_frontend_modules_name(cls):
mods = super(IrHttp, cls)._get_translation_frontend_modules_name()
mods = super()._get_translation_frontend_modules_name()
return mods + ['payment']

View file

@ -1,21 +0,0 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PaymentIcon(models.Model):
_name = 'payment.icon'
_description = 'Payment Icon'
_order = 'sequence, name'
name = fields.Char(string="Name")
provider_ids = fields.Many2many(
string="Providers", comodel_name='payment.provider',
help="The list of providers supporting this payment icon")
image = fields.Image(
string="Image", max_width=64, max_height=64,
help="This field holds the image used for this payment icon, limited to 64x64 px")
image_payment_form = fields.Image(
string="Image displayed on the payment form", related='image', store=True, max_width=45,
max_height=30)
sequence = fields.Integer('Sequence', default=1)

View file

@ -0,0 +1,341 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.fields import Command, Domain
from odoo.addons.payment import utils as payment_utils
from odoo.addons.payment.const import REPORT_REASONS_MAPPING
class PaymentMethod(models.Model):
_name = 'payment.method'
_description = "Payment Method"
_order = 'active desc, sequence, name'
name = fields.Char(string="Name", required=True, translate=True)
code = fields.Char(
string="Code", help="The technical code of this payment method.", required=True
)
sequence = fields.Integer(string="Sequence", default=1)
primary_payment_method_id = fields.Many2one(
string="Primary Payment Method",
help="The primary payment method of the current payment method, if the latter is a brand."
"\nFor example, \"Card\" is the primary payment method of the card brand \"VISA\".",
comodel_name='payment.method',
index='btree_not_null',
)
brand_ids = fields.One2many(
string="Brands",
help="The brands of the payment methods that will be displayed on the payment form.",
comodel_name='payment.method',
inverse_name='primary_payment_method_id',
)
is_primary = fields.Boolean(
string="Is Primary Payment Method",
compute='_compute_is_primary',
search='_search_is_primary',
)
provider_ids = fields.Many2many(
string="Providers",
help="The list of providers supporting this payment method.",
comodel_name='payment.provider',
)
active = fields.Boolean(string="Active", default=True)
image = fields.Image(
string="Image",
help="The base image used for this payment method; in a 64x64 px format.",
max_width=64,
max_height=64,
required=True,
)
image_payment_form = fields.Image(
string="The resized image displayed on the payment form.",
related='image',
store=True,
max_width=45,
max_height=30,
)
# Feature support fields.
support_tokenization = fields.Boolean(
string="Tokenization",
help="Tokenization is the process of saving the payment details as a token that can later"
" be reused without having to enter the payment details again.",
)
support_express_checkout = fields.Boolean(
string="Express Checkout",
help="Express checkout allows customers to pay faster by using a payment method that"
" provides all required billing and shipping information, thus allowing to skip the"
" checkout process.",
)
support_manual_capture = fields.Selection(
string="Manual Capture",
help="The payment is authorized and captured in two steps instead of one.",
selection=[
('none', "Unsupported"),
('full_only', "Full Only"),
('partial', "Full & Partial"),
],
required=True,
default='none'
)
support_refund = fields.Selection(
string="Refund",
help="Refund is a feature allowing to refund customers directly from the payment in Odoo.",
selection=[
('none', "Unsupported"),
('full_only', "Full Only"),
('partial', "Full & Partial"),
],
required=True,
default='none',
)
supported_country_ids = fields.Many2many(
string="Countries",
comodel_name='res.country',
help="The list of countries in which this payment method can be used (if the provider"
" allows it). In other countries, this payment method is not available to customers."
)
supported_currency_ids = fields.Many2many(
string="Currencies",
comodel_name='res.currency',
help="The list of currencies for that are supported by this payment method (if the provider"
" allows it). When paying with another currency, this payment method is not available "
"to customers.",
context={'active_test': False},
)
# === COMPUTE METHODS === #
def _compute_is_primary(self):
for payment_method in self:
payment_method.is_primary = not payment_method.primary_payment_method_id
def _search_is_primary(self, operator, value):
if operator not in ('in', 'not in'):
return NotImplemented
return [('primary_payment_method_id', operator, [False])]
# === ONCHANGE METHODS === #
@api.onchange('active', 'provider_ids', 'support_tokenization')
def _onchange_warn_before_disabling_tokens(self):
""" Display a warning about the consequences of archiving the payment method, detaching it
from a provider, or removing its support for tokenization.
Let the user know that the related tokens will be archived.
:return: A client action with the warning message, if any.
:rtype: dict
"""
disabling = self._origin.active and not self.active
detached_providers = self._origin.provider_ids.filtered(
lambda p: p.id not in self.provider_ids.ids
) # Cannot use recordset difference operation because self.provider_ids is a set of NewIds.
blocking_tokenization = self._origin.support_tokenization and not self.support_tokenization
if disabling or detached_providers or blocking_tokenization:
related_tokens = self.env['payment.token'].with_context(active_test=True).search(
Domain('payment_method_id', 'in', (self._origin + self._origin.brand_ids).ids)
& (Domain('provider_id', 'in', detached_providers.ids) if detached_providers else Domain.TRUE),
) # Fix `active_test` in the context forwarded by the view.
if related_tokens:
return {
'warning': {
'title': _("Warning"),
'message': _(
"This action will also archive %s tokens that are registered with this "
"payment method.", len(related_tokens)
)
}
}
@api.onchange('provider_ids')
def _onchange_provider_ids_warn_before_attaching_payment_method(self):
""" Display a warning before attaching a payment method to a provider.
:return: A client action with the warning message, if any.
:rtype: dict
"""
attached_providers = self.provider_ids.filtered(
lambda p: p.id.origin not in self._origin.provider_ids.ids
)
if attached_providers:
return {
'warning': {
'title': _("Warning"),
'message': _(
"Please make sure that %(payment_method)s is supported by %(provider)s.",
payment_method=self.name,
provider=', '.join(attached_providers.mapped('name'))
)
}
}
# === CONSTRAINT METHODS === #
@api.constrains('active', 'support_manual_capture')
def _check_manual_capture_supported_by_providers(self):
incompatible_pms = self.filtered(
lambda pm:
pm.active
and (pm.primary_payment_method_id or pm).support_manual_capture == 'none'
and any(provider.capture_manually for provider in pm.provider_ids),
)
if incompatible_pms:
raise ValidationError(_(
"The following payment methods cannot be enabled because their payment provider has"
" manual capture activated: %s", ", ".join(incompatible_pms.mapped('name'))
))
# === CRUD METHODS === #
def write(self, vals):
# Handle payment methods being archived, detached from providers, or blocking tokenization.
archiving = vals.get('active') is False
detached_provider_ids = [
v[0] for command, *v in vals['provider_ids'] if command == Command.UNLINK
] if 'provider_ids' in vals else []
blocking_tokenization = vals.get('support_tokenization') is False
if archiving or detached_provider_ids or blocking_tokenization:
linked_tokens = self.env['payment.token'].with_context(active_test=True).search(
Domain('payment_method_id', 'in', (self + self.brand_ids).ids)
& (Domain('provider_id', 'in', detached_provider_ids) if detached_provider_ids else Domain.TRUE),
) # Fix `active_test` in the context forwarded by the view.
linked_tokens.active = False
# Prevent enabling a payment method if it is not linked to an enabled provider.
if vals.get('active'):
for pm in self:
primary_pm = pm if pm.is_primary else pm.primary_payment_method_id
if (
not primary_pm.active # Don't bother for already enabled payment methods.
and all(p.state == 'disabled' for p in primary_pm.provider_ids)
):
raise UserError(_(
"This payment method needs a partner in crime; you should enable a payment"
" provider supporting this method first."
))
return super().write(vals)
@api.ondelete(at_uninstall=False)
def _unlink_if_not_default_payment_method(self):
payment_method_unknown = self.env.ref('payment.payment_method_unknown')
if payment_method_unknown in self:
raise UserError(_("You cannot delete the default payment method."))
# === BUSINESS METHODS === #
def _get_compatible_payment_methods(
self, provider_ids, partner_id, currency_id=None, force_tokenization=False,
is_express_checkout=False, report=None, **kwargs
):
""" Search and return the payment methods matching the compatibility criteria.
The compatibility criteria are that payment methods must: be supported by at least one of
the providers; support the country of the partner if it exists; be primary payment methods
(not a brand). If provided, the optional keyword arguments further refine the criteria.
:param list provider_ids: The list of providers by which the payment methods must be at
least partially supported to be considered compatible, as a list
of `payment.provider` ids.
:param int partner_id: The partner making the payment, as a `res.partner` id.
:param int currency_id: The payment currency, if known beforehand, as a `res.currency` id.
:param bool force_tokenization: Whether only payment methods supporting tokenization can be
matched.
:param bool is_express_checkout: Whether the payment is made through express checkout.
:param dict report: The report in which each provider's availability status and reason must
be logged.
:param dict kwargs: Optional data. This parameter is not used here.
:return: The compatible payment methods.
:rtype: payment.method
"""
# Search compatible payment methods with the base domain.
payment_methods = self.env['payment.method'].search([('is_primary', '=', True)])
payment_utils.add_to_report(report, payment_methods)
# Filter by compatible providers.
unfiltered_pms = payment_methods
payment_methods = payment_methods.filtered(
lambda pm: any(p in provider_ids for p in pm.provider_ids.ids)
)
payment_utils.add_to_report(
report,
unfiltered_pms - payment_methods,
available=False,
reason=REPORT_REASONS_MAPPING['provider_not_available'],
)
# Handle the partner country; allow all countries if the list is empty.
partner = self.env['res.partner'].browse(partner_id)
if partner.country_id: # The partner country must either not be set or be supported.
unfiltered_pms = payment_methods
payment_methods = payment_methods.filtered(
lambda pm: (
not pm.supported_country_ids
or partner.country_id.id in pm.supported_country_ids.ids
)
)
payment_utils.add_to_report(
report,
unfiltered_pms - payment_methods,
available=False,
reason=REPORT_REASONS_MAPPING['incompatible_country'],
)
# Handle the supported currencies; allow all currencies if the list is empty.
if currency_id:
unfiltered_pms = payment_methods
payment_methods = payment_methods.filtered(
lambda pm: (
not pm.supported_currency_ids
or currency_id in pm.supported_currency_ids.ids
)
)
payment_utils.add_to_report(
report,
unfiltered_pms - payment_methods,
available=False,
reason=REPORT_REASONS_MAPPING['incompatible_currency'],
)
# Handle tokenization support requirements.
if force_tokenization:
unfiltered_pms = payment_methods
payment_methods = payment_methods.filtered('support_tokenization')
payment_utils.add_to_report(
report,
unfiltered_pms - payment_methods,
available=False,
reason=REPORT_REASONS_MAPPING['tokenization_not_supported'],
)
# Handle express checkout.
if is_express_checkout:
unfiltered_pms = payment_methods
payment_methods = payment_methods.filtered('support_express_checkout')
payment_utils.add_to_report(
report,
unfiltered_pms - payment_methods,
available=False,
reason=REPORT_REASONS_MAPPING['express_checkout_not_supported'],
)
return payment_methods
def _get_from_code(self, code, mapping=None):
""" Get the payment method corresponding to the given provider-specific code.
If a mapping is given, the search uses the generic payment method code that corresponds to
the given provider-specific code.
:param str code: The provider-specific code of the payment method to get.
:param dict mapping: A non-exhaustive mapping of generic payment method codes to
provider-specific codes.
:return: The corresponding payment method, if any.
:rtype: payment.method
"""
generic_to_specific_mapping = mapping or {}
specific_to_generic_mapping = {v: k for k, v in generic_to_specific_mapping.items()}
return self.search([('code', '=', specific_to_generic_mapping.get(code, code))], limit=1)

Some files were not shown because too many files have changed in this diff Show more