From e534ab41e7b3b71a8286eba849fd4d70f4b27334 Mon Sep 17 00:00:00 2001 From: "Valentin Vallaeys (vava)" Date: Mon, 12 Dec 2022 19:07:01 +0000 Subject: [PATCH] [REF] payment, (website_)sale: factorize payment values logic in controllers The payment values in the Qweb context of sale, sale_subscription and website_sale were computed separately, although there is a large common basis. This split made it hard to communicate between modules and generated a lot of duplicates. The new function `_get_payment_values` in sales is now used as a common basis method for all sale_* modules to get the common payment values. closes odoo/odoo#107788 Related: odoo/enterprise#34922 Signed-off-by: Vallaeys Valentin (vava) --- addons/account_payment/controllers/portal.py | 7 +- addons/payment/controllers/portal.py | 17 ++--- addons/payment/models/payment_token.py | 31 ++++++++ addons/payment/views/payment_templates.xml | 11 ++- .../views/payment_stripe_templates.xml | 10 +-- addons/sale/controllers/portal.py | 55 +++++++------- addons/website_sale/controllers/main.py | 73 +++++-------------- addons/website_sale/models/payment_token.py | 21 ++++++ 8 files changed, 119 insertions(+), 106 deletions(-) create mode 100644 addons/website_sale/models/payment_token.py diff --git a/addons/account_payment/controllers/portal.py b/addons/account_payment/controllers/portal.py index 481a52d3818..d337a7a62a9 100644 --- a/addons/account_payment/controllers/portal.py +++ b/addons/account_payment/controllers/portal.py @@ -30,9 +30,9 @@ class PortalAccount(portal.PortalAccount): invoice.amount_total, 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'].search( - [('provider_id', 'in', providers_sudo.ids), ('partner_id', '=', partner_sudo.id)] - ) # Tokens are cleared at the end if the user is not logged in + tokens = request.env['payment.token']._get_available_tokens( + providers_sudo.ids, partner_id=partner_sudo.id, logged_in=logged_in + ) # Make sure that the partner's company matches the invoice's company. company_mismatch = not PaymentPortal._can_partner_pay_in_company( @@ -69,6 +69,5 @@ class PortalAccount(portal.PortalAccount): # inform that logging in will make them available. values.update({ 'existing_token': bool(tokens), - 'tokens': request.env['payment.token'], }) return values diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py index b255c2c0b04..e2587bad825 100644 --- a/addons/payment/controllers/portal.py +++ b/addons/payment/controllers/portal.py @@ -112,9 +112,9 @@ class PaymentPortal(portal.CustomerPortal): ) # 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'] + payment_tokens = request.env['payment.token']._get_available_tokens( + providers_sudo.ids, partner_id=partner_sudo.id, logged_in=logged_in + ) # Make sure that the partner's company matches the company passed as parameter. company_mismatch = not PaymentPortal._can_partner_pay_in_company(partner_sudo, company) @@ -192,17 +192,12 @@ class PaymentPortal(portal.CustomerPortal): 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']), - ]) - access_token = payment_utils.generate_access_token(partner_sudo.id, None, None) rendering_context = { 'providers': providers_sudo, - 'tokens': tokens_sudo, + 'tokens': request.env['payment.token'].sudo()._get_available_tokens( + None, partner_id=partner_sudo.id, is_validation=True, logged_in=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, 'access_token': access_token, diff --git a/addons/payment/models/payment_token.py b/addons/payment/models/payment_token.py index 792046c3ae0..8a58c2beed2 100644 --- a/addons/payment/models/payment_token.py +++ b/addons/payment/models/payment_token.py @@ -89,6 +89,37 @@ class PaymentToken(models.Model): #=== BUSINESS METHODS ===# + def _get_available_tokens( + self, providers_ids, partner_id=None, is_validation=False, logged_in=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 + information in the kwargs to define the context of the request. + + :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` + """ + 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)] + ) + else: + # Get all the tokens of the partner and of their commercial partner, regardless of + # whether the providers are available. + partner = self.env['res.partner'].browse(partner_id) + return self.env['payment.token'].search( + [('partner_id', 'in', [partner.id, partner.commercial_partner_id.id])] + ) + 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`. diff --git a/addons/payment/views/payment_templates.xml b/addons/payment/views/payment_templates.xml index 28ab49ce385..12680add1e9 100644 --- a/addons/payment/views/payment_templates.xml +++ b/addons/payment/views/payment_templates.xml @@ -324,7 +324,10 @@ - 'minor_amount' - The amount to pay in the minor units of its currency. - 'currency' - The currency of the transaction, as a `res.currency` record. - 'merchant_name' - The merchant name. - - 'access_token' - The access token used to authenticate the partner. + - 'payment_access_token' - The access token used to authenticate the partner. Since this + template is loaded in the shopping cart, this parameter is + called `payment_access_token` to prevent mixing up with the + `access_token` used for abandoned carts. - 'shipping_info_required' - Whether the shipping information is required or not. - 'transaction_route' - The route used to create a transaction when the user clicks Pay. - 'shipping_address_update_route' - The route where available carriers are computed @@ -348,12 +351,12 @@ t-att-data-shipping-address-update-route="shipping_address_update_route" t-att-data-express-checkout-route="express_checkout_route" t-att-data-landing-route="landing_route"> - + + t-value="provider.express_checkout_form_view_id.xml_id"/> - + diff --git a/addons/payment_stripe/views/payment_stripe_templates.xml b/addons/payment_stripe/views/payment_stripe_templates.xml index 1b551a86088..7caa991a439 100644 --- a/addons/payment_stripe/views/payment_stripe_templates.xml +++ b/addons/payment_stripe/views/payment_stripe_templates.xml @@ -3,11 +3,11 @@ diff --git a/addons/sale/controllers/portal.py b/addons/sale/controllers/portal.py index 503eaa46d48..d79c5032bf5 100644 --- a/addons/sale/controllers/portal.py +++ b/addons/sale/controllers/portal.py @@ -168,54 +168,55 @@ class CustomerPortal(portal.CustomerPortal): return request.render('sale.sale_order_portal_template', values) - def _get_payment_values(self, order_sudo): + def _get_payment_values(self, order_sudo, **kwargs): """ Return the payment-specific QWeb context values. - :param recordset order_sudo: The sales order being paid, as a `sale.order` record. + :param sale.order order_sudo: The sales order being paid. + :param dict kwargs: Locally unused data passed to `_get_compatible_providers` and + `_get_available_tokens`. :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 + currency = order_sudo.currency_id providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers( - order_sudo.company_id.id, - order_sudo.partner_id.id, - order_sudo.amount_total, - currency_id=order_sudo.currency_id.id, + company.id, + partner.id, + amount, + currency_id=currency.id, sale_order_id=order_sudo.id, - ) # In sudo mode to read the fields of providers and partner (if not logged in) - tokens = request.env['payment.token'].search([ - ('provider_id', 'in', providers_sudo.ids), - ('partner_id', '=', order_sudo.partner_id.id) - ]) if logged_in else request.env['payment.token'] - # Make sure that the partner's company matches the order's company. - company_mismatch = not payment_portal.PaymentPortal._can_partner_pay_in_company( - order_sudo.partner_id, order_sudo.company_id - ) + **kwargs, + ) # In sudo mode to read the fields of providers and partner (if not logged in). fees_by_provider = { provider: provider._compute_fees( - order_sudo.amount_total, - order_sudo.currency_id, - order_sudo.partner_id.country_id, + amount, currency, partner.country_id ) for provider in providers_sudo.filtered('fees_active') } - portal_page_values = { - 'company_mismatch': company_mismatch, - 'expected_company': order_sudo.company_id, - } payment_form_values = { 'providers': providers_sudo, - 'tokens': tokens, + 'tokens': request.env['payment.token']._get_available_tokens( + providers_sudo.ids, partner_id=partner.id, logged_in=logged_in, **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 ), - 'amount': order_sudo.amount_total, - 'currency': order_sudo.currency_id, - 'partner_id': order_sudo.partner_id.id, - 'access_token': order_sudo.access_token, + 'amount': amount, + 'currency': currency, + 'partner_id': partner.id, + 'access_token': order_sudo._portal_ensure_token(), 'transaction_route': order_sudo.get_portal_url(suffix='/transaction'), 'landing_route': order_sudo.get_portal_url(), } + + company_mismatch = not payment_portal.PaymentPortal._can_partner_pay_in_company( + partner, company + ) # Make sure that the partner's company matches the order's company. + portal_page_values = {'company_mismatch': company_mismatch, 'expected_company': company} + return {**portal_page_values, **payment_form_values} @http.route(['/my/orders//accept'], type='json', auth="public", website=True) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index f0bffb6406b..283c37a4abd 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -23,7 +23,7 @@ from odoo.exceptions import AccessError, MissingError, ValidationError from odoo.addons.portal.controllers.portal import _build_url_w_params from odoo.addons.website.controllers import main from odoo.addons.website.controllers.form import WebsiteForm -from odoo.addons.sale.controllers import portal +from odoo.addons.sale.controllers import portal as sale_portal from odoo.osv import expression from odoo.tools import lazy from odoo.tools.json import scriptsafe as json_scriptsafe @@ -1424,76 +1424,39 @@ class WebsiteSale(http.Controller): # ------------------------------------------------------ def _get_express_shop_payment_values(self, order, **kwargs): - logged_in = not request.website.is_public_user() - providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers( - order.company_id.id, - order.partner_id.id, - order.amount_total, - currency_id=order.currency_id.id, - is_express_checkout=True, - sale_order_id=order.id, - website_id=request.website.id, - ) # In sudo mode to read the fields of providers, order and partner (if not logged in) - fees_by_provider = { - p_sudo: p_sudo._compute_fees( - order.amount_total, order.currency_id, order.partner_id.country_id - ) for p_sudo in providers_sudo.filtered('fees_active') - } - return { - # Payment express form values - 'providers_sudo': providers_sudo, - 'fees_by_provider': fees_by_provider, - 'amount': order.amount_total, + payment_form_values = sale_portal.CustomerPortal._get_payment_values( + self, order, website_id=request.website.id, is_express_checkout=True + ) + payment_form_values.update({ + 'payment_access_token': payment_form_values.pop('access_token'), # Rename the key. 'minor_amount': payment_utils.to_minor_currency_units( - order.amount_total, order.currency_id + order.amount_total, order.currency_id ), 'merchant_name': request.website.name, - 'currency': order.currency_id, - 'partner_id': order.partner_id.id if logged_in else -1, - 'payment_access_token': order._portal_ensure_token(), 'transaction_route': f'/shop/payment/transaction/{order.id}', 'express_checkout_route': self._express_checkout_route, 'landing_route': '/shop/payment/validate', - } + }) + if request.website.is_public_user(): + payment_form_values['partner_id'] = -1 + return payment_form_values def _get_shop_payment_values(self, order, **kwargs): - logged_in = not request.env.user._is_public() - providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers( - order.company_id.id, - order.partner_id.id, - order.amount_total, - currency_id=order.currency_id.id, - sale_order_id=order.id, - website_id=request.website.id, - ) # In sudo mode to read the fields of providers, order and partner (if not logged in) - tokens = request.env['payment.token'].search( - [('provider_id', 'in', providers_sudo.ids), ('partner_id', '=', order.partner_id.id)] - ) if logged_in else request.env['payment.token'] - fees_by_provider = { - p_sudo: p_sudo._compute_fees( - order.amount_total, order.currency_id, order.partner_id.country_id - ) for p_sudo in providers_sudo.filtered('fees_active') - } - return { + portal_page_values = { 'website_sale_order': order, 'errors': [], 'partner': order.partner_id, 'order': order, 'payment_action_id': request.env.ref('payment.action_payment_provider').id, - # Payment form common (checkout and manage) values - 'providers': providers_sudo, - 'tokens': tokens, - '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.id + } + payment_form_values = { + **sale_portal.CustomerPortal._get_payment_values( + self, order, website_id=request.website.id ), - 'amount': order.amount_total, - 'currency': order.currency_id, - 'partner_id': order.partner_id.id, - 'access_token': order._portal_ensure_token(), 'transaction_route': f'/shop/payment/transaction/{order.id}', 'landing_route': '/shop/payment/validate', } + return {**portal_page_values, **payment_form_values} @http.route('/shop/payment', type='http', auth='public', website=True, sitemap=False) def shop_payment(self, **post): @@ -1777,7 +1740,7 @@ class PaymentPortal(payment_portal.PaymentPortal): return tx_sudo._get_processing_values() -class CustomerPortal(portal.CustomerPortal): +class CustomerPortal(sale_portal.CustomerPortal): def _sale_reorder_get_line_context(self): return {} diff --git a/addons/website_sale/models/payment_token.py b/addons/website_sale/models/payment_token.py new file mode 100644 index 00000000000..71fe1563556 --- /dev/null +++ b/addons/website_sale/models/payment_token.py @@ -0,0 +1,21 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class PaymentToken(models.Model): + _inherit = 'payment.token' + + def _get_available_tokens(self, *args, is_express_checkout=False, **kwargs): + """ Override of `payment` not to return the tokens in case of express checkout. + + :param dict args: Locally unused arguments. + :param bool is_express_checkout: Whether the payment is made through express checkout. + :param dict kwargs: Locally unused keywords arguments. + :return: The available tokens. + :rtype: payment.token + """ + if is_express_checkout: + return self.env['payment.token'] + + return super()._get_available_tokens(*args, **kwargs)