diff --git a/addons/account/views/account_menuitem.xml b/addons/account/views/account_menuitem.xml index 6c5865b0cd3..012c00c7617 100644 --- a/addons/account/views/account_menuitem.xml +++ b/addons/account/views/account_menuitem.xml @@ -62,7 +62,7 @@ - + diff --git a/addons/account_payment/__manifest__.py b/addons/account_payment/__manifest__.py index eda961eafea..1ee48e5717c 100644 --- a/addons/account_payment/__manifest__.py +++ b/addons/account_payment/__manifest__.py @@ -16,10 +16,10 @@ 'views/account_payment_menus.xml', 'views/account_portal_templates.xml', - 'views/payment_templates.xml', 'views/account_move_views.xml', 'views/account_journal_views.xml', 'views/account_payment_views.xml', + 'views/payment_form_templates.xml', 'views/payment_provider_views.xml', 'views/payment_transaction_views.xml', diff --git a/addons/account_payment/controllers/payment.py b/addons/account_payment/controllers/payment.py index 279c0c9504a..7f5bab691fb 100644 --- a/addons/account_payment/controllers/payment.py +++ b/addons/account_payment/controllers/payment.py @@ -80,26 +80,24 @@ class PaymentPortal(payment_portal.PaymentPortal): }) return super().payment_pay(*args, amount=amount, access_token=access_token, **kwargs) - def _get_custom_rendering_context_values(self, invoice_id=None, **kwargs): - """ Override of `payment` to add the invoice id in the custom rendering context values. + def _get_extra_payment_form_values(self, invoice_id=None, **kwargs): + """ Override of `payment` to add the invoice id to the payment form values. :param int invoice_id: The invoice for which a payment id made, as an `account.move` id. :param dict kwargs: Optional data. This parameter is not used here. :return: The extended rendering context values. :rtype: dict """ - rendering_context_values = super()._get_custom_rendering_context_values( - invoice_id=invoice_id, **kwargs - ) + form_values = super()._get_extra_payment_form_values(invoice_id=invoice_id, **kwargs) if invoice_id: - rendering_context_values['invoice_id'] = invoice_id + form_values['invoice_id'] = invoice_id # Interrupt the payment flow if the invoice has been canceled. invoice_sudo = request.env['account.move'].sudo().browse(invoice_id) if invoice_sudo.state == 'cancel': - rendering_context_values['amount'] = 0.0 + form_values['amount'] = 0.0 - return rendering_context_values + return form_values def _create_transaction(self, *args, invoice_id=None, custom_create_values=None, **kwargs): """ Override of `payment` to add the invoice id in the custom create values. diff --git a/addons/account_payment/controllers/portal.py b/addons/account_payment/controllers/portal.py index 600050c4e8d..218d361ef1e 100644 --- a/addons/account_payment/controllers/portal.py +++ b/addons/account_payment/controllers/portal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.http import request @@ -8,7 +7,7 @@ from odoo.addons.payment.controllers.portal import PaymentPortal from odoo.addons.portal.controllers.portal import _build_url_w_params -class PortalAccount(portal.PortalAccount): +class PortalAccount(portal.PortalAccount, PaymentPortal): def _invoice_get_page_view_values(self, invoice, access_token, **kwargs): values = super()._invoice_get_page_view_values(invoice, access_token, **kwargs) @@ -24,15 +23,21 @@ class PortalAccount(portal.PortalAccount): partner_sudo = request.env.user.partner_id if logged_in else invoice.partner_id invoice_company = invoice.company_id or request.env.company + # Select all the payment methods and tokens that match the payment context. providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers( invoice_company.id, partner_sudo.id, invoice.amount_total, currency_id=invoice.currency_id.id - ) # In sudo mode to read the fields of providers and partner (if not logged in) + ) # 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=invoice.currency_id.id, + ) # 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 read the partner's tokens (if logged out). + ) # In sudo mode to read the partner's tokens (if logged out) and provider fields. # Make sure that the partner's company matches the invoice's company. company_mismatch = not PaymentPortal._can_partner_pay_in_company( @@ -44,17 +49,25 @@ class PortalAccount(portal.PortalAccount): 'expected_company': invoice_company, } payment_form_values = { - 'providers': providers_sudo, - 'tokens': tokens_sudo, - 'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping( + 'show_tokenize_input_mapping': PaymentPortal._compute_show_tokenize_input_mapping( providers_sudo ), + } + payment_context = { 'amount': invoice.amount_residual, 'currency': invoice.currency_id, 'partner_id': partner_sudo.id, - 'access_token': access_token, + 'providers_sudo': providers_sudo, + 'payment_methods_sudo': payment_methods_sudo, + 'tokens_sudo': tokens_sudo, 'transaction_route': f'/invoice/transaction/{invoice.id}/', - 'landing_route': _build_url_w_params(invoice.access_url, {'access_token': access_token}) + 'landing_route': invoice.get_portal_url(), + 'access_token': access_token, } - values.update(**portal_page_values, **payment_form_values) + values.update( + **portal_page_values, + **payment_form_values, + **payment_context, + **self._get_extra_payment_form_values(**kwargs), + ) return values diff --git a/addons/account_payment/models/account_payment.py b/addons/account_payment/models/account_payment.py index c6d5f3f7ac6..7df7492dad0 100644 --- a/addons/account_payment/models/account_payment.py +++ b/addons/account_payment/models/account_payment.py @@ -48,7 +48,11 @@ class AccountPayment(models.Model): def _compute_amount_available_for_refund(self): for payment in self: tx_sudo = payment.payment_transaction_id.sudo() - if tx_sudo.provider_id.support_refund and tx_sudo.operation != 'refund': + if ( + tx_sudo.provider_id.support_refund + and tx_sudo.payment_method_id.support_refund + and tx_sudo.operation != 'refund' + ): # Only consider refund transactions that are confirmed by summing the amounts of # payments linked to such refund transactions. Indeed, should a refund transaction # be stuck forever in a transient state (due to webhook failure, for example), the @@ -192,6 +196,7 @@ class AccountPayment(models.Model): self.ensure_one() return { 'provider_id': self.payment_token_id.provider_id.id, + 'payment_method_id': self.payment_token_id.payment_method_id.id, 'reference': self.ref, 'amount': self.amount, 'currency_id': self.currency_id.id, diff --git a/addons/account_payment/static/src/js/payment_form.js b/addons/account_payment/static/src/js/payment_form.js index 58f808a4976..fcb32f1468c 100644 --- a/addons/account_payment/static/src/js/payment_form.js +++ b/addons/account_payment/static/src/js/payment_form.js @@ -1,33 +1,25 @@ /** @odoo-module **/ -import checkoutForm from '@payment/js/checkout_form'; -import manageForm from '@payment/js/manage_form'; +import paymentForm from '@payment/js/payment_form'; -const PaymentMixin = { +paymentForm.include({ - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + // #=== PAYMENT FLOW ===# /** - * Add `invoice_id` to the transaction route params if it is provided. + * Add `invoice_id` to the params for the RPC to the transaction route. * - * @override method from @payment/js/payment_form_mixin + * @override method from @payment/js/payment_form * @private - * @param {string} code - The provider code of the selected payment option. - * @param {number} paymentOptionId - The id of the selected payment option. - * @param {string} flow - The online payment flow of the selected payment option. * @return {object} The extended transaction route params. */ - _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { + _prepareTransactionRouteParams() { const transactionRouteParams = this._super(...arguments); return { ...transactionRouteParams, - 'invoice_id': this.txContext.invoiceId ? parseInt(this.txContext.invoiceId) : null, + 'invoice_id': this.paymentContext['invoiceId'] + ? parseInt(this.paymentContext['invoiceId']) : null, }; }, -}; - -checkoutForm.include(PaymentMixin); -manageForm.include(PaymentMixin); +}); diff --git a/addons/account_payment/tests/test_payment_flows.py b/addons/account_payment/tests/test_payment_flows.py index 329cb26d8f7..f55b5bf0a17 100644 --- a/addons/account_payment/tests/test_payment_flows.py +++ b/addons/account_payment/tests/test_payment_flows.py @@ -16,7 +16,7 @@ class TestFlows(AccountPaymentCommon, PaymentHttpCommon): # Pay for this invoice (no impact even if amounts do not match) route_values = self._prepare_pay_values() route_values['invoice_id'] = self.invoice.id - tx_context = self._get_tx_checkout_context(**route_values) + tx_context = self._get_portal_pay_context(**route_values) self.assertEqual(tx_context['invoice_id'], self.invoice.id) # payment/transaction @@ -33,8 +33,10 @@ class TestFlows(AccountPaymentCommon, PaymentHttpCommon): ] } route_values.update({ + 'provider_id': self.provider.id, + 'payment_method_id': self.payment_method_id, + 'token_id': None, 'flow': 'direct', - 'payment_option_id': self.provider.id, 'tokenization_requested': False, }) with mute_logger('odoo.addons.payment.models.payment_transaction'): diff --git a/addons/account_payment/views/account_payment_menus.xml b/addons/account_payment/views/account_payment_menus.xml index 4c148cb7a88..4ef657df4c6 100644 --- a/addons/account_payment/views/account_payment_menus.xml +++ b/addons/account_payment/views/account_payment_menus.xml @@ -8,7 +8,6 @@ -
- -
-
- No suitable payment option could be found.
- If you believe that it is an error, please contact the website administrator. +
+ No suitable payment method could be found.
+ If you believe that it is an error, please contact the website + administrator.
+
diff --git a/addons/account_payment/views/payment_form_templates.xml b/addons/account_payment/views/payment_form_templates.xml new file mode 100644 index 00000000000..61bb8a455c3 --- /dev/null +++ b/addons/account_payment/views/payment_form_templates.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/addons/account_payment/views/payment_templates.xml b/addons/account_payment/views/payment_templates.xml deleted file mode 100644 index aa2fe46fe4b..00000000000 --- a/addons/account_payment/views/payment_templates.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - diff --git a/addons/account_payment/wizards/payment_refund_wizard.py b/addons/account_payment/wizards/payment_refund_wizard.py index 2949f4d24bb..4924cbc8d4e 100644 --- a/addons/account_payment/wizards/payment_refund_wizard.py +++ b/addons/account_payment/wizards/payment_refund_wizard.py @@ -26,7 +26,11 @@ class PaymentRefundWizard(models.TransientModel): string="Refund Amount", compute='_compute_amount_to_refund', store=True, readonly=False ) currency_id = fields.Many2one(string="Currency", related='transaction_id.currency_id') - support_refund = fields.Selection(related='transaction_id.provider_id.support_refund') + support_refund = fields.Selection( + string="Type of Refund Supported", + selection=[('full_only', "Full Only"), ('partial', "Partial")], + compute='_compute_support_refund', + ) has_pending_refund = fields.Boolean( string="Has a pending refund", compute='_compute_has_pending_refund' ) @@ -51,6 +55,18 @@ class PaymentRefundWizard(models.TransientModel): for wizard in self: wizard.amount_to_refund = wizard.amount_available_for_refund + @api.depends('transaction_id.provider_id', 'transaction_id.payment_method_id') + def _compute_support_refund(self): + for wizard in self: + p_support_refund = wizard.transaction_id.provider_id.support_refund + pm_support_refund = wizard.transaction_id.payment_method_id.support_refund + if not p_support_refund or not pm_support_refund: + wizard.support_refund = False + elif p_support_refund == 'full_only' or pm_support_refund == 'full_only': + wizard.support_refund = 'full_only' + else: # Both support partial refunds. + wizard.support_refund = 'partial' + @api.depends('payment_id') # To always trigger the compute def _compute_has_pending_refund(self): for wizard in self: diff --git a/addons/l10n_ch/tests/test_swissqr.py b/addons/l10n_ch/tests/test_swissqr.py index f49da319b85..a87d00b3b6c 100644 --- a/addons/l10n_ch/tests/test_swissqr.py +++ b/addons/l10n_ch/tests/test_swissqr.py @@ -210,6 +210,7 @@ class TestSwissQR(AccountTestInvoicingCommon): }) payment_transaction = self.env['payment.transaction'].create({ 'provider_id': provider.id, + 'payment_method_id': self.env.ref('payment.payment_method_unknown').id, 'sale_order_ids': [order.id], 'partner_id': self.env['res.partner'].search([("name", '=', 'Partner')])[0].id, 'amount': 100, diff --git a/addons/payment/__manifest__.py b/addons/payment/__manifest__.py index 9ce064d5eaf..02a343dc63e 100644 --- a/addons/payment/__manifest__.py +++ b/addons/payment/__manifest__.py @@ -7,38 +7,37 @@ 'summary': "The payment engine used by payment provider modules.", 'depends': ['onboarding', 'portal'], 'data': [ + # Record data. 'data/onboarding_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', - 'views/payment_provider_views.xml', + # Model views. 'views/payment_method_views.xml', + 'views/payment_provider_views.xml', # Depends on `action_payment_method`. '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', ], 'assets': { 'web.assets_frontend': [ - '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/*.xml', + 'payment/static/src/**/*', ], 'web.assets_backend': [ 'payment/static/src/scss/payment_provider.scss', diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py index d88c6639f5d..1deb7084710 100644 --- a/addons/payment/controllers/portal.py +++ b/addons/payment/controllers/portal.py @@ -1,10 +1,11 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import urllib.parse + import werkzeug from odoo import _, http -from odoo.exceptions import AccessError, UserError, ValidationError +from odoo.exceptions import AccessError, ValidationError 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,26 +48,27 @@ 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 werkzeug.exceptions.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) @@ -106,15 +108,18 @@ class PaymentPortal(portal.CustomerPortal): if not currency or not currency.active: raise werkzeug.exceptions.NotFound() # The currency must exist and be active. - # Select all providers and tokens that match the constraints + # 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) + ) # 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, + ) # 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. + ) # 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. company_mismatch = not PaymentPortal._can_partner_pay_in_company(partner_sudo, company) @@ -123,27 +128,34 @@ class PaymentPortal(portal.CustomerPortal): access_token = payment_utils.generate_access_token(partner_sudo.id, amount, currency.id) 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 = { - 'providers': providers_sudo, - 'tokens': tokens_sudo, - 'show_tokenize_input': self._compute_show_tokenize_input_mapping( + 'show_tokenize_input_mapping': PaymentPortal._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, 'transaction_route': '/payment/transaction', 'landing_route': '/payment/confirmation', - 'res_company': company, # Display the correct logo in a multi-company environment - **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(**kwargs), } - rendering_context = {**portal_page_values, **payment_form_values} return request.render(self._get_payment_page_template_xmlid(**kwargs), rendering_context) @staticmethod @@ -176,33 +188,54 @@ class PaymentPortal(portal.CustomerPortal): :rtype: str """ partner_sudo = request.env.user.partner_id # env.user is always sudoed + + # 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, - ) + **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, + ) # 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': request.env['payment.token'].sudo()._get_available_tokens( - None, partner_sudo.id, is_validation=True - ), # In sudo mode to read the commercial partner's fields. + + 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, '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 {} @@ -231,38 +264,39 @@ class PaymentPortal(portal.CustomerPortal): 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, reference_prefix, amount, currency_id, + partner_id, flow, tokenization_requested, landing_route, 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 str reference_prefix: The custom prefix to compute the full reference. + :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 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 + :raise UserError: If the flow is invalid. """ # Prepare create values if flow in ['redirect', 'direct']: # Direct payment or payment with redirection - provider_sudo = request.env['payment.provider'].sudo().browse(payment_option_id) + provider_sudo = request.env['payment.provider'].sudo().browse(provider_id) token_id = None tokenize = bool( # Don't tokenize if the user tried to force it through the browser's developer tools @@ -271,7 +305,7 @@ class PaymentPortal(portal.CustomerPortal): 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 @@ -281,10 +315,10 @@ class PaymentPortal(portal.CustomerPortal): raise AccessError(_("You do not have access to this payment token.")) provider_sudo = token_sudo.provider_id - token_id = payment_option_id + payment_method_id = token_sudo.payment_method_id.id tokenize = False else: - raise UserError( + raise ValidationError( _("The payment should either be direct, with redirection, or made by a token.") ) @@ -301,6 +335,7 @@ class PaymentPortal(portal.CustomerPortal): # 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, diff --git a/addons/payment/data/payment_method_data.xml b/addons/payment/data/payment_method_data.xml index 9f4f6cfc7d7..e66118c318d 100644 --- a/addons/payment/data/payment_method_data.xml +++ b/addons/payment/data/payment_method_data.xml @@ -1,184 +1,3990 @@ - + + + + + + Card + card + 10 + False + + True + False + + + + + Paypal + paypal + 20 + False + + False + False + partial + + + + Unknown + unknown + 1000 + False + + True + True + partial + + + + Alipay+ + alipay_plus + 1000 + False + + False + False + + + + + Apple Pay + apple_pay + 1000 + False + + False + False + partial + + + + Bancontact + bancontact + 1000 + False + + False + False + partial + + + + + + Western Union + western_union + 1000 + False + + False + False + + + + + SEPA Direct Debit + sepa_direct_debit + 1000 + False + + False + False + partial + + + + + + iDEAL + ideal + 1000 + False + + False + False + partial + + + + + + + Giropay + giropay + 1000 + False + + False + False + partial + + + + + + EPS + eps + 1000 + False + + False + False + partial + + + + + + P24 + p24 + 1000 + False + + False + False + partial + + + + + + KBC/CBC + kbc_cbc + 1000 + False + + False + False + partial + + + + + + M-Pesa + mpesa + 1000 + False + + False + False + + full_only + + + + + + USSD by Flutterwave + ussd + 1000 + False + + False + False + + full_only + + + + QR payment by Flutterwave + nqr_by_flutterwave + 1000 + False + + False + False + + full_only + + + + Mada + mada + 1000 + False + + False + False + + + + + + + Klarna + klarna + 1000 + False + + False + False + partial + + + + + + Klarna - Pay Now + klarna_paynow + 1000 + False + + False + False + partial + + + + + + Klarna - Pay over time + klarna_pay_over_time + 1000 + False + + False + False + partial + + + + + + WeChat Pay + wechat_pay + 1000 + False + + False + False + partial + + + + + Cetelem + cetelem + 1000 + False + + False + False + + + + + + + Alipay + alipay + 1000 + False + + False + False + partial + + + + Sofort + sofort + 1000 + False + + False + False + partial + + + + + + ACH Direct Debit + ach_direct_debit + 1000 + False + + False + False + partial + + + + + + BACS Direct Debit + bacs_direct_debit + 1000 + False + + False + False + partial + + + + + + BECS Direct Debit + becs_direct_debit + 1000 + False + + False + False + partial + + + + + + BLIK + blik + 1000 + False + + False + False + partial + + + + + + Cash App Pay + cash_app_pay + 1000 + False + + False + False + partial + + + + + + FPX + fpx + 1000 + False + + False + False + partial + + + + + + Multibanco + multibanco + 1000 + False + + False + False + + + + + + + PayNow + paynow + 1000 + False + + False + False + partial + + + + + + eNETS + enets + 1000 + False + + False + False + + + + + + + Pix + pix + 1000 + False + + False + False + partial + + + + + + Prompt Pay + promptpay + 1000 + False + + False + False + partial + + + + + + Tamilnad Mercantile Bank Limited + tmb + 1000 + False + + False + False + + + + + + + SEPA Transfer + sepa_transfer + 1000 + False + + False + False + partial + + + + Google Pay + google_pay + 1000 + False + + False + True + partial + + + + Link + link + 1000 + False + + False + True + partial + + + + MobilePay + mobile_pay + 1000 + False + + False + False + partial + + + + + + Affirm + affirm + 1000 + False + + False + False + partial + + + + + + AfterPay + afterpay_riverty + 1000 + False + + False + False + + + + + + + Afterpay + afterpay + 1000 + False + + False + False + partial + + + + + + Clearpay + clearpay + 1000 + False + + False + False + partial + + + + + + Zip + zip + 1000 + False + + False + False + partial + + + + + + Revolut Pay + revolut_pay + 1000 + False + + False + False + partial + + + + + + UPI + upi + 1000 + False + + False + False + partial + + + + + + GrabPay + grabpay + 1000 + False + + False + False + partial + + + + + + Swish + swish + 1000 + False + + False + False + partial + + + + + + Trustly + trustly + 1000 + False + + False + False + partial + + + + + + Twint + twint + 1000 + False + + False + False + partial + + + + + + PaySafeCard + paysafecard + 1000 + False + + False + False + + full_only + + + + + + AliPayHK + alipay_hk + 1000 + False + + False + False + partial + + + + + + Vipps + vipps + 1000 + False + + False + False + partial + + + + + + MB WAY + mbway + 1000 + False + + False + False + partial + + + + + + Alma + alma + 1000 + False + + False + False + partial + + + + + + Amazon Pay + amazon_pay + 1000 + False + + False + False + partial + + + + + + Atome + atome + 1000 + False + + False + False + partial + + + + + + Benefit + benefit + 1000 + False + + False + False + partial + + + + + + Bizum + bizum + 1000 + False + + False + False + partial + + + + + + Dana + dana + 1000 + False + + False + False + partial + + + + + + DuitNow + duitnow + 1000 + False + + False + False + partial + + + + + + GCash + gcash + 1000 + False + + False + False + partial + + + + + + GoPay + gopay + 1000 + False + + False + False + partial + + + + + + KakaoPay + kakaopay + 1000 + False + + False + False + partial + + + + + + KNET + knet + 1000 + False + + False + False + partial + + + + + + MoMo + momo + 1000 + False + + False + False + partial + + + + + + Napas Card + napas_card + 1000 + False + + False + False + + full_only + + + + + + Paytrail + paytrail + 1000 + False + + False + False + partial + + + + + + Online Banking Czech Republic + online_banking_czech_republic + 1000 + False + + False + False + partial + + + + + + Online Banking India + online_banking_india + 1000 + False + + False + False + + full_only + + + + + + Online Banking Slovakia + online_banking_slovakia + 1000 + False + + False + False + partial + + + + + + Online Banking Thailand + online_banking_thailand + 1000 + False + + False + False + + + + + + + Open banking + open_banking + 1000 + False + + False + False + partial + + + + + + PayBright + paybright + 1000 + False + + False + False + partial + + + + + + Pre-authorized debit in Canada + acss_debit + 1000 + False + + False + False + partial + + + + + + Paytm + paytm + 1000 + False + + False + False + partial + + + + + + Ratepay + ratepay + 1000 + False + + False + False + partial + + + + + + Samsung Pay + samsung_pay + 1000 + False + + False + False + partial + + + + Touch'n Go + touch_n_go + 1000 + False + + False + False + partial + + + + + + Walley + walley + 1000 + False + + False + False + partial + + + + + + Rabbit LINE Pay + rabbit_line_pay + 1000 + False + + False + False + + + + + + + TrueMoney + truemoney + 1000 + False + + False + False + + + + + + + Boost + boost + 1000 + False + + False + False + + + + + + + BillEase + billease + 1000 + False + + False + False + + + + + + + Bank of the Philippine Islands + bpi + 1000 + False + + False + False + + + + + + + Maya + maya + 1000 + False + + False + False + + + + + + + Akulaku PayLater + akulaku + 1000 + False + + False + False + + + + + + + Kredivo + kredivo + 1000 + False + + False + False + + + + + + + Naver Pay + naver_pay + 1000 + False + + False + False + + + + + + + Toss Pay + toss_pay + 1000 + False + + False + False + + + + + + + Dolfin + dolfin + 1000 + False + + False + False + + + + + + + LinkAja + linkaja + 1000 + False + + False + False + + + + + + + OVO + ovo + 1000 + False + + False + False + + + + + + + PayPay + paypay + 1000 + False + + False + False + + + + + + + Zalopay + zalopay + 1000 + False + + False + False + + + + + + + Bangkok Bank + bangkok_bank + 1000 + False + + False + False + + + + + + + Bank of Ayudhya + bank_of_ayudhya + 1000 + False + + False + False + + + + + + + KrungThai Bank + krungthai_bank + 1000 + False + + False + False + + + + + + + Siam Commerical Bank + scb + 1000 + False + + False + False + + + + + + + Government Savings Bank + gsb + 1000 + False + + False + False + + + + + + + Kasikorn Bank + kasikorn_bank + 1000 + False + + False + False + + + + + + + Bank Negara Indonesia + bni + 1000 + False + + False + False + + + + + + + Mandiri + mandiri + 1000 + False + + False + False + + + + + + + Maybank + maybank + 1000 + False + + False + False + + + + + + + CIMB Niaga + cimb_niaga + 1000 + False + + False + False + + + + + + + Bank Syariah Indonesia + bsi + 1000 + False + + False + False + + + + + + + QRIS + qris + 1000 + False + + False + False + + + + + + + PayU + payu + 1000 + False + + False + False + + + + + + + BANCOMAT Pay + bancomat_pay + 1000 + False + + False + False + + + + + + + BRANKAS + brankas + 1000 + False + + False + False + + + + + + + Pay-easy + pay_easy + 1000 + False + + False + False + + + + + + + Paylib + paylib + 1000 + False + + False + False + + + + + + + Venmo + venmo + 1000 + False + + False + False + partial + + + + + + MyBank + mybank + 1000 + False + + False + False + + + + + + + POLi + poli + 1000 + False + + False + False + + + + + + + Humm + humm + 1000 + False + + False + False + + + + + + + Pace. + pace + 1000 + False + + False + False + + + + + + + ShopBack + shopback + 1000 + False + + False + False + + + + + + + Hoolah + hoolah + 1000 + False + + False + False + + + + + + + BancNet + bancnet + 1000 + False + + False + False + + + + + + + LINE Pay + linepay + 1000 + False + + False + False + + + + + + + United Overseas Bank + uob + 1000 + False + + False + False + + + + + + + FPS + fps + 1000 + False + + False + False + + + + + + + HD Bank + hd + 1000 + False + + False + False + + + + + + + PayID + pay_id + 1000 + False + + False + False + + + + + + + PPS + pps + 1000 + False + + False + False + + + + + + + Techcombank + techcom + 1000 + False + + False + False + + + + + + + Tienphong + tienphong + 1000 + False + + False + False + + + + + + + TTB + ttb + 1000 + False + + False + False + + + + + + + Vietcombank + vietcom + 1000 + False + + False + False + + + + + + + TENPAY + tenpay + 1000 + False + + False + False + + + + + + + TendoPay + tendopay + 1000 + False + + False + False + + + + + + + BharatQR + bharatqr + 1000 + False + + False + False + + + + + + + Masterpass + masterpass + 1000 + False + + False + False + + + + + Octopus + octopus + 1000 + False + + False + False + + + + + + + Universal Air Travel Plan + uatp + 1000 + False + + False + False + + + + + Payconiq + payconiq + 1000 + False + + False + False + + + + + + + in3 + in3 + 1000 + False + + False + False + + + + + + + Tinka + tinka + 1000 + False + + False + False + + + + + + + Billink + billink + 1000 + False + + False + False + + + + + + + Belfius + belfius + 1000 + False + + False + False + + + + + + + cofidis + cofidis + 1000 + False + + False + False + + + + + + + Frafinance + frafinance + 1000 + False + + False + False + + + + + + + Jkopay + jkopay + 1000 + False + + False + False + + + + + + + Visa Electron + visa_electron + 1000 + False + + False + False + + + + + + V PAY + vpay + 1000 + False + + False + False + + + + + + Mobile money Ghana + mobile_money_ghana + 1000 + False + + False + False + + + + + + + Mobile money Francophone Africa + mobile_money_francophone_africa + 1000 + False + + False + False + + + + + + + Mobile money Uganda + mobile_money_uganda + 1000 + False + + False + False + + + + + + + Mobile money Rwanda + mobile_money_rwanda + 1000 + False + + False + False + + + + + + + Mobile money Zambia + mobile_money_zambia + 1000 + False + + False + False + + + + + + + PSE + pse + 1000 + False + + False + False + + + + + + + PostePay + poste_pay + 1000 + False + + False + False + + + + + + + Netbanking + netbanking + 1000 + False + + False + False + partial + + + + + + EMI + emi + 1000 + False + + False + False + partial + + + + + + Wallets India + wallets_india + 1000 + False + + False + False + + full_only + + + + + + Bank Transfer + bank_transfer + 1000 + False + + False + False + partial + + + + + Bank Account + bank_account + 1000 + False + + False + False + + + + + LyfPay + lyfpay + 1000 + False + + False + False + + + + + + + Lydia + lydia + 1000 + False + + False + False + + + + + + + Axis + axis + 1000 + False + + False + False + + + + + + + Bank reference + bank_reference + 1000 + False + + False + False + + + + + Floa Bank + floa_bank + 1000 + False + + False + False + + + + + + + PayMe + payme + 1000 + False + + False + False + + + + + + + WeLend + welend + 1000 + False + + False + False + + + + + + - 10 VISA + visa + + 1000 + False - 20 MasterCard + mastercard + + 1000 + False - - 30 + American Express - + amex + + 1000 + False + - 40 Discover + discover + + 1000 + False - - 50 + Diners Club International - - - - - 60 - Paypal - + diners + + 1000 + False + - 65 - Rupay + RuPay + rupay + + 1000 + False - - 70 - Apple Pay - - - - 80 JCB + jcb + + 1000 + False - 90 Maestro + maestro + + 1000 + False - 100 Cirrus + cirrus + + 1000 + False - 110 UnionPay + unionpay + + 1000 + False - - 120 - Bancontact - + + Carnet + carnet + + 1000 + False + - - 130 - Western Union - + + Cartes Bancaires + cartes_bancaires + + 1000 + False + - - 140 - SEPA Direct Debit - + + Elo + elo + + 1000 + False + - - 150 - iDEAL - + + Hipercard + hipercard + + 1000 + False + - - 160 - WebMoney - + + Dankort + dankort + + 1000 + False + - - 170 - Giropay - + + Meeza + meeza + + 1000 + False + - - 180 - EPS - + + NAPS + naps + + 1000 + False + - - 190 - P24 - + + OmanNet + omannet + + 1000 + False + - - 200 - Codensa Easy Credit - + + Argencard + argencard + + 1000 + False + - - 210 - KBC - + + Cabal + cabal + + 1000 + False + - - 220 - M-Pesa - + + Caixa + caixa + + 1000 + False + - - 230 - Airtel Money - + + Ceconsund + ceconsund + + 1000 + False + - - 240 - MTN Mobile Money - + + CMR + cmr + + 1000 + False + - - 250 - Barter by Flutterwave - + + Cordial + cordial + + 1000 + False + - - 260 - Sadad - + + Cordobesa + cordobesa + + 1000 + False + - - 270 - Mada - + + Codensa + codensa + + 1000 + False + - - 280 - BBVA Bancomer - + + Lider + lider + + 1000 + False + - - 280 - CitiBanamex - + + Magna + magna + + 1000 + False + + + + + Tarjeta MercadoPago + tarjeta_mercadopago + + 1000 + False + + + + + Mercado Livre + mercado_livre + + 1000 + False + + + + + Naranja + naranja + + 1000 + False + + + + + Nativa + nativa + + 1000 + False + + + + + Oca + oca + + 1000 + False + + + + + Presto + presto + + 1000 + False + + + + + Shopping Card + shopping + + 1000 + False + + + + + Credit Payment + credit + + 1000 + False + + + + + Cencosud + cencosud + + 1000 + False + + + + + Banco de Bogota + banco_de_bogota + + 1000 + False + + + + + Bancolombia + bancolombia + + 1000 + False + + + + + Davivienda + davivienda + + 1000 + False + diff --git a/addons/payment/data/payment_provider_data.xml b/addons/payment/data/payment_provider_data.xml index d7dcd7204e1..b600fd2cf64 100644 --- a/addons/payment/data/payment_provider_data.xml +++ b/addons/payment/data/payment_provider_data.xml @@ -3,87 +3,217 @@ Adyen - Credit Card (powered by Adyen) + 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_amazon_pay'), + ref('payment.payment_method_apple_pay'), + 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_giropay'), + ref('payment.payment_method_google_pay'), + 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'), + ])]" + /> Amazon Payment Services - Amazon Payment Services + 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'), + ])]" + /> Asiapay - Credit Card (powered by Asiapay) + 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_apple_pay'), + ref('payment.payment_method_google_pay'), + 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_masterpass'), + 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'), + ])]" + /> Authorize.net - Credit Card (powered by Authorize) + eval="[Command.set([ + ref('payment.payment_method_ach_direct_debit'), + ref('payment.payment_method_card'), + ])]" + /> Buckaroo - Credit Card (powered by Buckaroo) + 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'), + ref('payment.payment_method_sepa_direct_debit'), + ref('payment.payment_method_alipay'), + ref('payment.payment_method_apple_pay'), + ref('payment.payment_method_wechat_pay'), + ref('payment.payment_method_payconiq'), + 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_giropay'), + ref('payment.payment_method_p24'), + ref('payment.payment_method_poste_pay'), + ref('payment.payment_method_eps'), + ref('payment.payment_method_sepa_transfer'), + ref('payment.payment_method_visa_electron'), + ref('payment.payment_method_cartes_bancaires'), + ])]" + /> @@ -100,20 +230,27 @@ file="payment_flutterwave/static/description/icon.png"/> - + Mercado Pago - Credit Card (powered by Mercado Pago) @@ -123,13 +260,12 @@ https://www.mercadopago.com.ar/developers/en/reference/payment_methods/_payment_methods/ --> + eval="[Command.set([ + ref('payment.payment_method_card'), + ref('payment.payment_method_bank_transfer'), + ref('payment.payment_method_paypal'), + ])]" + /> @@ -137,21 +273,23 @@ - + @@ -161,33 +299,25 @@ + eval="[Command.set([ + ref('payment.payment_method_paypal'), + ])]" + /> Razorpay - Credit & Debit Card, UPI (Powered by Razorpay) + eval="[Command.set([ + ref('payment.payment_method_card'), + ref('payment.payment_method_netbanking'), + ref('payment.payment_method_upi'), + ref('payment.payment_method_wallets_india'), + ])]" + /> @@ -197,31 +327,41 @@ type="base64" file="base/static/img/icons/payment_sepa_direct_debit.png"/> + Sips - Credit Card (powered by Sips) + eval="[Command.set([ + ref('payment.payment_method_card'), + ref('payment.payment_method_ideal'), + ref('payment.payment_method_sofort'), + ref('payment.payment_method_giropay'), + ref('payment.payment_method_kbc_cbc'), + ref('payment.payment_method_paypal'), + ref('payment.payment_method_apple_pay'), + ref('payment.payment_method_samsung_pay'), + ref('payment.payment_method_bancontact'), + ref('payment.payment_method_lyfpay'), + ref('payment.payment_method_lydia'), + ref('payment.payment_method_floa_bank'), + ref('payment.payment_method_cetelem'), + ref('payment.payment_method_cofidis'), + ref('payment.payment_method_frafinance'), + ])]" + /> Stripe - Credit & Debit Card + eval="[Command.set([ + ref('payment.payment_method_ach_direct_debit'), + ref('payment.payment_method_acss_debit'), + ref('payment.payment_method_affirm'), + ref('payment.payment_method_afterpay'), + ref('payment.payment_method_alipay'), + ref('payment.payment_method_apple_pay'), + ref('payment.payment_method_bacs_direct_debit'), + ref('payment.payment_method_bancontact'), + ref('payment.payment_method_becs_direct_debit'), + 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_eps'), + ref('payment.payment_method_fpx'), + ref('payment.payment_method_giropay'), + ref('payment.payment_method_google_pay'), + ref('payment.payment_method_grabpay'), + ref('payment.payment_method_ideal'), + ref('payment.payment_method_klarna'), + ref('payment.payment_method_link'), + ref('payment.payment_method_mobile_pay'), + ref('payment.payment_method_multibanco'), + ref('payment.payment_method_netbanking'), + 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_sepa_transfer'), + ref('payment.payment_method_sofort'), + ref('payment.payment_method_upi'), + ref('payment.payment_method_wechat_pay'), + ref('payment.payment_method_zip'), + ])]" + /> diff --git a/addons/payment/models/payment_method.py b/addons/payment/models/payment_method.py index 57012b93910..289883c564e 100644 --- a/addons/payment/models/payment_method.py +++ b/addons/payment/models/payment_method.py @@ -1,24 +1,225 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import _, api, fields, models, Command +from odoo.osv import expression class PaymentMethod(models.Model): _name = 'payment.method' - _description = 'Payment Method' - _order = 'sequence, name' + _description = "Payment Method" + _order = 'active desc, sequence, name' name = fields.Char(string="Name", required=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', + ) + 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", comodel_name='payment.provider', - help="The list of providers supporting this payment method") + 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, - help="This field holds the image used for this payment method, 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) + 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 Supported") + support_express_checkout = fields.Boolean(string="Express Checkout Supported") + support_refund = fields.Selection( + string="Type of Refund Supported", + selection=[('full_only', "Full Only"), ('partial', "Partial")], + ) + supported_country_ids = fields.Many2many( + string="Supported Countries", comodel_name='res.country' + ) + supported_currency_ids = fields.Many2many( + string="Supported Currencies", comodel_name='res.currency' + ) + + #=== 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 == '=' and value is True: + return [('primary_payment_method_id', '=', False)] + elif operator == '=' and value is False: + return [('primary_payment_method_id', '!=', False)] + else: + raise NotImplementedError(_("Operation not supported.")) + + #=== ONCHANGE METHODS ===# + + @api.onchange('provider_ids') + def _onchange_provider_ids_warn_before_disabling_tokens(self): + """ Display a warning about the consequences of detaching a payment method from a provider. + + Let the user know that tokens related to a provider get archived if it is detached from the + payment methods associated with those tokens. + + :return: A client action with the warning message, if any. + :rtype: dict + """ + 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. + if detached_providers: + related_tokens = self.env['payment.token'].with_context(active_test=True).search([ + ('payment_method_id', 'in', (self._origin + self._origin.brand_ids).ids), + ('provider_id', 'in', detached_providers.ids), + ]) # 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. Archiving tokens is irreversible.", 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')) + ) + } + } + + #=== CRUD METHODS ===# + + def write(self, values): + # Handle payment methods being detached from providers. + if 'provider_ids' in values: + detached_provider_ids = [ + vals[0] for command, *vals in values['provider_ids'] if command == Command.UNLINK + ] + if detached_provider_ids: + linked_tokens = self.env['payment.token'].with_context(active_test=True).search([ + ('provider_id', 'in', detached_provider_ids), + ('payment_method_id', 'in', (self + self.brand_ids).ids), + ]) # Fix `active_test` in the context forwarded by the view. + linked_tokens.active = False + return super().write(values) + + # === BUSINESS METHODS === # + + def _get_compatible_payment_methods( + self, provider_ids, partner_id, currency_id=None, force_tokenization=False, + is_express_checkout=False + ): + """ 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. + :return: The compatible payment methods. + :rtype: payment.method + """ + # Compute the base domain for compatible payment methods. + domain = [('provider_ids', 'in', provider_ids), ('is_primary', '=', True)] + + # 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. + domain = expression.AND([ + domain, [ + '|', + ('supported_country_ids', '=', False), + ('supported_country_ids', 'in', [partner.country_id.id]), + ] + ]) + + # Handle the supported currencies; allow all currencies if the list is empty. + if currency_id: + domain = expression.AND([ + domain, [ + '|', + ('supported_currency_ids', '=', False), + ('supported_currency_ids', 'in', [currency_id]), + ] + ]) + + # Handle tokenization support requirements. + if force_tokenization: + domain = expression.AND([domain, [('support_tokenization', '=', True)]]) + + # Handle express checkout. + if is_express_checkout: + domain = expression.AND([domain, [('support_express_checkout', '=', True)]]) + + # Search the payment methods matching the compatibility criteria. + compatible_payment_methods = self.env['payment.method'].search(domain) + return compatible_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. + :type: 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) diff --git a/addons/payment/models/payment_provider.py b/addons/payment/models/payment_provider.py index c57f71bd7e8..09ea73c7931 100644 --- a/addons/payment/models/payment_provider.py +++ b/addons/payment/models/payment_provider.py @@ -49,7 +49,8 @@ class PaymentProvider(models.Model): help="The main currency of the company, used to display monetary fields.", ) payment_method_ids = fields.Many2many( - string="Supported Payment Methods", comodel_name='payment.method') + string="Supported Payment Methods", comodel_name='payment.method' + ) allow_tokenization = fields.Boolean( string="Allow Saving Payment Methods", help="This controls whether customers can save their payment methods as payment tokens.\n" @@ -124,9 +125,6 @@ class PaymentProvider(models.Model): ) # Message fields - display_as = fields.Char( - string="Displayed as", help="Description of the provider for customers", - translate=True) pre_msg = fields.Html( string="Help Message", help="The message displayed to explain and help the payment process", translate=True) @@ -183,7 +181,6 @@ class PaymentProvider(models.Model): show_credentials_page = fields.Boolean(compute='_compute_view_configuration_fields') show_allow_tokenization = fields.Boolean(compute='_compute_view_configuration_fields') show_allow_express_checkout = fields.Boolean(compute='_compute_view_configuration_fields') - show_payment_method_ids = fields.Boolean(compute='_compute_view_configuration_fields') show_pre_msg = fields.Boolean(compute='_compute_view_configuration_fields') show_pending_msg = fields.Boolean(compute='_compute_view_configuration_fields') show_auth_msg = fields.Boolean(compute='_compute_view_configuration_fields') @@ -236,7 +233,6 @@ class PaymentProvider(models.Model): - `show_credentials_page`: Whether the "Credentials" notebook page should be shown. - `show_allow_tokenization`: Whether the `allow_tokenization` field should be shown. - `show_allow_express_checkout`: Whether the `allow_express_checkout` field should be shown. - - `show_payment_method_ids`: Whether the `payment_method_ids` field should be shown. - `show_pre_msg`: Whether the `pre_msg` field should be shown. - `show_pending_msg`: Whether the `pending_msg` field should be shown. - `show_auth_msg`: Whether the `auth_msg` field should be shown. @@ -254,7 +250,6 @@ class PaymentProvider(models.Model): 'show_credentials_page': True, 'show_allow_tokenization': True, 'show_allow_express_checkout': True, - 'show_payment_method_ids': True, 'show_pre_msg': True, 'show_pending_msg': True, 'show_auth_msg': True, @@ -421,6 +416,17 @@ class PaymentProvider(models.Model): else: raise UserError(_("You cannot publish a disabled provider.")) + def action_view_payment_methods(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': _("Payment Methods"), + 'res_model': 'payment.method', + 'view_mode': 'tree', + 'domain': [('id', 'in', self.with_context(active_test=False).payment_method_ids.ids)], + 'context': {'active_test': False}, + } + #=== BUSINESS METHODS ===# @api.model @@ -428,11 +434,12 @@ class PaymentProvider(models.Model): self, company_id, partner_id, amount, currency_id=None, force_tokenization=False, is_express_checkout=False, is_validation=False, **kwargs ): - """ Select and return the providers matching the criteria. + """ Search and return the providers matching the compatibility criteria. - The criteria are that providers must not be disabled, be in the company that is provided, - support the country of the partner if it exists, and be compatible with the currency if - provided. The criteria can be further refined by providing the keyword arguments. + The compatibility criteria are that providers must: not be disabled; be in the company that + is provided; support the country of the partner if it exists; be compatible with the + currency if provided. If provided, the optional keyword arguments further refine the + criteria. :param int company_id: The company to which providers must belong, as a `res.company` id. :param int partner_id: The partner making the payment, as a `res.partner` id. @@ -443,7 +450,7 @@ class PaymentProvider(models.Model): :param bool is_validation: Whether the operation is a validation. :param dict kwargs: Optional data. This parameter is not used here. :return: The compatible providers. - :rtype: recordset of `payment.provider` + :rtype: payment.provider """ # Compute the base domain for compatible providers. domain = [ @@ -455,7 +462,7 @@ class PaymentProvider(models.Model): if not self.env.user._is_internal(): domain = expression.AND([domain, [('is_published', '=', True)]]) - # Handle partner country. + # 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. domain = expression.AND([ @@ -481,7 +488,7 @@ class PaymentProvider(models.Model): ] ]) - # Handle the available currencies (only if supported currencies list is not empty). + # Handle the available currencies; allow all currencies if the list is empty. if currency: domain = expression.AND([ domain, [ @@ -499,6 +506,7 @@ class PaymentProvider(models.Model): if is_express_checkout: domain = expression.AND([domain, [('allow_express_checkout', '=', True)]]) + # Search the providers matching the compatibility criteria. compatible_providers = self.env['payment.provider'].search(domain) return compatible_providers @@ -520,10 +528,10 @@ class PaymentProvider(models.Model): def _is_tokenization_required(self, **kwargs): """ Return whether tokenizing the transaction is required given its context. - For a module to make the tokenization required based on the transaction context, it must + For a module to make the tokenization required based on the payment context, it must override this method and return whether it is required. - :param dict kwargs: The transaction context. This parameter is not used here. + :param dict kwargs: The payment context. This parameter is not used here. :return: Whether tokenizing the transaction is required. :rtype: bool """ diff --git a/addons/payment/models/payment_token.py b/addons/payment/models/payment_token.py index 9746d1d2e94..6afd227d9cb 100644 --- a/addons/payment/models/payment_token.py +++ b/addons/payment/models/payment_token.py @@ -13,19 +13,28 @@ class PaymentToken(models.Model): _check_company_auto = True provider_id = fields.Many2one(string="Provider", comodel_name='payment.provider', required=True) - provider_code = fields.Selection(related='provider_id.code') + provider_code = fields.Selection(string="Provider Code", related='provider_id.code') + company_id = fields.Many2one( + related='provider_id.company_id', store=True, index=True + ) # Indexed to speed-up ORM searches (from ir_rule or others). + payment_method_id = fields.Many2one( + string="Payment Method", comodel_name='payment.method', readonly=True, required=True + ) + payment_method_code = fields.Char( + string="Payment Method Code", related='payment_method_id.code' + ) 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='provider_id.company_id', store=True, index=True) provider_ref = fields.Char( - string="Provider Reference", help="The provider reference of the token of the transaction", - required=True) # This is not the same thing as the provider reference of the transaction. + string="Provider Reference", + help="The provider reference of the token of the transaction.", + required=True, + ) # This is not the same thing as the provider reference of the transaction. transaction_ids = fields.One2many( - string="Payment Transactions", comodel_name='payment.transaction', inverse_name='token_id') - verified = fields.Boolean(string="Verified") + string="Payment Transactions", comodel_name='payment.transaction', inverse_name='token_id' + ) active = fields.Boolean(string="Active", default=True) #=== COMPUTE METHODS ===# diff --git a/addons/payment/models/payment_transaction.py b/addons/payment/models/payment_transaction.py index 293a21fd98a..67a94f912ac 100644 --- a/addons/payment/models/payment_transaction.py +++ b/addons/payment/models/payment_transaction.py @@ -31,10 +31,18 @@ class PaymentTransaction(models.Model): return self.env['res.lang'].get_installed() provider_id = fields.Many2one( - string="Provider", comodel_name='payment.provider', readonly=True, required=True) - provider_code = fields.Selection(related='provider_id.code') + string="Provider", comodel_name='payment.provider', readonly=True, required=True + ) + provider_code = fields.Selection(string="Provider Code", related='provider_id.code') company_id = fields.Many2one( # Indexed to speed-up ORM searches (from ir_rule or others) - related='provider_id.company_id', store=True, index=True) + related='provider_id.company_id', store=True, index=True + ) + payment_method_id = fields.Many2one( + string="Payment Method", comodel_name='payment.method', readonly=True, required=True + ) + payment_method_code = fields.Char( + string="Payment Method Code", related='payment_method_id.code' + ) reference = fields.Char( string="Reference", help="The internal reference of the transaction", readonly=True, required=True) # Already has an index from the UNIQUE SQL constraint. @@ -635,6 +643,7 @@ class PaymentTransaction(models.Model): return self.create({ 'provider_id': self.provider_id.id, + 'payment_method_id': self.payment_method_id.id, 'reference': self._compute_reference(self.provider_code, prefix=reference_prefix), 'amount': amount, 'currency_id': self.currency_id.id, diff --git a/addons/payment/static/img/ach_direct_debit.png b/addons/payment/static/img/ach_direct_debit.png new file mode 100644 index 00000000000..08c7cc6a7dc Binary files /dev/null and b/addons/payment/static/img/ach_direct_debit.png differ diff --git a/addons/payment/static/img/affirm.png b/addons/payment/static/img/affirm.png new file mode 100644 index 00000000000..a693b530f63 Binary files /dev/null and b/addons/payment/static/img/affirm.png differ diff --git a/addons/payment/static/img/afterpay.png b/addons/payment/static/img/afterpay.png new file mode 100644 index 00000000000..af7b2e5b52e Binary files /dev/null and b/addons/payment/static/img/afterpay.png differ diff --git a/addons/payment/static/img/afterpay_riverty.png b/addons/payment/static/img/afterpay_riverty.png new file mode 100644 index 00000000000..c9098c4f52b Binary files /dev/null and b/addons/payment/static/img/afterpay_riverty.png differ diff --git a/addons/payment/static/img/airtel-money.png b/addons/payment/static/img/airtel-money.png deleted file mode 100644 index d000b513e61..00000000000 Binary files a/addons/payment/static/img/airtel-money.png and /dev/null differ diff --git a/addons/payment/static/img/akulaku.png b/addons/payment/static/img/akulaku.png new file mode 100644 index 00000000000..dab12d000b1 Binary files /dev/null and b/addons/payment/static/img/akulaku.png differ diff --git a/addons/payment/static/img/alipay.png b/addons/payment/static/img/alipay.png new file mode 100644 index 00000000000..8023af18a76 Binary files /dev/null and b/addons/payment/static/img/alipay.png differ diff --git a/addons/payment/static/img/alipay_hk.png b/addons/payment/static/img/alipay_hk.png new file mode 100644 index 00000000000..7d3a98e8135 Binary files /dev/null and b/addons/payment/static/img/alipay_hk.png differ diff --git a/addons/payment/static/img/alipay_plus.png b/addons/payment/static/img/alipay_plus.png new file mode 100644 index 00000000000..3fcca89456f Binary files /dev/null and b/addons/payment/static/img/alipay_plus.png differ diff --git a/addons/payment/static/img/alma.png b/addons/payment/static/img/alma.png new file mode 100644 index 00000000000..fee6bca3f51 Binary files /dev/null and b/addons/payment/static/img/alma.png differ diff --git a/addons/payment/static/img/amazon_pay.png b/addons/payment/static/img/amazon_pay.png new file mode 100644 index 00000000000..b0354025bda Binary files /dev/null and b/addons/payment/static/img/amazon_pay.png differ diff --git a/addons/payment/static/img/american_express.png b/addons/payment/static/img/american_express.png deleted file mode 100644 index 9f31d21ef20..00000000000 Binary files a/addons/payment/static/img/american_express.png and /dev/null differ diff --git a/addons/payment/static/img/amex.png b/addons/payment/static/img/amex.png new file mode 100644 index 00000000000..7df1766d0c7 Binary files /dev/null and b/addons/payment/static/img/amex.png differ diff --git a/addons/payment/static/img/apple_pay.png b/addons/payment/static/img/apple_pay.png new file mode 100644 index 00000000000..fd3ba661131 Binary files /dev/null and b/addons/payment/static/img/apple_pay.png differ diff --git a/addons/payment/static/img/applepay.png b/addons/payment/static/img/applepay.png deleted file mode 100644 index 212ff3b4479..00000000000 Binary files a/addons/payment/static/img/applepay.png and /dev/null differ diff --git a/addons/payment/static/img/argencard.png b/addons/payment/static/img/argencard.png new file mode 100644 index 00000000000..8cd9098461c Binary files /dev/null and b/addons/payment/static/img/argencard.png differ diff --git a/addons/payment/static/img/atome.png b/addons/payment/static/img/atome.png new file mode 100644 index 00000000000..dd2d10ed0ad Binary files /dev/null and b/addons/payment/static/img/atome.png differ diff --git a/addons/payment/static/img/axis.png b/addons/payment/static/img/axis.png new file mode 100644 index 00000000000..539461de2a7 Binary files /dev/null and b/addons/payment/static/img/axis.png differ diff --git a/addons/payment/static/img/bacs_direct_debit.png b/addons/payment/static/img/bacs_direct_debit.png new file mode 100644 index 00000000000..9c7d73d35e0 Binary files /dev/null and b/addons/payment/static/img/bacs_direct_debit.png differ diff --git a/addons/payment/static/img/bancnet.png b/addons/payment/static/img/bancnet.png new file mode 100644 index 00000000000..b5adb24fa39 Binary files /dev/null and b/addons/payment/static/img/bancnet.png differ diff --git a/addons/payment/static/img/banco_de_bogota.png b/addons/payment/static/img/banco_de_bogota.png new file mode 100644 index 00000000000..661c32880c2 Binary files /dev/null and b/addons/payment/static/img/banco_de_bogota.png differ diff --git a/addons/payment/static/img/bancolombia.png b/addons/payment/static/img/bancolombia.png new file mode 100644 index 00000000000..11603bbec88 Binary files /dev/null and b/addons/payment/static/img/bancolombia.png differ diff --git a/addons/payment/static/img/bancomat_pay.png b/addons/payment/static/img/bancomat_pay.png new file mode 100644 index 00000000000..4acf035271e Binary files /dev/null and b/addons/payment/static/img/bancomat_pay.png differ diff --git a/addons/payment/static/img/bancontact.png b/addons/payment/static/img/bancontact.png index 7941a69cf64..f585a84c4f8 100644 Binary files a/addons/payment/static/img/bancontact.png and b/addons/payment/static/img/bancontact.png differ diff --git a/addons/payment/static/img/bank.png b/addons/payment/static/img/bank.png new file mode 100644 index 00000000000..71cc91d3985 Binary files /dev/null and b/addons/payment/static/img/bank.png differ diff --git a/addons/payment/static/img/barter-by-flutterwave.png b/addons/payment/static/img/barter-by-flutterwave.png deleted file mode 100644 index 5db09322cc8..00000000000 Binary files a/addons/payment/static/img/barter-by-flutterwave.png and /dev/null differ diff --git a/addons/payment/static/img/bbva-bancomer.png b/addons/payment/static/img/bbva-bancomer.png deleted file mode 100644 index 5f455b6b3bb..00000000000 Binary files a/addons/payment/static/img/bbva-bancomer.png and /dev/null differ diff --git a/addons/payment/static/img/becs_direct_debit.png b/addons/payment/static/img/becs_direct_debit.png new file mode 100644 index 00000000000..1b12ee52227 Binary files /dev/null and b/addons/payment/static/img/becs_direct_debit.png differ diff --git a/addons/payment/static/img/belfius.png b/addons/payment/static/img/belfius.png new file mode 100644 index 00000000000..6e969430b1e Binary files /dev/null and b/addons/payment/static/img/belfius.png differ diff --git a/addons/payment/static/img/benefit.png b/addons/payment/static/img/benefit.png new file mode 100644 index 00000000000..9a0f651edbd Binary files /dev/null and b/addons/payment/static/img/benefit.png differ diff --git a/addons/payment/static/img/bharatqr.png b/addons/payment/static/img/bharatqr.png new file mode 100644 index 00000000000..59c846fe046 Binary files /dev/null and b/addons/payment/static/img/bharatqr.png differ diff --git a/addons/payment/static/img/billease.png b/addons/payment/static/img/billease.png new file mode 100644 index 00000000000..0c1e5b99b64 Binary files /dev/null and b/addons/payment/static/img/billease.png differ diff --git a/addons/payment/static/img/billink.png b/addons/payment/static/img/billink.png new file mode 100644 index 00000000000..be70a722df3 Binary files /dev/null and b/addons/payment/static/img/billink.png differ diff --git a/addons/payment/static/img/bizum.png b/addons/payment/static/img/bizum.png new file mode 100644 index 00000000000..41fdbab497d Binary files /dev/null and b/addons/payment/static/img/bizum.png differ diff --git a/addons/payment/static/img/blik.png b/addons/payment/static/img/blik.png new file mode 100644 index 00000000000..b025f6bc8e3 Binary files /dev/null and b/addons/payment/static/img/blik.png differ diff --git a/addons/payment/static/img/boost.png b/addons/payment/static/img/boost.png new file mode 100644 index 00000000000..cdb5e9ffeaa Binary files /dev/null and b/addons/payment/static/img/boost.png differ diff --git a/addons/payment/static/img/brankas.png b/addons/payment/static/img/brankas.png new file mode 100644 index 00000000000..19ef51976e0 Binary files /dev/null and b/addons/payment/static/img/brankas.png differ diff --git a/addons/payment/static/img/cabal.png b/addons/payment/static/img/cabal.png new file mode 100644 index 00000000000..818bcfbdbe1 Binary files /dev/null and b/addons/payment/static/img/cabal.png differ diff --git a/addons/payment/static/img/caixa.png b/addons/payment/static/img/caixa.png new file mode 100644 index 00000000000..0fc3b7f49b6 Binary files /dev/null and b/addons/payment/static/img/caixa.png differ diff --git a/addons/payment/static/img/card.png b/addons/payment/static/img/card.png new file mode 100644 index 00000000000..1b538c8da30 Binary files /dev/null and b/addons/payment/static/img/card.png differ diff --git a/addons/payment/static/img/cash_app_pay.png b/addons/payment/static/img/cash_app_pay.png new file mode 100644 index 00000000000..921388c453c Binary files /dev/null and b/addons/payment/static/img/cash_app_pay.png differ diff --git a/addons/payment/static/img/cencosud.png b/addons/payment/static/img/cencosud.png new file mode 100644 index 00000000000..6775142fffa Binary files /dev/null and b/addons/payment/static/img/cencosud.png differ diff --git a/addons/payment/static/img/cetelem.png b/addons/payment/static/img/cetelem.png new file mode 100644 index 00000000000..a566ec5860f Binary files /dev/null and b/addons/payment/static/img/cetelem.png differ diff --git a/addons/payment/static/img/cimb_niaga.png b/addons/payment/static/img/cimb_niaga.png new file mode 100644 index 00000000000..ce44f15ab47 Binary files /dev/null and b/addons/payment/static/img/cimb_niaga.png differ diff --git a/addons/payment/static/img/cirrus.png b/addons/payment/static/img/cirrus.png index eb4b2aa81f6..bf972139a62 100644 Binary files a/addons/payment/static/img/cirrus.png and b/addons/payment/static/img/cirrus.png differ diff --git a/addons/payment/static/img/citibanamex.png b/addons/payment/static/img/citibanamex.png deleted file mode 100644 index 166a4f8d307..00000000000 Binary files a/addons/payment/static/img/citibanamex.png and /dev/null differ diff --git a/addons/payment/static/img/clearpay.png b/addons/payment/static/img/clearpay.png new file mode 100644 index 00000000000..abf53ea4ce5 Binary files /dev/null and b/addons/payment/static/img/clearpay.png differ diff --git a/addons/payment/static/img/codensa.png b/addons/payment/static/img/codensa.png new file mode 100644 index 00000000000..fcc0a24d068 Binary files /dev/null and b/addons/payment/static/img/codensa.png differ diff --git a/addons/payment/static/img/codensa_easy_credit.png b/addons/payment/static/img/codensa_easy_credit.png deleted file mode 100644 index 7f5d48750b4..00000000000 Binary files a/addons/payment/static/img/codensa_easy_credit.png and /dev/null differ diff --git a/addons/payment/static/img/cofidis.png b/addons/payment/static/img/cofidis.png new file mode 100644 index 00000000000..3bb3c5ff022 Binary files /dev/null and b/addons/payment/static/img/cofidis.png differ diff --git a/addons/payment/static/img/cordial.png b/addons/payment/static/img/cordial.png new file mode 100644 index 00000000000..28e425e1b16 Binary files /dev/null and b/addons/payment/static/img/cordial.png differ diff --git a/addons/payment/static/img/cordobesa.png b/addons/payment/static/img/cordobesa.png new file mode 100644 index 00000000000..c2e2c8fa5c0 Binary files /dev/null and b/addons/payment/static/img/cordobesa.png differ diff --git a/addons/payment/static/img/dana.png b/addons/payment/static/img/dana.png new file mode 100644 index 00000000000..da29b6373a3 Binary files /dev/null and b/addons/payment/static/img/dana.png differ diff --git a/addons/payment/static/img/dankort.png b/addons/payment/static/img/dankort.png new file mode 100644 index 00000000000..760164ed747 Binary files /dev/null and b/addons/payment/static/img/dankort.png differ diff --git a/addons/payment/static/img/davivienda.png b/addons/payment/static/img/davivienda.png new file mode 100644 index 00000000000..758ed8fad1e Binary files /dev/null and b/addons/payment/static/img/davivienda.png differ diff --git a/addons/payment/static/img/diners.png b/addons/payment/static/img/diners.png new file mode 100644 index 00000000000..2c653cc1d71 Binary files /dev/null and b/addons/payment/static/img/diners.png differ diff --git a/addons/payment/static/img/diners_club_intl.png b/addons/payment/static/img/diners_club_intl.png deleted file mode 100644 index 888860a140e..00000000000 Binary files a/addons/payment/static/img/diners_club_intl.png and /dev/null differ diff --git a/addons/payment/static/img/discover.png b/addons/payment/static/img/discover.png index d9222265651..dfde4f08df5 100644 Binary files a/addons/payment/static/img/discover.png and b/addons/payment/static/img/discover.png differ diff --git a/addons/payment/static/img/dolfin.png b/addons/payment/static/img/dolfin.png new file mode 100644 index 00000000000..709eefba38d Binary files /dev/null and b/addons/payment/static/img/dolfin.png differ diff --git a/addons/payment/static/img/duitnow.png b/addons/payment/static/img/duitnow.png new file mode 100644 index 00000000000..557234d1c06 Binary files /dev/null and b/addons/payment/static/img/duitnow.png differ diff --git a/addons/payment/static/img/elo.png b/addons/payment/static/img/elo.png new file mode 100644 index 00000000000..8781a2219d4 Binary files /dev/null and b/addons/payment/static/img/elo.png differ diff --git a/addons/payment/static/img/enets.png b/addons/payment/static/img/enets.png new file mode 100644 index 00000000000..7e1838816ea Binary files /dev/null and b/addons/payment/static/img/enets.png differ diff --git a/addons/payment/static/img/eps.png b/addons/payment/static/img/eps.png index 71ca1305539..c09a9e6e09a 100644 Binary files a/addons/payment/static/img/eps.png and b/addons/payment/static/img/eps.png differ diff --git a/addons/payment/static/img/floa_bank.png b/addons/payment/static/img/floa_bank.png new file mode 100644 index 00000000000..e1faea1a04e Binary files /dev/null and b/addons/payment/static/img/floa_bank.png differ diff --git a/addons/payment/static/img/flutterwave.png b/addons/payment/static/img/flutterwave.png new file mode 100644 index 00000000000..74c371a3c58 Binary files /dev/null and b/addons/payment/static/img/flutterwave.png differ diff --git a/addons/payment/static/img/fpx.png b/addons/payment/static/img/fpx.png new file mode 100644 index 00000000000..1a521f55615 Binary files /dev/null and b/addons/payment/static/img/fpx.png differ diff --git a/addons/payment/static/img/frafinance.png b/addons/payment/static/img/frafinance.png new file mode 100644 index 00000000000..23208019acf Binary files /dev/null and b/addons/payment/static/img/frafinance.png differ diff --git a/addons/payment/static/img/gcash.png b/addons/payment/static/img/gcash.png new file mode 100644 index 00000000000..71b135ea9be Binary files /dev/null and b/addons/payment/static/img/gcash.png differ diff --git a/addons/payment/static/img/giropay.png b/addons/payment/static/img/giropay.png index 8869385fabb..0f85e768ac2 100644 Binary files a/addons/payment/static/img/giropay.png and b/addons/payment/static/img/giropay.png differ diff --git a/addons/payment/static/img/google_pay.png b/addons/payment/static/img/google_pay.png new file mode 100644 index 00000000000..a7b3835eb6a Binary files /dev/null and b/addons/payment/static/img/google_pay.png differ diff --git a/addons/payment/static/img/gopay.png b/addons/payment/static/img/gopay.png new file mode 100644 index 00000000000..09467003284 Binary files /dev/null and b/addons/payment/static/img/gopay.png differ diff --git a/addons/payment/static/img/grabpay.png b/addons/payment/static/img/grabpay.png new file mode 100644 index 00000000000..0ed50acc74f Binary files /dev/null and b/addons/payment/static/img/grabpay.png differ diff --git a/addons/payment/static/img/hipercard.png b/addons/payment/static/img/hipercard.png new file mode 100644 index 00000000000..6ec6821073b Binary files /dev/null and b/addons/payment/static/img/hipercard.png differ diff --git a/addons/payment/static/img/hoolah.png b/addons/payment/static/img/hoolah.png new file mode 100644 index 00000000000..77d93c4f4e5 Binary files /dev/null and b/addons/payment/static/img/hoolah.png differ diff --git a/addons/payment/static/img/humm.png b/addons/payment/static/img/humm.png new file mode 100644 index 00000000000..9c51de94c88 Binary files /dev/null and b/addons/payment/static/img/humm.png differ diff --git a/addons/payment/static/img/ideal.png b/addons/payment/static/img/ideal.png index e506097ca55..2e0e7386089 100644 Binary files a/addons/payment/static/img/ideal.png and b/addons/payment/static/img/ideal.png differ diff --git a/addons/payment/static/img/in3.png b/addons/payment/static/img/in3.png new file mode 100644 index 00000000000..934c7fa095d Binary files /dev/null and b/addons/payment/static/img/in3.png differ diff --git a/addons/payment/static/img/jcb.png b/addons/payment/static/img/jcb.png index f3e3c5bfb06..11d41b111da 100644 Binary files a/addons/payment/static/img/jcb.png and b/addons/payment/static/img/jcb.png differ diff --git a/addons/payment/static/img/jkopay.png b/addons/payment/static/img/jkopay.png new file mode 100644 index 00000000000..694291e25f9 Binary files /dev/null and b/addons/payment/static/img/jkopay.png differ diff --git a/addons/payment/static/img/kakaopay.png b/addons/payment/static/img/kakaopay.png new file mode 100644 index 00000000000..3f17e55ab3e Binary files /dev/null and b/addons/payment/static/img/kakaopay.png differ diff --git a/addons/payment/static/img/kbc.png b/addons/payment/static/img/kbc.png index 183b5fae71b..41caa70be13 100644 Binary files a/addons/payment/static/img/kbc.png and b/addons/payment/static/img/kbc.png differ diff --git a/addons/payment/static/img/klarna.png b/addons/payment/static/img/klarna.png new file mode 100644 index 00000000000..1298b9b60f0 Binary files /dev/null and b/addons/payment/static/img/klarna.png differ diff --git a/addons/payment/static/img/knet.png b/addons/payment/static/img/knet.png new file mode 100644 index 00000000000..19bea8ac27b Binary files /dev/null and b/addons/payment/static/img/knet.png differ diff --git a/addons/payment/static/img/kredivo.png b/addons/payment/static/img/kredivo.png new file mode 100644 index 00000000000..b7afea460b3 Binary files /dev/null and b/addons/payment/static/img/kredivo.png differ diff --git a/addons/payment/static/img/lider.png b/addons/payment/static/img/lider.png new file mode 100644 index 00000000000..7d68fd1d6c0 Binary files /dev/null and b/addons/payment/static/img/lider.png differ diff --git a/addons/payment/static/img/linepay.png b/addons/payment/static/img/linepay.png new file mode 100644 index 00000000000..9a5679d3157 Binary files /dev/null and b/addons/payment/static/img/linepay.png differ diff --git a/addons/payment/static/img/link.png b/addons/payment/static/img/link.png new file mode 100644 index 00000000000..e831dbe6726 Binary files /dev/null and b/addons/payment/static/img/link.png differ diff --git a/addons/payment/static/img/linkaja.png b/addons/payment/static/img/linkaja.png new file mode 100644 index 00000000000..d0e70d2dc44 Binary files /dev/null and b/addons/payment/static/img/linkaja.png differ diff --git a/addons/payment/static/img/lydia.png b/addons/payment/static/img/lydia.png new file mode 100644 index 00000000000..f256ba274e0 Binary files /dev/null and b/addons/payment/static/img/lydia.png differ diff --git a/addons/payment/static/img/lyfpay.png b/addons/payment/static/img/lyfpay.png new file mode 100644 index 00000000000..518112a39b6 Binary files /dev/null and b/addons/payment/static/img/lyfpay.png differ diff --git a/addons/payment/static/img/m-pesa.png b/addons/payment/static/img/m-pesa.png deleted file mode 100644 index 7d208c7c844..00000000000 Binary files a/addons/payment/static/img/m-pesa.png and /dev/null differ diff --git a/addons/payment/static/img/mada.png b/addons/payment/static/img/mada.png index 9ba33e92309..65bcf9822d3 100644 Binary files a/addons/payment/static/img/mada.png and b/addons/payment/static/img/mada.png differ diff --git a/addons/payment/static/img/maestro.png b/addons/payment/static/img/maestro.png index a0a6b328380..9f1bd3ecd38 100644 Binary files a/addons/payment/static/img/maestro.png and b/addons/payment/static/img/maestro.png differ diff --git a/addons/payment/static/img/magna.png b/addons/payment/static/img/magna.png new file mode 100644 index 00000000000..8ad312ff242 Binary files /dev/null and b/addons/payment/static/img/magna.png differ diff --git a/addons/payment/static/img/mandiri.png b/addons/payment/static/img/mandiri.png new file mode 100644 index 00000000000..c69bab15d56 Binary files /dev/null and b/addons/payment/static/img/mandiri.png differ diff --git a/addons/payment/static/img/mastercard.png b/addons/payment/static/img/mastercard.png index 2572a33b373..7772479e912 100644 Binary files a/addons/payment/static/img/mastercard.png and b/addons/payment/static/img/mastercard.png differ diff --git a/addons/payment/static/img/maya.png b/addons/payment/static/img/maya.png new file mode 100644 index 00000000000..3640ee49280 Binary files /dev/null and b/addons/payment/static/img/maya.png differ diff --git a/addons/payment/static/img/maybank.png b/addons/payment/static/img/maybank.png new file mode 100644 index 00000000000..3c638db464a Binary files /dev/null and b/addons/payment/static/img/maybank.png differ diff --git a/addons/payment/static/img/mbway.png b/addons/payment/static/img/mbway.png new file mode 100644 index 00000000000..9b7204ab63a Binary files /dev/null and b/addons/payment/static/img/mbway.png differ diff --git a/addons/payment/static/img/meeza.png b/addons/payment/static/img/meeza.png new file mode 100644 index 00000000000..5cceb63c9e4 Binary files /dev/null and b/addons/payment/static/img/meeza.png differ diff --git a/addons/payment/static/img/mercado_livre.png b/addons/payment/static/img/mercado_livre.png new file mode 100644 index 00000000000..dce93e5bc91 Binary files /dev/null and b/addons/payment/static/img/mercado_livre.png differ diff --git a/addons/payment/static/img/mobile_pay.png b/addons/payment/static/img/mobile_pay.png new file mode 100644 index 00000000000..5656c14b5af Binary files /dev/null and b/addons/payment/static/img/mobile_pay.png differ diff --git a/addons/payment/static/img/momo.png b/addons/payment/static/img/momo.png new file mode 100644 index 00000000000..007bcf4610e Binary files /dev/null and b/addons/payment/static/img/momo.png differ diff --git a/addons/payment/static/img/mpesa.png b/addons/payment/static/img/mpesa.png new file mode 100644 index 00000000000..c129f404865 Binary files /dev/null and b/addons/payment/static/img/mpesa.png differ diff --git a/addons/payment/static/img/mtn-mobile-money.png b/addons/payment/static/img/mtn-mobile-money.png index 2bc9ec29fb9..ffaf51bb616 100644 Binary files a/addons/payment/static/img/mtn-mobile-money.png and b/addons/payment/static/img/mtn-mobile-money.png differ diff --git a/addons/payment/static/img/multibanco.png b/addons/payment/static/img/multibanco.png new file mode 100644 index 00000000000..252235a681d Binary files /dev/null and b/addons/payment/static/img/multibanco.png differ diff --git a/addons/payment/static/img/mybank.png b/addons/payment/static/img/mybank.png new file mode 100644 index 00000000000..7fedaf7b8e0 Binary files /dev/null and b/addons/payment/static/img/mybank.png differ diff --git a/addons/payment/static/img/napas_card.png b/addons/payment/static/img/napas_card.png new file mode 100644 index 00000000000..c77afdc39f1 Binary files /dev/null and b/addons/payment/static/img/napas_card.png differ diff --git a/addons/payment/static/img/naps.png b/addons/payment/static/img/naps.png new file mode 100644 index 00000000000..6a3d1d80395 Binary files /dev/null and b/addons/payment/static/img/naps.png differ diff --git a/addons/payment/static/img/naranja.png b/addons/payment/static/img/naranja.png new file mode 100644 index 00000000000..3cd873d9b38 Binary files /dev/null and b/addons/payment/static/img/naranja.png differ diff --git a/addons/payment/static/img/nativa.png b/addons/payment/static/img/nativa.png new file mode 100644 index 00000000000..813c1462008 Binary files /dev/null and b/addons/payment/static/img/nativa.png differ diff --git a/addons/payment/static/img/naver_pay.png b/addons/payment/static/img/naver_pay.png new file mode 100644 index 00000000000..8556f93efdf Binary files /dev/null and b/addons/payment/static/img/naver_pay.png differ diff --git a/addons/payment/static/img/octopus.png b/addons/payment/static/img/octopus.png new file mode 100644 index 00000000000..f039f313b38 Binary files /dev/null and b/addons/payment/static/img/octopus.png differ diff --git a/addons/payment/static/img/omannet.png b/addons/payment/static/img/omannet.png new file mode 100644 index 00000000000..6666cc827ff Binary files /dev/null and b/addons/payment/static/img/omannet.png differ diff --git a/addons/payment/static/img/ovo.png b/addons/payment/static/img/ovo.png new file mode 100644 index 00000000000..77503a06b67 Binary files /dev/null and b/addons/payment/static/img/ovo.png differ diff --git a/addons/payment/static/img/p24.png b/addons/payment/static/img/p24.png index 5ed3f7bfdfe..0bc2550223d 100644 Binary files a/addons/payment/static/img/p24.png and b/addons/payment/static/img/p24.png differ diff --git a/addons/payment/static/img/pace.png b/addons/payment/static/img/pace.png new file mode 100644 index 00000000000..e41f403eac3 Binary files /dev/null and b/addons/payment/static/img/pace.png differ diff --git a/addons/payment/static/img/pay_easy.png b/addons/payment/static/img/pay_easy.png new file mode 100644 index 00000000000..17cc3734a68 Binary files /dev/null and b/addons/payment/static/img/pay_easy.png differ diff --git a/addons/payment/static/img/pay_id.png b/addons/payment/static/img/pay_id.png new file mode 100644 index 00000000000..219bf8f7a10 Binary files /dev/null and b/addons/payment/static/img/pay_id.png differ diff --git a/addons/payment/static/img/paybright.png b/addons/payment/static/img/paybright.png new file mode 100644 index 00000000000..893d07772c4 Binary files /dev/null and b/addons/payment/static/img/paybright.png differ diff --git a/addons/payment/static/img/payconiq.png b/addons/payment/static/img/payconiq.png new file mode 100644 index 00000000000..b6d2cce9c2e Binary files /dev/null and b/addons/payment/static/img/payconiq.png differ diff --git a/addons/payment/static/img/paylib.png b/addons/payment/static/img/paylib.png new file mode 100644 index 00000000000..cf99dc2241a Binary files /dev/null and b/addons/payment/static/img/paylib.png differ diff --git a/addons/payment/static/img/payme.png b/addons/payment/static/img/payme.png new file mode 100644 index 00000000000..c61d35ce1ed Binary files /dev/null and b/addons/payment/static/img/payme.png differ diff --git a/addons/payment/static/img/paynow.png b/addons/payment/static/img/paynow.png new file mode 100644 index 00000000000..c80ed2670a8 Binary files /dev/null and b/addons/payment/static/img/paynow.png differ diff --git a/addons/payment/static/img/paypal.png b/addons/payment/static/img/paypal.png index a816d7201f5..bfa24e7f23a 100644 Binary files a/addons/payment/static/img/paypal.png and b/addons/payment/static/img/paypal.png differ diff --git a/addons/payment/static/img/paypay.png b/addons/payment/static/img/paypay.png new file mode 100644 index 00000000000..1df61812210 Binary files /dev/null and b/addons/payment/static/img/paypay.png differ diff --git a/addons/payment/static/img/paysafecard.png b/addons/payment/static/img/paysafecard.png new file mode 100644 index 00000000000..e00802c03ad Binary files /dev/null and b/addons/payment/static/img/paysafecard.png differ diff --git a/addons/payment/static/img/paytm.png b/addons/payment/static/img/paytm.png new file mode 100644 index 00000000000..652c4424c0b Binary files /dev/null and b/addons/payment/static/img/paytm.png differ diff --git a/addons/payment/static/img/paytrail.png b/addons/payment/static/img/paytrail.png new file mode 100644 index 00000000000..c5efd8dc4f1 Binary files /dev/null and b/addons/payment/static/img/paytrail.png differ diff --git a/addons/payment/static/img/payu.png b/addons/payment/static/img/payu.png new file mode 100644 index 00000000000..8cdc29fc75f Binary files /dev/null and b/addons/payment/static/img/payu.png differ diff --git a/addons/payment/static/img/pix.png b/addons/payment/static/img/pix.png new file mode 100644 index 00000000000..576cfd7d62d Binary files /dev/null and b/addons/payment/static/img/pix.png differ diff --git a/addons/payment/static/img/poli.png b/addons/payment/static/img/poli.png new file mode 100644 index 00000000000..b947e9b91b5 Binary files /dev/null and b/addons/payment/static/img/poli.png differ diff --git a/addons/payment/static/img/poste_pay.png b/addons/payment/static/img/poste_pay.png new file mode 100644 index 00000000000..9b4366492c4 Binary files /dev/null and b/addons/payment/static/img/poste_pay.png differ diff --git a/addons/payment/static/img/presto.png b/addons/payment/static/img/presto.png new file mode 100644 index 00000000000..75e4cf31c3c Binary files /dev/null and b/addons/payment/static/img/presto.png differ diff --git a/addons/payment/static/img/promptpay.png b/addons/payment/static/img/promptpay.png new file mode 100644 index 00000000000..6d2016d6231 Binary files /dev/null and b/addons/payment/static/img/promptpay.png differ diff --git a/addons/payment/static/img/qris.png b/addons/payment/static/img/qris.png new file mode 100644 index 00000000000..37fd2af75ac Binary files /dev/null and b/addons/payment/static/img/qris.png differ diff --git a/addons/payment/static/img/rabbit_line_pay.png b/addons/payment/static/img/rabbit_line_pay.png new file mode 100644 index 00000000000..0ea016670e4 Binary files /dev/null and b/addons/payment/static/img/rabbit_line_pay.png differ diff --git a/addons/payment/static/img/ratepay.png b/addons/payment/static/img/ratepay.png new file mode 100644 index 00000000000..d3fba04e692 Binary files /dev/null and b/addons/payment/static/img/ratepay.png differ diff --git a/addons/payment/static/img/revolut_pay.png b/addons/payment/static/img/revolut_pay.png new file mode 100644 index 00000000000..8b54676fe91 Binary files /dev/null and b/addons/payment/static/img/revolut_pay.png differ diff --git a/addons/payment/static/img/rupay.png b/addons/payment/static/img/rupay.png index 7dd3771a493..f529a0b88af 100644 Binary files a/addons/payment/static/img/rupay.png and b/addons/payment/static/img/rupay.png differ diff --git a/addons/payment/static/img/sadad.png b/addons/payment/static/img/sadad.png deleted file mode 100644 index 1910bcd1cf0..00000000000 Binary files a/addons/payment/static/img/sadad.png and /dev/null differ diff --git a/addons/payment/static/img/samsung_pay.png b/addons/payment/static/img/samsung_pay.png new file mode 100644 index 00000000000..8af9c52d8f2 Binary files /dev/null and b/addons/payment/static/img/samsung_pay.png differ diff --git a/addons/payment/static/img/sepa.png b/addons/payment/static/img/sepa.png index 514ff845b84..07231015ff3 100644 Binary files a/addons/payment/static/img/sepa.png and b/addons/payment/static/img/sepa.png differ diff --git a/addons/payment/static/img/shopback.png b/addons/payment/static/img/shopback.png new file mode 100644 index 00000000000..694d9f2a95d Binary files /dev/null and b/addons/payment/static/img/shopback.png differ diff --git a/addons/payment/static/img/shopping.png b/addons/payment/static/img/shopping.png new file mode 100644 index 00000000000..e9b22ea9ed2 Binary files /dev/null and b/addons/payment/static/img/shopping.png differ diff --git a/addons/payment/static/img/sofort.png b/addons/payment/static/img/sofort.png new file mode 100644 index 00000000000..f05f1791e8d Binary files /dev/null and b/addons/payment/static/img/sofort.png differ diff --git a/addons/payment/static/img/swish.png b/addons/payment/static/img/swish.png new file mode 100644 index 00000000000..5659289c4f5 Binary files /dev/null and b/addons/payment/static/img/swish.png differ diff --git a/addons/payment/static/img/tarjeta_mercadopago.png b/addons/payment/static/img/tarjeta_mercadopago.png new file mode 100644 index 00000000000..c4a08a5e68a Binary files /dev/null and b/addons/payment/static/img/tarjeta_mercadopago.png differ diff --git a/addons/payment/static/img/techcom.png b/addons/payment/static/img/techcom.png new file mode 100644 index 00000000000..ec8d1bd92fa Binary files /dev/null and b/addons/payment/static/img/techcom.png differ diff --git a/addons/payment/static/img/tendopay.png b/addons/payment/static/img/tendopay.png new file mode 100644 index 00000000000..d5bfd2676b2 Binary files /dev/null and b/addons/payment/static/img/tendopay.png differ diff --git a/addons/payment/static/img/tenpay.png b/addons/payment/static/img/tenpay.png new file mode 100644 index 00000000000..34da3df2827 Binary files /dev/null and b/addons/payment/static/img/tenpay.png differ diff --git a/addons/payment/static/img/tinka.png b/addons/payment/static/img/tinka.png new file mode 100644 index 00000000000..c2cc9d85580 Binary files /dev/null and b/addons/payment/static/img/tinka.png differ diff --git a/addons/payment/static/img/tmb.png b/addons/payment/static/img/tmb.png new file mode 100644 index 00000000000..0bbd558f47d Binary files /dev/null and b/addons/payment/static/img/tmb.png differ diff --git a/addons/payment/static/img/toss_pay.png b/addons/payment/static/img/toss_pay.png new file mode 100644 index 00000000000..01e9abb17e1 Binary files /dev/null and b/addons/payment/static/img/toss_pay.png differ diff --git a/addons/payment/static/img/touch_n_go.png b/addons/payment/static/img/touch_n_go.png new file mode 100644 index 00000000000..02e1236561c Binary files /dev/null and b/addons/payment/static/img/touch_n_go.png differ diff --git a/addons/payment/static/img/truemoney.png b/addons/payment/static/img/truemoney.png new file mode 100644 index 00000000000..c7ef516de6c Binary files /dev/null and b/addons/payment/static/img/truemoney.png differ diff --git a/addons/payment/static/img/trustly.png b/addons/payment/static/img/trustly.png new file mode 100644 index 00000000000..d6fa199326a Binary files /dev/null and b/addons/payment/static/img/trustly.png differ diff --git a/addons/payment/static/img/twint.png b/addons/payment/static/img/twint.png new file mode 100644 index 00000000000..85ac05401c7 Binary files /dev/null and b/addons/payment/static/img/twint.png differ diff --git a/addons/payment/static/img/uatp.png b/addons/payment/static/img/uatp.png new file mode 100644 index 00000000000..f4749373162 Binary files /dev/null and b/addons/payment/static/img/uatp.png differ diff --git a/addons/payment/static/img/unionpay.png b/addons/payment/static/img/unionpay.png index 8d830c5f944..179bb2f6dd6 100644 Binary files a/addons/payment/static/img/unionpay.png and b/addons/payment/static/img/unionpay.png differ diff --git a/addons/payment/static/img/unknown.png b/addons/payment/static/img/unknown.png new file mode 100644 index 00000000000..f6911fac168 Binary files /dev/null and b/addons/payment/static/img/unknown.png differ diff --git a/addons/payment/static/img/upi.png b/addons/payment/static/img/upi.png new file mode 100644 index 00000000000..d6cc1610c83 Binary files /dev/null and b/addons/payment/static/img/upi.png differ diff --git a/addons/payment/static/img/venmo.png b/addons/payment/static/img/venmo.png new file mode 100644 index 00000000000..274a8d03737 Binary files /dev/null and b/addons/payment/static/img/venmo.png differ diff --git a/addons/payment/static/img/vietcom.png b/addons/payment/static/img/vietcom.png new file mode 100644 index 00000000000..5896f29349b Binary files /dev/null and b/addons/payment/static/img/vietcom.png differ diff --git a/addons/payment/static/img/vipps.png b/addons/payment/static/img/vipps.png new file mode 100644 index 00000000000..d4c4ccd972e Binary files /dev/null and b/addons/payment/static/img/vipps.png differ diff --git a/addons/payment/static/img/visa.png b/addons/payment/static/img/visa.png index 58cfab08725..732b03ba896 100644 Binary files a/addons/payment/static/img/visa.png and b/addons/payment/static/img/visa.png differ diff --git a/addons/payment/static/img/vpay.png b/addons/payment/static/img/vpay.png new file mode 100644 index 00000000000..cd072083160 Binary files /dev/null and b/addons/payment/static/img/vpay.png differ diff --git a/addons/payment/static/img/wallet.png b/addons/payment/static/img/wallet.png new file mode 100644 index 00000000000..3223468195c Binary files /dev/null and b/addons/payment/static/img/wallet.png differ diff --git a/addons/payment/static/img/walley.png b/addons/payment/static/img/walley.png new file mode 100644 index 00000000000..296806fe1d6 Binary files /dev/null and b/addons/payment/static/img/walley.png differ diff --git a/addons/payment/static/img/webmoney.png b/addons/payment/static/img/webmoney.png deleted file mode 100644 index 2e4e591669c..00000000000 Binary files a/addons/payment/static/img/webmoney.png and /dev/null differ diff --git a/addons/payment/static/img/wechat_pay.png b/addons/payment/static/img/wechat_pay.png new file mode 100644 index 00000000000..26154e88ca5 Binary files /dev/null and b/addons/payment/static/img/wechat_pay.png differ diff --git a/addons/payment/static/img/welend.png b/addons/payment/static/img/welend.png new file mode 100644 index 00000000000..09f64fbdc54 Binary files /dev/null and b/addons/payment/static/img/welend.png differ diff --git a/addons/payment/static/img/western_union.png b/addons/payment/static/img/western_union.png index 02969bd5624..52cd2b060d2 100644 Binary files a/addons/payment/static/img/western_union.png and b/addons/payment/static/img/western_union.png differ diff --git a/addons/payment/static/img/zalopay.png b/addons/payment/static/img/zalopay.png new file mode 100644 index 00000000000..cafcad59de9 Binary files /dev/null and b/addons/payment/static/img/zalopay.png differ diff --git a/addons/payment/static/img/zip.png b/addons/payment/static/img/zip.png new file mode 100644 index 00000000000..31d65c55134 Binary files /dev/null and b/addons/payment/static/img/zip.png differ diff --git a/addons/payment/static/src/js/checkout_form.js b/addons/payment/static/src/js/checkout_form.js deleted file mode 100644 index b0db9ec5265..00000000000 --- a/addons/payment/static/src/js/checkout_form.js +++ /dev/null @@ -1,98 +0,0 @@ -/** @odoo-module **/ - -import publicWidget from '@web/legacy/js/public/public_widget'; -import paymentFormMixin from '@payment/js/payment_form_mixin'; -import { debounce } from '@web/core/utils/timing'; - -publicWidget.registry.PaymentCheckoutForm = publicWidget.Widget.extend(paymentFormMixin, { - selector: 'form[name="o_payment_checkout"]', - events: Object.assign({}, publicWidget.Widget.prototype.events, { - 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', - 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', - 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', - 'click button[name="o_payment_submit_button"]': '_onClickPay', - 'submit': '_onSubmit', - }), - - /** - * @constructor - */ - init: function () { - this.formType = 'checkout'; - const preventDoubleClick = handlerMethod => { - return debounce(handlerMethod, 500, true); - }; - this._super(...arguments); - // Prevent double-clicks and browser glitches on all inputs - this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); - this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); - this._onClickPay = preventDoubleClick(this._onClickPay); - this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); - this._onSubmit = preventDoubleClick(this._onSubmit); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Handle a direct payment, a payment with redirection, or a payment by token. - * - * Called when clicking on the 'Pay' button or when submitting the form. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickPay: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - - // Check that the user has selected a payment option - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if (!this._ensureRadioIsChecked($checkedRadios)) { - return; - } - const checkedRadio = $checkedRadios[0]; - - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(checkedRadio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); - const flow = this._getPaymentFlowFromRadio(checkedRadio); - - // Update the tx context with the value of the "Save my payment details" checkbox - if (flow !== 'token') { - const $inlineForm = this._getInlineFormFromRadio(checkedRadio); - const $tokenizeCheckbox = $inlineForm.find('input[name="o_payment_save_as_token"]'); - this.txContext.tokenizationRequested = $tokenizeCheckbox.length === 1 - && $tokenizeCheckbox[0].checked; - } else { - this.txContext.tokenizationRequested = false; - } - - // Make the payment - this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 - this._disableButton(true); // Disable until it is needed again - this.call('ui', 'block'); - this._processPayment(provider, paymentOptionId, flow); - }, - - /** - * Delegate the handling of the payment request to `_onClickPay`. - * - * Called when submitting the form (e.g. through the Return key). - * - * @private - * @param {Event} ev - * @return {void} - */ - _onSubmit: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - - this._onClickPay(ev); - }, - -}); - -export default publicWidget.registry.PaymentCheckoutForm; diff --git a/addons/payment/static/src/js/express_checkout_form.js b/addons/payment/static/src/js/express_checkout_form.js index b9198e249a4..39e588d41f1 100644 --- a/addons/payment/static/src/js/express_checkout_form.js +++ b/addons/payment/static/src/js/express_checkout_form.js @@ -11,9 +11,9 @@ publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({ */ start: async function () { await this._super(...arguments); - this.txContext = {}; - Object.assign(this.txContext, this.$el.data()); - this.txContext.shippingInfoRequired = !!this.txContext.shippingInfoRequired; + this.paymentContext = {}; + Object.assign(this.paymentContext, this.el.dataset); + this.paymentContext.shippingInfoRequired = !!this.paymentContext['shippingInfoRequired']; const expressCheckoutForms = this._getExpressCheckoutForms(); for (const expressCheckoutForm of expressCheckoutForms) { await this._prepareExpressCheckoutForm(expressCheckoutForm.dataset); @@ -50,27 +50,24 @@ publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({ async _prepareExpressCheckoutForm(providerData) {}, /** - * Prepare the params to send to the transaction route. - * - * For a provider to overwrite generic params or to add provider-specific ones, it must override - * this method and return the extended transaction route params. + * Prepare the params for the RPC to the transaction route. * * @private * @param {number} providerId - The id of the provider handling the transaction. - * @returns {object} - The transaction route params + * @returns {object} - The transaction route params. */ _prepareTransactionRouteParams(providerId) { return { 'payment_option_id': parseInt(providerId), - 'reference_prefix': this.txContext.referencePrefix && - this.txContent.referencePrefix.toString(), - 'currency_id': this.txContext.currencyId && - parseInt(this.txContext.currencyId), - 'partner_id': parseInt(this.txContext.partnerId), + 'reference_prefix': this.paymentContext['referencePrefix'] && + this.paymentContext['referencePrefix'].toString(), + 'currency_id': this.paymentContext['currencyId'] && + parseInt(this.paymentContext['currencyId']), + 'partner_id': parseInt(this.paymentContext['partnerId']), 'flow': 'direct', 'tokenization_requested': false, - 'landing_route': this.txContext.landingRoute, - 'access_token': this.txContext.accessToken, + 'landing_route': this.paymentContext['landingRoute'], + 'access_token': this.paymentContext['accessToken'], 'csrf_token': odoo.csrf_token, }; }, @@ -86,8 +83,8 @@ publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({ * @return {void} */ _updateAmount(newAmount, newMinorAmount) { - this.txContext.amount = parseFloat(newAmount); - this.txContext.minorAmount = parseInt(newMinorAmount); + this.paymentContext.amount = parseFloat(newAmount); + this.paymentContext.minorAmount = parseInt(newMinorAmount); }, }); diff --git a/addons/payment/static/src/js/manage_form.js b/addons/payment/static/src/js/manage_form.js deleted file mode 100644 index 3c1ad48144f..00000000000 --- a/addons/payment/static/src/js/manage_form.js +++ /dev/null @@ -1,223 +0,0 @@ -/** @odoo-module **/ - -import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; -import { renderToMarkup } from "@web/core/utils/render"; -import { _t } from '@web/core/l10n/translation'; -import publicWidget from '@web/legacy/js/public/public_widget'; - -import paymentFormMixin from '@payment/js/payment_form_mixin'; -import { debounce } from '@web/core/utils/timing'; - -publicWidget.registry.PaymentManageForm = publicWidget.Widget.extend(paymentFormMixin, { - selector: 'form[name="o_payment_manage"]', - events: Object.assign({}, publicWidget.Widget.prototype.events, { - 'click div[name="o_payment_option_card"]': '_onClickPaymentOption', - 'click a[name="o_payment_icon_more"]': '_onClickMorePaymentIcons', - 'click a[name="o_payment_icon_less"]': '_onClickLessPaymentIcons', - 'click button[name="o_payment_submit_button"]': '_onClickSaveToken', - 'click button[name="o_payment_delete_token"]': '_onClickDeleteToken', - 'submit': '_onSubmit', - }), - - /** - * @constructor - */ - init: function () { - this.formType = 'manage'; - const preventDoubleClick = handlerMethod => { - return debounce(handlerMethod, 500, true); - }; - this._super(...arguments); - // Prevent double-clicks and browser glitches on all inputs - this._onClickDeleteToken = preventDoubleClick(this._onClickDeleteToken); - this._onClickLessPaymentIcons = preventDoubleClick(this._onClickLessPaymentIcons); - this._onClickMorePaymentIcons = preventDoubleClick(this._onClickMorePaymentIcons); - this._onClickPaymentOption = preventDoubleClick(this._onClickPaymentOption); - this._onClickSaveToken = preventDoubleClick(this._onClickSaveToken); - this._onSubmit = preventDoubleClick(this._onSubmit); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Assign the token to a record. - * - * @private - * @param {number} tokenId - The id of the token to assign - * @return {void} - */ - _assignToken: function (tokenId) { - // Call the assign route to assign the token to a record - this._rpc({ - route: this.txContext['assignTokenRoute'], - params: { - 'access_token': this.txContext.accessToken, - 'token_id': tokenId, - } - }).then(() => { - window.location = this.txContext['landingRoute']; - }).guardedCatch(error => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to save your payment method."), - error.message.data.message, - ); - }); - }, - - /** - * Build and open the confirmation dialog based on the linked records' information. - * - * @private - * @param {Array} linkedRecordsInfo - The list of information about linked records. - * @param confirmCallback - The callback method called when the user clicks on the confirmation - * button. - * @return {void} - */ - _openConfirmationDialog: function (linkedRecordsInfo, confirmCallback) { - const body = renderToMarkup('payment.ManageFormConfirmationDialog', { linkedRecordsInfo }); - this.call('dialog', 'add', ConfirmationDialog, { - title: _t("Warning!"), - body, - confirmLabel: _t("Confirm Deletion"), - confirm: confirmCallback, - cancel: () => {}, - }); - }, - - /** - * Search for documents linked to the token and ask the user for confirmation. - * - * If any such document is found, a confirmation dialog is shown. - * - * @private - * @param {number} tokenId - The id of the token to delete - * @return {void} - */ - _deleteToken: function (tokenId) { - const execute = () => { - this._rpc({ - route: '/payment/archive_token', - params: { - 'token_id': tokenId, - }, - }).then(() => { - const $tokenCard = this.$( - `input[name="o_payment_radio"][data-payment-option-id="${tokenId}"]` + - `[data-payment-option-type="token"]` - ).closest('div[name="o_payment_option_card"]'); - $tokenCard.siblings(`#o_payment_token_inline_manage_form_${tokenId}`).remove(); - $tokenCard.remove(); - this._disableButton(false); - }).guardedCatch(error => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to delete your payment method."), - error.message.data.message, - ); - }); - }; - - // Fetch documents linked to the token - this._rpc({ - model: 'payment.token', - method: 'get_linked_records_info', - args: [tokenId], - }).then(linkedRecordsInfo => { - this._openConfirmationDialog(linkedRecordsInfo, execute); - }).guardedCatch(error => { - this._displayError( - _t("Server Error"), - _t("We are not able to delete your payment method."), - error.message.data.message, - ); - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Find the radio button linked to the click 'Delete' button and trigger the token deletion. - * - * Let `_onClickPaymentOption` select the radio button and display the inline form. - * - * Called when clicking on the 'Delete' button of a token. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickDeleteToken: function (ev) { - ev.preventDefault(); - - // Extract contextual values from the delete button - const linkedRadio = $(ev.currentTarget).siblings().find('input[name="o_payment_radio"]')[0]; - const tokenId = this._getPaymentOptionIdFromRadio(linkedRadio); - - // Delete the token - this._deleteToken(tokenId); - }, - - /** - * Handle the creation of a new token or the assignation of a token to a record. - * - * Called when clicking on the 'Save Payment Method' button of when submitting the form. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickSaveToken: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - - // Check that the user has selected a payment option - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if (!this._ensureRadioIsChecked($checkedRadios)) { - return; - } - const checkedRadio = $checkedRadios[0]; - - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(checkedRadio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(checkedRadio); - const flow = this._getPaymentFlowFromRadio(checkedRadio); - - // Save the payment method - this._hideError(); // Don't keep the error displayed if the user is going through 3DS2 - this._disableButton(true); // Disable until it is needed again - this.call('ui', 'block'); - if (flow !== 'token') { // Creation of a new token - this.txContext.tokenizationRequested = true; - this.txContext.isValidation = true; - this._processPayment(provider, paymentOptionId, flow); - } else if (this.txContext['allowTokenSelection']) { // Assignation of a token to a record - this._assignToken(paymentOptionId); - } - }, - - /** - * Delegate the handling of the token to `_onClickSaveToken`. - * - * Called when submitting the form (e.g. through the Return key). - * - * @private - * @param {Event} ev - * @return {void} - */ - _onSubmit: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - - this._onClickSaveToken(ev); - }, - -}); - -export default publicWidget.registry.PaymentManageForm; diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js new file mode 100644 index 00000000000..8b2f5a9e03c --- /dev/null +++ b/addons/payment/static/src/js/payment_form.js @@ -0,0 +1,620 @@ +/** @odoo-module **/ + +import publicWidget from '@web/legacy/js/public/public_widget'; +import { browser } from '@web/core/browser/browser'; +import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog'; +import { _t } from '@web/core/l10n/translation'; +import { renderToMarkup } from '@web/core/utils/render'; + +publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ + selector: '#o_payment_form', + events: Object.assign({}, publicWidget.Widget.prototype.events, { + 'click [name="o_payment_radio"]': '_selectPaymentOption', + 'click [name="o_payment_delete_token"]': '_fetchTokenData', + 'click [name="o_payment_expand_button"]': '_hideExpandButton', + 'click [name="o_payment_submit_button"]': '_submitForm', + }), + + // #=== WIDGET LIFECYCLE ===# + + /** + * @override + */ + async start() { + // Synchronously initialize paymentContext before any await. + this.paymentContext = {}; + Object.assign(this.paymentContext, this.el.dataset); + + await this._super(...arguments); + + // Expand the payment form of the selected payment option if there is only one. + const checkedRadio = document.querySelector('input[name="o_payment_radio"]:checked'); + if (checkedRadio) { + await this._expandInlineForm(checkedRadio); + this._enableButton(false); + } else { + this._setPaymentFlow(); // Initialize the payment flow to let providers overwrite it. + } + + this.$('[data-bs-toggle="tooltip"]').tooltip(); + }, + + // #=== EVENT HANDLERS ===# + + /** + * Open the inline form of the selected payment option, if any. + * + * @private + * @param {Event} ev + * @return {void} + */ + async _selectPaymentOption(ev) { + // Show the inputs in case they have been hidden. + this._showInputs(); + + // Disable the submit button while preparing the inline form. + this._disableButton(); + + // Unfold and prepare the inline form of the selected payment option. + const checkedRadio = ev.target; + await this._expandInlineForm(checkedRadio); + + // Re-enable the submit button after the inline form has been prepared. + this._enableButton(false); + }, + + /** + * Fetch data relative to the documents linked to the token and delegate them to the token + * deletion confirmation dialog. + * + * @private + * @param {Event} ev + * @return {void} + */ + _fetchTokenData(ev) { + ev.preventDefault(); + + const linkedRadio = document.getElementById(ev.currentTarget.dataset['linkedRadio']); + const tokenId = this._getPaymentOptionId(linkedRadio); + this._rpc({ + model: 'payment.token', + method: 'get_linked_records_info', + args: [tokenId], + }).then(linkedRecordsInfo => { + this._challengeTokenDeletion(tokenId, linkedRecordsInfo); + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayErrorDialog( + _t("Cannot delete payment method"), error.message.data.message + ); + }); + }, + + /** + * Hide the button to expand the payment methods section once it has been clicked. + * + * @private + * @param {Event} ev + * @return {void} + */ + _hideExpandButton(ev) { + ev.target.classList.add('d-none'); + }, + + /** + * Update the payment context with the selected payment option and initiate its payment flow. + * + * @private + * @param {Event} ev + * @return {void} + */ + async _submitForm(ev) { + ev.stopPropagation(); + ev.preventDefault(); + + const checkedRadio = this.el.querySelector('input[name="o_payment_radio"]:checked'); + + // Block the entire UI to prevent fiddling with other widgets. + this._disableButton(true); + + // Initiate the payment flow of the selected payment option. + const flow = this.paymentContext.flow = this._getPaymentFlow(checkedRadio); + const paymentOptionId = this.paymentContext.paymentOptionId = this._getPaymentOptionId( + checkedRadio + ); + if (flow === 'token' && this.paymentContext['assignTokenRoute']) { // Assign token flow. + await this._assignToken(paymentOptionId); + } else { // Both tokens and payment methods must process a payment operation. + const providerCode = this.paymentContext.providerCode = this._getProviderCode( + checkedRadio + ); + const pmCode = this.paymentContext.paymentMethodCode = this._getPaymentMethodCode( + checkedRadio + ); + this.paymentContext.providerId = this._getProviderId(checkedRadio); + if (this._getPaymentOptionType(checkedRadio) === 'token') { + this.paymentContext.tokenId = paymentOptionId; + } else { // 'payment_method' + this.paymentContext.paymentMethodId = paymentOptionId; + } + const inlineForm = this._getInlineForm(checkedRadio); + this.paymentContext.tokenizationRequested = inlineForm?.querySelector( + '[name="o_payment_tokenize_checkbox"]' + )?.checked ?? this.paymentContext['mode'] === 'validation'; + await this._initiatePaymentFlow(providerCode, paymentOptionId, pmCode, flow); + } + }, + + // #=== DOM MANIPULATION ===# + + /** + * Check if the submit button can be enabled and do it if so. + * + * @private + * @param {boolean} unblockUI - Whether the UI should also be unblocked. + * @return {void} + */ + _enableButton(unblockUI = true) { + if (this._canSubmit()) { + this._getSubmitButton().removeAttribute('disabled'); + } + if (unblockUI) { + this.call('ui', 'unblock'); + } + }, + + /** + * Disable the submit button. + * + * @private + * @param {boolean} blockUI - Whether the UI should also be blocked. + * @return {void} + */ + _disableButton(blockUI = false) { + this._getSubmitButton().setAttribute('disabled', true); + if (blockUI) { + this.call('ui', 'block'); + } + }, + + /** + * Show the tokenization checkbox, its label, and the submit button. + * + * @private + * @return {void} + */ + _showInputs() { + // Show the tokenization checkbox and its label. + const tokenizeContainer = this.el.querySelector('[name="o_payment_tokenize_container"]'); + tokenizeContainer?.classList.remove('d-none'); + + // Show the submit button. + this._getSubmitButton().classList.remove('d-none'); + }, + + /** + * Hide the tokenization checkbox, its label, and the submit button. + * + * The inputs should typically be hidden when the customer has to perform additional actions in + * the inline form. All inputs are automatically shown again when the customer selects another + * payment option. + * + * @private + * @return {void} + */ + _hideInputs() { + // Hide the tokenization checkbox and its label. + const tokenizeContainer = this.el.querySelector('[name="o_payment_tokenize_container"]'); + tokenizeContainer?.classList.add('d-none'); + + // Hide the submit button. + this._getSubmitButton().classList.add('d-none'); + }, + + /** + * Open the inline form of the selected payment option and collapse the others. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option. + * @return {void} + */ + async _expandInlineForm(radio) { + this._collapseInlineForms(); // Collapse previously opened inline forms. + this._setPaymentFlow(); // Reset the payment flow to let providers overwrite it. + + // Prepare the inline form of the selected payment option. + const providerId = this._getProviderId(radio); + const providerCode = this._getProviderCode(radio); + const paymentOptionId = this._getPaymentOptionId(radio); + const paymentMethodCode = this._getPaymentMethodCode(radio); + const flow = this._getPaymentFlow(radio); + await this._prepareInlineForm( + providerId, providerCode, paymentOptionId, paymentMethodCode, flow + ); + + // Display the prepared inline form if it is not empty. + const inlineForm = this._getInlineForm(radio); + if (inlineForm && inlineForm.children.length > 0) { + inlineForm.classList.remove('d-none'); + } + }, + + /** + * Prepare the provider-specific inline form of the selected payment option. + * + * For a provider to manage an inline form, it must override this method and render the content + * of the form. + * + * @private + * @param {number} providerId - The id of the selected payment option's provider. + * @param {string} providerCode - The code of the selected payment option's provider. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} paymentMethodCode - The code of the selected payment method, if any. + * @param {string} flow - The online payment flow of the selected payment option. + * @return {void} + */ + async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {}, + + /** + * Collapse all inline forms of the current widget. + * + * @private + * @return {void} + */ + _collapseInlineForms() { + this.el.querySelectorAll('[name="o_payment_inline_form"]').forEach(inlineForm => { + inlineForm.classList.add('d-none'); + }); + }, + + /** + * Display an error dialog. + * + * @private + * @param {string} title - The title of the dialog. + * @param {string} errorMessage - The error message. + * @return {void} + */ + _displayErrorDialog(title, errorMessage = '') { + this.call('dialog', 'add', ConfirmationDialog, { title: title, body: errorMessage || "" }); + }, + + /** + * Display the token deletion confirmation dialog. + * + * @private + * @param {number} tokenId - The id of the token whose deletion was requested. + * @param {object} linkedRecordsInfo - The data relative to the documents linked to the token. + * @return {void} + */ + _challengeTokenDeletion(tokenId, linkedRecordsInfo) { + const body = renderToMarkup('payment.deleteTokenDialog', { linkedRecordsInfo }); + this.call('dialog', 'add', ConfirmationDialog, { + title: _t("Warning!"), + body, + confirmLabel: _t("Confirm Deletion"), + confirm: () => this._archiveToken(tokenId), + cancel: () => {}, + }); + }, + + // #=== PAYMENT FLOW ===# + + /** + * Check whether the payment form can be submitted, i.e. whether exactly one payment option is + * selected. + * + * For a module to add a condition on the submission of the form, it must override this method + * and return whether both this method's condition and the override method's condition are met. + * + * @private + * @return {boolean} Whether the form can be submitted. + */ + _canSubmit() { + return this.el.querySelectorAll('input[name="o_payment_radio"]:checked').length === 1; + }, + + /** + * Set the payment flow for the selected payment option. + * + * For a provider to manage direct payments, it must call this method and set the payment flow + * when its payment option is selected. + * + * @private + * @param {string} flow - The flow for the selected payment option. Either 'redirect', 'direct', + * or 'token' + * @return {void} + */ + _setPaymentFlow(flow = 'redirect') { + if (['redirect', 'direct', 'token'].includes(flow)) { + this.paymentContext.flow = flow; + } else { + console.warn(`The value ${flow} is not a supported flow. Falling back to redirect.`); + this.paymentContext.flow = 'redirect'; + } + }, + + /** + * Assign the selected token to a document through the `assignTokenRoute`. + * + * @private + * @param {number} tokenId - The id of the token to assign. + * @return {void} + */ + async _assignToken(tokenId) { + this._rpc({ + route: this.paymentContext['assignTokenRoute'], + params: { + 'token_id': tokenId, + 'access_token': this.paymentContext['accessToken'], + } + }).then(() => { + window.location = this.paymentContext['landingRoute']; + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayErrorDialog(_t("Cannot save payment method"), error.message.data.message); + this._enableButton(); // The button has been disabled before initiating the flow. + }); + }, + + /** + * Make an RPC to initiate the payment flow by creating a new transaction. + * + * For a provider to do pre-processing work (e.g., perform checks on the form inputs), or to + * process the payment flow in its own terms (e.g., re-schedule the RPC to the transaction + * route), it must override this method. + * + * To alter the flow-specific processing, it is advised to override `_processRedirectFlow`, + * `_processDirectFlow`, or `_processTokenFlow` instead. + * + * @private + * @param {string} providerCode - The code of the selected payment option's provider. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} paymentMethodCode - The code of the selected payment method, if any. + * @param {string} flow - The payment flow of the selected payment option. + * @return {void} + */ + async _initiatePaymentFlow(providerCode, paymentOptionId, paymentMethodCode, flow) { + // Create a transaction and retrieve its processing values. + this._rpc({ + route: this.paymentContext['transactionRoute'], + params: this._prepareTransactionRouteParams(), + }).then(processingValues => { + if (flow === 'redirect') { + this._processRedirectFlow( + providerCode, paymentOptionId, paymentMethodCode, processingValues + ); + } else if (flow === 'direct') { + this._processDirectFlow( + providerCode, paymentOptionId, paymentMethodCode, processingValues + ); + } else if (flow === 'token') { + this._processTokenFlow( + providerCode, paymentOptionId, paymentMethodCode, processingValues + ); + } + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayErrorDialog(_t("Payment processing failed"), error.message.data.message); + this._enableButton(); // The button has been disabled before initiating the flow. + }); + }, + + /** + * Prepare the params for the RPC to the transaction route. + * + * @private + * @return {object} The transaction route params. + */ + _prepareTransactionRouteParams() { + return { + 'provider_id': this.paymentContext.providerId, + 'payment_method_id': this.paymentContext.paymentMethodId ?? null, + 'token_id': this.paymentContext.tokenId ?? null, + 'reference_prefix': this.paymentContext['referencePrefix']?.toString() ?? null, + 'amount': this.paymentContext['amount'] !== undefined + ? parseFloat(this.paymentContext['amount']) : null, + 'currency_id': this.paymentContext['currencyId'] + ? parseInt(this.paymentContext['currencyId']) : null, + 'partner_id': parseInt(this.paymentContext['partnerId']), + 'flow': this.paymentContext['flow'], + 'tokenization_requested': this.paymentContext['tokenizationRequested'], + 'landing_route': this.paymentContext['landingRoute'], + 'is_validation': this.paymentContext['mode'] === 'validation', + 'access_token': this.paymentContext['accessToken'], + 'csrf_token': odoo.csrf_token, + }; + }, + + /** + * Redirect the customer by submitting the redirect form included in the processing values. + * + * @private + * @param {string} providerCode - The code of the selected payment option's provider. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} paymentMethodCode - The code of the selected payment method, if any. + * @param {object} processingValues - The processing values of the transaction. + * @return {void} + */ + _processRedirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) { + // Create and configure the form element with the content rendered by the server. + const div = document.createElement('div'); + div.innerHTML = processingValues['redirect_form_html']; + const redirectForm = div.querySelector('form'); + redirectForm.setAttribute('id', 'o_payment_redirect_form'); + redirectForm.setAttribute('target', '_top'); // Ensures redirections when in an iframe. + + // Submit the form. + document.body.appendChild(redirectForm); + redirectForm.submit(); + }, + + /** + * Process the provider-specific implementation of the direct payment flow. + * + * @private + * @param {string} providerCode - The code of the selected payment option's provider. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} paymentMethodCode - The code of the selected payment method, if any. + * @param {object} processingValues - The processing values of the transaction. + * @return {void} + */ + _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {}, + + /** + * Redirect the customer to the status route. + * + * @private + * @param {string} providerCode - The code of the selected payment option's provider. + * @param {number} paymentOptionId - The id of the selected payment option. + * @param {string} paymentMethodCode - The code of the selected payment method, if any. + * @param {object} processingValues - The processing values of the transaction. + * @return {void} + */ + _processTokenFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) { + // The flow is already completed as payments by tokens are immediately processed. + window.location = '/payment/status'; + }, + + /** + * Archive the provided token. + * + * @private + * @param {number} tokenId - The id of the token whose deletion was requested. + * @return {void} + */ + _archiveToken(tokenId) { + this._rpc({ + route: '/payment/archive_token', + params: { + 'token_id': tokenId, + }, + }).then(() => { + browser.location.reload(); + }).guardedCatch(error => { + error.event.preventDefault(); + this._displayErrorDialog( + _t("Cannot delete payment method"), error.message.data.message + ); + }); + }, + + // #=== GETTERS ===# + + /** + * Determine and return the inline form of the selected payment option. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option. + * @return {Element | null} The inline form of the selected payment option, if any. + */ + _getInlineForm(radio) { + const inlineFormContainer = radio.closest('[name="o_payment_option"]'); + return inlineFormContainer?.querySelector('[name="o_payment_inline_form"]'); + }, + + /** + * Find and return the submit button. + * + * The button is searched in the whole document, rather than only in the current form, to allow + * modules to place it outside the payment form (e.g., eCommerce). + * + * @private + * @return {Element} The submit button. + */ + _getSubmitButton() { + return document.querySelector('[name="o_payment_submit_button"]'); + }, + + /** + * Determine and return the payment flow of the selected payment option. + * + * As some providers implement both direct payments and the payment with redirection flow, we + * cannot infer it from the radio button only. The radio button indicates only whether the + * payment option is a token. If not, the payment context is looked up to determine whether the + * flow is 'direct' or 'redirect'. + * + * @private + * @param {HTMLInputElement} radio - The radio button linked to the payment option. + * @return {string} The flow of the selected payment option: 'redirect', 'direct' or 'token'. + */ + _getPaymentFlow(radio) { + // The flow is read from the payment context too in case it was forced in a custom implem. + if (this._getPaymentOptionType(radio) === 'token' || this.paymentContext.flow === 'token') { + return 'token'; + } else if (this.paymentContext.flow === 'redirect') { + return 'redirect'; + } else { + return 'direct'; + } + }, + + /** + * Determine and return the code of the selected payment method. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment method. + * @return {string} The code of the selected payment method. + */ + _getPaymentMethodCode(radio) { + return radio.dataset['paymentMethodCode']; + }, + + /** + * Determine and return the id of the selected payment option. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment option. + * @return {number} The id of the selected payment option. + */ + _getPaymentOptionId(radio) { + return Number(radio.dataset['paymentOptionId']); + }, + + /** + * Determine and return the type of the selected payment option. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment option. + * @return {string} The type of the selected payment option: 'token' or 'payment_method'. + */ + _getPaymentOptionType(radio) { + return radio.dataset['paymentOptionType']; + }, + + /** + * Determine and return the id of the provider of the selected payment option. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment option. + * @return {number} The id of the provider of the selected payment option. + */ + _getProviderId(radio) { + return Number(radio.dataset['providerId']); + }, + + /** + * Determine and return the code of the provider of the selected payment option. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment option. + * @return {string} The code of the provider of the selected payment option. + */ + _getProviderCode(radio) { + return radio.dataset['providerCode']; + }, + + /** + * Determine and return the state of the provider of the selected payment option. + * + * @private + * @param {HTMLElement} radio - The radio button linked to the payment option. + * @return {string} The state of the provider of the selected payment option. + */ + _getProviderState(radio) { + return radio.dataset['providerState']; + }, + +}); + +export default publicWidget.registry.PaymentForm; diff --git a/addons/payment/static/src/js/payment_form_mixin.js b/addons/payment/static/src/js/payment_form_mixin.js deleted file mode 100644 index bb840a1532b..00000000000 --- a/addons/payment/static/src/js/payment_form_mixin.js +++ /dev/null @@ -1,549 +0,0 @@ -/** @odoo-module **/ - -import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog'; -import { escape } from '@web/core/utils/strings'; -import core from '@web/legacy/js/services/core'; -import { _t } from '@web/core/l10n/translation'; - - export default { - - /** - * @override - */ - start: async function () { - this.txContext = {}; // Synchronously initialize txContext before any await. - Object.assign(this.txContext, this.$el.data()); - await this._super(...arguments); - window.addEventListener('pageshow', function (event) { - if (event.persisted) { - window.location.reload(); - } - }); - this.$('[data-bs-toggle="tooltip"]').tooltip(); - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if ($checkedRadios.length === 1) { - const checkedRadio = $checkedRadios[0]; - this._displayInlineForm(checkedRadio); - this._enableButton(); - } else { - this._setPaymentFlow(); // Initialize the payment flow to let providers overwrite it - } - // When a module wants to activate the button, - // it must test its conditions and then call this bus. - core.bus.on('enableButton', this, this._enableButton); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Disable the submit button. - * - * The icons are updated to either show that an action is processing or that the button is - * not ready, depending on the value of `showLoadingAnimation`. - * - * @private - * @param {boolean} showLoadingAnimation - Whether a spinning loader should be shown - * @return {void} - */ - _disableButton(showLoadingAnimation = true) { - const $submitButton = $('button[name="o_payment_submit_button"]'); - const iconClass = $submitButton.data('icon-class'); - $submitButton.attr('disabled', true); - if (showLoadingAnimation) { - $submitButton.find('i').removeClass(iconClass); - $submitButton.prepend( - ' ' - ); - } - }, - - /** - * Display an error in the payment form. - * - * If no payment option is selected, the error is displayed in a dialog. If exactly one - * payment option is selected, the error is displayed in the inline form of that payment - * option and the view is focused on the error. - * - * @private - * @param {string} title - The title of the error - * @param {string} description - The description of the error - * @param {string} error - The raw error message - * @return {void} - */ - _displayError: function (title, description = '', error = '') { - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if ($checkedRadios.length !== 1) { // Cannot find selected payment option, show dialog - this.call('dialog', 'add', ConfirmationDialog, { - title: _t("Error: %s", title), - body: description || "", - }); - } else { // Show error in inline form - this._hideError(); // Remove any previous error - - // Build the html for the error - let errorHtml = `
- ${escape(title)}`; - if (description !== '') { - errorHtml += `
${escape(description)}`; - } - if (error !== '') { - errorHtml += `
${escape(error)}`; - } - errorHtml += '
'; - - // Append error to inline form and center the page on the error - const checkedRadio = $checkedRadios[0]; - const $inlineForm = this._getInlineFormFromRadio(checkedRadio); - $inlineForm.removeClass('d-none'); // Show the inline form even if it was empty - $inlineForm.append(errorHtml).find('div[name="o_payment_error"]')[0] - .scrollIntoView({behavior: 'smooth', block: 'center'}); - } - this._enableButton(); // Enable button back after it was disabled before processing - this.call('ui', 'unblock'); // The page is blocked at this point, unblock it - }, - - /** - * Display the inline form of the selected payment option and hide others. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {void} - */ - _displayInlineForm: function (radio) { - this._hideInlineForms(); // Collapse previously opened inline forms - this._hideError(); // The error is only relevant until it is hidden with its inline form - this._setPaymentFlow(); // Reset the payment flow to let providers overwrite it - - // Extract contextual values from the radio button - const provider = this._getProviderFromRadio(radio); - const paymentOptionId = this._getPaymentOptionIdFromRadio(radio); - const flow = this._getPaymentFlowFromRadio(radio); - - // Prepare the inline form of the selected payment option and display it if not empty - this._prepareInlineForm(provider, paymentOptionId, flow); - const $inlineForm = this._getInlineFormFromRadio(radio); - if ($inlineForm.children().length > 0) { - $inlineForm.removeClass('d-none'); - } - }, - - /** - * Check if the submit button can be enabled and do it if so. - * - * The icons are updated to show that the button is ready. - * - * @private - * @return {boolean} Whether the button was enabled. - */ - _enableButton: function () { - if (this._isButtonReady()) { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const iconClass = $submitButton.data('icon-class'); - $submitButton.attr('disabled', false); - $submitButton.find('i').addClass(iconClass); - $submitButton.find('span.o_loader').remove(); - return true; - } - return false; - }, - - /** - * Verify that exactly one radio button is checked and display an error otherwise. - * - * @private - * @param {jQuery} $checkedRadios - The currently check radio buttons - * - * @return {boolean} Whether exactly one radio button among the provided radios is checked - */ - _ensureRadioIsChecked: function ($checkedRadios) { - if ($checkedRadios.length === 0) { - this._displayError( - _t("No payment option selected"), - _t("Please select a payment option.") - ); - return false; - } else if ($checkedRadios.length > 1) { - this._displayError( - _t("Multiple payment options selected"), - _t("Please select only one payment option.") - ); - return false; - } - return true; - }, - - /** - * Find and return the inline form of the selected payment option. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option. - * @return {jQuery} The inline form of the selected payment option. - */ - _getInlineFormFromRadio(radio) { - const paymentOptionId = this._getPaymentOptionIdFromRadio(radio); - const paymentOptionType = $(radio).data('payment-option-type'); - const $inlineForm = this.$( - `#o_payment_${paymentOptionType}_inline_${this.formType}_form_${paymentOptionId}` - ); - return $inlineForm; - }, - - /** - * Determine and return the online payment flow of the selected payment option. - * - * As some providers implement both the direct payment and the payment with redirection, the - * flow cannot be inferred from the radio button only. The radio button only indicates - * whether the payment option is a token. If not, the transaction context is looked up to - * determine whether the flow is 'direct' or 'redirect'. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {string} The flow of the selected payment option. redirect, direct or token. - */ - _getPaymentFlowFromRadio: function (radio) { - if ( - $(radio).data('payment-option-type') === 'token' - || this.txContext.flow === 'token' - ) { - return 'token'; - } else if (this.txContext.flow === 'redirect') { - return 'redirect'; - } else { - return 'direct'; - } - }, - - /** - * Determine and return the id of the selected payment option. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {number} The provider id or the token id or of the payment option linked to the - * radio button. - */ - _getPaymentOptionIdFromRadio(radio) { - return $(radio).data('payment-option-id'); - }, - - /** - * Determine and return the provider of the selected payment option. - * - * @private - * @param {HTMLInputElement} radio - The radio button linked to the payment option - * @return {number} The provider of the payment option linked to the radio button. - */ - _getProviderFromRadio(radio) { - return $(radio).data('provider'); - }, - - /** - * Remove the error in the inline form of the current widget. - * - * @private - * @return {jQuery} The removed error - */ - _hideError() { - return this.$('div[name="o_payment_error"]').remove(); - }, - - /** - * Collapse all inline forms of the current widget. - * - * @private - * @return {void}. - */ - _hideInlineForms() { - this.$('[name="o_payment_inline_form"]').addClass('d-none'); - }, - - /** - * Hide the "Save my payment details" label and checkbox, and the submit button. - * - * The inputs should typically be hidden when the customer has to perform additional actions - * in the inline form. All inputs are automatically shown again when the customer clicks on - * another inline form. - * - * @private - * @return {void} - */ - _hideInputs: function () { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); - $submitButton.addClass('d-none'); - $tokenizeCheckboxes.closest('label').addClass('d-none'); - }, - - /** - * Verify that the submit button is ready to be enabled. - * - * For a module to support a custom behavior for the submit button, it must override this - * method and only return true if the result of this method is true and if nothing prevents - * enabling the submit button for that custom behavior. - * - * @private - * - * @return {boolean} Whether the submit button can be enabled - */ - _isButtonReady: function () { - const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); - if ($checkedRadios.length === 1) { - const checkedRadio = $checkedRadios[0]; - const flow = this._getPaymentFlowFromRadio(checkedRadio); - return flow !== 'token' || this.txContext['allowTokenSelection']; - } else { - return false; - } - }, - - /** - * Prepare the params to send to the transaction route. - * - * For a provider to overwrite generic params or to add provider-specific ones, it must - * override this method and return the extended transaction route params. - * - * @private - * @param {string} code - The code of the selected payment option provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {object} The transaction route params - */ - _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { - return { - 'payment_option_id': paymentOptionId, - 'reference_prefix': this.txContext['referencePrefix'] !== undefined - ? this.txContext['referencePrefix'].toString() : null, - 'amount': this.txContext.amount !== undefined - ? parseFloat(this.txContext.amount) : null, - 'currency_id': this.txContext.currencyId - ? parseInt(this.txContext.currencyId) : null, - 'partner_id': parseInt(this.txContext.partnerId), - 'flow': flow, - 'tokenization_requested': this.txContext.tokenizationRequested, - 'landing_route': this.txContext['landingRoute'], - 'is_validation': this.txContext.isValidation, - 'access_token': this.txContext.accessToken - ? this.txContext.accessToken : undefined, - 'csrf_token': odoo.csrf_token, - }; - }, - - /** - * Prepare the provider-specific inline form of the selected payment option. - * - * For a provider to manage an inline form, it must override this method. When the override - * is called, it must determine whether it is necessary to prepare its inline form. Otherwise, - * the call must be sent back to the parent method. - * - * @private - * @param {string} code - The code of the selected payment option's provider - * @param {number} paymentOptionId - The id of the selected payment option - * @param {string} flow - The online payment flow of the selected payment option - * @return {void} - */ - _prepareInlineForm(code, paymentOptionId, flow) {}, - - /** - * Process the payment. - * - * For a provider to do pre-processing work on the transaction processing flow, or to - * define its entire own flow that requires re-scheduling the RPC to the transaction route, - * it must override this method. - * If only post-processing work is needed, an override of `_processRedirectPayment`, - * `_processDirectPayment` or `_processTokenPayment` might be more appropriate. - * - * @private - * @param {string} code - The code of the payment option's provider - * @param {number} paymentOptionId - The id of the payment option handling the transaction - * @param {string} flow - The online payment flow of the transaction - * @return {void} - */ - _processPayment: function (code, paymentOptionId, flow) { - // Call the transaction route to create a tx and retrieve the processing values - this._rpc({ - route: this.txContext['transactionRoute'], - params: this._prepareTransactionRouteParams(code, paymentOptionId, flow), - }).then(processingValues => { - if (flow === 'redirect') { - this._processRedirectPayment(code, paymentOptionId, processingValues); - } else if (flow === 'direct') { - this._processDirectPayment(code, paymentOptionId, processingValues); - } else if (flow === 'token') { - this._processTokenPayment(code, paymentOptionId, processingValues); - } - }).guardedCatch(error => { - error.event.preventDefault(); - this._displayError( - _t("Server Error"), - _t("We are not able to process your payment."), - error.message.data.message, - ); - }); - }, - - /** - * Execute the provider-specific implementation of the direct payment flow. - * - * For a provider to redefine the processing of the direct payment flow, it must override - * this method. - * - * @private - * @param {string} code - The code of the provider - * @param {number} providerId - The id of the provider handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {void} - */ - _processDirectPayment(code, providerId, processingValues) {}, - - /** - * Redirect the customer by submitting the redirect form included in the processing values. - * - * For a provider to redefine the processing of the payment with redirection flow, it must - * override this method. - * - * @private - * @param {string} code - The code of the provider - * @param {number} providerId - The id of the provider handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {void} - */ - _processRedirectPayment(code, providerId, processingValues) { - // Append the redirect form to the body - const $redirectForm = $(processingValues['redirect_form_html']).attr( - 'id', 'o_payment_redirect_form' - ); - // Ensures external redirections when in an iframe. - $redirectForm[0].setAttribute('target', '_top'); - $(document.getElementsByTagName('body')[0]).append($redirectForm); - - // Submit the form - $redirectForm.submit(); - }, - - /** - * Redirect the customer to the status route. - * - * For a provider to redefine the processing of the payment by token flow, it must override - * this method. - * - * @private - * @param {string} provider_code - The code of the token's provider - * @param {number} tokenId - The id of the token handling the transaction - * @param {object} processingValues - The processing values of the transaction - * @return {void} - */ - _processTokenPayment(provider_code, tokenId, processingValues) { - // The flow is already completed as payments by tokens are immediately processed - window.location = '/payment/status'; - }, - - /** - * Set the online payment flow for the selected payment option. - * - * For a provider to manage direct payments, it must call this method from within its - * override of `_prepareInlineForm` to declare its payment flow for the selected payment - * option. - * - * @private - * @param {string} flow - The flow for the selected payment option. Either 'redirect', - * 'direct' or 'token' - * @return {void} - */ - _setPaymentFlow: function (flow = 'redirect') { - if (flow !== 'redirect' && flow !== 'direct' && flow !== 'token') { - console.warn( - `payment_form_mixin: method '_setPaymentFlow' was called with invalid flow: - ${flow}. Falling back to 'redirect'.` - ); - this.txContext.flow = 'redirect'; - } else { - this.txContext.flow = flow; - } - }, - - /** - * Show the "Save my payment details" label and checkbox, and the submit button. - * - * @private - * @return {void}. - */ - _showInputs: function () { - const $submitButton = this.$('button[name="o_payment_submit_button"]'); - const $tokenizeCheckboxes = this.$('input[name="o_payment_save_as_token"]'); - $submitButton.removeClass('d-none'); - $tokenizeCheckboxes.closest('label').removeClass('d-none'); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Hide all extra payment method icons of the provider linked to the clicked button. - * - * Called when clicking on the "show less" button. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickLessPaymentIcons(ev) { - ev.preventDefault(); - ev.stopPropagation(); - // Hide the extra payment icons, and the "show less" button - const $itemList = $(ev.currentTarget).parents('ul'); - const maxIconNumber = $itemList.data('max-icons-displayed'); - $itemList.children('li').slice(maxIconNumber).addClass('d-none'); - // Show the "show more" button - $itemList.find('a[name="o_payment_icon_more"]').parents('li').removeClass('d-none'); - }, - - /** - * Display all the payment methods icons of the provider linked to the clicked button. - * - * Called when clicking on the "show more" button. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickMorePaymentIcons(ev) { - ev.preventDefault(); - ev.stopPropagation(); - // Display all the payment methods icons, and the "show less" button - $(ev.currentTarget).parents('ul').children('li').removeClass('d-none'); - // Hide the "show more" button - $(ev.currentTarget).parents('li').addClass('d-none'); - }, - - /** - * Mark the clicked card radio button as checked and open the inline form, if any. - * - * Called when clicking on the card of a payment option. - * - * @private - * @param {Event} ev - * @return {void} - */ - _onClickPaymentOption: function (ev) { - // Uncheck all radio buttons - this.$('input[name="o_payment_radio"]').prop('checked', false); - // Check radio button linked to selected payment option - const checkedRadio = $(ev.currentTarget).find('input[name="o_payment_radio"]')[0]; - $(checkedRadio).prop('checked', true); - - // Show the inputs in case they had been hidden - this._showInputs(); - - // Disable the submit button while building the content - this._disableButton(false); - - // Unfold and prepare the inline form of selected payment option - this._displayInlineForm(checkedRadio); - - // Re-enable the submit button - this._enableButton(); - }, - -}; diff --git a/addons/payment/static/src/js/post_processing.js b/addons/payment/static/src/js/post_processing.js index 4254701cad1..c7f141a526f 100644 --- a/addons/payment/static/src/js/post_processing.js +++ b/addons/payment/static/src/js/post_processing.js @@ -1,9 +1,9 @@ /** @odoo-module **/ - import publicWidget from '@web/legacy/js/public/public_widget'; - import { renderToElement } from '@web/core/utils/render'; - import { markup } from "@odoo/owl"; - import { _t } from '@web/core/l10n/translation'; +import publicWidget from '@web/legacy/js/public/public_widget'; +import { renderToElement } from '@web/core/utils/render'; +import { markup } from "@odoo/owl"; +import { _t } from '@web/core/l10n/translation'; publicWidget.registry.PaymentPostProcessing = publicWidget.Widget.extend({ selector: 'div[name="o_payment_status"]', diff --git a/addons/payment/static/src/scss/payment_form.scss b/addons/payment/static/src/scss/payment_form.scss index 68a0b06dfe5..a334d4203bc 100644 --- a/addons/payment/static/src/scss/payment_form.scss +++ b/addons/payment/static/src/scss/payment_form.scss @@ -1,62 +1,20 @@ -.o_payment_form { - label > input[type="radio"], input[type="checkbox"]{ - vertical-align: middle; - margin-right: 5px; +.o_payment_form .o_outline { + + &:hover { + border-color: $primary; } - .payment_option_name { - font-size: 14px; - font-weight: normal !important; - font-family: Helvetica Neue, sans-serif; - line-height: 1.3em; - color: #4d4d4d; + &:not(:first-child):hover { + // Since list-group items, except the first child, have no top border, this emulates the top + // border for the hovered state. + box-shadow: 0 (-$border-width) 0 $primary; } - label { - font-weight: normal; - margin-top: 5px; + .o_payment_option_label:before { + position: absolute; + inset: 0; + content: ''; + cursor: pointer; } - .card { - border-radius: 5px; - overflow: hidden; - } - - .card-body { - &:not(:first-child) { - border-top: 1px solid #dddddd; - } - padding: 1.14em !important; - &.o_payment_option_card:hover { - cursor: pointer; - } - } - - .card-footer { - padding: 0.5rem; - label { - margin-top: 15px; - } - } - - .card-footer:last-child { - border-bottom-right-radius: 10px !important; - border-bottom-left-radius: 10px !important; - } - - .payment_icon_list { - position: relative; - li { - padding-left: 5px !important; - padding-right: 0px !important; - } - - .more_option { - @include o-position-absolute($right: 10px); - font-size:10px; - } - - margin-top: 0px !important; - margin-bottom: -5px !important; - } } diff --git a/addons/payment/static/src/scss/payment_templates.scss b/addons/payment/static/src/scss/payment_templates.scss deleted file mode 100644 index 9e0598328ac..00000000000 --- a/addons/payment/static/src/scss/payment_templates.scss +++ /dev/null @@ -1,12 +0,0 @@ -div[name="o_payment_status_alert"] div > p { - margin-bottom: 0; -} - -.o_payment_details .col:not(:last-child) { - border-bottom: $border-width solid $border-color; - - @include media-breakpoint-up(md) { - border-right: $border-width solid $border-color; - border-bottom: none; - } -} diff --git a/addons/payment/static/src/scss/portal_templates.scss b/addons/payment/static/src/scss/portal_templates.scss new file mode 100644 index 00000000000..feef51d4e3c --- /dev/null +++ b/addons/payment/static/src/scss/portal_templates.scss @@ -0,0 +1,9 @@ +div[name="o_payment_status_alert"] div > p { + margin-bottom: 0; +} + +.o_payment_summary_separator { + @include media-breakpoint-up(md) { + border-left: $border-width solid $border-color; + } +} diff --git a/addons/payment/static/src/xml/payment_form_templates.xml b/addons/payment/static/src/xml/payment_form_templates.xml new file mode 100644 index 00000000000..42db6dc5995 --- /dev/null +++ b/addons/payment/static/src/xml/payment_form_templates.xml @@ -0,0 +1,22 @@ + + + + + +
+

