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 d337a7a62a9ba..95969365aba21 100644 --- a/addons/account_payment/controllers/portal.py +++ b/addons/account_payment/controllers/portal.py @@ -31,7 +31,7 @@ def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): 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 @@ def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): '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 @@ def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): '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 8c8a8021c378e..14aa3c3760495 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 e7f14cbde9146..b3d73357210bb 100644 --- a/addons/payment/controllers/portal.py +++ b/addons/payment/controllers/portal.py @@ -113,7 +113,7 @@ def payment_pay( 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 @@ def payment_pay( '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 @@ def payment_pay( 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 @@ def _compute_show_tokenize_input_mapping(providers_sudo, logged_in=False, **kwar 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 @@ def payment_method(self, **kwargs): 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 8a58c2beed270..1450e89b90727 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 @@ def write(self, values): 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 @@ def name_get(self): #=== 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 @@ def _get_available_tokens( :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 d98c6f2071574..2d8abc9799ef9 100644 --- a/addons/payment/tests/test_flows.py +++ b/addons/payment/tests/test_flows.py @@ -379,16 +379,23 @@ def test_payment_by_token_triggers_exactly_one_payment_request(self): ) 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 cb653dad9cd47..29b5695fe243f 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 @@ def test_users_have_no_access_to_other_users_tokens(self): 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 d79c5032bf579..24d515d1c621e 100644 --- a/addons/sale/controllers/portal.py +++ b/addons/sale/controllers/portal.py @@ -177,7 +177,6 @@ def _get_payment_values(self, order_sudo, **kwargs): :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 @@ def _get_payment_values(self, order_sudo, **kwargs): 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 bfcc4ea106d61..d3f2a0f7ed276 100644 --- a/addons/sale/tests/test_payment_flow.py +++ b/addons/sale/tests/test_payment_flow.py @@ -32,7 +32,7 @@ def test_11_so_payment_link(self): '._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 9516f15010ce4..c9d72e2fe9fac 100644 --- a/addons/website_payment/controllers/portal.py +++ b/addons/website_payment/controllers/portal.py @@ -64,7 +64,13 @@ def donation_transaction(self, amount, currency_id, partner_id, access_token, mi 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 )