From ddda5d4a2623d03699d5cb6860d5c984807b6e3d Mon Sep 17 00:00:00 2001 From: Horacio Tellez Date: Thu, 8 Dec 2022 10:03:53 +0000 Subject: [PATCH] [IMP] payment: allow the public user to pay with tokens Before this commit the public user did not have access to tokens or the possibility of saving payment methods. When receiving a link to pay the customer (even if not logged in) should be able to use tokens saved by the parter of the document and also save new payment methods. This is intuitively correct: as the possesor of the link, the customer have rights to pay with tokens linked to the partner. After this commit tokens linked to the partner of the document will be visible to the public user and also the possibily to save payment methods. Task - 2799296 closes odoo/odoo#104472 Related: odoo/enterprise#34792 Signed-off-by: Antoine Vandevenne (anv) --- addons/account_payment/controllers/portal.py | 10 ++----- .../views/account_portal_templates.xml | 5 ---- addons/payment/controllers/portal.py | 12 ++++----- addons/payment/models/payment_token.py | 19 ++++++------- addons/payment/tests/test_flows.py | 27 ++++++++++++------- addons/payment/tests/test_payment_token.py | 8 +++++- addons/sale/controllers/portal.py | 5 ++-- addons/sale/tests/test_payment_flow.py | 2 +- addons/website_payment/controllers/portal.py | 8 +++++- 9 files changed, 51 insertions(+), 45 deletions(-) diff --git a/addons/account_payment/controllers/portal.py b/addons/account_payment/controllers/portal.py index d337a7a62a9..95969365aba 100644 --- a/addons/account_payment/controllers/portal.py +++ b/addons/account_payment/controllers/portal.py @@ -31,7 +31,7 @@ class PortalAccount(portal.PortalAccount): currency_id=invoice.currency_id.id ) # In sudo mode to read the fields of providers and partner (if not logged in) tokens = request.env['payment.token']._get_available_tokens( - providers_sudo.ids, partner_id=partner_sudo.id, logged_in=logged_in + providers_sudo.ids, partner_sudo.id ) # Make sure that the partner's company matches the invoice's company. @@ -54,7 +54,7 @@ class PortalAccount(portal.PortalAccount): 'tokens': tokens, 'fees_by_provider': fees_by_provider, 'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping( - providers_sudo, logged_in=logged_in + providers_sudo ), 'amount': invoice.amount_residual, 'currency': invoice.currency_id, @@ -64,10 +64,4 @@ class PortalAccount(portal.PortalAccount): 'landing_route': _build_url_w_params(invoice.access_url, {'access_token': access_token}) } values.update(**portal_page_values, **payment_form_values) - if not logged_in: - # Don't display payment tokens of the invoice partner if the user is not logged in, but - # inform that logging in will make them available. - values.update({ - 'existing_token': bool(tokens), - }) return values diff --git a/addons/account_payment/views/account_portal_templates.xml b/addons/account_payment/views/account_portal_templates.xml index 8c8a8021c37..14aa3c37604 100644 --- a/addons/account_payment/views/account_portal_templates.xml +++ b/addons/account_payment/views/account_portal_templates.xml @@ -93,11 +93,6 @@
-
-
- You have credits card registered, you can log-in to be able to use them. -
-
diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py index e7f14cbde91..b3d73357210 100644 --- a/addons/payment/controllers/portal.py +++ b/addons/payment/controllers/portal.py @@ -113,7 +113,7 @@ class PaymentPortal(portal.CustomerPortal): if provider_id in providers_sudo.ids: # Only keep the desired provider if it's suitable providers_sudo = providers_sudo.browse(provider_id) tokens_sudo = request.env['payment.token'].sudo()._get_available_tokens( - providers_sudo.ids, partner_id=partner_sudo.id, logged_in=logged_in + providers_sudo.ids, partner_sudo.id ) # In sudo mode to be able to read tokens of other partners. # Make sure that the partner's company matches the company passed as parameter. @@ -138,7 +138,7 @@ class PaymentPortal(portal.CustomerPortal): 'tokens': tokens_sudo, 'fees_by_provider': fees_by_provider, 'show_tokenize_input': self._compute_show_tokenize_input_mapping( - providers_sudo, logged_in=logged_in, **kwargs + providers_sudo, **kwargs ), 'reference_prefix': reference, 'amount': amount, @@ -154,13 +154,12 @@ class PaymentPortal(portal.CustomerPortal): 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 @@ -168,8 +167,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 @@ -196,7 +194,7 @@ class PaymentPortal(portal.CustomerPortal): rendering_context = { 'providers': providers_sudo, 'tokens': request.env['payment.token'].sudo()._get_available_tokens( - None, partner_id=partner_sudo.id, is_validation=True, logged_in=True + None, partner_sudo.id, is_validation=True ), # In sudo mode to read the commercial partner's fields. 'reference_prefix': payment_utils.singularize_reference_prefix(prefix='V'), 'partner_id': partner_sudo.id, diff --git a/addons/payment/models/payment_token.py b/addons/payment/models/payment_token.py index 8a58c2beed2..1450e89b907 100644 --- a/addons/payment/models/payment_token.py +++ b/addons/payment/models/payment_token.py @@ -3,7 +3,7 @@ import logging from odoo import _, api, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError class PaymentToken(models.Model): @@ -74,6 +74,13 @@ class PaymentToken(models.Model): return super().write(values) + @api.constrains('partner_id') + def _check_partner_is_never_public(self): + """ Check that the partner associated with the token is never public. """ + for token in self: + if token.partner_id.is_public: + raise ValidationError(_("No token can be assigned to the public partner.")) + def _handle_archiving(self): """ Handle the archiving of tokens. @@ -89,9 +96,7 @@ class PaymentToken(models.Model): #=== BUSINESS METHODS ===# - def _get_available_tokens( - self, providers_ids, partner_id=None, is_validation=False, logged_in=False, **kwargs - ): + def _get_available_tokens(self, providers_ids, partner_id, is_validation=False, **kwargs): """ Return the available tokens linked to the given providers and partner. For a module to retrieve the available tokens, it must override this method and add @@ -100,14 +105,10 @@ class PaymentToken(models.Model): :param list providers_ids: The ids of the providers available for the transaction. :param int partner_id: The id of the partner. :param bool is_validation: Whether the transaction is a validation operation. - :param bool logged_in: Whether the user is logged in. :param dict kwargs: Locally unused keywords arguments. :return: The available tokens. - :rtype: `payment.token` + :rtype: payment.token """ - if partner_id is None or not logged_in: - return self.env['payment.token'] - if not is_validation: return self.env['payment.token'].search( [('provider_id', 'in', providers_ids), ('partner_id', '=', partner_id)] diff --git a/addons/payment/tests/test_flows.py b/addons/payment/tests/test_flows.py index d98c6f20715..2d8abc9799e 100644 --- a/addons/payment/tests/test_flows.py +++ b/addons/payment/tests/test_flows.py @@ -379,16 +379,23 @@ class TestFlows(PaymentHttpCommon): ) self.assertEqual(patched.call_count, 1) - def test_tokenization_input_is_show_to_logged_in_users(self): + def test_tokenization_input_is_shown_to_logged_in_users(self): + # Test both for portal and internal users + self.user = self.portal_user self.provider.allow_tokenization = True - show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping( - self.provider, logged_in=True - ) + + show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider) self.assertDictEqual(show_tokenize_input, {self.provider.id: True}) - def test_tokenization_input_is_hidden_for_logged_out_users(self): - self.provider.allow_tokenization = False - show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping( - self.provider, logged_in=True - ) - self.assertDictEqual(show_tokenize_input, {self.provider.id: False}) + self.user = self.internal_user + self.provider.allow_tokenization = True + + show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider) + self.assertDictEqual(show_tokenize_input, {self.provider.id: True}) + + def test_tokenization_input_is_shown_to_logged_out_users(self): + self.user = self.public_user + self.provider.allow_tokenization = True + + show_tokenize_input = PaymentPortal._compute_show_tokenize_input_mapping(self.provider) + self.assertDictEqual(show_tokenize_input, {self.provider.id: True}) diff --git a/addons/payment/tests/test_payment_token.py b/addons/payment/tests/test_payment_token.py index cb653dad9cd..29b5695fe24 100644 --- a/addons/payment/tests/test_payment_token.py +++ b/addons/payment/tests/test_payment_token.py @@ -2,7 +2,7 @@ from datetime import date -from odoo.exceptions import AccessError, UserError +from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tests import tagged from odoo.tools import mute_logger @@ -20,6 +20,12 @@ class TestPaymentToken(PaymentCommon): with self.assertRaises(AccessError): token.with_user(user).read() + def test_cannot_assign_token_to_public_partner(self): + """ Test that no token can be assigned to the public partner. """ + token = self._create_token() + with self.assertRaises(ValidationError): + token.partner_id = self.public_user.partner_id + def test_token_cannot_be_unarchived(self): """ Test that unarchiving disabled tokens is forbidden. """ token = self._create_token(active=False) diff --git a/addons/sale/controllers/portal.py b/addons/sale/controllers/portal.py index d79c5032bf5..24d515d1c62 100644 --- a/addons/sale/controllers/portal.py +++ b/addons/sale/controllers/portal.py @@ -177,7 +177,6 @@ class CustomerPortal(portal.CustomerPortal): :return: The payment-specific values. :rtype: dict """ - logged_in = not request.env.user._is_public() partner = order_sudo.partner_id company = order_sudo.company_id amount = order_sudo.amount_total @@ -198,11 +197,11 @@ class CustomerPortal(portal.CustomerPortal): payment_form_values = { 'providers': providers_sudo, 'tokens': request.env['payment.token']._get_available_tokens( - providers_sudo.ids, partner_id=partner.id, logged_in=logged_in, **kwargs + providers_sudo.ids, partner.id, **kwargs ), 'fees_by_provider': fees_by_provider, 'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping( - providers_sudo, logged_in=logged_in, sale_order_id=order_sudo.id + providers_sudo, sale_order_id=order_sudo.id ), 'amount': amount, 'currency': currency, diff --git a/addons/sale/tests/test_payment_flow.py b/addons/sale/tests/test_payment_flow.py index bfcc4ea106d..d3f2a0f7ed2 100644 --- a/addons/sale/tests/test_payment_flow.py +++ b/addons/sale/tests/test_payment_flow.py @@ -32,7 +32,7 @@ class TestSalePayment(AccountPaymentCommon, SaleCommon, PaymentHttpCommon): '._compute_show_tokenize_input_mapping' ) as patched: tx_context = self._get_tx_checkout_context(**route_values) - patched.assert_called_once_with(ANY, logged_in=ANY, sale_order_id=ANY) + patched.assert_called_once_with(ANY, sale_order_id=ANY) self.assertEqual(tx_context['currency_id'], self.sale_order.currency_id.id) self.assertEqual(tx_context['partner_id'], self.sale_order.partner_id.id) diff --git a/addons/website_payment/controllers/portal.py b/addons/website_payment/controllers/portal.py index 9516f15010c..c9d72e2fe9f 100644 --- a/addons/website_payment/controllers/portal.py +++ b/addons/website_payment/controllers/portal.py @@ -64,7 +64,13 @@ class PaymentPortal(payment_portal.PaymentPortal): else: partner_id = request.env.user.partner_id.id - kwargs.pop('custom_create_values', None) # Don't allow passing arbitrary create values + # Don't allow passing arbitrary create values and avoid tokenization for + # the public user. + if use_public_partner: + kwargs['custom_create_values'] = {'tokenize': False} + else: + kwargs.pop('custom_create_values', None) + tx_sudo = self._create_transaction( amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs )