diff --git a/addons/payment/models/payment_token.py b/addons/payment/models/payment_token.py
index f9b958d23af..82f38414370 100644
--- a/addons/payment/models/payment_token.py
+++ b/addons/payment/models/payment_token.py
@@ -16,9 +16,9 @@ class PaymentToken(models.Model):
acquirer_id = fields.Many2one(
string="Acquirer Account", comodel_name='payment.acquirer', required=True)
provider = fields.Selection(related='acquirer_id.provider')
- name = fields.Char(
- string="Name", help="The anonymized acquirer reference of the payment method",
- required=True)
+ payment_details = fields.Char(
+ string="Payment Details", help="The clear part of the payment method's payment details.",
+ )
partner_id = fields.Many2one(string="Partner", comodel_name='res.partner', required=True)
company_id = fields.Many2one( # Indexed to speed-up ORM searches (from ir_rule or others)
related='acquirer_id.company_id', store=True, index=True)
@@ -77,8 +77,6 @@ class PaymentToken(models.Model):
return super().write(values)
- #=== BUSINESS METHODS ===#
-
def _handle_archiving(self):
""" Handle the archiving of the current tokens.
@@ -89,6 +87,45 @@ class PaymentToken(models.Model):
"""
return None
+ def name_get(self):
+ return [(token.id, token._build_display_name()) for token in self]
+
+ #=== BUSINESS METHODS ===#
+
+ def _build_display_name(self, *args, max_length=34, should_pad=True, **kwargs):
+ """ Build a token name of the desired maximum length with the format •••• 1234.
+
+ The payment details are padded on the left with up to four padding characters. The padding
+ is only added if there is enough room for it. If not, it is either reduced or not added at
+ all. If there is not enough room for the payment details either, they are trimmed from the
+ left.
+
+ For a module to customize the display name of a token, it must override this method.
+
+ Note: self.ensure_one()
+
+ :param list args: The arguments passed by QWeb when calling this method.
+ :param int max_length: The desired maximum length of the token name.
+ :param bool should_pad: Whether the token should be padded or not.
+ :param dict kwargs: Optional data used in overrides of this method.
+ :return: The padded token name.
+ :rtype: str
+ """
+ self.ensure_one()
+
+ padding_length = max_length - len(self.payment_details)
+ if not self.payment_details:
+ create_date_str = self.create_date.strftime('%Y/%m/%d')
+ display_name = _("Payment details saved on %(date)s", date=create_date_str)
+ elif padding_length >= 2: # Enough room for padding.
+ padding = '•' * min(padding_length - 1, 4) + ' ' if should_pad else ''
+ display_name = ''.join([padding, self.payment_details])
+ elif padding_length > 0: # Not enough room for padding.
+ display_name = self.payment_details
+ else: # Not enough room for neither padding nor the payment details.
+ display_name = self.payment_details[-max_length:] if max_length > 0 else ''
+ return display_name
+
def get_linked_records_info(self):
""" Return a list of information about records linked to the current token.
diff --git a/addons/payment/models/payment_transaction.py b/addons/payment/models/payment_transaction.py
index 279f588f717..45d53510e22 100644
--- a/addons/payment/models/payment_transaction.py
+++ b/addons/payment/models/payment_transaction.py
@@ -1059,8 +1059,10 @@ class PaymentTransaction(models.Model):
elif self.operation in ('online_token', 'offline'):
message = _(
"A transaction with reference %(ref)s has been initiated using the payment method "
- "%(token_name)s (%(acq_name)s).",
- ref=self.reference, token_name=self.token_id.name, acq_name=self.acquirer_id.name
+ "%(token)s (%(acq_name)s).",
+ ref=self.reference,
+ token=self.token_id._build_display_name(),
+ acq_name=self.acquirer_id.name
)
else: # 'validation'
message = _(
diff --git a/addons/payment/tests/__init__.py b/addons/payment/tests/__init__.py
index fb7389458f4..61d0926039d 100644
--- a/addons/payment/tests/__init__.py
+++ b/addons/payment/tests/__init__.py
@@ -6,4 +6,5 @@ from . import test_account_payment
from . import test_flows
from . import test_multicompany_flows
from . import test_payment_acquirer
+from . import test_payment_token
from . import test_payment_transaction
diff --git a/addons/payment/tests/common.py b/addons/payment/tests/common.py
index 45aa009d1a2..f5911183881 100644
--- a/addons/payment/tests/common.py
+++ b/addons/payment/tests/common.py
@@ -188,7 +188,7 @@ class PaymentCommon(AccountTestInvoicingCommon):
def _create_token(self, sudo=True, **values):
default_values = {
- 'name': "XXXXXXXXXXXXXXX-2565 (TEST)",
+ 'payment_details': "1234",
'acquirer_id': self.acquirer.id,
'partner_id': self.partner.id,
'acquirer_ref': "Acquirer Ref (TEST)",
diff --git a/addons/payment/tests/test_payment_token.py b/addons/payment/tests/test_payment_token.py
index c788cb9ebc6..2e3428ac4b1 100644
--- a/addons/payment/tests/test_payment_token.py
+++ b/addons/payment/tests/test_payment_token.py
@@ -1,5 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import date
+
from odoo.exceptions import UserError
from odoo.tests import tagged
@@ -14,3 +16,26 @@ class TestPaymentToken(PaymentCommon):
token = self._create_token(active=False)
with self.assertRaises(UserError):
token.active = True
+
+ def test_display_name_is_padded(self):
+ """ Test that the display name is built by padding the payment details. """
+ token = self._create_token()
+ self.assertEqual(token._build_display_name(), '•••• 1234')
+
+ def test_display_name_for_empty_payment_details(self):
+ """ Test that the display name is still built for token without payment details. """
+ token = self._create_token(payment_details='')
+ self.assertEqual(
+ token._build_display_name(),
+ f"Payment details saved on {date.today().strftime('%Y/%m/%d')}",
+ )
+
+ def test_display_name_is_shortened_to_max_length(self):
+ """ Test that the display name is not fully padded when a `max_length` is passed. """
+ token = self._create_token()
+ self.assertEqual(token._build_display_name(max_length=6), '• 1234')
+
+ def test_display_name_is_not_padded(self):
+ """ Test that the display name is not padded when `should_pad` is `False`. """
+ token = self._create_token()
+ self.assertEqual(token._build_display_name(should_pad=False), '1234')
diff --git a/addons/payment/utils.py b/addons/payment/utils.py
index 961f212c3c0..32ebf969cd0 100644
--- a/addons/payment/utils.py
+++ b/addons/payment/utils.py
@@ -115,20 +115,6 @@ def to_minor_currency_units(major_amount, currency, arbitrary_decimal_number=Non
return int(float_round(major_amount * (10**decimal_number), precision_digits=0))
-# Token values formatting
-
-def build_token_name(payment_details_short=None, final_length=16):
- """ Pad plain payment details with leading X's to build a token name of the desired length.
-
- :param str payment_details_short: The plain part of the payment details (usually last 4 digits)
- :param int final_length: The desired final length of the token name (16 for a bank card)
- :return: The padded token name
- :rtype: str
- """
- payment_details_short = payment_details_short or '????'
- return f"{'X' * (final_length - len(payment_details_short))}{payment_details_short}"
-
-
# Partner values formatting
def format_partner_address(address1="", address2=""):
diff --git a/addons/payment/views/payment_templates.xml b/addons/payment/views/payment_templates.xml
index 1cbbb9f839d..f9eb75c487e 100644
--- a/addons/payment/views/payment_templates.xml
+++ b/addons/payment/views/payment_templates.xml
@@ -120,7 +120,7 @@
t-att-data-provider="token.provider"
data-payment-option-type="token"/>
-
+
@@ -254,7 +254,7 @@
t-att-data-provider="token.provider"
data-payment-option-type="token"/>
-
+
diff --git a/addons/payment/views/payment_token_views.xml b/addons/payment/views/payment_token_views.xml
index 3bd5eb043a7..69c6cb86d06 100644
--- a/addons/payment/views/payment_token_views.xml
+++ b/addons/payment/views/payment_token_views.xml
@@ -16,7 +16,7 @@
-
+
@@ -34,7 +34,7 @@
payment.token
-
+
diff --git a/addons/payment_adyen/models/payment_transaction.py b/addons/payment_adyen/models/payment_transaction.py
index 2fd6f765364..1cdb35f0a16 100644
--- a/addons/payment_adyen/models/payment_transaction.py
+++ b/addons/payment_adyen/models/payment_transaction.py
@@ -415,7 +415,7 @@ class PaymentTransaction(models.Model):
additional_data = notification_data['additionalData']
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': payment_utils.build_token_name(additional_data.get('cardSummary')),
+ 'payment_details': additional_data.get('cardSummary'),
'partner_id': self.partner_id.id,
'acquirer_ref': additional_data['recurring.recurringDetailReference'],
'adyen_shopper_reference': additional_data['recurring.shopperReference'],
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index 36cfa8a75ba..4d0df3f70e8 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -138,9 +138,10 @@ class AuthorizeAPI:
payment = response.get('paymentProfile', {}).get('payment', {})
if self.payment_method_type == 'credit_card':
- res['name'] = payment.get('creditCard', {}).get('cardNumber')
+ # Authorize.net pads the card and account numbers with X's.
+ res['payment_details'] = payment.get('creditCard', {}).get('cardNumber')[-4:]
else:
- res['name'] = payment.get('bankAccount', {}).get('accountNumber')
+ res['payment_details'] = payment.get('bankAccount', {}).get('accountNumber')[-4:]
return res
def delete_customer_profile(self, profile_id):
diff --git a/addons/payment_authorize/models/payment_transaction.py b/addons/payment_authorize/models/payment_transaction.py
index cbce1681255..7a4fc1e29f4 100644
--- a/addons/payment_authorize/models/payment_transaction.py
+++ b/addons/payment_authorize/models/payment_transaction.py
@@ -293,7 +293,7 @@ class PaymentTransaction(models.Model):
if cust_profile:
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': cust_profile.get('name'),
+ 'payment_details': cust_profile.get('payment_details'),
'partner_id': self.partner_id.id,
'acquirer_ref': cust_profile.get('payment_profile_id'),
'authorize_profile': cust_profile.get('profile_id'),
diff --git a/addons/payment_flutterwave/models/payment_transaction.py b/addons/payment_flutterwave/models/payment_transaction.py
index ec2625d4b0c..0994799d2c8 100644
--- a/addons/payment_flutterwave/models/payment_transaction.py
+++ b/addons/payment_flutterwave/models/payment_transaction.py
@@ -178,7 +178,7 @@ class PaymentTransaction(models.Model):
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': payment_utils.build_token_name(notification_data['card']['last_4digits']),
+ 'payment_details': notification_data['card']['last_4digits'],
'partner_id': self.partner_id.id,
'acquirer_ref': notification_data['card']['token'],
'flutterwave_customer_email': notification_data['customer']['email'],
diff --git a/addons/payment_ogone/models/payment_transaction.py b/addons/payment_ogone/models/payment_transaction.py
index 9d195ec15fe..45d0c5c7446 100644
--- a/addons/payment_ogone/models/payment_transaction.py
+++ b/addons/payment_ogone/models/payment_transaction.py
@@ -228,10 +228,9 @@ class PaymentTransaction(models.Model):
:param dict notification_data: The notification data sent by the provider
:return: None
"""
- token_name = notification_data.get('CARDNO') or payment_utils.build_token_name()
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': token_name, # Already padded with 'X's
+ 'payment_details': notification_data.get('CARDNO')[-4:], # Ogone pads details with X's.
'partner_id': self.partner_id.id,
'acquirer_ref': notification_data['ALIAS'],
'verified': True, # The payment is authorized, so the payment method is valid
diff --git a/addons/payment_stripe/models/payment_transaction.py b/addons/payment_stripe/models/payment_transaction.py
index 07a739af574..a219a41c5a2 100644
--- a/addons/payment_stripe/models/payment_transaction.py
+++ b/addons/payment_stripe/models/payment_transaction.py
@@ -463,7 +463,7 @@ class PaymentTransaction(models.Model):
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': payment_utils.build_token_name(payment_method['card'].get('last4')),
+ 'payment_details': payment_method['card'].get('last4'),
'partner_id': self.partner_id.id,
'acquirer_ref': customer_id,
'verified': True,
diff --git a/addons/payment_test/models/payment_token.py b/addons/payment_test/models/payment_token.py
index 7bddb51285a..16cd53a142c 100644
--- a/addons/payment_test/models/payment_token.py
+++ b/addons/payment_test/models/payment_token.py
@@ -16,3 +16,18 @@ class PaymentToken(models.Model):
('error', "Error"),
],
)
+
+ def _build_display_name(self, *args, should_pad=True, **kwargs):
+ """ Override of `payment` to build the display name without padding.
+
+ Note: self.ensure_one()
+
+ :param list args: The arguments passed by QWeb when calling this method.
+ :param bool should_pad: Whether the token should be padded or not.
+ :param dict kwargs: Optional data.
+ :return: The test token name.
+ :rtype: str
+ """
+ if self.provider != 'test':
+ return super()._build_display_name(*args, should_pad=should_pad, **kwargs)
+ return super()._build_display_name(*args, should_pad=False, **kwargs)
diff --git a/addons/payment_test/models/payment_transaction.py b/addons/payment_test/models/payment_transaction.py
index 109f7a0cbee..c244d69bee7 100644
--- a/addons/payment_test/models/payment_transaction.py
+++ b/addons/payment_test/models/payment_transaction.py
@@ -202,11 +202,10 @@ class PaymentTransaction(models.Model):
"""
self.ensure_one()
- payment_details_short = notification_data['cc_summary']
state = notification_data['simulated_state']
token = self.env['payment.token'].create({
'acquirer_id': self.acquirer_id.id,
- 'name': payment_utils.build_token_name(payment_details_short=payment_details_short),
+ 'payment_details': notification_data['payment_details'],
'partner_id': self.partner_id.id,
'acquirer_ref': 'fake acquirer reference',
'verified': True,
diff --git a/addons/payment_test/static/src/js/payment_form.js b/addons/payment_test/static/src/js/payment_form.js
index 13ae6f9e6d6..d622270a6fa 100644
--- a/addons/payment_test/static/src/js/payment_form.js
+++ b/addons/payment_test/static/src/js/payment_form.js
@@ -31,7 +31,7 @@ odoo.define('payment_test.payment_form', require => {
route: '/payment/test/simulate_payment',
params: {
'reference': processingValues.reference,
- 'cc_summary': customerInput.slice(-4),
+ 'payment_details': customerInput,
'simulated_state': simulatedPaymentState,
},
}).then(() => {
diff --git a/addons/payment_test/tests/common.py b/addons/payment_test/tests/common.py
index 1ed165a5201..78b5582d076 100644
--- a/addons/payment_test/tests/common.py
+++ b/addons/payment_test/tests/common.py
@@ -13,6 +13,6 @@ class PaymentTestCommon(PaymentCommon):
cls.notification_data = {
'reference': cls.reference,
- 'cc_summary': '1234',
+ 'payment_details': '1234',
'simulated_state': 'done',
}