[IMP] payment: allow (un)publishing of payment acquirers
Before this commit, it was impossible to migrate from one payment acquirer to another as the only way to do so was to change a payment acquirer’s state to ‘disabled’, subsequently disabling all tokens of that acquirer (detrimental for eg. running subscriptions). After this commit, payment acquirers have an additional boolean ‘Published’. With this functionality, users can safely migrate by keeping an acquirer ‘enabled’, but invisible for customers. There are five combined states an acquirer can take: ‘enabled’ & ‘published’: Visible to all users ‘enabled’ & ‘unpublished’: Visible only to internal users ‘disabled’ & ‘unpublished’: Same as previously ‘disabled’ ‘test’ & ‘published’: Same as previously ‘test’ ‘test’ & ‘unpublished’: visible only to internal users task-2871459 closes odoo/odoo#94242 Related: odoo/upgrade#3688 Related: odoo/documentation#2308 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import logging
|
||||
from psycopg2 import sql
|
||||
|
||||
from odoo import _, api, fields, models, SUPERUSER_ID
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.osv import expression
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -31,6 +31,11 @@ class PaymentAcquirer(models.Model):
|
||||
"This mode is advised when setting up the acquirer.",
|
||||
selection=[('disabled', "Disabled"), ('enabled', "Enabled"), ('test', "Test Mode")],
|
||||
default='disabled', required=True, copy=False)
|
||||
is_published = fields.Boolean(
|
||||
string="Published",
|
||||
help="Whether the acquirer is visible on the website or not. Tokens remain functional but "
|
||||
"are only visible on manage forms.",
|
||||
)
|
||||
company_id = fields.Many2one( # Indexed to speed-up ORM searches (from ir_rule or others)
|
||||
string="Company", comodel_name='res.company', default=lambda self: self.env.company.id,
|
||||
required=True, index=True)
|
||||
@@ -236,7 +241,15 @@ class PaymentAcquirer(models.Model):
|
||||
#=== ONCHANGE METHODS ===#
|
||||
|
||||
@api.onchange('state')
|
||||
def _onchange_state(self):
|
||||
def _onchange_state_switch_is_published(self):
|
||||
""" Automatically publish or unpublish the acquirer depending on its state.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.is_published = self.state == 'enabled'
|
||||
|
||||
@api.onchange('state')
|
||||
def _onchange_state_warn_before_disabling_tokens(self):
|
||||
""" Display a warning about the consequences of disabling an acquirer.
|
||||
|
||||
Let the user know that tokens related to an acquirer get archived if it is disabled or if
|
||||
@@ -348,6 +361,17 @@ class PaymentAcquirer(models.Model):
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
def action_toggle_is_published(self):
|
||||
""" Toggle the acquirer's is_published state.
|
||||
|
||||
:return: none
|
||||
:raise UserError: If the acquirer is disabled.
|
||||
"""
|
||||
if self.state != 'disabled':
|
||||
self.is_published = not self.is_published
|
||||
else:
|
||||
raise UserError(_("You cannot publish a disabled acquirer."))
|
||||
|
||||
#=== BUSINESS METHODS ===#
|
||||
|
||||
@api.model
|
||||
@@ -372,6 +396,10 @@ class PaymentAcquirer(models.Model):
|
||||
# Compute the base domain for compatible acquirers
|
||||
domain = ['&', ('state', 'in', ['enabled', 'test']), ('company_id', '=', company_id)]
|
||||
|
||||
# Handle the is_published state.
|
||||
if not self.env.user._is_internal():
|
||||
domain = expression.AND([domain, [('is_published', '=', True)]])
|
||||
|
||||
# Handle partner country
|
||||
partner = self.env['res.partner'].browse(partner_id)
|
||||
if partner.country_id: # The partner country must either not be set or be supported
|
||||
|
||||
@@ -5,4 +5,5 @@ from . import http_common
|
||||
from . import test_account_payment
|
||||
from . import test_flows
|
||||
from . import test_multicompany_flows
|
||||
from . import test_payment_acquirer
|
||||
from . import test_payment_transaction
|
||||
|
||||
@@ -92,6 +92,7 @@ class PaymentCommon(AccountTestInvoicingCommon):
|
||||
'name': "Dummy Acquirer",
|
||||
'provider': 'none',
|
||||
'state': 'test',
|
||||
'is_published': True,
|
||||
'allow_tokenization': True,
|
||||
'redirect_form_view_id': redirect_form.id,
|
||||
'journal_id': cls.company_data['default_journal_bank'].id,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentAcquirer(PaymentCommon):
|
||||
|
||||
def test_published_acquirer_compatible_with_all_users(self):
|
||||
for user in (self.public_user, self.portal_user):
|
||||
self.env = self.env(user=user)
|
||||
|
||||
compatible_acquirers = self.env['payment.acquirer'].sudo()._get_compatible_acquirers(
|
||||
self.company.id, self.partner.id
|
||||
)
|
||||
self.assertIn(self.acquirer, compatible_acquirers)
|
||||
|
||||
def test_unpublished_acquirer_compatible_with_internal_user(self):
|
||||
self.acquirer.is_published = False
|
||||
|
||||
compatible_acquirers = self.env['payment.acquirer']._get_compatible_acquirers(
|
||||
self.company.id, self.partner.id
|
||||
)
|
||||
self.assertIn(self.acquirer, compatible_acquirers)
|
||||
|
||||
def test_unpublished_acquirer_not_compatible_with_non_internal_user(self):
|
||||
self.acquirer.is_published = False
|
||||
for user in (self.public_user, self.portal_user):
|
||||
self.env = self.env(user=user)
|
||||
|
||||
compatible_acquirers = self.env['payment.acquirer'].sudo()._get_compatible_acquirers(
|
||||
self.company.id, self.partner.id
|
||||
)
|
||||
self.assertNotIn(self.acquirer, compatible_acquirers)
|
||||
@@ -6,6 +6,7 @@
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payment Acquirer">
|
||||
<field name="is_published" invisible="1"/>
|
||||
<field name="support_fees" invisible="1"/>
|
||||
<field name="support_manual_capture" invisible="1"/>
|
||||
<field name="support_tokenization" invisible="1"/>
|
||||
@@ -21,6 +22,27 @@
|
||||
<field name="show_done_msg" invisible="1"/>
|
||||
<field name="show_cancel_msg" invisible="1"/>
|
||||
<sheet>
|
||||
<!-- === Stat Buttons === -->
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_toggle_is_published"
|
||||
attrs="{'invisible': [('is_published', '=', False)]}"
|
||||
class="oe_stat_button"
|
||||
type="object"
|
||||
icon="fa-globe">
|
||||
<div class="o_stat_info o_field_widget">
|
||||
<span class="text-success">Published</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="action_toggle_is_published"
|
||||
attrs="{'invisible': [('is_published', '=', True)]}"
|
||||
class="oe_stat_button"
|
||||
type="object"
|
||||
icon="fa-eye-slash">
|
||||
<div class="o_stat_info o_field_widget">
|
||||
<span class="text-danger">Unpublished</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<field name="image_128" widget="image" class="oe_avatar"/>
|
||||
<widget name="web_ribbon" title="Disabled" bg_color="bg-danger" attrs="{'invisible': [('state', '!=', 'disabled')]}"/>
|
||||
<widget name="web_ribbon" title="Test Mode" bg_color="bg-warning" attrs="{'invisible': [('state', '!=', 'test')]}"/>
|
||||
@@ -117,6 +139,7 @@
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="is_published"/>
|
||||
<field name="provider"/>
|
||||
<field name="module_id"/>
|
||||
<field name="module_state"/>
|
||||
@@ -126,6 +149,7 @@
|
||||
<t t-name="kanban-box">
|
||||
<t t-set="installed" t-value="!record.module_id.value || (record.module_id.value && record.module_state.raw_value === 'installed')"/>
|
||||
<t t-set="to_buy" t-value="record.module_to_buy.raw_value === true"/>
|
||||
<t t-set="is_published" t-value="record.is_published.raw_value === true"/>
|
||||
<div t-attf-class="oe_kanban_global_click #{kanban_color(record.color.raw_value)}">
|
||||
<div class="o_payment_acquirer_desc">
|
||||
<div class="o_kanban_image">
|
||||
@@ -138,6 +162,8 @@
|
||||
<t t-if="installed">
|
||||
<field name="state" widget="label_selection" options="{'classes': {'enabled': 'success', 'test': 'warning', 'disabled' : 'danger'}}"/>
|
||||
</t>
|
||||
<span t-if="is_published && installed" class="badge text-bg-success ms-1">Published</span>
|
||||
<span t-if="!is_published && installed" class="badge text-bg-danger ms-1">Unpublished</span>
|
||||
<button t-if="!installed and !selection_mode and !to_buy" type="object" class="btn btn-secondary float-end" name="button_immediate_install">Install</button>
|
||||
<t t-if="!installed and to_buy">
|
||||
<button href="https://odoo.com/pricing?utm_source=db&utm_medium=module" target="_blank" class="btn btn-info btn-sm float_right">Upgrade</button>
|
||||
|
||||
@@ -66,6 +66,11 @@
|
||||
class="rounded-pill text-bg-warning ms-1">
|
||||
Test Mode
|
||||
</span>
|
||||
<!-- === "Unpublished" badge === -->
|
||||
<span t-if="not acquirer.is_published"
|
||||
class="badge text-bg-danger ms-1">
|
||||
Unpublished
|
||||
</span>
|
||||
<!-- === Extra fees badge === -->
|
||||
<t t-if="fees_by_acquirer.get(acquirer)">
|
||||
<span class="rounded-pill text-bg-secondary ms-1">
|
||||
@@ -118,6 +123,10 @@
|
||||
<span class="payment_option_name" t-esc="token.name"/>
|
||||
<!-- === "V" check mark === -->
|
||||
<t t-call="payment.verified_token_checkmark"/>
|
||||
<!-- === "Unpublished" badge === -->
|
||||
<span t-if="not token.acquirer_id.is_published" class="badge text-bg-danger ms-1">
|
||||
Unpublished
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- === Token inline form === -->
|
||||
@@ -195,7 +204,7 @@
|
||||
data-payment-option-type="acquirer"/>
|
||||
<!-- === Acquirer name === -->
|
||||
<span class="payment_option_name">
|
||||
<b><t t-esc="acquirer.display_as or acquirer.name"/></b>
|
||||
<b t-esc="acquirer.display_as or acquirer.name"/>
|
||||
</span>
|
||||
<!-- === "Test Mode" badge === -->
|
||||
<span t-if="acquirer.state == 'test'"
|
||||
@@ -203,6 +212,10 @@
|
||||
style="margin-left:5px">
|
||||
Test Mode
|
||||
</span>
|
||||
<!-- === "Unpublished" badge === -->
|
||||
<span t-if="not acquirer.is_published" class="badge text-bg-danger">
|
||||
Unpublished
|
||||
</span>
|
||||
</label>
|
||||
<!-- === Payment icon list === -->
|
||||
<t t-call="payment.icon_list"/>
|
||||
@@ -244,6 +257,11 @@
|
||||
<span class="payment_option_name" t-esc="token.name"/>
|
||||
<!-- === "V" check mark === -->
|
||||
<t t-call="payment.verified_token_checkmark"/>
|
||||
<!-- === "Unpublished" badge === -->
|
||||
<span t-if="not token.acquirer_id.is_published and token.env.user._is_internal()"
|
||||
class="badge text-bg-danger ms-1">
|
||||
Unpublished
|
||||
</span>
|
||||
</label>
|
||||
<!-- === "Delete" token button === -->
|
||||
<button name="o_payment_delete_token"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<record id="payment.payment_acquirer_transfer" model="payment.acquirer">
|
||||
<field name="provider">transfer</field>
|
||||
<field name="state">enabled</field>
|
||||
<field name="is_published">True</field>
|
||||
<field name="redirect_form_view_id" ref="redirect_form"/>
|
||||
<!-- Clear the default value to trigger the computation of the message -->
|
||||
<field name="pending_msg" eval="False"/>
|
||||
|
||||
Reference in New Issue
Block a user