Are you sure you want to delete this payment method?

+ +

It is currently linked to the following documents:

+ +
+
+
+ +
diff --git a/addons/payment/static/src/xml/payment_manage_form_confirmation_dialog.xml b/addons/payment/static/src/xml/payment_manage_form_confirmation_dialog.xml deleted file mode 100644 index bd76528d062..00000000000 --- a/addons/payment/static/src/xml/payment_manage_form_confirmation_dialog.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - -
- Are you sure you want to delete this payment method? - -
- It is currently linked to the following documents: -
    - -
  • - -
-
-
-
-
diff --git a/addons/payment/static/src/xml/payment_post_processing.xml b/addons/payment/static/src/xml/payment_post_processing.xml index b7e2801dac2..2450d25d537 100644 --- a/addons/payment/static/src/xml/payment_post_processing.xml +++ b/addons/payment/static/src/xml/payment_post_processing.xml @@ -54,12 +54,25 @@
-
+
+ + +
+
+
- +
diff --git a/addons/payment/tests/common.py b/addons/payment/tests/common.py index 0c66cd23563..84b4792ff60 100644 --- a/addons/payment/tests/common.py +++ b/addons/payment/tests/common.py @@ -61,6 +61,11 @@ class PaymentCommon(BaseCommon): 'country_id': cls.country_belgium.id, }) + # Activate pm + cls.env.ref('payment.payment_method_unknown').write({ + 'active': True, + 'support_tokenization': True, + }) # Create a dummy provider to allow basic tests without any specific provider implementation arch = """
@@ -79,6 +84,7 @@ class PaymentCommon(BaseCommon): 'code': 'none', 'state': 'test', 'is_published': True, + 'payment_method_ids': [Command.set([cls.env.ref('payment.payment_method_unknown').id])], 'allow_tokenization': True, 'redirect_form_view_id': redirect_form.id, 'available_currency_ids': [Command.set( @@ -87,6 +93,9 @@ class PaymentCommon(BaseCommon): }) cls.provider = cls.dummy_provider + cls.payment_methods = cls.provider.payment_method_ids + cls.payment_method_id = cls.provider.payment_method_ids[:1].id + cls.payment_method_code = cls.provider.payment_method_ids[:1].code cls.amount = 1111.11 cls.company = cls.env.company cls.company_id = cls.company.id @@ -140,7 +149,10 @@ class PaymentCommon(BaseCommon): AND([provider_domain, [('company_id', '=', company.id)]]), limit=1 ) if not provider: - base_provider = cls.env['payment.provider'].sudo().search(provider_domain, limit=1) + if code != 'none': + base_provider = cls.env['payment.provider'].sudo().search(provider_domain, limit=1) + else: + base_provider = cls.provider if not base_provider: _logger.error("no payment.provider found for code %s", code) return cls.env['payment.provider'] @@ -157,6 +169,7 @@ class PaymentCommon(BaseCommon): def _create_transaction(self, flow, sudo=True, **values): default_values = { + 'payment_method_id': self.payment_method_id, 'amount': self.amount, 'currency_id': self.currency.id, 'provider_id': self.provider.id, @@ -168,8 +181,9 @@ class PaymentCommon(BaseCommon): def _create_token(self, sudo=True, **values): default_values = { - 'payment_details': "1234", 'provider_id': self.provider.id, + 'payment_method_id': self.payment_method_id, + 'payment_details': "1234", 'partner_id': self.partner.id, 'provider_ref': "provider Ref (TEST)", 'active': True, diff --git a/addons/payment/tests/http_common.py b/addons/payment/tests/http_common.py index 51d44a9057b..2d341b3c0c3 100644 --- a/addons/payment/tests/http_common.py +++ b/addons/payment/tests/http_common.py @@ -76,12 +76,11 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): yield self.assertEqual(cm.exception.code, 404) - def _get_tx_context(self, response, form_name): - """Extracts txContext & other form info (provider & token ids) - from a payment response (with manage/checkout html form) + def _get_payment_context(self, response): + """Extracts the payment context & other form info (provider & token ids) + from a payment response :param response: http Response, with a payment form as text - :param str form_name: o_payment_manage / o_payment_checkout :return: Transaction context (+ provider_ids & token_ids) :rtype: dict """ @@ -92,9 +91,9 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): response.text, parser=etree.HTMLParser(), ) - checkout_form = html_tree.xpath(f"//form[@name='{form_name}']")[0] + payment_form = html_tree.xpath('//form[@id="o_payment_form"]')[0] values = {} - for key, val in checkout_form.items(): + for key, val in payment_form.items(): if key.startswith("data-"): formatted_key = key[5:].replace('-', '_') if formatted_key.endswith('_id'): @@ -106,21 +105,21 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): values[formatted_key] = formatted_val payment_options_inputs = html_tree.xpath("//input[@name='o_payment_radio']") - provider_ids = [] token_ids = [] + payment_method_ids = [] for p_o_input in payment_options_inputs: data = dict() for key, val in p_o_input.items(): if key.startswith('data-'): data[key[5:]] = val - if data['payment-option-type'] == 'provider': - provider_ids.append(int(data['payment-option-id'])) - else: + if data['payment-option-type'] == 'token': token_ids.append(int(data['payment-option-id'])) + else: # 'payment_method' + payment_method_ids.append(int(data['payment-option-id'])) values.update({ - 'provider_ids': provider_ids, 'token_ids': token_ids, + 'payment_method_ids': payment_method_ids, }) return values @@ -148,7 +147,7 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): } def _portal_pay(self, **route_kwargs): - """/payment/pay txContext feedback + """/payment/pay payment context feedback NOTE: must be authenticated before calling method. Or an access_token should be specified in route_kwargs @@ -157,18 +156,18 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): url = self._build_url(uri) return self._make_http_get_request(url, route_kwargs) - def _get_tx_checkout_context(self, **route_kwargs): + def _get_portal_pay_context(self, **route_kwargs): response = self._portal_pay(**route_kwargs) self.assertEqual(response.status_code, 200) - return self._get_tx_context(response, 'o_payment_checkout') + return self._get_payment_context(response) # /my/payment_method # ###################### def _portal_payment_method(self): - """/my/payment_method txContext feedback + """/my/payment_method payment context feedback NOTE: must be authenticated before calling method validation flow is restricted to logged users @@ -177,33 +176,35 @@ class PaymentHttpCommon(PaymentCommon, HttpCase): url = self._build_url(uri) return self._make_http_get_request(url, {}) - def _get_tx_manage_context(self): + def _get_portal_payment_method_context(self): response = self._portal_payment_method() self.assertEqual(response.status_code, 200) - return self._get_tx_context(response, 'o_payment_manage') + return self._get_payment_context(response) # payment/transaction # ####################### - def _prepare_transaction_values(self, payment_option_id, flow): + def _prepare_transaction_values(self, payment_method_id, token_id, flow): """ Prepare the basic payment/transaction route values. :param int payment_option_id: The payment option handling the transaction, as a - `payment.provider` id or a `payment.token` id + `payment.method` id or a `payment.token` id :param str flow: The payment flow :return: The route values :rtype: dict """ return { + 'provider_id': self.provider.id, + 'payment_method_id': payment_method_id, + 'token_id': token_id, 'amount': self.amount, 'currency_id': self.currency.id, 'partner_id': self.partner.id, 'access_token': self._generate_test_access_token( self.partner.id, self.amount, self.currency.id ), - 'payment_option_id': payment_option_id, 'reference_prefix': 'test', 'tokenization_requested': True, 'landing_route': 'Test', diff --git a/addons/payment/tests/test_flows.py b/addons/payment/tests/test_flows.py index e5355360d2b..993bce8be33 100644 --- a/addons/payment/tests/test_flows.py +++ b/addons/payment/tests/test_flows.py @@ -26,16 +26,14 @@ class TestFlows(PaymentHttpCommon): route_values = self._prepare_pay_values() # /payment/pay - tx_context = self._get_tx_checkout_context(**route_values) - for key, val in tx_context.items(): + payment_context = self._get_portal_pay_context(**route_values) + for key, val in payment_context.items(): if key in route_values: self.assertEqual(val, route_values[key]) - self.assertIn(self.provider.id, tx_context['provider_ids']) - - # Route values are taken from tx_context result of /pay route to correctly simulate the flow + # Route values are taken from payment_context result of /pay route to correctly simulate the flow route_values = { - k: tx_context[k] + k: payment_context[k] for k in [ 'amount', 'currency_id', @@ -46,14 +44,13 @@ class TestFlows(PaymentHttpCommon): ] } route_values.update({ + 'provider_id': self.provider.id, + 'payment_method_id': self.payment_method_id if flow != 'token' else None, + 'token_id': self._create_token().id if flow == 'token' else None, 'flow': flow, - 'payment_option_id': self.provider.id, 'tokenization_requested': False, }) - if flow == 'token': - route_values['payment_option_id'] = self._create_token().id - with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = self._get_processing_values(**route_values) tx_sudo = self._get_tx(processing_values['reference']) @@ -163,26 +160,28 @@ class TestFlows(PaymentHttpCommon): validation_amount = self.provider._get_validation_amount() validation_currency = self.provider._get_validation_currency() - tx_context = self._get_tx_manage_context() + payment_context = self._get_portal_payment_method_context() expected_values = { 'partner_id': self.partner.id, 'access_token': self._generate_test_access_token(self.partner.id, None, None), 'reference_prefix': expected_reference } - for key, val in tx_context.items(): + for key, val in payment_context.items(): if key in expected_values: self.assertEqual(val, expected_values[key]) transaction_values = { + 'provider_id': self.provider.id, + 'payment_method_id': self.payment_method_id, + 'token_id': None, 'amount': None, 'currency_id': None, - 'partner_id': tx_context['partner_id'], - 'access_token': tx_context['access_token'], + 'partner_id': payment_context['partner_id'], + 'access_token': payment_context['access_token'], 'flow': flow, - 'payment_option_id': self.provider.id, 'tokenization_requested': True, - 'reference_prefix': tx_context['reference_prefix'], - 'landing_route': tx_context['landing_route'], + 'reference_prefix': payment_context['reference_prefix'], + 'landing_route': payment_context['landing_route'], 'is_validation': True, } with mute_logger('odoo.addons.payment.models.payment_transaction'): @@ -196,7 +195,6 @@ class TestFlows(PaymentHttpCommon): self.assertEqual(tx_sudo.partner_id.id, self.partner.id) self.assertEqual(tx_sudo.reference, expected_reference) # processing_values == given values - self.assertEqual(processing_values['provider_id'], self.provider.id) self.assertEqual(processing_values['amount'], validation_amount) self.assertEqual(processing_values['currency_id'], validation_currency.id) self.assertEqual(processing_values['partner_id'], self.partner.id) @@ -237,7 +235,7 @@ class TestFlows(PaymentHttpCommon): # Pay without a partner specified (but logged) --> pay with the partner of current user. self.authenticate(self.portal_user.login, self.portal_user.login) - tx_context = self._get_tx_checkout_context(**route_values) + tx_context = self._get_portal_pay_context(**route_values) self.assertEqual(tx_context['partner_id'], self.portal_partner.id) def test_pay_no_token(self): @@ -253,7 +251,7 @@ class TestFlows(PaymentHttpCommon): # Pay without a partner specified (but logged) --> pay with the partner of current user. self.authenticate(self.portal_user.login, self.portal_user.login) - tx_context = self._get_tx_checkout_context(**route_values) + tx_context = self._get_portal_pay_context(**route_values) self.assertEqual(tx_context['partner_id'], self.portal_partner.id) def test_pay_wrong_token(self): @@ -312,27 +310,24 @@ class TestFlows(PaymentHttpCommon): provider_b.state = 'test' token_b = self._create_token(provider_id=provider_b.id) - # User must see both enabled providers and tokens - manage_context = self._get_tx_manage_context() - self.assertEqual(manage_context['partner_id'], self.partner.id) - self.assertIn(self.provider.id, manage_context['provider_ids']) - self.assertIn(provider_b.id, manage_context['provider_ids']) - self.assertIn(token.id, manage_context['token_ids']) - self.assertIn(token_b.id, manage_context['token_ids']) + # User must see both tokens and compatible payment methods. + payment_context = self._get_portal_payment_method_context() + self.assertEqual(payment_context['partner_id'], self.partner.id) + self.assertIn(token.id, payment_context['token_ids']) + self.assertIn(token_b.id, payment_context['token_ids']) + self.assertIn(self.payment_method_id, payment_context['payment_method_ids']) - # Token of disabled provider(s) & disabled providers should not be shown + # Token of disabled provider(s) should not be shown. self.provider.state = 'disabled' - manage_context = self._get_tx_manage_context() - self.assertEqual(manage_context['partner_id'], self.partner.id) - self.assertEqual(manage_context['provider_ids'], [provider_b.id]) - self.assertEqual(manage_context['token_ids'], [token_b.id]) + payment_context = self._get_portal_payment_method_context() + self.assertEqual(payment_context['partner_id'], self.partner.id) + self.assertEqual(payment_context['token_ids'], [token_b.id]) # Archived tokens must be hidden from the user token_b.active = False - manage_context = self._get_tx_manage_context() - self.assertEqual(manage_context['partner_id'], self.partner.id) - self.assertEqual(manage_context['provider_ids'], [provider_b.id]) - self.assertEqual(manage_context['token_ids'], []) + payment_context = self._get_portal_payment_method_context() + self.assertEqual(payment_context['partner_id'], self.partner.id) + self.assertEqual(payment_context['token_ids'], []) @mute_logger('odoo.addons.payment.models.payment_transaction') def test_direct_payment_triggers_no_payment_request(self): @@ -344,7 +339,7 @@ class TestFlows(PaymentHttpCommon): '._send_payment_request' ) as patched: self._portal_transaction( - **self._prepare_transaction_values(self.provider.id, 'direct') + **self._prepare_transaction_values(self.payment_method_id, None, 'direct') ) self.assertEqual(patched.call_count, 0) @@ -358,7 +353,7 @@ class TestFlows(PaymentHttpCommon): '._send_payment_request' ) as patched: self._portal_transaction( - **self._prepare_transaction_values(self.provider.id, 'redirect') + **self._prepare_transaction_values(self.payment_method_id, None, 'redirect') ) self.assertEqual(patched.call_count, 0) @@ -372,7 +367,7 @@ class TestFlows(PaymentHttpCommon): '._send_payment_request' ) as patched: self._portal_transaction( - **self._prepare_transaction_values(self._create_token().id, 'token') + **self._prepare_transaction_values(None, self._create_token().id, 'token') ) self.assertEqual(patched.call_count, 1) diff --git a/addons/payment/tests/test_multicompany_flows.py b/addons/payment/tests/test_multicompany_flows.py index 8c23fda264e..ebff47439a9 100644 --- a/addons/payment/tests/test_multicompany_flows.py +++ b/addons/payment/tests/test_multicompany_flows.py @@ -48,8 +48,8 @@ class TestMultiCompanyFlows(PaymentHttpCommon): # Pay in company B route_values['company_id'] = self.company_b.id - tx_context = self._get_tx_checkout_context(**route_values) - for key, val in tx_context.items(): + payment_context = self._get_portal_pay_context(**route_values) + for key, val in payment_context.items(): if key in route_values: if key == 'access_token': continue # access_token was modified due to the change of partner. @@ -59,12 +59,8 @@ class TestMultiCompanyFlows(PaymentHttpCommon): else: self.assertEqual(val, route_values[key]) - available_providers = self.env['payment.provider'].sudo().browse(tx_context['provider_ids']) - self.assertIn(self.provider_company_b, available_providers) - self.assertEqual(available_providers.company_id, self.company_b) - validation_values = { - k: tx_context[k] + k: payment_context[k] for k in [ 'amount', 'currency_id', @@ -75,8 +71,10 @@ class TestMultiCompanyFlows(PaymentHttpCommon): ] } validation_values.update({ + 'provider_id': self.provider_company_b.id, + 'payment_method_id': self.provider_company_b.payment_method_ids[:1].id, + 'token_id': None, 'flow': 'direct', - 'payment_option_id': self.provider_company_b.id, 'tokenization_requested': False, }) with mute_logger('odoo.addons.payment.models.payment_transaction'): @@ -108,11 +106,9 @@ class TestMultiCompanyFlows(PaymentHttpCommon): # A partner should see all his tokens on the /my/payment_method route, # even if they are in other companies otherwise he won't ever see them. - manage_context = self._get_tx_manage_context() - self.assertEqual(manage_context['partner_id'], self.partner.id) - self.assertEqual(manage_context['provider_ids'], self.provider.ids) - self.assertIn(token.id, manage_context['token_ids']) - self.assertIn(token_company_b.id, manage_context['token_ids']) + payment_context = self._get_portal_payment_method_context() + self.assertIn(token.id, payment_context['token_ids']) + self.assertIn(token_company_b.id, payment_context['token_ids']) def test_archive_token_logged_in_another_company(self): """User archives his token from another company.""" diff --git a/addons/payment/views/express_checkout_templates.xml b/addons/payment/views/express_checkout_templates.xml new file mode 100644 index 00000000000..bcb2da3498e --- /dev/null +++ b/addons/payment/views/express_checkout_templates.xml @@ -0,0 +1,52 @@ + + + + + + diff --git a/addons/payment/views/payment_form_templates.xml b/addons/payment/views/payment_form_templates.xml new file mode 100644 index 00000000000..5da3e5e0e38 --- /dev/null +++ b/addons/payment/views/payment_form_templates.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + diff --git a/addons/payment/views/payment_method_views.xml b/addons/payment/views/payment_method_views.xml index 760dcad522e..5182fcf9917 100644 --- a/addons/payment/views/payment_method_views.xml +++ b/addons/payment/views/payment_method_views.xml @@ -7,13 +7,26 @@ +

+ + + + - - + + + + + + + + + +
@@ -25,17 +38,32 @@ payment.method.tree payment.method - + + + + + + payment.method.search + payment.method + + + + + + + Payment Methods payment.method - tree,form + tree,kanban,form + [('is_primary', '=', True)] + {'active_test': False}

Create a payment method diff --git a/addons/payment/views/payment_portal_templates.xml b/addons/payment/views/payment_portal_templates.xml deleted file mode 100644 index f018819aaf1..00000000000 --- a/addons/payment/views/payment_portal_templates.xml +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/payment/views/payment_provider_views.xml b/addons/payment/views/payment_provider_views.xml index 49372a3c12d..c25813d4f09 100644 --- a/addons/payment/views/payment_provider_views.xml +++ b/addons/payment/views/payment_provider_views.xml @@ -18,7 +18,6 @@ - @@ -78,11 +77,21 @@ - + /> +

@@ -131,7 +140,7 @@ - + diff --git a/addons/payment/views/payment_templates.xml b/addons/payment/views/payment_templates.xml deleted file mode 100644 index d9cd1a5115a..00000000000 --- a/addons/payment/views/payment_templates.xml +++ /dev/null @@ -1,511 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/payment/views/payment_token_views.xml b/addons/payment/views/payment_token_views.xml index 0032ccbfeb7..936d820aec1 100644 --- a/addons/payment/views/payment_token_views.xml +++ b/addons/payment/views/payment_token_views.xml @@ -18,6 +18,7 @@ + @@ -38,6 +39,7 @@ + diff --git a/addons/payment/views/payment_transaction_views.xml b/addons/payment/views/payment_transaction_views.xml index 3f77b141b35..d267f54b9f4 100644 --- a/addons/payment/views/payment_transaction_views.xml +++ b/addons/payment/views/payment_transaction_views.xml @@ -29,6 +29,7 @@ invisible="not source_transaction_id"/> + @@ -71,6 +72,7 @@ + diff --git a/addons/payment/views/portal_templates.xml b/addons/payment/views/portal_templates.xml new file mode 100644 index 00000000000..41c62977de7 --- /dev/null +++ b/addons/payment/views/portal_templates.xml @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/payment/wizards/payment_link_wizard.py b/addons/payment/wizards/payment_link_wizard.py index 437d5642109..464f3091ea2 100644 --- a/addons/payment/wizards/payment_link_wizard.py +++ b/addons/payment/wizards/payment_link_wizard.py @@ -3,7 +3,6 @@ from werkzeug import urls from odoo import _, api, fields, models -from odoo.exceptions import ValidationError from odoo.addons.payment import utils as payment_utils @@ -34,25 +33,6 @@ class PaymentLinkWizard(models.TransientModel): description = fields.Char("Payment Ref") link = fields.Char(string="Payment Link", compute='_compute_link') company_id = fields.Many2one('res.company', compute='_compute_company_id') - available_provider_ids = fields.Many2many( - comodel_name='payment.provider', - string="Payment Providers Available", - compute='_compute_available_provider_ids', - compute_sudo=True, - ) - has_multiple_providers = fields.Boolean( - string="Has Multiple Providers", - compute='_compute_has_multiple_providers', - ) - payment_provider_selection = fields.Selection( - string="Allow Payment Provider", - help="If a specific payment provider is selected, customers will only be allowed to pay " - "via this one. If 'All' is selected, customers can pay via any available payment " - "provider.", - selection='_selection_payment_provider_selection', - default='all', - required=True, - ) warning_message = fields.Char(compute='_compute_warning_message') @api.depends('amount', 'amount_max') @@ -72,69 +52,13 @@ class PaymentLinkWizard(models.TransientModel): record = self.env[link.res_model].browse(link.res_id) link.company_id = record.company_id if 'company_id' in record else False - @api.depends('company_id', 'partner_id', 'currency_id') - def _compute_available_provider_ids(self): - for link in self: - link.available_provider_ids = link._get_payment_provider_available( - res_model=link.res_model, - res_id=link.res_id, - company_id=link.company_id.id, - partner_id=link.partner_id.id, - amount=link.amount, - currency_id=link.currency_id.id, - ) - - def _selection_payment_provider_selection(self): - """ Specify available providers in the selection field. - :return: The selection list of available providers. - :rtype: list[tuple] - """ - defaults = self.default_get(['res_model', 'res_id']) - selection = [('all', "All")] - res_model, res_id = defaults.get('res_model'), defaults.get('res_id') - if res_id and res_model in ['account.move', "sale.order"]: - # At module install, the selection method is called - # but the document context isn't specified. - related_document = self.env[res_model].browse(res_id) - company_id = related_document.company_id - partner_id = related_document.partner_id - currency_id = related_document.currency_id - selection.extend( - (payment_provider.id, payment_provider.display_name) - for payment_provider in self._get_payment_provider_available( - res_model=res_model, - res_id=res_id, - company_id=company_id.id, - partner_id=partner_id.id, - amount=related_document.amount_total, - currency_id=currency_id.id, - ) - ) - return selection - - def _get_payment_provider_available(self, **kwargs): - """ Select and return the providers matching the criteria. - - :return: The compatible providers - :rtype: recordset of `payment.provider` - """ - return self.env['payment.provider'].sudo()._get_compatible_providers(**kwargs) - - @api.depends('available_provider_ids') - def _compute_has_multiple_providers(self): - for link in self: - link.has_multiple_providers = len(link.available_provider_ids) > 1 - def _get_access_token(self): self.ensure_one() return payment_utils.generate_access_token( self.partner_id.id, self.amount, self.currency_id.id ) - @api.depends( - 'description', 'amount', 'currency_id', 'partner_id', 'company_id', - 'payment_provider_selection', - ) + @api.depends('description', 'amount', 'currency_id', 'partner_id', 'company_id') def _compute_link(self): for payment_link in self: related_document = self.env[payment_link.res_model].browse(payment_link.res_id) @@ -145,8 +69,6 @@ class PaymentLinkWizard(models.TransientModel): 'access_token': self._get_access_token(), **self._get_additional_link_values(), } - if payment_link.payment_provider_selection != 'all': - url_params['provider_id'] = str(payment_link.payment_provider_selection) payment_link.link = f'{base_url}/payment/pay?{urls.url_encode(url_params)}' def _get_additional_link_values(self): diff --git a/addons/payment/wizards/payment_link_wizard_views.xml b/addons/payment/wizards/payment_link_wizard_views.xml index bdbd602445e..656130ab694 100644 --- a/addons/payment/wizards/payment_link_wizard_views.xml +++ b/addons/payment/wizards/payment_link_wizard_views.xml @@ -19,14 +19,10 @@ - - -